X-Git-Url: https://git.wh0rd.org/?a=blobdiff_plain;f=include%2Ffunctions.php;h=d88e96dd600ce75b3f32a6fcbb4d4099cd05ce2d;hb=ec5687a62b706f3ee91eb21aaeea278fcaa994bc;hp=dc2e9f64d31d17f9130cf464c36583fce12fa9d8;hpb=5e78b0c253d56d85b0339636c8793f93c835b792;p=tt-rss.git diff --git a/include/functions.php b/include/functions.php old mode 100644 new mode 100755 index dc2e9f64..d88e96dd --- a/include/functions.php +++ b/include/functions.php @@ -1,6 +1,6 @@ prepare("SELECT owner_uid FROM ttrss_feeds WHERE id = ?"); + $sth->execute([$feed_id]); $owner_uid = false; - if (db_num_rows($result) == 1) { - $owner_uid = db_fetch_result($result, 0, "owner_uid"); + if ($row = $sth->fetch()) { + $owner_uid = $row["owner_uid"]; } if ($purge_interval == -1 || !$purge_interval) { @@ -251,34 +259,36 @@ $purge_interval = FORCE_ARTICLE_PURGE; } - if (!$purge_unread) $query_limit = " unread = false AND "; + if (!$purge_unread) + $query_limit = " unread = false AND "; + else + $query_limit = ""; + + $purge_interval = (int) $purge_interval; if (DB_TYPE == "pgsql") { - $result = db_query("DELETE FROM ttrss_user_entries + $sth = $pdo->prepare("DELETE FROM ttrss_user_entries USING ttrss_entries WHERE ttrss_entries.id = ref_id AND marked = false AND - feed_id = '$feed_id' AND + feed_id = ? AND $query_limit ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'"); + $sth->execute([$feed_id]); } else { - -/* $result = db_query("DELETE FROM ttrss_user_entries WHERE - marked = false AND feed_id = '$feed_id' AND - (SELECT date_updated FROM ttrss_entries WHERE - id = ref_id) < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); */ - - $result = db_query("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 = '$feed_id' AND + feed_id = ? AND $query_limit ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); + $sth->execute([$feed_id]); + } - $rows = db_affected_rows($result); + $rows = $sth->rowCount(); CCache::update($feed_id, $owner_uid); @@ -291,12 +301,15 @@ function feed_purge_interval($feed_id) { - $result = db_query("SELECT purge_interval, owner_uid FROM ttrss_feeds - WHERE id = '$feed_id'"); + $pdo = DB::pdo(); + + $sth = $pdo->prepare("SELECT purge_interval, owner_uid FROM ttrss_feeds + WHERE id = ?"); + $sth->execute([$feed_id]); - if (db_num_rows($result) == 1) { - $purge_interval = db_fetch_result($result, 0, "purge_interval"); - $owner_uid = db_fetch_result($result, 0, "owner_uid"); + if ($row = $sth->fetch()) { + $purge_interval = $row["purge_interval"]; + $owner_uid = $row["owner_uid"]; if ($purge_interval == 0) $purge_interval = get_pref( 'PURGE_OLD_DAYS', $owner_uid); @@ -308,33 +321,17 @@ } } - /*function get_feed_update_interval($feed_id) { - $result = db_query("SELECT owner_uid, update_interval FROM - ttrss_feeds WHERE id = '$feed_id'"); - - if (db_num_rows($result) == 1) { - $update_interval = db_fetch_result($result, 0, "update_interval"); - $owner_uid = db_fetch_result($result, 0, "owner_uid"); - - if ($update_interval != 0) { - return $update_interval; - } else { - return get_pref('DEFAULT_UPDATE_INTERVAL', $owner_uid, false); - } - - } else { - return -1; - } - }*/ - + // 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; @@ -342,11 +339,13 @@ $fetch_last_error_content = ""; $fetch_last_content_type = ""; $fetch_curl_used = false; + $fetch_last_modified = ""; + $fetch_effective_url = ""; if (!is_array($options)) { // falling back on compatibility shim - $option_names = [ "url", "type", "login", "pass", "post_query", "timeout", "timestamp", "useragent" ]; + $option_names = [ "url", "type", "login", "pass", "post_query", "timeout", "last_modified", "useragent" ]; $tmp = []; for ($i = 0; $i < func_num_args(); $i++) { @@ -373,9 +372,11 @@ $pass = isset($options["pass"]) ? $options["pass"] : false; $post_query = isset($options["post_query"]) ? $options["post_query"] : false; $timeout = isset($options["timeout"]) ? $options["timeout"] : false; - $timestamp = isset($options["timestamp"]) ? $options["timestamp"] : 0; + $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); @@ -389,10 +390,16 @@ $ch = curl_init($url); - if ($timestamp && !$post_query) { - curl_setopt($ch, CURLOPT_HTTPHEADER, - array("If-Modified-Since: ".gmdate('D, d M Y H:i:s \G\M\T', $timestamp))); - } + $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); @@ -400,18 +407,33 @@ 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, ""); //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) { @@ -422,35 +444,56 @@ if ($login && $pass) curl_setopt($ch, CURLOPT_USERPWD, "$login:$pass"); - $contents = @curl_exec($ch); + $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); } - if ($contents === false) { - $fetch_last_error = curl_errno($ch) . " " . curl_error($ch); - curl_close($ch); - return false; - } - $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) { + if (curl_errno($ch) != 0) { - $fetch_last_error = curl_errno($ch) . " " . curl_error($ch); - } else { - $fetch_last_error = "HTTP Code: $http_code"; + $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); return $contents; @@ -472,39 +515,57 @@ // TODO: should this support POST requests or not? idk - if (!$post_query && $timestamp) { - $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: ".gmdate("D, d M Y H:i:s \\G\\M\\T\r\n", $timestamp) - ))); - } 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 $h) { - if (substr(strtolower($h), 0, 13) == 'content-type:') { - $fetch_last_content_type = substr($h, 14); - // 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 + 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($h), 0, 7) == 'http/1.') { - $fetch_last_error_code = (int) substr($h, 9, 3); + if (substr(strtolower($header), 0, 7) == 'http/1.') { + $fetch_last_error_code = (int) substr($header, 9, 3); + $fetch_last_error = $header; } } } @@ -513,9 +574,7 @@ $error = error_get_last(); if ($error['message'] != $old_error['message']) { - $fetch_last_error = $error["message"]; - } else { - $fetch_last_error = "HTTP Code: $fetch_last_error_code"; + $fetch_last_error .= "; " . $error["message"]; } $fetch_last_error_content = $data; @@ -548,9 +607,9 @@ $doc->loadHTML($html); $xpath = new DOMXPath($doc); - $base = $xpath->query('/html/head/base'); + $base = $xpath->query('/html/head/base[@href]'); foreach ($base as $b) { - $url = $b->getAttribute("href"); + $url = rewrite_relative_url($url, $b->getAttribute("href")); break; } @@ -571,52 +630,53 @@ function initialize_user_prefs($uid, $profile = false) { - $uid = db_escape_string($uid); + if (get_schema_version() < 63) $profile_qpart = ""; - if (!$profile) { - $profile = "NULL"; - $profile_qpart = "AND profile IS NULL"; - } else { - $profile_qpart = "AND profile = '$profile'"; - } + $pdo = DB::pdo(); + $in_nested_tr = false; - if (get_schema_version() < 63) $profile_qpart = ""; + try { + $pdo->beginTransaction(); + } catch (Exception $e) { + $in_nested_tr = true; + } - db_query("BEGIN"); + $sth = $pdo->query("SELECT pref_name,def_value FROM ttrss_prefs"); - $result = db_query("SELECT pref_name,def_value FROM ttrss_prefs"); + $profile = $profile ? $profile : null; - $u_result = db_query("SELECT pref_name - FROM ttrss_user_prefs WHERE owner_uid = '$uid' $profile_qpart"); + $u_sth = $pdo->prepare("SELECT pref_name + 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]); $active_prefs = array(); - while ($line = db_fetch_assoc($u_result)) { + while ($line = $u_sth->fetch()) { array_push($active_prefs, $line["pref_name"]); } - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { if (array_search($line["pref_name"], $active_prefs) === FALSE) { // print "adding " . $line["pref_name"] . "
"; - $line["def_value"] = db_escape_string($line["def_value"]); - $line["pref_name"] = db_escape_string($line["pref_name"]); - if (get_schema_version() < 63) { - db_query("INSERT INTO ttrss_user_prefs + $i_sth = $pdo->prepare("INSERT INTO ttrss_user_prefs (owner_uid,pref_name,value) VALUES - ('$uid', '".$line["pref_name"]."','".$line["def_value"]."')"); + (?, ?, ?)"); + $i_sth->execute([$uid, $line["pref_name"], $line["def_value"]]); } else { - db_query("INSERT INTO ttrss_user_prefs + $i_sth = $pdo->prepare("INSERT INTO ttrss_user_prefs (owner_uid,pref_name,value, profile) VALUES - ('$uid', '".$line["pref_name"]."','".$line["def_value"]."', $profile)"); + (?, ?, ?, ?)"); + $i_sth->execute([$uid, $line["pref_name"], $line["def_value"], $profile]); } } } - db_query("COMMIT"); + if (!$in_nested_tr) $pdo->commit(); } @@ -657,19 +717,22 @@ $_SESSION["uid"] = $user_id; $_SESSION["version"] = VERSION_STATIC; - $result = db_query("SELECT login,access_level,pwd_hash FROM ttrss_users - WHERE id = '$user_id'"); + $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"] = db_fetch_result($result, 0, "login"); - $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level"); + $_SESSION["name"] = $row["login"]; + $_SESSION["access_level"] = $row["access_level"]; $_SESSION["csrf_token"] = uniqid_short(); - db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " . - $_SESSION["uid"]); + $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"] = db_fetch_result($result, 0, "pwd_hash"); + $_SESSION["pwd_hash"] = $row["pwd_hash"]; $_SESSION["last_version_check"] = time(); @@ -703,12 +766,23 @@ } } + // 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); + } else if (is_string($param)) { + return strip_tags($param); + } else { + return $param; + } + } + function make_password($length = 8) { $password = ""; $possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ"; - $i = 0; + $i = 0; while ($i < $length) { $char = substr($possible, mt_rand(0, strlen($possible)-1), 1); @@ -728,16 +802,20 @@ function initialize_user($uid) { - db_query("insert into ttrss_feeds (owner_uid,title,feed_url) - values ('$uid', 'Tiny Tiny RSS: Forum', + $pdo = DB::pdo(); + + $sth = $pdo->prepare("insert into ttrss_feeds (owner_uid,title,feed_url) + values (?, 'Tiny Tiny RSS: Forum', 'http://tt-rss.org/forum/rss.php')"); + $sth->execute([$uid]); } function logout_user() { - session_destroy(); + @session_destroy(); if (isset($_COOKIE[session_name()])) { setcookie(session_name(), '', time()-42000, '/'); } + session_commit(); } function validate_csrf($csrf_token) { @@ -760,6 +838,8 @@ } function login_sequence() { + $pdo = Db::pdo(); + if (SINGLE_USER_MODE) { @session_start(); authenticate_user("admin", null); @@ -771,14 +851,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; @@ -786,8 +865,9 @@ } else { /* bump login timestamp */ - db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " . - $_SESSION["uid"]); + $sth = $pdo->prepare("UPDATE ttrss_users SET last_login = NOW() WHERE id = ?"); + $sth->execute([$_SESSION['uid']]); + $_SESSION["last_login_update"] = time(); } @@ -797,16 +877,19 @@ /* cleanup ccache */ - db_query("DELETE FROM ttrss_counters_cache WHERE owner_uid = ". - $_SESSION["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"); - db_query("DELETE FROM ttrss_cat_counters_cache WHERE owner_uid = ". - $_SESSION["uid"] . " AND + $sth->execute([$_SESSION['uid']]); + + $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']]); } } @@ -910,19 +993,11 @@ } function sql_bool_to_bool($s) { - if ($s == "t" || $s == "1" || strtolower($s) == "true") { - return true; - } else { - return false; - } + return $s && ($s !== "f" && $s !== "false"); //no-op for PDO, backwards compat for legacy layer } function bool_to_sql_bool($s) { - if ($s) { - return "true"; - } else { - return "false"; - } + return $s ? 1 : 0; } // Session caching removed due to causing wrong redirects to upgrade @@ -931,9 +1006,11 @@ function get_schema_version($nocache = false) { global $schema_version; + $pdo = DB::pdo(); + if (!$schema_version && !$nocache) { - $result = db_query("SELECT schema_version FROM ttrss_version"); - $version = db_fetch_result($result, 0, "schema_version"); + $row = $pdo->query("SELECT schema_version FROM ttrss_version")->fetch(); + $version = $row["schema_version"]; $schema_version = $version; return $version; } else { @@ -952,17 +1029,6 @@ $error_code = 5; } - if (DB_TYPE == "mysql") { - $result = db_query("SELECT true", false); - if (db_num_rows($result) != 1) { - $error_code = 10; - } - } - - if (db_escape_string("testTEST") != "testTEST") { - $error_code = 12; - } - return array("code" => $error_code, "message" => $ERRORS[$error_code]); } @@ -1038,36 +1104,9 @@ return Feeds::getFeedArticles($feed, $is_cat, true, $_SESSION["uid"]); } - - /*function get_pgsql_version() { - $result = db_query("SELECT version() AS version"); - $version = explode(" ", db_fetch_result($result, 0, "version")); - return $version[1]; - }*/ - function checkbox_to_sql_bool($val) { - return ($val == "on") ? "true" : "false"; - } - - /*function getFeedCatTitle($id) { - if ($id == -1) { - return __("Special"); - } else if ($id < LABEL_BASE_INDEX) { - return __("Labels"); - } else if ($id > 0) { - $result = db_query("SELECT ttrss_feed_categories.title - FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND - cat_id = ttrss_feed_categories.id"); - if (db_num_rows($result) == 1) { - return db_fetch_result($result, 0, "title"); - } else { - return __("Uncategorized"); - } - } else { - return "getFeedCatTitle($id) failed"; - } - - }*/ + return ($val == "on") ? 1 : 0; + } function uniqid_short() { return uniqid(base_convert(rand(), 10, 36)); @@ -1090,6 +1129,7 @@ $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT"); $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY"); $params["bw_limit"] = (int) $_SESSION["bw_limit"]; + $params["is_default_pw"] = Pref_Prefs::isdefaultpassword(); $params["label_base_index"] = (int) LABEL_BASE_INDEX; $theme = get_pref( "USER_CSS_THEME", false, false); @@ -1102,11 +1142,15 @@ $params["sanity_checksum"] = sha1(file_get_contents("include/sanity_check.php")); - $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM - ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]); + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM + ttrss_feeds WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + $row = $sth->fetch(); - $max_feed_id = db_fetch_result($result, 0, "mid"); - $num_feeds = db_fetch_result($result, 0, "nf"); + $max_feed_id = $row["mid"]; + $num_feeds = $row["nf"]; $params["max_feed_id"] = (int) $max_feed_id; $params["num_feeds"] = (int) $num_feeds; @@ -1307,11 +1351,15 @@ function make_runtime_info($disable_update_check = false) { $data = array(); - $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM - ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]); + $pdo = Db::pdo(); - $max_feed_id = db_fetch_result($result, 0, "mid"); - $num_feeds = db_fetch_result($result, 0, "nf"); + $sth = $pdo->prepare("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM + ttrss_feeds WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + $row = $sth->fetch(); + + $max_feed_id = $row['mid']; + $num_feeds = $row['nf']; $data["max_feed_id"] = (int) $max_feed_id; $data["num_feeds"] = (int) $num_feeds; @@ -1369,10 +1417,12 @@ $search_words = array(); $search_query_leftover = array(); + $pdo = Db::pdo(); + if ($search_language) - $search_language = db_escape_string(mb_strtolower($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) { @@ -1387,21 +1437,21 @@ switch ($commandpair[0]) { case "title": if ($commandpair[1]) { - array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%". - db_escape_string(mb_strtolower($commandpair[1]))."%'))"); + array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE ". + $pdo->quote('%' . mb_strtolower($commandpair[1]) . '%') ."))"); } else { array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); array_push($search_words, $k); } break; case "author": if ($commandpair[1]) { - array_push($query_keywords, "($not (LOWER(author) LIKE '%". - db_escape_string(mb_strtolower($commandpair[1]))."%'))"); + array_push($query_keywords, "($not (LOWER(author) LIKE ". + $pdo->quote('%' . mb_strtolower($commandpair[1]) . '%')."))"); } else { array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); array_push($search_words, $k); } break; @@ -1412,11 +1462,11 @@ else if ($commandpair[1] == "false") array_push($query_keywords, "($not (note IS NULL OR note = ''))"); else - array_push($query_keywords, "($not (LOWER(note) LIKE '%". - db_escape_string(mb_strtolower($commandpair[1]))."%'))"); + array_push($query_keywords, "($not (LOWER(note) LIKE ". + $pdo->quote('%' . mb_strtolower($commandpair[1]) . '%')."))"); } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER(".$pdo->quote("%$k%").") + OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); if (!$not) array_push($search_words, $k); } break; @@ -1428,8 +1478,8 @@ else array_push($query_keywords, "($not (marked = false))"); } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER(".$pdo->quote("%$k%").") + OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); if (!$not) array_push($search_words, $k); } break; @@ -1442,7 +1492,7 @@ } else { array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); if (!$not) array_push($search_words, $k); } break; @@ -1454,8 +1504,8 @@ array_push($query_keywords, "($not (unread = false))"); } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER(".$pdo->quote("%$k%").") + OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); if (!$not) array_push($search_words, $k); } break; @@ -1475,8 +1525,8 @@ $k = mb_strtolower($k); array_push($search_query_leftover, $not ? "!$k" : $k); } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER(".$pdo->quote("%$k%").") + OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); } if (!$not) array_push($search_words, $k); @@ -1485,11 +1535,11 @@ } if (count($search_query_leftover) > 0) { - $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover)); + $search_query_leftover = $pdo->quote(implode(" & ", $search_query_leftover)); if (DB_TYPE == "pgsql") { array_push($query_keywords, - "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))"); + "(tsvector_combined @@ to_tsquery($search_language, $search_query_leftover))"); } } @@ -1514,38 +1564,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)) { @@ -1563,19 +1606,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'; @@ -1591,22 +1682,32 @@ } } } + } - if (($owner && get_pref("STRIP_IMAGES", $owner)) || - $force_remove_images || $_SESSION["bw_limit"]) { + if ($entry->hasAttribute('src') && + ($owner && get_pref("STRIP_IMAGES", $owner)) || $force_remove_images || $_SESSION["bw_limit"]) { - $p = $doc->createElement('p'); + $p = $doc->createElement('p'); - $a = $doc->createElement('a'); - $a->setAttribute('href', $entry->getAttribute('src')); + $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'); + $a->appendChild(new DOMText($entry->getAttribute('src'))); + $a->setAttribute('target', '_blank'); + $a->setAttribute('rel', 'noopener noreferrer'); - $p->appendChild($a); + $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); - $entry->parentNode->replaceChild($p, $entry); } } @@ -1629,12 +1730,12 @@ } } - $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', 'dt', 'em', 'footer', 'figure', 'figcaption', - 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i', + 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hr', 'html', 'i', 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript', 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section', 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary', @@ -1743,11 +1844,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; } @@ -1814,6 +1912,7 @@ function load_filters($feed_id, $owner_uid) { $filters = array(); + $feed_id = (int) $feed_id; $cat_id = (int)Feeds::getFeedCategory($feed_id); if ($cat_id == 0) @@ -1821,8 +1920,11 @@ else $null_cat_qpart = ""; - $result = db_query("SELECT * FROM ttrss_filters2 WHERE - owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title"); + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT * FROM ttrss_filters2 WHERE + owner_uid = ? AND enabled = true ORDER BY order_id, title"); + $sth->execute([$owner_uid]); $check_cats = array_merge( Feeds::getParentCategories($cat_id, $owner_uid), @@ -1831,74 +1933,76 @@ $check_cats_str = join(",", $check_cats); $check_cats_fullids = array_map(function($a) { return "CAT:$a"; }, $check_cats); - while ($line = db_fetch_assoc($result)) { + 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"]); - $result2 = db_query("SELECT + $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 = '$feed_id'))) AND - filter_type = t.id AND filter_id = '$filter_id'"); + (feed_id IS NULL OR feed_id = ?))) AND + filter_type = t.id AND filter_id = ?"); + $sth2->execute([$feed_id, $filter_id]); $rules = array(); $actions = array(); - while ($rule_line = db_fetch_assoc($result2)) { + 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) { - $result2 = db_query("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 = '$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 = db_fetch_assoc($result2)) { - # 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"]); @@ -1928,10 +2032,6 @@ } } - function feed_has_icon($id) { - return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0; - } - function init_plugins() { PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL); @@ -1942,33 +2042,36 @@ if (!$feed_cat) return false; - db_query("BEGIN"); + $feed_cat = mb_substr($feed_cat, 0, 250); + if (!$parent_cat_id) $parent_cat_id = null; - if ($parent_cat_id) { - $parent_qpart = "parent_cat = '$parent_cat_id'"; - $parent_insert = "'$parent_cat_id'"; - } else { - $parent_qpart = "parent_cat IS NULL"; - $parent_insert = "NULL"; - } + $pdo = Db::pdo(); + $tr_in_progress = false; - $feed_cat = mb_substr($feed_cat, 0, 250); + try { + $pdo->beginTransaction(); + } catch (Exception $e) { + $tr_in_progress = true; + } - $result = db_query( - "SELECT id FROM ttrss_feed_categories - WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]); + $sth = $pdo->prepare("SELECT id FROM ttrss_feed_categories + 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']]); - if (db_num_rows($result) == 0) { + if (!$sth->fetch()) { - $result = db_query( - "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat) - VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)"); + $sth = $pdo->prepare("INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat) + VALUES (?, ?, ?)"); + $sth->execute([$_SESSION['uid'], $feed_cat, $parent_cat_id]); - db_query("COMMIT"); + if (!$tr_in_progress) $pdo->commit(); return true; } + $pdo->commit(); + return false; } @@ -2038,24 +2141,28 @@ if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - $sql_is_cat = bool_to_sql_bool($is_cat); + $is_cat = bool_to_sql_bool($is_cat); + + $pdo = Db::pdo(); - $result = db_query("SELECT access_key FROM ttrss_access_keys - WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat - AND owner_uid = " . $owner_uid); + $sth = $pdo->prepare("SELECT access_key FROM ttrss_access_keys + WHERE feed_id = ? AND is_cat = ? + AND owner_uid = ?"); + $sth->execute([$feed_id, (int)$is_cat, $owner_uid]); - if (db_num_rows($result) == 1) { - return db_fetch_result($result, 0, "access_key"); + if ($row = $sth->fetch()) { + return $row["access_key"]; } else { - $key = db_escape_string(uniqid_short()); + $key = uniqid_short(); - $result = db_query("INSERT INTO ttrss_access_keys + $sth = $pdo->prepare("INSERT INTO ttrss_access_keys (access_key, feed_id, is_cat, owner_uid) - VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')"); + VALUES (?, ?, ?, ?)"); + + $sth->execute([$key, $feed_id, (int)$is_cat, $owner_uid]); return $key; } - return false; } function get_feeds_from_html($url, $content) @@ -2148,6 +2255,8 @@ function cleanup_tags($days = 14, $limit = 1000) { + $days = (int) $days; + if (DB_TYPE == "pgsql") { $interval_query = "date_updated < NOW() - INTERVAL '$days days'"; } else if (DB_TYPE == "mysql") { @@ -2156,27 +2265,28 @@ $tags_deleted = 0; + $pdo = Db::pdo(); + while ($limit > 0) { $limit_part = 500; - $query = "SELECT ttrss_tags.id AS id + $sth = $pdo->prepare("SELECT ttrss_tags.id AS id FROM ttrss_tags, ttrss_user_entries, ttrss_entries WHERE post_int_id = int_id AND $interval_query AND - ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part"; - - $result = db_query($query); + ref_id = ttrss_entries.id AND tag_cache != '' LIMIT ?"); + $sth->execute([$limit]); $ids = array(); - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { array_push($ids, $line['id']); } if (count($ids) > 0) { $ids = join(",", $ids); - $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)"); - $tags_deleted += db_affected_rows($tmp_result); + $usth = $pdo->query("DELETE FROM ttrss_tags WHERE id IN ($ids)"); + $tags_deleted = $usth->rowCount(); } else { break; } @@ -2201,6 +2311,8 @@ function filter_to_sql($filter, $owner_uid) { $query = array(); + $pdo = Db::pdo(); + if (DB_TYPE == "pgsql") $reg_qpart = "~"; else @@ -2213,7 +2325,7 @@ if ($regexp_valid) { - $rule['reg_exp'] = db_escape_string($rule['reg_exp']); + $rule['reg_exp'] = $pdo->quote($rule['reg_exp']); switch ($rule["type"]) { case "title": @@ -2246,7 +2358,7 @@ if (isset($rule['inverse'])) $qpart = "NOT ($qpart)"; if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) { - $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]); + $qpart .= " AND feed_id = " . $pdo->quote($rule["feed_id"]); } if (isset($rule["cat_id"])) { @@ -2254,6 +2366,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); @@ -2320,15 +2433,14 @@ } function get_minified_js($files) { - require_once 'lib/jshrink/Minifier.php'; $rv = ''; foreach ($files as $js) { if (!isset($_GET['debug'])) { - $cached_file = CACHE_DIR . "/js/".basename($js).".js"; + $cached_file = CACHE_DIR . "/js/".basename($js); - if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) { + if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js")) { list($header, $contents) = explode("\n", file_get_contents($cached_file), 2); @@ -2342,12 +2454,12 @@ } } - $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js")); + $minified = JShrink\Minifier::minify(file_get_contents("js/$js")); file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified); $rv .= $minified; } else { - $rv .= file_get_contents("js/$js.js"); // no cache in debug mode + $rv .= file_get_contents("js/$js"); // no cache in debug mode } } @@ -2410,6 +2522,9 @@ } function get_theme_path($theme) { + if ($theme == "default.php") + return "css/default.css"; + $check = "themes/$theme"; if (file_exists($check)) return $check; @@ -2487,3 +2602,69 @@ } } + /* this is essentially a wrapper for readfile() which allows plugins to hook + output with httpd-specific "fast" implementation i.e. X-Sendfile or whatever else + + hook function should return true if request was handled (or at least attempted to) + + note that this can be called without user context so the plugin to handle this + 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); + $tmppluginhost->load_data(); + + foreach ($tmppluginhost->get_hooks(PluginHost::HOOK_SEND_LOCAL_FILE) as $plugin) { + if ($plugin->hook_send_local_file($filename)) return true; + } + + $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"; + header("Last-Modified: $stamp", true); + + return readfile($filename); + } else { + return false; + } + } + + function check_mysql_tables() { + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT engine, table_name FROM information_schema.tables WHERE + table_schema = ? AND table_name LIKE 'ttrss_%' AND engine != 'InnoDB'"); + $sth->execute([DB_NAME]); + + $bad_tables = []; + + while ($line = $sth->fetch()) { + array_push($bad_tables, $line); + } + + return $bad_tables; + } + + function validate_field($string, $allowed, $default = "") { + if (in_array($string, $allowed)) + return $string; + else + return $default; + } + + function arr_qmarks($arr) { + return str_repeat('?,', count($arr) - 1) . '?'; + }