<?php
define('EXPECTED_CONFIG_VERSION', 26);
- define('SCHEMA_VERSION', 133);
+ define('SCHEMA_VERSION', 134);
define('LABEL_BASE_INDEX', -1024);
define('PLUGIN_FEED_BASE_INDEX', -128);
$fetch_last_error_code = false;
$fetch_last_content_type = false;
$fetch_last_error_content = false; // curl only for the time being
+ $fetch_effective_url = false;
$fetch_curl_used = false;
- $suppress_debugging = false;
libxml_disable_entity_loader(true);
// default sleep interval between feed updates (sec)
define_default('MIN_CACHE_FILE_SIZE', 1024);
// do not cache files smaller than that (bytes)
+ define_default('MAX_CACHE_FILE_SIZE', 64*1024*1024);
+ // do not cache files larger than that (bytes)
+ define_default('MAX_DOWNLOAD_FILE_SIZE', 16*1024*1024);
+ // do not download general files larger than that (bytes)
define_default('CACHE_MAX_DAYS', 7);
// max age in days for various automatically cached (temporary) files
- define_default('MAX_CONDITIONAL_INTERVAL', 3600*6);
- // max interval between forced unconditional updates for servers
- // not complying with http if-modified-since (seconds)
+ define_default('MAX_CONDITIONAL_INTERVAL', 3600*12);
+ // max interval between forced unconditional updates for servers
+ // not complying with http if-modified-since (seconds)
/* tunables end here */
$schema_version = false;
- function _debug_suppress($suppress) {
- global $suppress_debugging;
-
- $suppress_debugging = $suppress;
+ // TODO: compat wrapper, remove at some point
+ function _debug($msg) {
+ Debug::log($msg);
}
- /**
- * Print a timestamped debug message.
- *
- * @param string $msg The debug message.
- * @return void
- */
- function _debug($msg, $show = true) {
- global $suppress_debugging;
-
- //echo "[$suppress_debugging] $msg $show\n";
-
- if ($suppress_debugging) return false;
-
- $ts = strftime("%H:%M:%S", time());
- if (function_exists('posix_getpid')) {
- $ts = "$ts/" . posix_getpid();
- }
-
- if ($show && !(defined('QUIET') && QUIET)) {
- print "[$ts] $msg\n";
- }
-
- if (defined('LOGFILE')) {
- $fp = fopen(LOGFILE, 'a+');
-
- if ($fp) {
- $locked = false;
-
- if (function_exists("flock")) {
- $tries = 0;
-
- // try to lock logfile for writing
- while ($tries < 5 && !$locked = flock($fp, LOCK_EX | LOCK_NB)) {
- sleep(1);
- ++$tries;
- }
-
- if (!$locked) {
- fclose($fp);
- return;
- }
- }
-
- fputs($fp, "[$ts] $msg\n");
-
- if (function_exists("flock")) {
- flock($fp, LOCK_UN);
- }
-
- fclose($fp);
- }
- }
-
- } // function _debug
-
/**
* Purge a feed old posts.
*
* @access public
* @return void
*/
- function purge_feed($feed_id, $purge_interval, $debug = false) {
+ function purge_feed($feed_id, $purge_interval) {
if (!$purge_interval) $purge_interval = feed_purge_interval($feed_id);
}
if (!$purge_unread)
- $query_limit = " unread = false AND ";
+ $query_limit = " unread = false AND ";
else
- $query_limit = "";
+ $query_limit = "";
$purge_interval = (int) $purge_interval;
$sth->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]);
}
CCache::update($feed_id, $owner_uid);
- if ($debug) {
- _debug("Purged feed $feed_id ($purge_interval): deleted $rows articles");
- }
+ Debug::log("Purged feed $feed_id ($purge_interval): deleted $rows articles");
return $rows;
} // function purge_feed
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 = ?");
}
}
+ // 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;
$fetch_last_content_type = "";
$fetch_curl_used = false;
$fetch_last_modified = "";
+ $fetch_effective_url = "";
if (!is_array($options)) {
$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);
$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);
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::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('_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) {
$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 (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) {
$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) {
// 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);
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]);
$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]);
}
}
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);
if ($user_id) {
- $_SESSION["auth_module"] = strtolower(get_class($plugin));
+ $auth_module = strtolower(get_class($plugin));
break;
}
}
if ($user_id && !$check_only) {
- @session_start();
+
+ session_start();
+ session_regenerate_id(true);
$_SESSION["uid"] = $user_id;
$_SESSION["version"] = VERSION_STATIC;
+ $_SESSION["auth_module"] = $auth_module;
$pdo = DB::pdo();
$sth = $pdo->prepare("SELECT login,access_level,pwd_hash FROM ttrss_users
}
}
+ // 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);
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',
}
function logout_user() {
- session_destroy();
+ @session_destroy();
if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time()-42000, '/');
}
+ session_commit();
}
function validate_csrf($csrf_token) {
}
function login_sequence() {
- $pdo = Db::pdo();
+ $pdo = Db::pdo();
if (SINGLE_USER_MODE) {
@session_start();
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;
/* 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']]);
}
}
}
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
$error_code = 5;
}
- if (db_escape_string("testTEST") != "testTEST") {
- $error_code = 12;
- }
-
return array("code" => $error_code, "message" => $ERRORS[$error_code]);
}
}
function checkbox_to_sql_bool($val) {
- return ($val == "on") ? "true" : "false";
+ return ($val == "on") ? 1 : 0;
}
function uniqid_short() {
$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);
"feed_debug_viewfeed" => __("Debug viewfeed()"),
"catchup_all" => __("Mark all feeds as read"),
"cat_toggle_collapse" => __("Un/collapse current category"),
- "toggle_combined_mode" => __("Toggle combined mode"),
- "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
+ "toggle_cdm_expanded" => __("Toggle auto expand in combined mode"),
+ "toggle_combined_mode" => __("Toggle combined mode")),
__("Go to") => array(
"goto_all" => __("All articles"),
"goto_fresh" => __("Fresh"),
"^(191)|Ctrl+/" => "help_dialog",
);
- if (get_pref('COMBINED_DISPLAY_MODE')) {
- $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
- $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
- }
+ $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
+ $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) {
$hotkeys = $plugin->hook_hotkey_map($hotkeys);
$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) {
switch ($commandpair[0]) {
case "title":
if ($commandpair[1]) {
- array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
- $pdo->quote(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 '%".
- $pdo->quote(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;
else if ($commandpair[1] == "false")
array_push($query_keywords, "($not (note IS NULL OR note = ''))");
else
- array_push($query_keywords, "($not (LOWER(note) LIKE '%".
- $pdo->quote(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;
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;
} 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;
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;
$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);
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))");
}
}
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 = '<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>';
- $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')));
+ if ($entry->hasAttribute('src') || $entry->hasAttribute('poster')) {
- $entry->setAttribute('rel', 'noopener noreferrer');
- }
-
- 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)) {
$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 = '<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ </head>';
+
+ $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';
}
}
}
+ }
+
+ 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');
- if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
- $force_remove_images || $_SESSION["bw_limit"]) {
+ $p->appendChild($a);
- $p = $doc->createElement('p');
+ if ($entry->nodeName == 'source') {
- $a = $doc->createElement('a');
- $a->setAttribute('href', $entry->getAttribute('src'));
+ if ($entry->parentNode && $entry->parentNode->parentNode)
+ $entry->parentNode->parentNode->replaceChild($p, $entry->parentNode);
- $a->appendChild(new DOMText($entry->getAttribute('src')));
- $a->setAttribute('target', '_blank');
- $a->setAttribute('rel', 'noopener noreferrer');
+ } else if ($entry->nodeName == 'img') {
- $p->appendChild($a);
+ if ($entry->parentNode)
+ $entry->parentNode->replaceChild($p, $entry);
- $entry->parentNode->replaceChild($p, $entry);
}
}
}
}
- $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',
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);
}
}
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;
}
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 = ?");
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"]);
}
}
- 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);
}
$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']]);
return true;
}
- $pdo->commit();
+ $pdo->commit();
return false;
}
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'";
$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
function filter_to_sql($filter, $owner_uid) {
$query = array();
+ $pdo = Db::pdo();
+
if (DB_TYPE == "pgsql")
$reg_qpart = "~";
else
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":
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"])) {
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);
}
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);
}
}
- $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
}
}
}
function get_theme_path($theme) {
+ if ($theme == "default.php")
+ return "css/default.css";
+
$check = "themes/$theme";
if (file_exists($check)) return $check;
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);
}
$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";
return $bad_tables;
}
- function arr_qmarks($arr) {
- return str_repeat('?,', count($arr) - 1) . '?';
- }
+ 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) . '?';
+ }