<?php

if(!isset($_GET["s"])){
	
	header("X-Error: Missing parameter (s)ite");
	die();
}

include "data/config.php";
new favicon($_GET["s"]);

class favicon{
	
	public function __construct($url){
		
		header("Content-Type: image/png");
		
		if(
			preg_match(
				'/^https?:\/\/[A-Za-z0-9.-]+$/',
				$url
			) === 0
		){
			
			header("X-Error: Only provide the protocol and domain");
			$this->defaulticon();
		}
		
		$filename = str_replace(["https://", "http://"], "", $url);
		header("Content-Disposition: inline; filename=\"{$filename}.png\"");
		
		include "lib/curlproxy.php";
		$this->proxy = new proxy(false);
		
		$this->filename = parse_url($url, PHP_URL_HOST);
		
		/*
			Check if we have the favicon stored locally
		*/
		if(file_exists("icons/" . $filename . ".png")){
				
			$handle = fopen("icons/" . $filename . ".png", "r");
			echo fread($handle, filesize("icons/" . $filename . ".png"));
			fclose($handle);
			return;
		}
		
		/*
			Scrape html
		*/
		try{
			
			$payload = $this->proxy->get($url, $this->proxy::req_web, true);
			
		}catch(Exception $error){
			
			header("X-Error: Could not fetch HTML (" . $error->getMessage() . ")");
			$this->favicon404();
		}
		//$payload["body"] = '<link rel="manifest" id="MANIFEST_LINK" href="/data/manifest/" crossorigin="use-credentials" />';
		
		// get link tags
		preg_match_all(
			'/< *link +(.*)[\/]?>/Uixs',
			$payload["body"],
			$linktags
		);
		
		/*
			Get relevant tags
		*/
		
		$linktags = $linktags[1];
		$attributes = [];
		
		/*
		header("Content-Type: text/plain");
		print_r($linktags);
		print_r($payload);
		die();*/
		
		for($i=0; $i<count($linktags); $i++){
			
			// get attributes
			preg_match_all(
				'/([A-Za-z0-9]+) *= *("[^"]*"|[^" ]+)/s',
				$linktags[$i],
				$tags
			);
			
			for($k=0; $k<count($tags[1]); $k++){
				
				$attributes[$i][] = [
					"name" => $tags[1][$k],
					"value" => trim($tags[2][$k], "\" \n\r\t\v\x00")
				];
			}
		}

		unset($payload);
		unset($linktags);

		$href = [];
		
		// filter out the tags we want
		foreach($attributes as &$group){
			
			$tmp_href = null;
			$tmp_rel = null;
			$badtype = false;
			
			foreach($group as &$attribute){
				
				switch($attribute["name"]){
					
					case "rel":
						
						$attribute["value"] = strtolower($attribute["value"]);
						
						if(
							(
								$attribute["value"] == "icon" ||
								$attribute["value"] == "manifest" ||
								$attribute["value"] == "shortcut icon" ||
								$attribute["value"] == "apple-touch-icon" ||
								$attribute["value"] == "mask-icon"
							) === false
						){
							
							break;
						}
						
						$tmp_rel = $attribute["value"];
						break;
					
					case "type":
						$attribute["value"] = explode("/", $attribute["value"], 2);
						
						if(strtolower($attribute["value"][0]) != "image"){
							
							$badtype = true;
							break;
						}
						break;
					
					case "href":
						
						// must not contain invalid characters
						// must be bigger than 1
						if(
							filter_var($attribute["value"], FILTER_SANITIZE_URL) == $attribute["value"] &&
							strlen($attribute["value"]) > 0
						){
							
							$tmp_href = $attribute["value"];
							break;
						}
						break;
				}
			}
			
			if(
				$badtype === false &&
				$tmp_rel !== null &&
				$tmp_href !== null
			){
				
				$href[$tmp_rel] = $tmp_href;
			}
		}
		
		/*
			Priority list
		*/
		/*
		header("Content-Type: text/plain");
		print_r($href);
		die();*/
		
		if(isset($href["icon"])){ $href = $href["icon"]; }
		elseif(isset($href["apple-touch-icon"])){ $href = $href["apple-touch-icon"]; }
		elseif(isset($href["manifest"])){
			
			// attempt to parse manifest, but fallback to []
			$href = $this->parsemanifest($href["manifest"], $url);
		}
		
		if(is_array($href)){
			
			if(isset($href["mask-icon"])){ $href = $href["mask-icon"]; }
			elseif(isset($href["shortcut icon"])){ $href = $href["shortcut icon"]; }
			else{
				
				$href = "/favicon.ico";
			}
		}
		
		$href = $this->proxy->getabsoluteurl($href, $url);
		/*
		header("Content-type: text/plain");
		echo $href;
		die();*/
		
		
		/*
			Download the favicon
		*/
		//$href = "https://git.lolcat.ca/assets/img/logo.svg";
		
		try{
			$payload =
				$this->proxy->get(
					$href,
					$this->proxy::req_image,
					true,
					$url
				);
				
		}catch(Exception $error){
			
			header("X-Error: Could not fetch the favicon (" . $error->getMessage() . ")");
			$this->favicon404();
		}
		
		/*
			Parse the file format
		*/
		$image = null;
		$format = $this->proxy->getimageformat($payload, $image);
		
		/*
			Convert the image
		*/
		try{
			
			/*
				@todo: fix issues with avif+transparency
				maybe using GD as fallback?
			*/
			if($format !== false){
				$image->setFormat($format);
			}
			
			$image->setBackgroundColor(new ImagickPixel("transparent"));
			$image->readImageBlob($payload["body"]);
			$image->resizeImage(16, 16, imagick::FILTER_LANCZOS, 1);
			$image->setFormat("png");
			
			$image = $image->getImageBlob();
			
			// save favicon
			$handle = fopen("icons/" . $this->filename . ".png", "w");
			fwrite($handle, $image, strlen($image));
			fclose($handle);
			
			echo $image;
			
		}catch(ImagickException $error){
			
			header("X-Error: Could not convert the favicon: (" . $error->getMessage() . ")");
			$this->favicon404();
		}
		
		return;
	}
	
	private function parsemanifest($href, $url){
		
		if(
			// check if base64-encoded JSON manifest
			preg_match(
				'/^data:application\/json;base64,([A-Za-z0-9=]*)$/',
				$href,
				$json
			)
		){
			
			$json = base64_decode($json[1]);
			
			if($json === false){
				
				// could not decode the manifest regex
				return [];
			}
			
		}else{
			
			try{
				$json =
					$this->proxy->get(
						$this->proxy->getabsoluteurl($href, $url),
						$this->proxy::req_web,
						false,
						$url
					);
					
					$json = $json["body"];
					
			}catch(Exception $error){
				
				// could not fetch the manifest
				return [];
			}
		}
		
		$json = json_decode($json, true);
		
		if($json === null){
			
			// manifest did not return valid json
			return [];
		}
		
		if(
			isset($json["start_url"]) &&
			$this->proxy->validateurl($json["start_url"])
		){
			
			$url = $json["start_url"];
		}
		
		if(!isset($json["icons"][0]["src"])){
			
			// manifest does not contain a path to the favicon
			return [];
		}
		
		// horay, return the favicon path
		return $json["icons"][0]["src"];
	}
	
	private function favicon404(){
		
		// fallback to google favicons
		// ... probably blocked by cuckflare
		try{
			
			$image =
				$this->proxy->get(
					"https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://{$this->filename}&size=16",
					$this->proxy::req_image
				);
		}catch(Exception $error){
			
			$this->defaulticon();
		}
		
		// write favicon from google
		$handle = fopen("icons/" . $this->filename . ".png", "w");
		fwrite($handle, $image["body"], strlen($image["body"]));
		fclose($handle);
		
		echo $image["body"];
		die();
	}
	
	private function defaulticon(){
		
		// give 404 and fuck off
		http_response_code(404);
		
		$handle = fopen("lib/favicon404.png", "r");
		echo fread($handle, filesize("lib/favicon404.png"));
		fclose($handle);
		
		die();
	}
}
