From 54b3588c3a46f3d77d3b25c726ec4714325fb3c2 Mon Sep 17 00:00:00 2001 From: ngn Date: Fri, 11 Jul 2025 15:33:40 +0300 Subject: [PATCH] clean config, support read-only non-root docker Signed-off-by: ngn --- .gitignore | 33 +----- Dockerfile | 28 +++-- compose.example.yml | 18 ++++ docker-compose.example.yml | 12 --- docker/gen_config.php | 90 ---------------- docker/httpd.conf | 79 ++++---------- docker/init.sh | 22 ++-- src/.gitignore | 29 +++++ src/data/config.def.php | 87 +++++++++++++++ src/data/config.php | 173 ------------------------------ src/favicon.php | 213 +++++++++++++++++++------------------ src/lib/frontend.php | 55 ---------- 12 files changed, 300 insertions(+), 539 deletions(-) create mode 100644 compose.example.yml delete mode 100644 docker-compose.example.yml delete mode 100644 docker/gen_config.php create mode 100644 src/.gitignore create mode 100644 src/data/config.def.php delete mode 100644 src/data/config.php diff --git a/.gitignore b/.gitignore index 134e1b2..ed4ff25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,28 +1,5 @@ -lib/test.html -lib/postdata.json -lib/nextpage.json -scraper/brave.html -scraper/yandex.json -scraper/marginalia.json -banner_og/ -scraper/mojeek.html -scraper/google.html -scraper/google-img.html -scraper/google-video.html -scraper/google-news.html -scraper/google-img-nextpage.html -scraper/brave-image.html -scraper/brave-video.html -scraper/facebook.html -scraper/facebook-nextpage.json -scraper/yandex-video.json -scraper/yandex.html -scraper/soundcloud.json -scraper/mp3-pm.html -banner/* -data/captcha/birds/ -data/captcha/fumo_plushies/ -data/captcha/minecraft/ -!banner/*default* -scraper/curlie.html -icons/* +/compose.yml +/docker-compose.yml +/banners +/captcha +/config.php diff --git a/Dockerfile b/Dockerfile index 63f2d19..e232d5f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,20 @@ -FROM alpine:latest +FROM alpine -RUN apk update -RUN apk upgrade - -RUN apk add php apache2-ssl php83-fileinfo php83-openssl \ - php83-iconv php83-common php83-dom php83-sodium \ - php83-curl curl php83-pecl-apcu php83-apache2 \ - imagemagick php83-pecl-imagick php-mbstring \ - imagemagick-webp imagemagick-jpeg +RUN apk update && \ + apk upgrade && \ + apk add \ + php php83-fileinfo php83-iconv php83-common php83-dom php83-sodium \ + php83-curl php83-pecl-apcu php83-apache2 php-mbstring \ + php83-pecl-imagick imagemagick-webp imagemagick-jpeg COPY ./docker/httpd.conf /etc/apache2/httpd.conf COPY ./docker/init.sh / -WORKDIR /var/www/html +WORKDIR /srv COPY ./src ./4get -WORKDIR /var/www/html/4get -COPY ./docker/gen_config.php . +RUN adduser -DSH -u 1000 -h /srv runner +RUN chown -R runner /srv && chmod +x /init.sh -RUN chmod 777 /var/www/html/4get/icons -RUN chmod +x /init.sh - -CMD ["/init.sh"] +USER runner +CMD ["/init.sh"] diff --git a/compose.example.yml b/compose.example.yml new file mode 100644 index 0000000..92e5cd2 --- /dev/null +++ b/compose.example.yml @@ -0,0 +1,18 @@ +services: + fourget: + container_name: 4get + image: git.ngn.tf/ngn/4get + ports: + - 80:8080 + volumes: + - ./config.php:/srv/4get/data/config.php:ro + - ./banners:/srv/4get/banner:ro + - ./captcha:/srv/4get/data/captcha:ro + - type: tmpfs + target: /tmp/icons + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + read_only: true + restart: unless-stopped diff --git a/docker-compose.example.yml b/docker-compose.example.yml deleted file mode 100644 index a2bb99a..0000000 --- a/docker-compose.example.yml +++ /dev/null @@ -1,12 +0,0 @@ -services: - fourget: - container_name: 4get - image: git.ngn.tf/ngn/4get - environment: - - FOURGET_SERVER_NAME=example.com - ports: - - 80:80 - volumes: - - ./banners:/var/www/html/4get/banner - - ./captcha:/var/www/html/4get/data/captcha - restart: unless-stopped diff --git a/docker/gen_config.php b/docker/gen_config.php deleted file mode 100644 index a456ac3..0000000 --- a/docker/gen_config.php +++ /dev/null @@ -1,90 +0,0 @@ - getConstants()); -$from_env = array(); - -$env = getenv(); -$fourget_env = array_filter($env, function($v, $k) { - return str_starts_with($k, "FOURGET"); -}, ARRAY_FILTER_USE_BOTH); - -foreach($fourget_env as $key => $val) { - $target_key = preg_replace('/^FOURGET_/', '', $key); - $from_env[$target_key] = trim($val, '\'"'); -}; - -$merged_config = array_merge($from_config, $from_env); - -function type_to_string($n) { - $type = gettype($n); - if ($type === "NULL") { - return "null"; - } - if ($type === "boolean") { - return $n ? 'true' : 'false'; - } - if ($type === "string") { - if(is_numeric($n)) { - return $n; - } - return "\"$n\""; - } - if ($type === "array") { - return json_encode($n, JSON_UNESCAPED_SLASHES); - } - return $n; -} - - -function detect_captcha_dirs() { - $captcha_dir = "/var/www/html/4get/data/captcha/"; - $categories = (array_map(function ($n) { - return explode("/", $n)[7]; - }, glob($captcha_dir . "*"))); - - - $result = array_map(function($category) { - return [$category, count(glob("/var/www/html/4get/data/captcha/" . $category . "/*" ))]; - }, $categories); - - return $result; -} - - -$special_keys = ["PROTO", "CAPTCHA_DATASET"]; - -$output = " $val){ - if(!in_array($key, $special_keys)) { - $stored_value = $val; - // conversion between arrays and comma separated env value. - // Handle case when original type of field is array and there is a type mismatch when a comma separted string is passed, - // then split on comma if string (and not numeric, boolean, null, etc) - // - // except in the case where the inital value in default config is null or boolean. Assuming null and boolean - // in default config will be never be assigned an array - - if(gettype($from_config[$key]) != gettype($val) && !is_numeric($val) && !is_null($from_config[$key]) && gettype($from_config[$key]) != "boolean") { - $stored_value = explode(",", $val); - } - $output = $output . "\tconst " . $key . " = " . type_to_string($stored_value) . ";\n"; - - continue; - } - - - if($key === "CAPTCHA_DATASET") { - $output = $output . "\tconst " . $key . " = " . type_to_string(detect_captcha_dirs()) . ";\n"; - } -} - -$output = $output . "}\n"; -$output = $output . "?>"; - -file_put_contents("./data/config.php", $output); -?> diff --git a/docker/httpd.conf b/docker/httpd.conf index 57d8df5..490f756 100644 --- a/docker/httpd.conf +++ b/docker/httpd.conf @@ -1,16 +1,19 @@ -Listen 80 -ServerTokens OS +Listen 8080 + ServerRoot /var/www -ServerSignature On ServerName localhost -DocumentRoot "/var/www/html/4get" +ServerSignature Off +ServerTokens Prod -LogLevel error +PidFile /dev/shm/httpd.pid +DocumentRoot /srv/4get + +LogLevel error CustomLog /dev/null common -ErrorLog /dev/null +ErrorLog /dev/stderr - + RewriteEngine On RewriteCond %{THE_REQUEST} ^\w+\ /(.*)\.php(\?.*)?\ HTTP/ RewriteRule ^ http://%{HTTP_HOST}/%1 [R=301] @@ -22,68 +25,32 @@ ErrorLog /dev/null # deny access to private resources - + Require all denied - + Require all denied -LoadModule rewrite_module modules/mod_rewrite.so -LoadModule mpm_prefork_module modules/mod_mpm_prefork.so -LoadModule authn_file_module modules/mod_authn_file.so -LoadModule authn_core_module modules/mod_authn_core.so -LoadModule authz_host_module modules/mod_authz_host.so -LoadModule authz_groupfile_module modules/mod_authz_groupfile.so -LoadModule authz_user_module modules/mod_authz_user.so -LoadModule authz_core_module modules/mod_authz_core.so +LoadModule rewrite_module modules/mod_rewrite.so +LoadModule mpm_prefork_module modules/mod_mpm_prefork.so +LoadModule authz_core_module modules/mod_authz_core.so LoadModule access_compat_module modules/mod_access_compat.so -LoadModule auth_basic_module modules/mod_auth_basic.so -LoadModule reqtimeout_module modules/mod_reqtimeout.so -LoadModule filter_module modules/mod_filter.so -LoadModule mime_module modules/mod_mime.so -LoadModule log_config_module modules/mod_log_config.so -LoadModule env_module modules/mod_env.so -LoadModule headers_module modules/mod_headers.so -LoadModule setenvif_module modules/mod_setenvif.so -LoadModule version_module modules/mod_version.so -LoadModule unixd_module modules/mod_unixd.so -LoadModule status_module modules/mod_status.so -LoadModule autoindex_module modules/mod_autoindex.so -LoadModule dir_module modules/mod_dir.so -LoadModule alias_module modules/mod_alias.so -LoadModule negotiation_module modules/mod_negotiation.so - - - User apache - Group apache - +LoadModule filter_module modules/mod_filter.so +LoadModule mime_module modules/mod_mime.so +LoadModule log_config_module modules/mod_log_config.so +LoadModule unixd_module modules/mod_unixd.so +LoadModule negotiation_module modules/mod_negotiation.so +LoadModule dir_module modules/mod_dir.so AllowOverride none Require all denied - - DirectoryIndex index.html - - Require all denied - - RequestHeader unset Proxy early - - - - TypesConfig /etc/apache2/mime.types - AddType application/x-compress .Z - AddType application/x-gzip .gz .tgz - - - - MIMEMagicFile /etc/apache2/magic - - -IncludeOptional /etc/apache2/conf.d/*.conf +Include /etc/apache2/conf.d/languages.conf +Include /etc/apache2/conf.d/php83-module.conf diff --git a/docker/init.sh b/docker/init.sh index 5ffb1d2..4fa7d24 100644 --- a/docker/init.sh +++ b/docker/init.sh @@ -1,11 +1,17 @@ -#!/bin/sh -set -e +#!/bin/sh -e -if [ ! -f '/var/www/html/4get/data/config.php' ] && [ -f './gen_config.php' ] -then - php ./gen_config.php - rm -f ./gen_config.php +config='/srv/4get/data/config.php' +defconfig='/srv/4get/data/config.def.php' + +# check for the configuration file +if [ ! -f "${config}" ]; then + echo "configuration file not specified" + echo "here's the default configuration, modify and mount this to ${config}" + echo + cat "${defconfig}" + exit 1 fi -echo "Starting up apache2" -exec httpd -DFOREGROUND +# execute apache +echo "starting apache web server" +exec httpd -D FOREGROUND diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..c4c987c --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,29 @@ +lib/test.html +lib/postdata.json +lib/nextpage.json + +scraper/brave.html +scraper/yandex.json +scraper/marginalia.json +scraper/mojeek.html +scraper/google.html +scraper/google-img.html +scraper/google-video.html +scraper/google-news.html +scraper/google-img-nextpage.html +scraper/brave-image.html +scraper/brave-video.html +scraper/facebook.html +scraper/facebook-nextpage.json +scraper/yandex-video.json +scraper/yandex.html +scraper/soundcloud.json +scraper/mp3-pm.html +scraper/curlie.html + +icons/* +banner/* +!banner/*default* + +data/captcha +data/config.php diff --git a/src/data/config.def.php b/src/data/config.def.php new file mode 100644 index 0000000..b502248 --- /dev/null +++ b/src/data/config.def.php @@ -0,0 +1,87 @@ + tag on home page + const SERVER_SHORT_DESCRIPTION = "4get is a proxy search engine that doesn't suck."; + + // Will be shown in server list ping (null for no description) + const SERVER_LONG_DESCRIPTION = null; + + // Add your own themes in "static/themes". Set to "Dark" for default theme. + // Eg. To use "static/themes/Cream.css", specify "Cream". + const DEFAULT_THEME = "black"; + + // Default user agent to use for scraper requests. Sometimes ignored to get specific webpages + // Changing this might break things. + const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0"; + + // Temporary directory for saving the page icons + const ICON_DIR = "/tmp/icons"; + + // List of domains that point to your servers. Include your tor/i2p + // addresses here! Must be a valid URL. Won't affect links placed on + // the homepage. + const ALT_ADDRESSES = [ + //"https://4get.alt-tld", + //"http://4getwebfrq5zr4sxugk6htxvawqehxtdgjrbcn2oslllcol2vepa23yd.onion" + ]; + + // Proxy pool assignments for each scraper + // false = Use server's raw IP + // string = will load a proxy list from data/proxies + // Eg. "onion" will load data/proxies/onion.txt + const PROXY_DDG = false; // duckduckgo + const PROXY_BRAVE = false; + const PROXY_FB = false; // facebook + const PROXY_GOOGLE = false; + const PROXY_GOOGLE_API = false; + const PROXY_GOOGLE_CSE = false; + const PROXY_STARTPAGE = false; + const PROXY_QWANT = false; + const PROXY_GHOSTERY = false; + const PROXY_MARGINALIA = false; + const PROXY_MOJEEK = false; + const PROXY_SC = false; // soundcloud + const PROXY_SPOTIFY = false; + const PROXY_SOLOFIELD = false; + const PROXY_WIBY = false; + const PROXY_CURLIE = false; + const PROXY_YT = false; // youtube + const PROXY_YEP = false; + const PROXY_PINTEREST = false; + const PROXY_SANKAKUCOMPLEX = false; + const PROXY_FLICKR = false; + const PROXY_FIVEHPX = false; + const PROXY_VSCO = false; + const PROXY_SEZNAM = false; + const PROXY_NAVER = false; + const PROXY_GREPPR = false; + const PROXY_CROWDVIEW = false; + const PROXY_MWMBL = false; + const PROXY_FTM = false; // findthatmeme + const PROXY_IMGUR = false; + const PROXY_YANDEX_W = false; // yandex web + const PROXY_YANDEX_I = false; // yandex images + const PROXY_YANDEX_V = false; // yandex videos + + // + // Scraper-specific parameters + // + + // GOOGLE CSE & GOOGLE API + const GOOGLE_CX_ENDPOINT = "d4e68b99b876541f0"; + + // MARGINALIA + // Use "null" to default out to HTML scraping OR specify a string to + // use the API (Eg: "public"). API has less filters. + const MARGINALIA_API_KEY = null; +} diff --git a/src/data/config.php b/src/data/config.php deleted file mode 100644 index bb8d767..0000000 --- a/src/data/config.php +++ /dev/null @@ -1,173 +0,0 @@ - tag on home page - const SERVER_SHORT_DESCRIPTION = "4get is a proxy search engine that doesn't suck."; - - // Will be shown in server list ping (null for no description) - const SERVER_LONG_DESCRIPTION = null; - - // Add your own themes in "static/themes". Set to "Dark" for default theme. - // Eg. To use "static/themes/Cream.css", specify "Cream". - const DEFAULT_THEME = "black"; - - // Enable the API? - const API_ENABLED = true; - - // - // BOT PROTECTION - // - - // 0 = disabled, 1 = ask for image captcha, @TODO: 2 = invite only (users needs a pass) - // VERY useful against a targetted attack - const BOT_PROTECTION = 0; - - // if BOT_PROTECTION is set to 1, specify the available datasets here - // images should be named from 1.png to X.png, and be 100x100 in size - // Eg. data/captcha/birds/1.png up to 2263.png - const CAPTCHA_DATASET = [ - // example: - //["birds", 2263], - //["fumo_plushies", 1006], - //["minecraft", 848] - ]; - - // If this regex expression matches on the user agent, it blocks the request - // Not useful at all against a targetted attack - const HEADER_REGEX = '/bot|wget|curl|python-requests|scrapy|go-http-client|ruby|yahoo|spider|qwant/i'; - - // Block clients who present any of the following headers in their request (SPECIFY IN !!lowercase!!) - // Eg: ["x-forwarded-for", "x-via", "forwarded-for", "via"]; - // Useful for blocking *some* proxies used for botting - const FILTERED_HEADER_KEYS = [ - //"x-forwarded-for", - //"x-cluster-client-ip", - //"x-client-ip", - //"x-real-ip", - //"client-ip", - //"real-ip", - //"forwarded-for", - //"forwarded-for-ip", - //"forwarded", - //"proxy-connection", - //"remote-addr", - //"via" - ]; - - // Block SSL ciphers used by CLI tools used for botting - // Basically a primitive version of Cloudflare's browser integrity check - // ** If curl can still access the site (with spoofed headers), please make sure you use the new apache2 config ** - // https://git.lolcat.ca/lolcat/4get/docs/apache2.md - const DISALLOWED_SSL = [ - // "TLS_AES_256_GCM_SHA384" // used by WGET and CURL - ]; - - // Maximal number of searches per captcha key/pass issued. Counter gets - // reset on every APCU cache clear (should happen once a day). - // Only useful when BOT_PROTECTION is NOT set to 0 - const MAX_SEARCHES = 100; - - // List of domains that point to your servers. Include your tor/i2p - // addresses here! Must be a valid URL. Won't affect links placed on - // the homepage. - const ALT_ADDRESSES = [ - //"https://4get.alt-tld", - //"http://4getwebfrq5zr4sxugk6htxvawqehxtdgjrbcn2oslllcol2vepa23yd.onion" - ]; - - // Known 4get instances. MUST use the https protocol if your instance uses - // it. Is used to generate a distributed list of instances. - // To appear in the list of an instance, contact the host and if everyone added - // eachother your serber should appear everywhere. - const INSTANCES = [ - "https://4get.ca", - "https://4get.zzls.xyz", - "https://4getus.zzls.xyz", - "https://4get.silly.computer", - "https://4get.konakona.moe", - "https://4get.lvkaszus.pl", - "https://4g.ggtyler.dev", - "https://4get.perennialte.ch", - "https://4get.sijh.net", - "https://4get.hbubli.cc", - "https://4get.plunked.party", - "https://4get.seitan-ayoub.lol", - "https://4get.etenie.pl", - "https://4get.lunar.icu", - "https://4get.dcs0.hu", - "https://4get.kizuki.lol", - "https://4get.psily.garden", - "https://search.milivojevic.in.rs", - "https://4get.snine.nl", - "https://4get.datura.network", - "https://4get.neco.lol", - "https://4get.lol", - "https://4get.ch", - "https://4get.edmateo.site", - "https://4get.sudovanilla.org", - "https://search.mint.lgbt" - ]; - - // Default user agent to use for scraper requests. Sometimes ignored to get specific webpages - // Changing this might break things. - const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0"; - - // Proxy pool assignments for each scraper - // false = Use server's raw IP - // string = will load a proxy list from data/proxies - // Eg. "onion" will load data/proxies/onion.txt - const PROXY_DDG = false; // duckduckgo - const PROXY_BRAVE = false; - const PROXY_FB = false; // facebook - const PROXY_GOOGLE = false; - const PROXY_GOOGLE_API = false; - const PROXY_GOOGLE_CSE = false; - const PROXY_STARTPAGE = false; - const PROXY_QWANT = false; - const PROXY_GHOSTERY = false; - const PROXY_MARGINALIA = false; - const PROXY_MOJEEK = false; - const PROXY_SC = false; // soundcloud - const PROXY_SPOTIFY = false; - const PROXY_SOLOFIELD = false; - const PROXY_WIBY = false; - const PROXY_CURLIE = false; - const PROXY_YT = false; // youtube - const PROXY_YEP = false; - const PROXY_PINTEREST = false; - const PROXY_SANKAKUCOMPLEX = false; - const PROXY_FLICKR = false; - const PROXY_FIVEHPX = false; - const PROXY_VSCO = false; - const PROXY_SEZNAM = false; - const PROXY_NAVER = false; - const PROXY_GREPPR = false; - const PROXY_CROWDVIEW = false; - const PROXY_MWMBL = false; - const PROXY_FTM = false; // findthatmeme - const PROXY_IMGUR = false; - const PROXY_YANDEX_W = false; // yandex web - const PROXY_YANDEX_I = false; // yandex images - const PROXY_YANDEX_V = false; // yandex videos - - // - // Scraper-specific parameters - // - - // GOOGLE CSE & GOOGLE API - const GOOGLE_CX_ENDPOINT = "d4e68b99b876541f0"; - - // MARGINALIA - // Use "null" to default out to HTML scraping OR specify a string to - // use the API (Eg: "public"). API has less filters. - const MARGINALIA_API_KEY = null; -} diff --git a/src/favicon.php b/src/favicon.php index a3c1113..f7f7a5f 100644 --- a/src/favicon.php +++ b/src/favicon.php @@ -1,7 +1,7 @@ defaulticon(); } - - $filename = str_replace(["https://", "http://"], "", $url); - header("Content-Disposition: inline; filename=\"{$filename}.png\""); - + + // validate the URL + $url = filter_var($url, FILTER_VALIDATE_URL); + if(!$url) { + header("X-Error: Invalid URL"); + $this->defaulticon(); + } + + // extract the hostname + $this->filename = parse_url($url, PHP_URL_HOST); + if(!$this->filename || is_null($this->filename) || $this->filename === ""){ + header("X-Error: Invalid URL"); + $this->defaulticon(); + } + + // specify the filename in content-disposition + header("Content-Disposition: inline; filename=\"{$this->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")); + if(file_exists($this->iconpath())){ + $handle = fopen($this->iconpath(), "r"); + echo fread($handle, filesize($this->iconpath())); 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") @@ -101,22 +107,22 @@ class favicon{ 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" || @@ -126,49 +132,49 @@ class favicon{ $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 */ @@ -176,37 +182,37 @@ class favicon{ 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( @@ -215,24 +221,24 @@ class favicon{ 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? @@ -240,32 +246,32 @@ class favicon{ 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"); + $handle = fopen($this->iconpath(), "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( @@ -274,17 +280,17 @@ class favicon{ $json ) ){ - + $json = base64_decode($json[1]); - + if($json === false){ - + // could not decode the manifest regex return []; } - + }else{ - + try{ $json = $this->proxy->get( @@ -293,76 +299,81 @@ class favicon{ 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 iconpath() { + // $this->filename can be trusted + return config::ICON_DIR . "/" . $this->filename . ".png"; + } + 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"); + $handle = fopen($this->iconpath(), "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(); } } diff --git a/src/lib/frontend.php b/src/lib/frontend.php index 8a479dd..8ffb806 100644 --- a/src/lib/frontend.php +++ b/src/lib/frontend.php @@ -73,7 +73,6 @@ class frontend{ } public function loadheader(array $get, array $filters, string $page){ - echo $this->load("header.html", [ "title" => trim(htmlspecialchars($get["s"]) . " ({$page})"), @@ -83,60 +82,6 @@ class frontend{ "tabs" => $this->generatehtmltabs($page, $get["s"]), "filters" => $this->generatehtmlfilters($filters, $get) ]); - - $headers_raw = getallheaders(); - $header_keys = []; - $user_agent = ""; - $bad_header = false; - - // block bots that present X-Forwarded-For, Via, etc - foreach($headers_raw as $headerkey => $headervalue){ - - $headerkey = strtolower($headerkey); - if($headerkey == "user-agent"){ - - $user_agent = $headervalue; - continue; - } - - // check header key - if(in_array($headerkey, config::FILTERED_HEADER_KEYS)){ - - $bad_header = true; - break; - } - } - - // SSL check - $bad_ssl = false; - if( - isset($_SERVER["https"]) && - $_SERVER["https"] == "on" && - isset($_SERVER["SSL_CIPHER"]) && - in_array($_SERVER["SSL_CIPHER"], config::FILTERED_HEADER_KEYS) - ){ - - $bad_ssl = true; - } - - if( - $bad_header === true || - $bad_ssl === true || - $user_agent == "" || - // user agent check - preg_match( - config::HEADER_REGEX, - $user_agent - ) - ){ - - // bot detected !! - $this->drawerror( - "Tshh, blocked!", - 'Your browser, IP or IP range has been blocked from this 4get instance.' - ); - die(); - } } public function drawerror($title, $error, $timetaken = null){