remove a lot of stuff from global context (functions.php), add a few helper classes instead
parent
d04ac399ff
commit
74568df4ff
@ -0,0 +1,217 @@
|
||||
<?php
|
||||
class Sanitizer {
|
||||
private static function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
|
||||
$xpath = new DOMXPath($doc);
|
||||
$entries = $xpath->query('//*');
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if (!in_array($entry->nodeName, $allowed_elements)) {
|
||||
$entry->parentNode->removeChild($entry);
|
||||
}
|
||||
|
||||
if ($entry->hasAttributes()) {
|
||||
$attrs_to_remove = array();
|
||||
|
||||
foreach ($entry->attributes as $attr) {
|
||||
|
||||
if (strpos($attr->nodeName, 'on') === 0) {
|
||||
array_push($attrs_to_remove, $attr);
|
||||
}
|
||||
|
||||
if (strpos($attr->nodeName, "data-") === 0) {
|
||||
array_push($attrs_to_remove, $attr);
|
||||
}
|
||||
|
||||
if ($attr->nodeName == 'href' && stripos($attr->value, 'javascript:') === 0) {
|
||||
array_push($attrs_to_remove, $attr);
|
||||
}
|
||||
|
||||
if (in_array($attr->nodeName, $disallowed_attributes)) {
|
||||
array_push($attrs_to_remove, $attr);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($attrs_to_remove as $attr) {
|
||||
$entry->removeAttributeNode($attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $doc;
|
||||
}
|
||||
|
||||
public static function iframe_whitelisted($entry) {
|
||||
@$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST);
|
||||
|
||||
if ($src) {
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_IFRAME_WHITELISTED) as $plugin) {
|
||||
if ($plugin->hook_iframe_whitelisted($src))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
|
||||
if (!$owner) $owner = $_SESSION["uid"];
|
||||
|
||||
$res = trim($str); if (!$res) return '';
|
||||
|
||||
$doc = new DOMDocument();
|
||||
$doc->loadHTML('<?xml encoding="UTF-8">' . $res);
|
||||
$xpath = new DOMXPath($doc);
|
||||
|
||||
$rewrite_base_url = $site_url ? $site_url : get_self_url_prefix();
|
||||
|
||||
$entries = $xpath->query('(//a[@href]|//img[@src]|//source[@srcset|@src])');
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
|
||||
if ($entry->hasAttribute('href')) {
|
||||
$entry->setAttribute('href',
|
||||
rewrite_relative_url($rewrite_base_url, $entry->getAttribute('href')));
|
||||
|
||||
$entry->setAttribute('rel', 'noopener noreferrer');
|
||||
$entry->setAttribute("target", "_blank");
|
||||
}
|
||||
|
||||
if ($entry->hasAttribute('src')) {
|
||||
$entry->setAttribute('src',
|
||||
rewrite_relative_url($rewrite_base_url, $entry->getAttribute('src')));
|
||||
}
|
||||
|
||||
if ($entry->nodeName == 'img') {
|
||||
$entry->setAttribute('referrerpolicy', 'no-referrer');
|
||||
$entry->setAttribute('loading', 'lazy');
|
||||
}
|
||||
|
||||
if ($entry->hasAttribute('srcset')) {
|
||||
$matches = RSSUtils::decode_srcset($entry->getAttribute('srcset'));
|
||||
|
||||
for ($i = 0; $i < count($matches); $i++) {
|
||||
$matches[$i]["url"] = rewrite_relative_url($rewrite_base_url, $matches[$i]["url"]);
|
||||
}
|
||||
|
||||
$entry->setAttribute("srcset", RSSUtils::encode_srcset($matches));
|
||||
}
|
||||
|
||||
if ($entry->hasAttribute('src') &&
|
||||
($owner && get_pref("STRIP_IMAGES", $owner)) || $force_remove_images || $_SESSION["bw_limit"]) {
|
||||
|
||||
$p = $doc->createElement('p');
|
||||
|
||||
$a = $doc->createElement('a');
|
||||
$a->setAttribute('href', $entry->getAttribute('src'));
|
||||
|
||||
$a->appendChild(new DOMText($entry->getAttribute('src')));
|
||||
$a->setAttribute('target', '_blank');
|
||||
$a->setAttribute('rel', 'noopener noreferrer');
|
||||
|
||||
$p->appendChild($a);
|
||||
|
||||
if ($entry->nodeName == 'source') {
|
||||
|
||||
if ($entry->parentNode && $entry->parentNode->parentNode)
|
||||
$entry->parentNode->parentNode->replaceChild($p, $entry->parentNode);
|
||||
|
||||
} else if ($entry->nodeName == 'img') {
|
||||
if ($entry->parentNode)
|
||||
$entry->parentNode->replaceChild($p, $entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$entries = $xpath->query('//iframe');
|
||||
foreach ($entries as $entry) {
|
||||
if (!Sanitizer::iframe_whitelisted($entry)) {
|
||||
$entry->setAttribute('sandbox', 'allow-scripts');
|
||||
} else {
|
||||
if (is_prefix_https()) {
|
||||
$entry->setAttribute("src",
|
||||
str_replace("http://", "https://",
|
||||
$entry->getAttribute("src")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$allowed_elements = array('a', 'abbr', 'address', 'acronym', 'audio', 'article', 'aside',
|
||||
'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
|
||||
'caption', 'cite', 'center', 'code', 'col', 'colgroup',
|
||||
'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font',
|
||||
'dt', 'em', 'footer', 'figure', 'figcaption',
|
||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hr', 'html', 'i',
|
||||
'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
|
||||
'ol', 'p', 'picture', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
|
||||
'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
|
||||
'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
|
||||
'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
|
||||
|
||||
if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
|
||||
|
||||
$disallowed_attributes = array('id', 'style', 'class', 'width', 'height', 'allow');
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) {
|
||||
$retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
|
||||
if (is_array($retval)) {
|
||||
$doc = $retval[0];
|
||||
$allowed_elements = $retval[1];
|
||||
$disallowed_attributes = $retval[2];
|
||||
} else {
|
||||
$doc = $retval;
|
||||
}
|
||||
}
|
||||
|
||||
$doc->removeChild($doc->firstChild); //remove doctype
|
||||
$doc = Sanitizer::strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
|
||||
|
||||
$entries = $xpath->query('//iframe');
|
||||
foreach ($entries as $entry) {
|
||||
$div = $doc->createElement('div');
|
||||
$div->setAttribute('class', 'embed-responsive');
|
||||
$entry->parentNode->replaceChild($div, $entry);
|
||||
$div->appendChild($entry);
|
||||
}
|
||||
|
||||
if ($highlight_words && is_array($highlight_words)) {
|
||||
foreach ($highlight_words as $word) {
|
||||
|
||||
// http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
|
||||
|
||||
$elements = $xpath->query("//*/text()");
|
||||
|
||||
foreach ($elements as $child) {
|
||||
|
||||
$fragment = $doc->createDocumentFragment();
|
||||
$text = $child->textContent;
|
||||
|
||||
while (($pos = mb_stripos($text, $word)) !== false) {
|
||||
$fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
|
||||
$word = mb_substr($text, $pos, mb_strlen($word));
|
||||
$highlight = $doc->createElement('span');
|
||||
$highlight->appendChild(new DomText($word));
|
||||
$highlight->setAttribute('class', 'highlight');
|
||||
$fragment->appendChild($highlight);
|
||||
$text = mb_substr($text, $pos + mb_strlen($word));
|
||||
}
|
||||
|
||||
if (!empty($text)) $fragment->appendChild(new DomText($text));
|
||||
|
||||
$child->parentNode->replaceChild($fragment, $child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$res = $doc->saveHTML();
|
||||
|
||||
/* strip everything outside of <body>...</body> */
|
||||
|
||||
$res_frag = array();
|
||||
if (preg_match('/<body>(.*)<\/body>/is', $res, $res_frag)) {
|
||||
return $res_frag[1];
|
||||
} else {
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,474 @@
|
||||
<?php
|
||||
class UrlHelper {
|
||||
static function build_url($parts) {
|
||||
$tmp = $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
|
||||
|
||||
if (isset($parts['query'])) $tmp .= '?' . $parts['query'];
|
||||
if (isset($parts['fragment'])) $tmp .= '#' . $parts['fragment'];
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a (possibly) relative URL to a absolute one.
|
||||
*
|
||||
* @param string $url Base URL (i.e. from where the document is)
|
||||
* @param string $rel_url Possibly relative URL in the document
|
||||
*
|
||||
* @return string Absolute URL
|
||||
*/
|
||||
public static function rewrite_relative($url, $rel_url) {
|
||||
|
||||
$rel_parts = parse_url($rel_url);
|
||||
|
||||
if ($rel_parts['host'] && $rel_parts['scheme']) {
|
||||
return UrlHelper::validate($rel_url);
|
||||
} else if (strpos($rel_url, "//") === 0) {
|
||||
# protocol-relative URL (rare but they exist)
|
||||
return UrlHelper::validate("https:" . $rel_url);
|
||||
} else if (strpos($rel_url, "magnet:") === 0) {
|
||||
# allow magnet links
|
||||
return $rel_url;
|
||||
} else {
|
||||
$parts = parse_url($url);
|
||||
|
||||
$rel_parts['host'] = $parts['host'];
|
||||
$rel_parts['scheme'] = $parts['scheme'];
|
||||
|
||||
if (strpos($rel_parts['path'], '/') !== 0)
|
||||
$rel_parts['path'] = '/' . $rel_parts['path'];
|
||||
|
||||
$rel_parts['path'] = str_replace("/./", "/", $rel_parts['path']);
|
||||
$rel_parts['path'] = str_replace("//", "/", $rel_parts['path']);
|
||||
|
||||
return UrlHelper::validate(UrlHelper::build_url($rel_parts));
|
||||
}
|
||||
}
|
||||
|
||||
// extended filtering involves validation for safe ports and loopback
|
||||
static function validate($url, $extended_filtering = false) {
|
||||
|
||||
$url = clean($url);
|
||||
|
||||
# fix protocol-relative URLs
|
||||
if (strpos($url, "//") === 0)
|
||||
$url = "https:" . $url;
|
||||
|
||||
if (filter_var($url, FILTER_VALIDATE_URL) === false)
|
||||
return false;
|
||||
|
||||
$tokens = parse_url($url);
|
||||
|
||||
if (!$tokens['host'])
|
||||
return false;
|
||||
|
||||
if (!in_array(strtolower($tokens['scheme']), ['http', 'https']))
|
||||
return false;
|
||||
|
||||
if ($extended_filtering) {
|
||||
if (!in_array($tokens['port'], [80, 443, '']))
|
||||
return false;
|
||||
|
||||
if (strtolower($tokens['host']) == 'localhost' || $tokens['host'] == '::1' || strpos($tokens['host'], '127.') === 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
//convert IDNA hostname to punycode if possible
|
||||
if (function_exists("idn_to_ascii")) {
|
||||
if (mb_detect_encoding($tokens['host']) != 'ASCII') {
|
||||
$parts['host'] = idn_to_ascii($tokens['host']);
|
||||
$url = UrlHelper::build_url($tokens);
|
||||
}
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
static function resolve_redirects($url, $timeout, $nest = 0) {
|
||||
|
||||
// too many redirects
|
||||
if ($nest > 10)
|
||||
return false;
|
||||
|
||||
if (version_compare(PHP_VERSION, '7.1.0', '>=')) {
|
||||
$context_options = array(
|
||||
'http' => array(
|
||||
'header' => array(
|
||||
'Connection: close'
|
||||
),
|
||||
'method' => 'HEAD',
|
||||
'timeout' => $timeout,
|
||||
'protocol_version'=> 1.1)
|
||||
);
|
||||
|
||||
if (defined('_HTTP_PROXY')) {
|
||||
$context_options['http']['request_fulluri'] = true;
|
||||
$context_options['http']['proxy'] = _HTTP_PROXY;
|
||||
}
|
||||
|
||||
$context = stream_context_create($context_options);
|
||||
|
||||
$headers = get_headers($url, 0, $context);
|
||||
} else {
|
||||
$headers = get_headers($url, 0);
|
||||
}
|
||||
|
||||
if (is_array($headers)) {
|
||||
$headers = array_reverse($headers); // last one is the correct one
|
||||
|
||||
foreach($headers as $header) {
|
||||
if (stripos($header, 'Location:') === 0) {
|
||||
$url = UrlHelper::rewrite_relative($url, trim(substr($header, strlen('Location:'))));
|
||||
|
||||
return resolve_redirects($url, $timeout, $nest + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
// request failed?
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: max_size currently only works for CURL transfers
|
||||
// TODO: multiple-argument way is deprecated, first parameter is a hash now
|
||||
public static function fetch($options /* previously: 0: $url , 1: $type = false, 2: $login = false, 3: $pass = false,
|
||||
4: $post_query = false, 5: $timeout = false, 6: $timestamp = 0, 7: $useragent = false*/) {
|
||||
|
||||
global $fetch_last_error;
|
||||
global $fetch_last_error_code;
|
||||
global $fetch_last_error_content;
|
||||
global $fetch_last_content_type;
|
||||
global $fetch_last_modified;
|
||||
global $fetch_effective_url;
|
||||
global $fetch_effective_ip_addr;
|
||||
global $fetch_curl_used;
|
||||
global $fetch_domain_hits;
|
||||
|
||||
$fetch_last_error = false;
|
||||
$fetch_last_error_code = -1;
|
||||
$fetch_last_error_content = "";
|
||||
$fetch_last_content_type = "";
|
||||
$fetch_curl_used = false;
|
||||
$fetch_last_modified = "";
|
||||
$fetch_effective_url = "";
|
||||
$fetch_effective_ip_addr = "";
|
||||
|
||||
if (!is_array($fetch_domain_hits))
|
||||
$fetch_domain_hits = [];
|
||||
|
||||
if (!is_array($options)) {
|
||||
|
||||
// falling back on compatibility shim
|
||||
$option_names = [ "url", "type", "login", "pass", "post_query", "timeout", "last_modified", "useragent" ];
|
||||
$tmp = [];
|
||||
|
||||
for ($i = 0; $i < func_num_args(); $i++) {
|
||||
$tmp[$option_names[$i]] = func_get_arg($i);
|
||||
}
|
||||
|
||||
$options = $tmp;
|
||||
|
||||
/*$options = array(
|
||||
"url" => func_get_arg(0),
|
||||
"type" => @func_get_arg(1),
|
||||
"login" => @func_get_arg(2),
|
||||
"pass" => @func_get_arg(3),
|
||||
"post_query" => @func_get_arg(4),
|
||||
"timeout" => @func_get_arg(5),
|
||||
"timestamp" => @func_get_arg(6),
|
||||
"useragent" => @func_get_arg(7)
|
||||
); */
|
||||
}
|
||||
|
||||
$url = $options["url"];
|
||||
$type = isset($options["type"]) ? $options["type"] : false;
|
||||
$login = isset($options["login"]) ? $options["login"] : false;
|
||||
$pass = isset($options["pass"]) ? $options["pass"] : false;
|
||||
$post_query = isset($options["post_query"]) ? $options["post_query"] : false;
|
||||
$timeout = isset($options["timeout"]) ? $options["timeout"] : false;
|
||||
$last_modified = isset($options["last_modified"]) ? $options["last_modified"] : "";
|
||||
$useragent = isset($options["useragent"]) ? $options["useragent"] : false;
|
||||
$followlocation = isset($options["followlocation"]) ? $options["followlocation"] : true;
|
||||
$max_size = isset($options["max_size"]) ? $options["max_size"] : MAX_DOWNLOAD_FILE_SIZE; // in bytes
|
||||
$http_accept = isset($options["http_accept"]) ? $options["http_accept"] : false;
|
||||
$http_referrer = isset($options["http_referrer"]) ? $options["http_referrer"] : false;
|
||||
|
||||
$url = ltrim($url, ' ');
|
||||
$url = str_replace(' ', '%20', $url);
|
||||
|
||||
$url = UrlHelper::validate($url, true);
|
||||
|
||||
if (!$url) {
|
||||
$fetch_last_error = "Requested URL failed extended validation.";
|
||||
return false;
|
||||
}
|
||||
|
||||
$url_host = parse_url($url, PHP_URL_HOST);
|
||||
$ip_addr = gethostbyname($url_host);
|
||||
|
||||
if (!$ip_addr || strpos($ip_addr, "127.") === 0) {
|
||||
$fetch_last_error = "URL hostname failed to resolve or resolved to a loopback address ($ip_addr)";
|
||||
return false;
|
||||
}
|
||||
|
||||
$fetch_domain_hits[$url_host] += 1;
|
||||
|
||||
/*if ($fetch_domain_hits[$url_host] > MAX_FETCH_REQUESTS_PER_HOST) {
|
||||
user_error("Exceeded fetch request quota for $url_host: " . $fetch_domain_hits[$url_host], E_USER_WARNING);
|
||||
#return false;
|
||||
}*/
|
||||
|
||||
if (!defined('NO_CURL') && function_exists('curl_init') && !ini_get("open_basedir")) {
|
||||
|
||||
$fetch_curl_used = true;
|
||||
|
||||
$ch = curl_init($url);
|
||||
|
||||
$curl_http_headers = [];
|
||||
|
||||
if ($last_modified && !$post_query)
|
||||
array_push($curl_http_headers, "If-Modified-Since: $last_modified");
|
||||
|
||||
if ($http_accept)
|
||||
array_push($curl_http_headers, "Accept: " . $http_accept);
|
||||
|
||||
if (count($curl_http_headers) > 0)
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_http_headers);
|
||||
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout ? $timeout : FILE_FETCH_CONNECT_TIMEOUT);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout ? $timeout : FILE_FETCH_TIMEOUT);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, !ini_get("open_basedir") && $followlocation);
|
||||
curl_setopt($ch, CURLOPT_MAXREDIRS, 20);
|
||||
curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, $useragent ? $useragent :
|
||||
SELF_USER_AGENT);
|
||||
curl_setopt($ch, CURLOPT_ENCODING, "");
|
||||
|
||||
if ($http_referrer)
|
||||
curl_setopt($ch, CURLOPT_REFERER, $http_referrer);
|
||||
|
||||
if ($max_size) {
|
||||
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
|
||||
curl_setopt($ch, CURLOPT_BUFFERSIZE, 16384); // needed to get 5 arguments in progress function?
|
||||
|
||||
// holy shit closures in php
|
||||
// download & upload are *expected* sizes respectively, could be zero
|
||||
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function($curl_handle, $download_size, $downloaded, $upload_size, $uploaded) use( &$max_size) {
|
||||
Debug::log("[curl progressfunction] $downloaded $max_size", Debug::$LOG_EXTENDED);
|
||||
|
||||
return ($downloaded > $max_size) ? 1 : 0; // if max size is set, abort when exceeding it
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if (!ini_get("open_basedir")) {
|
||||
curl_setopt($ch, CURLOPT_COOKIEJAR, "/dev/null");
|
||||
}
|
||||
|
||||
if (defined('_HTTP_PROXY')) {
|
||||
curl_setopt($ch, CURLOPT_PROXY, _HTTP_PROXY);
|
||||
}
|
||||
|
||||
if ($post_query) {
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_query);
|
||||
}
|
||||
|
||||
if ($login && $pass)
|
||||
curl_setopt($ch, CURLOPT_USERPWD, "$login:$pass");
|
||||
|
||||
$ret = @curl_exec($ch);
|
||||
|
||||
$headers_length = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$headers = explode("\r\n", substr($ret, 0, $headers_length));
|
||||
$contents = substr($ret, $headers_length);
|
||||
|
||||
foreach ($headers as $header) {
|
||||
if (strstr($header, ": ") !== false) {
|
||||
list ($key, $value) = explode(": ", $header);
|
||||
|
||||
if (strtolower($key) == "last-modified") {
|
||||
$fetch_last_modified = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (substr(strtolower($header), 0, 7) == 'http/1.') {
|
||||
$fetch_last_error_code = (int) substr($header, 9, 3);
|
||||
$fetch_last_error = $header;
|
||||
}
|
||||
}
|
||||
|
||||
if (curl_errno($ch) === 23 || curl_errno($ch) === 61) {
|
||||
curl_setopt($ch, CURLOPT_ENCODING, 'none');
|
||||
$contents = @curl_exec($ch);
|
||||
}
|
||||
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$fetch_last_content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||
|
||||
$fetch_effective_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
|
||||
|
||||
if (!UrlHelper::validate($fetch_effective_url, true)) {
|
||||
$fetch_last_error = "URL received after redirection failed extended validation.";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$fetch_effective_ip_addr = gethostbyname(parse_url($fetch_effective_url, PHP_URL_HOST));
|
||||
|
||||
if (!$fetch_effective_ip_addr || strpos($fetch_effective_ip_addr, "127.") === 0) {
|
||||
$fetch_last_error = "URL hostname received after redirection failed to resolve or resolved to a loopback address ($fetch_effective_ip_addr)";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$fetch_last_error_code = $http_code;
|
||||
|
||||
if ($http_code != 200 || $type && strpos($fetch_last_content_type, "$type") === false) {
|
||||
|
||||
if (curl_errno($ch) != 0) {
|
||||
$fetch_last_error .= "; " . curl_errno($ch) . " " . curl_error($ch);
|
||||
}
|
||||
|
||||
$fetch_last_error_content = $contents;
|
||||
curl_close($ch);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$contents) {
|
||||
$fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
|
||||
curl_close($ch);
|
||||
return false;
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
$is_gzipped = RSSUtils::is_gzipped($contents);
|
||||
|
||||
if ($is_gzipped) {
|
||||
$tmp = @gzdecode($contents);
|
||||
|
||||
if ($tmp) $contents = $tmp;
|
||||
}
|
||||
|
||||
return $contents;
|
||||
} else {
|
||||
|
||||
$fetch_curl_used = false;
|
||||
|
||||
if ($login && $pass){
|
||||
$url_parts = array();
|
||||
|
||||
preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
|
||||
|
||||
$pass = urlencode($pass);
|
||||
|
||||
if ($url_parts[1] && $url_parts[2]) {
|
||||
$url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: should this support POST requests or not? idk
|
||||
|
||||
$context_options = array(
|
||||
'http' => array(
|
||||
'header' => array(
|
||||
'Connection: close'
|
||||
),
|
||||
'method' => 'GET',
|
||||
'ignore_errors' => true,
|
||||
'timeout' => $timeout ? $timeout : FILE_FETCH_TIMEOUT,
|
||||
'protocol_version'=> 1.1)
|
||||
);
|
||||
|
||||
if (!$post_query && $last_modified)
|
||||
array_push($context_options['http']['header'], "If-Modified-Since: $last_modified");
|
||||
|
||||
if ($http_accept)
|
||||
array_push($context_options['http']['header'], "Accept: $http_accept");
|
||||
|
||||
if ($http_referrer)
|
||||
array_push($context_options['http']['header'], "Referer: $http_referrer");
|
||||
|
||||
if (defined('_HTTP_PROXY')) {
|
||||
$context_options['http']['request_fulluri'] = true;
|
||||
$context_options['http']['proxy'] = _HTTP_PROXY;
|
||||
}
|
||||
|
||||
$context = stream_context_create($context_options);
|
||||
|
||||
$old_error = error_get_last();
|
||||
|
||||
$fetch_effective_url = resolve_redirects($url, $timeout ? $timeout : FILE_FETCH_CONNECT_TIMEOUT);
|
||||
|
||||
if (!UrlHelper::validate($fetch_effective_url, true)) {
|
||||
$fetch_last_error = "URL received after redirection failed extended validation.";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$fetch_effective_ip_addr = gethostbyname(parse_url($fetch_effective_url, PHP_URL_HOST));
|
||||
|
||||
if (!$fetch_effective_ip_addr || strpos($fetch_effective_ip_addr, "127.") === 0) {
|
||||
$fetch_last_error = "URL hostname received after redirection failed to resolve or resolved to a loopback address ($fetch_effective_ip_addr)";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = @file_get_contents($url, false, $context);
|
||||
|
||||
if (isset($http_response_header) && is_array($http_response_header)) {
|
||||
foreach ($http_response_header as $header) {
|
||||
if (strstr($header, ": ") !== false) {
|
||||
list ($key, $value) = explode(": ", $header);
|
||||
|
||||
$key = strtolower($key);
|
||||
|
||||
if ($key == 'content-type') {
|
||||
$fetch_last_content_type = $value;
|
||||
// don't abort here b/c there might be more than one
|
||||
// e.g. if we were being redirected -- last one is the right one
|
||||
} else if ($key == 'last-modified') {
|
||||
$fetch_last_modified = $value;
|
||||
} else if ($key == 'location') {
|
||||
$fetch_effective_url = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (substr(strtolower($header), 0, 7) == 'http/1.') {
|
||||
$fetch_last_error_code = (int) substr($header, 9, 3);
|
||||
$fetch_last_error = $header;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($fetch_last_error_code != 200) {
|
||||
$error = error_get_last();
|
||||
|
||||
if ($error['message'] != $old_error['message']) {
|
||||
$fetch_last_error .= "; " . $error["message"];
|
||||
}
|
||||
|
||||
$fetch_last_error_content = $data;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$is_gzipped = RSSUtils::is_gzipped($data);
|
||||
|
||||
if ($is_gzipped) {
|
||||
$tmp = @gzdecode($data);
|
||||
|
||||
if ($tmp) $data = $tmp;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
<?php
|
||||
class UserHelper {
|
||||
|
||||
static function authenticate($login, $password, $check_only = false, $service = false) {
|
||||
|
||||
if (!SINGLE_USER_MODE) {
|
||||
$user_id = false;
|
||||
$auth_module = false;
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_AUTH_USER) as $plugin) {
|
||||
|
||||
$user_id = (int) $plugin->authenticate($login, $password, $service);
|
||||
|
||||
if ($user_id) {
|
||||
$auth_module = strtolower(get_class($plugin));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($user_id && !$check_only) {
|
||||
|
||||
session_start();
|
||||
session_regenerate_id(true);
|
||||
|
||||
$_SESSION["uid"] = $user_id;
|
||||
$_SESSION["auth_module"] = $auth_module;
|
||||
|
||||
$pdo = Db::pdo();
|
||||
$sth = $pdo->prepare("SELECT login,access_level,pwd_hash FROM ttrss_users
|
||||
WHERE id = ?");
|
||||
$sth->execute([$user_id]);
|
||||
$row = $sth->fetch();
|
||||
|
||||
$_SESSION["name"] = $row["login"];
|
||||
$_SESSION["access_level"] = $row["access_level"];
|
||||
$_SESSION["csrf_token"] = bin2hex(get_random_bytes(16));
|
||||
|
||||
$usth = $pdo->prepare("UPDATE ttrss_users SET last_login = NOW() WHERE id = ?");
|
||||
$usth->execute([$user_id]);
|
||||
|
||||
$_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
|
||||
$_SESSION["user_agent"] = sha1($_SERVER['HTTP_USER_AGENT']);
|
||||
$_SESSION["pwd_hash"] = $row["pwd_hash"];
|
||||
|
||||
Pref_Prefs::initialize_user_prefs($_SESSION["uid"]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
} else {
|
||||
|
||||
$_SESSION["uid"] = 1;
|
||||
$_SESSION["name"] = "admin";
|
||||
$_SESSION["access_level"] = 10;
|
||||
|
||||
$_SESSION["hide_hello"] = true;
|
||||
$_SESSION["hide_logout"] = true;
|
||||
|
||||
$_SESSION["auth_module"] = false;
|
||||
|
||||
if (!$_SESSION["csrf_token"])
|
||||
$_SESSION["csrf_token"] = bin2hex(get_random_bytes(16));
|
||||
|
||||
$_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
|
||||
|
||||
Pref_Prefs::initialize_user_prefs($_SESSION["uid"]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static function load_user_plugins($owner_uid, $pluginhost = false) {
|
||||
|
||||
if (!$pluginhost) $pluginhost = PluginHost::getInstance();
|
||||
|
||||
if ($owner_uid && SCHEMA_VERSION >= 100 && !$_SESSION["safe_mode"]) {
|
||||
$plugins = get_pref("_ENABLED_PLUGINS", $owner_uid);
|
||||
|
||||
$pluginhost->load($plugins, PluginHost::KIND_USER, $owner_uid);
|
||||
|
||||
if (get_schema_version() > 100) {
|
||||
$pluginhost->load_data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function login_sequence() {
|
||||
$pdo = Db::pdo();
|
||||
|
||||
if (SINGLE_USER_MODE) {
|
||||
@session_start();
|
||||
UserHelper::authenticate("admin", null);
|
||||
startup_gettext();
|
||||
UserHelper::load_user_plugins($_SESSION["uid"]);
|
||||
} else {
|
||||
if (!validate_session()) $_SESSION["uid"] = false;
|
||||
|
||||
if (!$_SESSION["uid"]) {
|
||||
|
||||
if (AUTH_AUTO_LOGIN && UserHelper::authenticate(null, null)) {
|
||||
$_SESSION["ref_schema_version"] = get_schema_version(true);
|
||||
} else {
|
||||
UserHelper::authenticate(null, null, true);
|
||||
}
|
||||
|
||||
if (!$_SESSION["uid"]) {
|
||||
Pref_Users::logout_user();
|
||||
|
||||
Handler_Public::render_login_form();
|
||||
exit;
|
||||
}
|
||||
|
||||
} else {
|
||||
/* bump login timestamp */
|
||||
$sth = $pdo->prepare("UPDATE ttrss_users SET last_login = NOW() WHERE id = ?");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
|
||||
$_SESSION["last_login_update"] = time();
|
||||
}
|
||||
|
||||
if ($_SESSION["uid"]) {
|
||||
startup_gettext();
|
||||
UserHelper::load_user_plugins($_SESSION["uid"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function print_user_stylesheet() {
|
||||
$value = get_pref('USER_STYLESHEET');
|
||||
|
||||
if ($value) {
|
||||
print "<style type='text/css' id='user_css_style'>";
|
||||
print str_replace("<br/>", "\n", $value);
|
||||
print "</style>";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue