fossq/lib/frontend.php
vdbhb59 4236b1df5a Synced
lmao forgot that little shit (2785f23a54)
xi changed the description format (b2ab422f62)
remove cara (89051f0129)
remove tracking links on video search using euroshit proxies (d7bb0755cb)
turn off history in ff (2d8e5838cd)
2026-08-29 16:14:40 +05:30

1077 lines
22 KiB
PHP

<?php
class frontend{
public function validateurl($url, $net_validate = false){
$url_parts = parse_url($url);
// check if required parts are there
if(
!isset($url_parts["scheme"]) ||
!(
$url_parts["scheme"] == "http" ||
$url_parts["scheme"] == "https"
) ||
!isset($url_parts["host"])
){
return false;
}
if($net_validate){
$ip =
str_replace(
["[", "]"], // handle ipv6
"",
$url_parts["host"]
);
// if its not an IP
if(!filter_var($ip, FILTER_VALIDATE_IP)){
// resolve domain's IP
$ip = gethostbyname($url_parts["host"] . ".");
}
// check if its localhost
if(
filter_var(
$ip,
FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
) === false
){
return false;
}
}
return true;
}
public function load($template, $replacements = []){
$replacements["server_name"] = htmlspecialchars(config::SERVER_NAME);
$replacements["version"] = config::VERSION;
if(isset($_COOKIE["theme"])){
$theme = str_replace(["/". "."], "", $_COOKIE["theme"]);
if(
$theme != "Dark" &&
!is_file("static/themes/" . $theme . ".css")
){
$theme = config::DEFAULT_THEME;
}
}else{
$theme = config::DEFAULT_THEME;
}
if($theme != "Dark"){
$replacements["style"] = '<link rel="stylesheet" href="/static/themes/' . rawurlencode($theme) . '.css?v' . config::VERSION . '">';
}else{
$replacements["style"] = "";
}
if(isset($_COOKIE["scraper_ac"])){
$replacements["ac"] = '?ac=' . htmlspecialchars($_COOKIE["scraper_ac"]);
}else{
$replacements["ac"] = '';
}
if(
isset($replacements["timetaken"]) &&
$replacements["timetaken"] !== null
){
$replacements["timetaken"] = '<div class="timetaken">Took ' . number_format(microtime(true) - $replacements["timetaken"], 2) . 's</div>';
}
$handle = fopen("template/{$template}", "r");
$data = fread($handle, filesize("template/{$template}"));
fclose($handle);
$data = explode("\n", $data);
$html = "";
for($i=0; $i<count($data); $i++){
$html .= trim($data[$i]);
}
foreach($replacements as $key => $value){
$html =
str_replace(
"{%{$key}%}",
$value,
$html
);
}
return trim($html);
}
public function loadheader(array $get, array $filters, string $page){
echo
$this->load("header.html", [
"title" => trim(htmlspecialchars($get["s"]) . " ({$page})"),
"description" => ucfirst($page) . ' search results for &quot;' . htmlspecialchars($get["s"]) . '&quot;',
"index" => "no",
"search" => htmlspecialchars($get["s"]),
"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 !!
apcu_inc("captcha_gen");
$this->drawerror(
"Tshh, blocked!",
'Your browser, IP or IP range has been blocked from this FOSS-Q instance. If this is an error, please <a href="/about">contact the administrator</a>.'
);
die();
}
}
public function drawerror($title, $error, $timetaken = null){
if($timetaken === null){
$timetaken = microtime(true);
}
echo
$this->load("search.html", [
"timetaken" => $timetaken,
"class" => "",
"right-left" => "",
"right-right" => "",
"left" =>
'<div class="infobox">' .
'<h1>' . htmlspecialchars($title) . '</h1>' .
$error .
'</div>'
]);
die();
}
public function drawscrapererror($error, $get, $target, $timetaken = null){
if($timetaken === null){
$timetaken = microtime(true);
}
$this->drawerror(
"Shit",
'This scraper returned an error:' .
'<div class="code">' . htmlspecialchars($error) . '</div>' .
'Things you can try:' .
'<ul>' .
'<li>Use a different scraper</li>' .
'<li>Remove keywords that could cause errors</li>' .
'<li><a href="/instances?target=' . $target . "&" . $this->buildquery($get, false) . '">Try your search on another FOSS-Q instance</a></li>' .
'</ul><br>' .
'If the error persists, please <a href="/about">contact the administrator</a>.',
$timetaken
);
}
public function drawtextresult($site, $greentext = null, $duration = null, $keywords, $tabindex = true, $customhtml = null){
$payload =
'<div class="text-result">';
// add favicon, link and archive links
$payload .= $this->drawlink($site["url"]);
/*
Draw title + description + filetype
*/
$payload .=
'<a href="' . htmlspecialchars($site["url"]) . '" class="hover" rel="noreferrer nofollow"';
if($tabindex === false){
$payload .= ' tabindex="-1"';
}
$payload .= '>';
if($site["thumb"]["url"] !== null){
$payload .=
'<div class="thumb-wrap';
switch($site["thumb"]["ratio"]){
case "16:9":
$size = "landscape";
break;
case "9:16":
$payload .= " portrait";
$size = "portrait";
break;
case "1:1":
$payload .= " square";
$size = "square";
break;
}
$payload .=
'">' .
'<img class="thumb" src="' . $this->htmlimage($site["thumb"]["url"], $size) . '" alt="thumb">';
if($duration !== null){
$payload .=
'<div class="duration">' .
htmlspecialchars($duration) .
'</div>';
}
$payload .=
'</div>';
}
$payload .=
'<div class="title">';
if(
isset($site["type"]) &&
$site["type"] != "web"
){
$payload .= '<div class="type">' . strtoupper($site["type"]) . '</div>';
}
$payload .=
$this->highlighttext($keywords, $site["title"]) .
'</div>';
if($greentext !== null){
$payload .=
'<div class="greentext">' .
htmlspecialchars($greentext) .
'</div>';
}
if($site["description"] !== null){
$payload .=
'<div class="description">' .
$this->highlighttext($keywords, $site["description"]) .
'</div>';
}
$payload .= $customhtml;
$payload .= '</a>';
/*
Sublinks
*/
if(
isset($site["sublink"]) &&
!empty($site["sublink"])
){
usort($site["sublink"], function($a, $b){
return strlen($a["description"]) > strlen($b["description"]);
});
$payload .=
'<div class="sublinks">' .
'<table>';
$opentr = false;
for($i=0; $i<count($site["sublink"]); $i++){
if(($i % 2) === 0){
$opentr = true;
$payload .= '<tr>';
}else{
$opentr = false;
}
$payload .=
'<td>' .
'<a href="' . htmlspecialchars($site["sublink"][$i]["url"]) . '" rel="noreferrer nofollow">' .
'<div class="title">' .
htmlspecialchars($site["sublink"][$i]["title"]) .
'</div>';
if(!empty($site["sublink"][$i]["date"])){
$payload .=
'<div class="greentext">' .
date("jS M y @ g:ia", $site["sublink"][$i]["date"]) .
'</div>';
}
if(!empty($site["sublink"][$i]["description"])){
$payload .=
'<div class="description">' .
$this->highlighttext($keywords, $site["sublink"][$i]["description"]) .
'</div>';
}
$payload .= '</a></td>';
if($opentr === false){
$payload .= '</tr>';
}
}
if($opentr === true){
$payload .= '<td></td></tr>';
}
$payload .= '</table></div>';
}
if(
isset($site["table"]) &&
!empty($site["table"])
){
$payload .= '<table class="info-table">';
foreach($site["table"] as $title => $value){
$payload .=
'<tr>' .
'<td>' . htmlspecialchars($title) . '</td>' .
'<td>' . htmlspecialchars($value) . '</td>' .
'</tr>';
}
$payload .= '</table>';
}
return $payload . '</div>';
}
public function highlighttext($keywords, $text){
$text = htmlspecialchars($text);
$keywords = explode(" ", $keywords);
$regex = [];
foreach($keywords as $word){
$regex[] = "\b" . preg_quote($word, "/") . "\b";
}
$regex = "/" . implode("|", $regex) . "/i";
return
preg_replace(
$regex,
'<b>${0}</b>',
$text
);
}
function highlightcode($text){
// https://www.php.net/highlight_string
ini_set("highlight.comment", "c-comment");
ini_set("highlight.default", "c-default");
ini_set("highlight.html", "c-default");
ini_set("highlight.keyword", "c-keyword");
ini_set("highlight.string", "c-string");
$text =
trim(
preg_replace(
'/<code [^>]+>/',
"",
str_replace(
[
"<br />",
"&nbsp;",
"<pre>",
"</pre>",
"</code>"
],
[
"\n",
" ",
"",
"",
""
],
explode(
"&lt;?php",
highlight_string("<?php " . $text, true),
2
)[1]
)
)
);
// replace colors
$classes = ["c-comment", "c-default", "c-keyword", "c-string"];
foreach($classes as $class){
$text = str_replace('<span style="color: ' . $class . '">', '<span class="' . $class . '">', $text);
}
return $text;
}
public function drawlink($link){
/*
Add favicon
*/
$host = parse_url($link);
// special case for when we're not drawing a full url
if(!isset($host["host"])){
$payload =
'<div class="url">' .
'<button class="favicon" tabindex="-1">' .
'<img src="/favicon?s=404" alt="xx">' .
'</button>';
}else{
$esc =
explode(
".",
$host["host"],
2
);
if(
count($esc) === 2 &&
$esc[0] == "www"
){
$esc = $esc[1];
}else{
$esc = $esc[0];
}
$esc = substr($esc, 0, 2);
$urlencode = urlencode($link);
$payload =
'<div class="url">' .
'<button class="favicon" tabindex="-1">' .
'<img src="/favicon?s=' . htmlspecialchars($host["scheme"] . "://" . $host["host"]) . '" alt="' . htmlspecialchars($esc) . '">' .
//'<img src="/404.php" alt="' . htmlspecialchars($esc) . '">' .
'</button>' .
'<div class="favicon-dropdown">';
$payload .=
'<a href="https://web.archive.org/web/' . $urlencode . '" class="list" target="_BLANK"><img src="/favicon?s=https://archive.org" alt="ar">Archive.org</a>' .
'<a href="https://archive.ph/newest/' . htmlspecialchars($link) . '" class="list" target="_BLANK"><img src="/favicon?s=https://archive.is" alt="ar">Archive.is</a>' .
'<a href="https://ghostarchive.org/search?term=' . $urlencode . '" class="list" target="_BLANK"><img src="/favicon?s=https://ghostarchive.org" alt="gh">Ghostarchive</a>' .
'<a href="https://arquivo.pt/wayback/' . htmlspecialchars($link) . '" class="list" target="_BLANK"><img src="/favicon?s=https://arquivo.pt" alt="ar">Arquivo.pt</a>' .
'<a href="https://www.bing.com/search?q=url%3A' . $urlencode . '" class="list" target="_BLANK"><img src="/favicon?s=https://bing.com" alt="bi">Bing cache</a>' .
'<a href="https://megalodon.jp/?url=' . $urlencode . '" class="list" target="_BLANK"><img src="/favicon?s=https://megalodon.jp" alt="me">Megalodon</a>' .
'</div>';
}
/*
Draw link
*/
$parts = explode("/", $link);
$clickurl = "";
// remove trailing /
$c = count($parts) - 1;
if($parts[$c] == ""){
$parts[$c - 1] = $parts[$c - 1] . "/";
unset($parts[$c]);
}
// merge https://site together
if(isset($host["host"])){
$parts = [
$parts[0] . $parts[1] . '//' . $parts[2],
...array_slice($parts, 3, count($parts) - 1)
];
}
$c = count($parts);
for($i=0; $i<$c; $i++){
if($i !== 0){ $clickurl .= "/"; }
$clickurl .= $parts[$i];
if($i === $c - 1){
$parts[$i] = rtrim($parts[$i], "/");
}
$payload .=
'<a class="part" href="' . htmlspecialchars($clickurl) . '" rel="noreferrer nofollow" tabindex="-1">' .
htmlspecialchars(urldecode($parts[$i])) .
'</a>';
if($i !== $c - 1){
$payload .= '<span class="separator"></span>';
}
}
return $payload . '</div>';
}
public function getscraperfilters($page){
$get_scraper = isset($_COOKIE["scraper_$page"]) ? $_COOKIE["scraper_$page"] : null;
if(
isset($_GET["scraper"]) &&
is_string($_GET["scraper"])
){
$get_scraper = $_GET["scraper"];
}else{
if(
isset($_GET["npt"]) &&
is_string($_GET["npt"])
){
$get_scraper = explode(".", $_GET["npt"], 2)[0];
$get_scraper =
preg_replace(
'/[0-9]+$/',
"",
$get_scraper
);
}
}
// add search field
$filters =
[
"s" => [
"option" => "_SEARCH"
]
];
// define default scrapers
switch($page){
case "web":
$filters["scraper"] = [
"display" => "Scraper",
"option" => [
//"fget" => "fget",
"ddg" => "DuckDuckGo",
//"yahoo" => "Yahoo!",
"brave" => "Brave",
"yandex" => "Yandex",
// "google" => "Google",
// "google_api" => "Google API",
"google_cse" => "Google CSE",
"yahoo_japan" => "Yahoo! JAPAN",
"startpage" => "Startpage",
"yep" => "Yep",
"mwmbl" => "Mwmbl",
"mojeek" => "Mojeek",
"naver" => "Naver",
"baidu" => "Baidu",
"coccoc" => "Cốc Cốc",
"solofield" => "Solofield",
"marginalia" => "Marginalia",
"purili" => "Purili",
"wiby" => "wiby"
]
];
break;
case "images":
$filters["scraper"] = [
"display" => "Scraper",
"option" => [
"ddg" => "DuckDuckGo",
"yandex" => "Yandex",
"brave" => "Brave",
"google" => "Google",
"google_api" => "Google API",
"google_cse" => "Google CSE",
"yahoo_japan" => "Yahoo! JAPAN",
"startpage" => "Startpage",
"naver" => "Naver",
"baidu" => "Baidu",
"solofield" => "Solofield",
"pinterest" => "Pinterest",
"flickr" => "Flickr",
"pexels" => "Pexels",
"pixabay" => "Pixabay",
"unsplash" => "Unsplash",
"fivehpx" => "500px",
"vsco" => "VSCO",
"imgur" => "Imgur",
"ftm" => "FindThatMeme"
]
];
break;
case "videos":
$filters["scraper"] = [
"display" => "Scraper",
"option" => [
"yt" => "YouTube",
//"archiveorg" => "Archive.org",
"vimeo" => "Vimeo",
//"odysee" => "Odysee",
"sepiasearch" => "Sepia Search",
//"fb" => "Facebook videos",
"ddg" => "DuckDuckGo",
"brave" => "Brave",
"yandex" => "Yandex",
"google" => "Google",
"yahoo_japan" => "Yahoo! JAPAN",
"startpage" => "Startpage",
"naver" => "Naver",
"baidu" => "Baidu",
"coccoc" => "Cốc Cốc",
"purili" => "Purili",
"solofield" => "Solofield"
]
];
break;
case "news":
$filters["scraper"] = [
"display" => "Scraper",
"option" => [
"ddg" => "DuckDuckGo",
"brave" => "Brave",
"google" => "Google",
"yahoo_japan" => "Yahoo! JAPAN",
"startpage" => "Startpage",
//"mojeek" => "Mojeek",
"baidu" => "Baidu"
]
];
break;
case "music":
$filters["scraper"] = [
"display" => "Scraper",
"option" => [
"sc" => "SoundCloud",
"swisscows" => "Swisscows (SoundCloud)"
//"spotify" => "Spotify"
]
];
break;
case "booru":
$filters["scraper"] = [
"display" => "Scraper",
"option" => [
"safebooru" => "Safebooru",
"konachan" => "Konachan",
"tbib" => "The Big Imageboard",
"gelbooru" => "Gelbooru",
"yandere" => "Yande.re",
"tbib" => "The Big Imageboard",
"sankakucomplex" => "SankakuComplex",
"soybooru" => "SoyBooru"
]
];
break;
}
// get scraper name from user input, or default out to preferred scraper
$scraper_out = null;
$first = true;
foreach($filters["scraper"]["option"] as $scraper_name => $scraper_pretty){
if($first === true){
$first = $scraper_name;
}
if($scraper_name == $get_scraper){
$scraper_out = $scraper_name;
}
}
if($scraper_out === null){
$scraper_out = $first;
}
include "scraper/$scraper_out.php";
$lib = new $scraper_out();
// set scraper on $_GET
$_GET["scraper"] = $scraper_out;
// set nsfw on $_GET
if(
isset($_COOKIE["nsfw"]) &&
!isset($_GET["nsfw"])
){
$_GET["nsfw"] = $_COOKIE["nsfw"];
}
return
[
$lib,
array_merge_recursive(
$filters,
$lib->getfilters($page)
)
];
}
public function parsegetfilters($parameters, $whitelist){
$sanitized = [];
// add npt token
if(
isset($parameters["npt"]) &&
is_string($parameters["npt"])
){
$sanitized["npt"] = $parameters["npt"];
}else{
$sanitized["npt"] = false;
}
// we're iterating over $whitelist, so
// you can't polluate $sanitized with useless
// parameters
foreach($whitelist as $parameter => $value){
if(isset($parameters[$parameter])){
if(!is_string($parameters[$parameter])){
$sanitized[$parameter] = null;
continue;
}
// parameter is already set, use that value
$sanitized[$parameter] = $parameters[$parameter];
}else{
// parameter is not set, add it
if(is_string($value["option"])){
// special field: set default value manually
switch($value["option"]){
case "_DATE":
// no date set
$sanitized[$parameter] = false;
break;
case "_SEARCH":
// no search set
$sanitized[$parameter] = "";
break;
}
}else{
// set a default value
$sanitized[$parameter] = array_keys($value["option"])[0];
}
}
// sanitize input
if(is_array($value["option"])){
if(
!in_array(
$sanitized[$parameter],
$keys = array_keys($value["option"])
)
){
$sanitized[$parameter] = $keys[0];
}
}else{
// sanitize search & string
switch($value["option"]){
case "_DATE":
if($sanitized[$parameter] !== false){
$sanitized[$parameter] = strtotime($sanitized[$parameter]);
if($sanitized[$parameter] <= 0){
$sanitized[$parameter] = false;
}
}
break;
case "_SEARCH":
// get search string
$sanitized["s"] = trim($sanitized[$parameter]);
}
}
}
// invert dates if needed
if(
isset($sanitized["older"]) &&
isset($sanitized["newer"]) &&
$sanitized["newer"] !== false &&
$sanitized["older"] !== false &&
$sanitized["newer"] > $sanitized["older"]
){
// invert
[
$sanitized["older"],
$sanitized["newer"]
] = [
$sanitized["newer"],
$sanitized["older"]
];
}
return $sanitized;
}
public function s_to_timestamp($seconds){
if(is_string($seconds)){
return "LIVE";
}
return ($seconds >= 60) ? ltrim(gmdate("H:i:s", $seconds), ":0") : gmdate("0:s", $seconds);
}
public function generatehtmltabs($page, $query){
$html = null;
//foreach(["web", "images", "videos", "news", "music", "booru"] as $type){
foreach(["web", "images", "videos", "news", "music"] as $type){
$html .= '<a href="/' . $type . '?s=' . urlencode($query);
if(!empty($params)){
$html .= $params;
}
$html .= '" class="tab';
if($type == $page){
$html .= ' selected';
}
$html .= '">' . ucfirst($type) . '</a>';
}
return $html;
}
public function generatehtmlfilters($filters, $params){
$html = null;
foreach($filters as $filter_name => $filter_values){
if(!isset($filter_values["display"])){
continue;
}
$output = true;
$tmp =
'<div class="filter">' .
'<div class="title">' . htmlspecialchars($filter_values["display"]) . '</div>';
if(is_array($filter_values["option"])){
$tmp .= '<select name="' . $filter_name . '">';
foreach($filter_values["option"] as $option_name => $option_title){
$tmp .= '<option value="' . $option_name . '"';
if($params[$filter_name] == $option_name){
$tmp .= ' selected';
}
$tmp .= '>' . htmlspecialchars($option_title) . '</option>';
}
$tmp .= '</select>';
}else{
switch($filter_values["option"]){
case "_DATE":
$tmp .= '<input type="date" name="' . $filter_name . '"';
if($params[$filter_name] !== false){
$tmp .= ' value="' . date("Y-m-d", $params[$filter_name]) . '"';
}
$tmp .= '>';
break;
default:
$output = false;
break;
}
}
$tmp .= '</div>';
if($output === true){
$html .= $tmp;
}
}
return $html;
}
public function buildquery($gets, $ommit = false){
$out = [];
foreach($gets as $key => $value){
if(
$value == null ||
$value == false ||
$key == "npt" ||
$key == "extendedsearch" ||
$value == "any" ||
$value == "all" ||
$key == "spellcheck" ||
(
$ommit === true &&
$key == "s"
)
){
continue;
}
if(
$key == "older" ||
$key == "newer"
){
$value = date("Y-m-d", (int)$value);
}
$out[$key] = $value;
}
return http_build_query($out);
}
public function htmlimage($image, $format){
if(
preg_match(
'/^data:/',
$image
)
){
return htmlspecialchars($image);
}
//return "https://fossq.com/proxy?i=" . urlencode($image) . "&s=" . $format;
return "/proxy?i=" . urlencode($image) . "&s=" . $format;
}
public function htmlnextpage($gets, $npt, $page){
$query = $this->buildquery($gets);
return $page . "?" . $query . "&npt=" . $npt;
}
}