X-Git-Url: https://git.wh0rd.org/?a=blobdiff_plain;f=include%2Ffunctions.php;h=3cd21969d27f90f3b86d509832b1c8bfb7d2f6fc;hb=65e98f40867862eb345676e23b633b9f52109d30;hp=e1e63c2a36f097584fbaf95b8c876a069b7ea36e;hpb=f70d456a5bc509e56991195a2f003cf2714d0e13;p=tt-rss.git diff --git a/include/functions.php b/include/functions.php old mode 100644 new mode 100755 index e1e63c2a..3cd21969 --- a/include/functions.php +++ b/include/functions.php @@ -1,6 +1,6 @@ execute([$feed_id]); } else { - $sth = $pdo->prepare("DELETE FROM ttrss_user_entries + $sth = $pdo->prepare("DELETE FROM ttrss_user_entries USING ttrss_user_entries, ttrss_entries WHERE ttrss_entries.id = ref_id AND marked = false AND feed_id = ? AND $query_limit ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); - $sth->execute([$feed_id]); + $sth->execute([$feed_id]); } @@ -296,7 +301,7 @@ function feed_purge_interval($feed_id) { - $pdo = DB::pdo(); + $pdo = DB::pdo(); $sth = $pdo->prepare("SELECT purge_interval, owner_uid FROM ttrss_feeds WHERE id = ?"); @@ -316,15 +321,17 @@ } } + // TODO: max_size currently only works for CURL transfers // TODO: multiple-argument way is deprecated, first parameter is a hash now function fetch_file_contents($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*/) { + 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_curl_used; $fetch_last_error = false; @@ -333,6 +340,7 @@ $fetch_last_content_type = ""; $fetch_curl_used = false; $fetch_last_modified = ""; + $fetch_effective_url = ""; if (!is_array($options)) { @@ -367,6 +375,8 @@ $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; $url = ltrim($url, ' '); $url = str_replace(' ', '%20', $url); @@ -380,10 +390,16 @@ $ch = curl_init($url); - if ($last_modified && !$post_query) { - curl_setopt($ch, CURLOPT_HTTPHEADER, - array("If-Modified-Since: $last_modified")); - } + $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); @@ -398,12 +414,26 @@ curl_setopt($ch, CURLOPT_ENCODING, ""); //curl_setopt($ch, CURLOPT_REFERER, $url); + 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("[curl progressfunction] $downloaded $max_size"); + + 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('_CURL_HTTP_PROXY')) { - curl_setopt($ch, CURLOPT_PROXY, _CURL_HTTP_PROXY); + if (defined('_HTTP_PROXY')) { + curl_setopt($ch, CURLOPT_PROXY, _HTTP_PROXY); } if ($post_query) { @@ -421,18 +451,18 @@ $contents = substr($ret, $headers_length); foreach ($headers as $header) { - if (strstr($header, ": ") !== FALSE) { - list ($key, $value) = explode(": ", $header); + if (strstr($header, ": ") !== FALSE) { + list ($key, $value) = explode(": ", $header); - if (strtolower($key) == "last-modified") { - $fetch_last_modified = $value; - } - } + 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 (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) { @@ -443,6 +473,8 @@ $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); + $fetch_last_error_code = $http_code; if ($http_code != 200 || $type && strpos($fetch_last_content_type, "$type") === false) { @@ -483,44 +515,53 @@ // TODO: should this support POST requests or not? idk - if (!$post_query && $last_modified) { - $context = stream_context_create(array( - 'http' => array( - 'method' => 'GET', - 'ignore_errors' => true, - 'timeout' => $timeout ? $timeout : FILE_FETCH_TIMEOUT, - 'protocol_version'=> 1.1, - 'header' => "If-Modified-Since: $last_modified\r\n") - )); - } else { - $context = stream_context_create(array( - 'http' => array( - 'method' => 'GET', - 'ignore_errors' => true, - 'timeout' => $timeout ? $timeout : FILE_FETCH_TIMEOUT, - 'protocol_version'=> 1.1 - ))); + $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 (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 = $url; + $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; - } - } + 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); @@ -591,21 +632,21 @@ if (get_schema_version() < 63) $profile_qpart = ""; - $pdo = DB::pdo(); - $in_nested_tr = false; + $pdo = DB::pdo(); + $in_nested_tr = false; - try { + try { $pdo->beginTransaction(); } catch (Exception $e) { - $in_nested_tr = true; + $in_nested_tr = true; } $sth = $pdo->query("SELECT pref_name,def_value FROM ttrss_prefs"); - $profile = $profile ? $profile : null; + $profile = $profile ? $profile : null; $u_sth = $pdo->prepare("SELECT pref_name - FROM ttrss_user_prefs WHERE owner_uid = :uid AND + FROM ttrss_user_prefs WHERE owner_uid = :uid AND (profile = :profile OR (:profile IS NULL AND profile IS NULL))"); $u_sth->execute([':uid' => $uid, ':profile' => $profile]); @@ -629,7 +670,7 @@ $i_sth = $pdo->prepare("INSERT INTO ttrss_user_prefs (owner_uid,pref_name,value, profile) VALUES (?, ?, ?, ?)"); - $i_sth->execute([$uid, $line["pref_name"], $line["def_value"], $profile]); + $i_sth->execute([$uid, $line["pref_name"], $line["def_value"], $profile]); } } @@ -671,7 +712,14 @@ } if ($user_id && !$check_only) { - @session_start(); + + if (session_status() != PHP_SESSION_NONE) { + session_destroy(); + session_commit(); + } + + session_start(); + session_regenerate_id(true); $_SESSION["uid"] = $user_id; $_SESSION["version"] = VERSION_STATIC; @@ -728,7 +776,7 @@ // this is used for user http parameters unless HTML code is actually needed function clean($param) { if (is_array($param)) { - return array_map(strip_tags, $param); + return array_map("strip_tags", $param); } else if (is_string($param)) { return strip_tags($param); } else { @@ -741,7 +789,7 @@ $password = ""; $possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ"; - $i = 0; + $i = 0; while ($i < $length) { $char = substr($possible, mt_rand(0, strlen($possible)-1), 1); @@ -761,7 +809,7 @@ function initialize_user($uid) { - $pdo = DB::pdo(); + $pdo = DB::pdo(); $sth = $pdo->prepare("insert into ttrss_feeds (owner_uid,title,feed_url) values (?, 'Tiny Tiny RSS: Forum', @@ -770,10 +818,11 @@ } function logout_user() { - session_destroy(); + @session_destroy(); if (isset($_COOKIE[session_name()])) { setcookie(session_name(), '', time()-42000, '/'); } + session_commit(); } function validate_csrf($csrf_token) { @@ -796,7 +845,7 @@ } function login_sequence() { - $pdo = Db::pdo(); + $pdo = Db::pdo(); if (SINGLE_USER_MODE) { @session_start(); @@ -809,14 +858,13 @@ if (!$_SESSION["uid"]) { if (AUTH_AUTO_LOGIN && authenticate_user(null, null)) { - $_SESSION["ref_schema_version"] = get_schema_version(true); + $_SESSION["ref_schema_version"] = get_schema_version(true); } else { authenticate_user(null, null, true); } if (!$_SESSION["uid"]) { - @session_destroy(); - setcookie(session_name(), '', time()-42000, '/'); + logout_user(); render_login_form(); exit; @@ -836,19 +884,19 @@ /* cleanup ccache */ - $sth = $pdo->prepare("DELETE FROM ttrss_counters_cache WHERE owner_uid = ? - AND + $sth = $pdo->prepare("DELETE FROM ttrss_counters_cache WHERE owner_uid = ? + AND (SELECT COUNT(id) FROM ttrss_feeds WHERE ttrss_feeds.id = feed_id) = 0"); $sth->execute([$_SESSION['uid']]); - $sth = $pdo->prepare("DELETE FROM ttrss_cat_counters_cache WHERE owner_uid = ? - AND + $sth = $pdo->prepare("DELETE FROM ttrss_cat_counters_cache WHERE owner_uid = ? + AND (SELECT COUNT(id) FROM ttrss_feed_categories WHERE ttrss_feed_categories.id = feed_id) = 0"); - $sth->execute([$_SESSION['uid']]); + $sth->execute([$_SESSION['uid']]); } } @@ -1377,11 +1425,11 @@ $search_query_leftover = array(); $pdo = Db::pdo(); - + if ($search_language) $search_language = $pdo->quote(mb_strtolower($search_language)); else - $search_language = "english"; + $search_language = $pdo->quote("english"); foreach ($keywords as $k) { if (strpos($k, "-") === 0) { @@ -1523,38 +1571,31 @@ return false; } - 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 ''; + // check for locally cached (media) URLs and rewrite to local versions + // this is called separately after sanitize() and plugin render article hooks to allow + // plugins work on original source URLs used before caching + function rewrite_cached_urls($str) { $charset_hack = ' '; - $res = trim($res); if (!$res) return ''; - - libxml_use_internal_errors(true); + $res = trim($str); if (!$res) return ''; $doc = new DOMDocument(); $doc->loadHTML($charset_hack . $res); $xpath = new DOMXPath($doc); - $rewrite_base_url = $site_url ? $site_url : get_self_url_prefix(); + $entries = $xpath->query('(//img[@src]|//video[@poster]|//video/source[@src]|//audio/source[@src])'); - $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src]|//audio/source[@src])'); + $need_saving = false; 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'); - } + if ($entry->hasAttribute('src') || $entry->hasAttribute('poster')) { - if ($entry->hasAttribute('src')) { - $src = rewrite_relative_url($rewrite_base_url, $entry->getAttribute('src')); + // should be already absolutized because this is called after sanitize() + $src = $entry->hasAttribute('poster') ? $entry->getAttribute('poster') : $entry->getAttribute('src'); $cached_filename = CACHE_DIR . '/images/' . sha1($src); if (file_exists($cached_filename)) { @@ -1572,19 +1613,67 @@ $src = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($src) . $suffix; - if ($entry->hasAttribute('srcset')) { - $entry->removeAttribute('srcset'); - } + if ($entry->hasAttribute('poster')) + $entry->setAttribute('poster', $src); + else + $entry->setAttribute('src', $src); - if ($entry->hasAttribute('sizes')) { - $entry->removeAttribute('sizes'); - } + $need_saving = true; } + } + } + + if ($need_saving) { + $doc->removeChild($doc->firstChild); //remove doctype + $res = $doc->saveHTML(); + } + + return $res; + } + + 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 ''; + + $charset_hack = ' + + '; + + $res = trim($res); if (!$res) return ''; + + libxml_use_internal_errors(true); + + $doc = new DOMDocument(); + $doc->loadHTML($charset_hack . $res); + $xpath = new DOMXPath($doc); + + $rewrite_base_url = $site_url ? $site_url : get_self_url_prefix(); + + $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src]|//audio/source[@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'); + } + + if ($entry->hasAttribute('src')) { + $src = rewrite_relative_url($rewrite_base_url, $entry->getAttribute('src')); + + // cache stuff has gone to rewrite_cached_urls() $entry->setAttribute('src', $src); } if ($entry->nodeName == 'img') { + $entry->setAttribute('referrerpolicy', 'no-referrer'); + + $entry->removeAttribute('width'); + $entry->removeAttribute('height'); if ($entry->hasAttribute('src')) { $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME) === 'https'; @@ -1600,22 +1689,32 @@ } } } + } + + if ($entry->hasAttribute('src') && + ($owner && get_pref("STRIP_IMAGES", $owner)) || $force_remove_images || $_SESSION["bw_limit"]) { + + $p = $doc->createElement('p'); - if (($owner && get_pref("STRIP_IMAGES", $owner)) || - $force_remove_images || $_SESSION["bw_limit"]) { + $a = $doc->createElement('a'); + $a->setAttribute('href', $entry->getAttribute('src')); - $p = $doc->createElement('p'); + $a->appendChild(new DOMText($entry->getAttribute('src'))); + $a->setAttribute('target', '_blank'); + $a->setAttribute('rel', 'noopener noreferrer'); - $a = $doc->createElement('a'); - $a->setAttribute('href', $entry->getAttribute('src')); + $p->appendChild($a); - $a->appendChild(new DOMText($entry->getAttribute('src'))); - $a->setAttribute('target', '_blank'); - $a->setAttribute('rel', 'noopener noreferrer'); + if ($entry->nodeName == 'source') { - $p->appendChild($a); + 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); - $entry->parentNode->replaceChild($p, $entry); } } @@ -1638,7 +1737,7 @@ } } - $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside', + $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', @@ -1752,11 +1851,8 @@ } function tag_is_valid($tag) { - if ($tag == '') return false; - if (is_numeric($tag)) return false; - if (mb_strlen($tag) > 250) return false; - - if (!$tag) return false; + if (!$tag || is_numeric($tag) || mb_strlen($tag) > 250) + return false; return true; } @@ -1847,14 +1943,14 @@ while ($line = $sth->fetch()) { $filter_id = $line["id"]; - $match_any_rule = sql_bool_to_bool($line["match_any_rule"]); + $match_any_rule = sql_bool_to_bool($line["match_any_rule"]); $sth2 = $pdo->prepare("SELECT r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, r.match_on, t.name AS type_name FROM ttrss_filters2_rules AS r, ttrss_filter_types AS t WHERE - (match_on IS NOT NULL OR + (match_on IS NOT NULL OR (($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats_str)) AND (feed_id IS NULL OR feed_id = ?))) AND filter_type = t.id AND filter_id = ?"); @@ -1866,54 +1962,54 @@ while ($rule_line = $sth2->fetch()) { # print_r($rule_line); - if ($rule_line["match_on"]) { - $match_on = json_decode($rule_line["match_on"], true); + if ($rule_line["match_on"]) { + $match_on = json_decode($rule_line["match_on"], true); - if (in_array("0", $match_on) || in_array($feed_id, $match_on) || count(array_intersect($check_cats_fullids, $match_on)) > 0) { + if (in_array("0", $match_on) || in_array($feed_id, $match_on) || count(array_intersect($check_cats_fullids, $match_on)) > 0) { - $rule = array(); - $rule["reg_exp"] = $rule_line["reg_exp"]; - $rule["type"] = $rule_line["type_name"]; - $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]); + $rule = array(); + $rule["reg_exp"] = $rule_line["reg_exp"]; + $rule["type"] = $rule_line["type_name"]; + $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]); - array_push($rules, $rule); - } else if (!$match_any_rule) { - // this filter contains a rule that doesn't match to this feed/category combination - // thus filter has to be rejected + array_push($rules, $rule); + } else if (!$match_any_rule) { + // this filter contains a rule that doesn't match to this feed/category combination + // thus filter has to be rejected - $rules = []; - break; - } + $rules = []; + break; + } - } else { + } else { - $rule = array(); - $rule["reg_exp"] = $rule_line["reg_exp"]; - $rule["type"] = $rule_line["type_name"]; - $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]); + $rule = array(); + $rule["reg_exp"] = $rule_line["reg_exp"]; + $rule["type"] = $rule_line["type_name"]; + $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]); - array_push($rules, $rule); - } + array_push($rules, $rule); + } } if (count($rules) > 0) { - $sth2 = $pdo->prepare("SELECT a.action_param,t.name AS type_name - FROM ttrss_filters2_actions AS a, - ttrss_filter_actions AS t - WHERE - action_id = t.id AND filter_id = ?"); - $sth2->execute([$filter_id]); + $sth2 = $pdo->prepare("SELECT a.action_param,t.name AS type_name + FROM ttrss_filters2_actions AS a, + ttrss_filter_actions AS t + WHERE + action_id = t.id AND filter_id = ?"); + $sth2->execute([$filter_id]); - while ($action_line = $sth2->fetch()) { - # print_r($action_line); + while ($action_line = $sth2->fetch()) { + # print_r($action_line); - $action = array(); - $action["type"] = $action_line["type_name"]; - $action["param"] = $action_line["action_param"]; + $action = array(); + $action["type"] = $action_line["type_name"]; + $action["param"] = $action_line["action_param"]; - array_push($actions, $action); - } - } + array_push($actions, $action); + } + } $filter = array(); $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]); @@ -1966,7 +2062,7 @@ } $sth = $pdo->prepare("SELECT id FROM ttrss_feed_categories - WHERE (parent_cat = :parent OR (:parent IS NULL AND parent_cat IS NULL)) + WHERE (parent_cat = :parent OR (:parent IS NULL AND parent_cat IS NULL)) AND title = :title AND owner_uid = :uid"); $sth->execute([':parent' => $parent_cat_id, ':title' => $feed_cat, ':uid' => $_SESSION['uid']]); @@ -1981,7 +2077,7 @@ return true; } - $pdo->commit(); + $pdo->commit(); return false; } @@ -2166,7 +2262,7 @@ function cleanup_tags($days = 14, $limit = 1000) { - $days = (int) $days; + $days = (int) $days; if (DB_TYPE == "pgsql") { $interval_query = "date_updated < NOW() - INTERVAL '$days days'"; @@ -2176,9 +2272,9 @@ $tags_deleted = 0; - $pdo = Db::pdo(); + $pdo = Db::pdo(); - while ($limit > 0) { + while ($limit > 0) { $limit_part = 500; $sth = $pdo->prepare("SELECT ttrss_tags.id AS id @@ -2277,6 +2373,7 @@ if ($rule["cat_id"] > 0) { $children = Feeds::getChildCategories($rule["cat_id"], $owner_uid); array_push($children, $rule["cat_id"]); + $children = array_map("intval", $children); $children = join(",", $children); @@ -2343,7 +2440,6 @@ } function get_minified_js($files) { - require_once 'lib/jshrink/Minifier.php'; $rv = ''; @@ -2522,6 +2618,9 @@ should be loaded systemwide in config.php */ function send_local_file($filename) { if (file_exists($filename)) { + + if (is_writable($filename)) touch($filename); + $tmppluginhost = new PluginHost(); $tmppluginhost->load(PLUGINS, PluginHost::KIND_SYSTEM); @@ -2532,6 +2631,13 @@ } $mimetype = mime_content_type($filename); + + // this is hardly ideal but 1) only media is cached in images/ and 2) seemingly only mp4 + // video files are detected as octet-stream by mime_content_type() + + if ($mimetype == "application/octet-stream") + $mimetype = "video/mp4"; + header("Content-type: $mimetype"); $stamp = gmdate("D, d M Y H:i:s", filemtime($filename)) . " GMT"; @@ -2566,6 +2672,6 @@ return $default; } - function arr_qmarks($arr) { - return str_repeat('?,', count($arr) - 1) . '?'; - } + function arr_qmarks($arr) { + return str_repeat('?,', count($arr) - 1) . '?'; + }