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"] = ''; // 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 $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 */ 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 http_response_code(404); $handle = fopen("lib/favicon404.png", "r"); echo fread($handle, filesize("lib/favicon404.png")); fclose($handle); die(); } }