summaryrefslogtreecommitdiff
path: root/classes
diff options
context:
space:
mode:
Diffstat (limited to 'classes')
-rwxr-xr-xclasses/api.php12
-rwxr-xr-xclasses/article.php36
-rw-r--r--classes/auth/base.php6
-rw-r--r--classes/backend.php2
-rw-r--r--classes/digest.php2
-rwxr-xr-xclasses/feeditem/atom.php8
-rwxr-xr-xclasses/feeditem/common.php31
-rwxr-xr-xclasses/feeditem/rss.php8
-rwxr-xr-xclasses/feeds.php25
-rwxr-xr-xclasses/handler/public.php20
-rw-r--r--classes/iauthmodule.php2
-rwxr-xr-xclasses/logger/sql.php11
-rw-r--r--classes/pluginhandler.php5
-rwxr-xr-xclasses/pluginhost.php66
-rwxr-xr-xclasses/pref/feeds.php2
-rw-r--r--classes/pref/prefs.php266
-rwxr-xr-xclasses/rpc.php13
-rwxr-xr-xclasses/rssutils.php136
18 files changed, 489 insertions, 162 deletions
diff --git a/classes/api.php b/classes/api.php
index 44c9841ce..6fb87d04f 100755
--- a/classes/api.php
+++ b/classes/api.php
@@ -74,10 +74,10 @@ class API extends Handler {
}
if (get_pref("ENABLE_API_ACCESS", $uid)) {
- if (authenticate_user($login, $password)) { // try login with normal password
+ if (authenticate_user($login, $password, false, Auth_Base::AUTH_SERVICE_API)) { // try login with normal password
$this->wrap(self::STATUS_OK, array("session_id" => session_id(),
"api_level" => self::API_LEVEL));
- } else if (authenticate_user($login, $password_base64)) { // else try with base64_decoded password
+ } else if (authenticate_user($login, $password_base64, false, Auth_Base::AUTH_SERVICE_API)) { // else try with base64_decoded password
$this->wrap(self::STATUS_OK, array("session_id" => session_id(),
"api_level" => self::API_LEVEL));
} else { // else we are not logged in
@@ -535,6 +535,7 @@ class API extends Handler {
/* Labels */
+ /* API only: -4 All feeds, including virtual feeds */
if ($cat_id == -4 || $cat_id == -2) {
$counters = Counters::getLabelCounters(true);
@@ -582,7 +583,7 @@ class API extends Handler {
if ($include_nested && $cat_id) {
$sth = $pdo->prepare("SELECT
id, title, order_id FROM ttrss_feed_categories
- WHERE parent_cat = ? AND owner_uid = ? ORDER BY id, title");
+ WHERE parent_cat = ? AND owner_uid = ? ORDER BY order_id, title");
$sth->execute([$cat_id, $_SESSION['uid']]);
@@ -611,12 +612,13 @@ class API extends Handler {
$limit_qpart = "";
}
+ /* API only: -3 All feeds, excluding virtual feeds (e.g. Labels and such) */
if ($cat_id == -4 || $cat_id == -3) {
$sth = $pdo->prepare("SELECT
id, feed_url, cat_id, title, order_id, ".
SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated
FROM ttrss_feeds WHERE owner_uid = ?
- ORDER BY cat_id, title " . $limit_qpart);
+ ORDER BY order_id, title " . $limit_qpart);
$sth->execute([$_SESSION['uid']]);
} else {
@@ -627,7 +629,7 @@ class API extends Handler {
FROM ttrss_feeds WHERE
(cat_id = :cat OR (:cat = 0 AND cat_id IS NULL))
AND owner_uid = :uid
- ORDER BY cat_id, title " . $limit_qpart);
+ ORDER BY order_id, title " . $limit_qpart);
$sth->execute([":uid" => $_SESSION['uid'], ":cat" => $cat_id]);
}
diff --git a/classes/article.php b/classes/article.php
index 943528f2a..fc81838ed 100755
--- a/classes/article.php
+++ b/classes/article.php
@@ -305,19 +305,9 @@ class Article extends Handler_Protected {
post_int_id = ? AND owner_uid = ?");
$sth->execute([$int_id, $_SESSION['uid']]);
- foreach ($tags as $tag) {
- $tag = Article::sanitize_tag($tag);
-
- if (!Article::tag_is_valid($tag)) {
- continue;
- }
-
- if (preg_match("/^[0-9]*$/", $tag)) {
- continue;
- }
-
- // print "<!-- $id : $int_id : $tag -->";
+ $tags = FeedItem_Common::normalize_categories($tags);
+ foreach ($tags as $tag) {
if ($tag != '') {
$sth = $this->pdo->prepare("INSERT INTO ttrss_tags
(post_int_id, owner_uid, tag_name)
@@ -331,7 +321,6 @@ class Article extends Handler_Protected {
/* update tag cache */
- sort($tags_to_cache);
$tags_str = join(",", $tags_to_cache);
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries
@@ -802,27 +791,6 @@ class Article extends Handler_Protected {
return $rv;
}
- static function sanitize_tag($tag) {
- $tag = trim($tag);
-
- $tag = mb_strtolower($tag, 'utf-8');
-
- $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
-
- if (DB_TYPE == "mysql") {
- $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
- }
-
- return $tag;
- }
-
- static function tag_is_valid($tag) {
- if (!$tag || is_numeric($tag) || mb_strlen($tag) > 250)
- return false;
-
- return true;
- }
-
static function get_article_image($enclosures, $content, $site_url) {
$article_image = "";
diff --git a/classes/auth/base.php b/classes/auth/base.php
index dbc77f8cd..4cbc23589 100644
--- a/classes/auth/base.php
+++ b/classes/auth/base.php
@@ -2,6 +2,8 @@
class Auth_Base {
private $pdo;
+ const AUTH_SERVICE_API = '_api';
+
function __construct() {
$this->pdo = Db::pdo();
}
@@ -9,14 +11,14 @@ class Auth_Base {
/**
* @SuppressWarnings(unused)
*/
- function check_password($owner_uid, $password) {
+ function check_password($owner_uid, $password, $service = '') {
return false;
}
/**
* @SuppressWarnings(unused)
*/
- function authenticate($login, $password) {
+ function authenticate($login, $password, $service = '') {
return false;
}
diff --git a/classes/backend.php b/classes/backend.php
index 5bd724728..122e28c65 100644
--- a/classes/backend.php
+++ b/classes/backend.php
@@ -88,7 +88,7 @@ class Backend extends Handler {
}
function help() {
- $topic = basename(clean($_REQUEST["topic"])); // only one for now
+ $topic = clean_filename($_REQUEST["topic"]); // only one for now
if ($topic == "main") {
$info = get_hotkeys_info();
diff --git a/classes/digest.php b/classes/digest.php
index f2533d160..c9e9f24e7 100644
--- a/classes/digest.php
+++ b/classes/digest.php
@@ -103,9 +103,11 @@ class Digest
$tpl->setVariable('CUR_DATE', date('Y/m/d', $local_ts));
$tpl->setVariable('CUR_TIME', date('G:i', $local_ts));
+ $tpl->setVariable('TTRSS_HOST', SELF_URL_PATH);
$tpl_t->setVariable('CUR_DATE', date('Y/m/d', $local_ts));
$tpl_t->setVariable('CUR_TIME', date('G:i', $local_ts));
+ $tpl_t->setVariable('TTRSS_HOST', SELF_URL_PATH);
$affected_ids = array();
diff --git a/classes/feeditem/atom.php b/classes/feeditem/atom.php
index a962b59f2..a03080981 100755
--- a/classes/feeditem/atom.php
+++ b/classes/feeditem/atom.php
@@ -103,20 +103,20 @@ class FeedItem_Atom extends FeedItem_Common {
function get_categories() {
$categories = $this->elem->getElementsByTagName("category");
- $cats = array();
+ $cats = [];
foreach ($categories as $cat) {
if ($cat->hasAttribute("term"))
- array_push($cats, trim($cat->getAttribute("term")));
+ array_push($cats, $cat->getAttribute("term"));
}
$categories = $this->xpath->query("dc:subject", $this->elem);
foreach ($categories as $cat) {
- array_push($cats, clean(trim($cat->nodeValue)));
+ array_push($cats, $cat->nodeValue);
}
- return $cats;
+ return $this->normalize_categories($cats);
}
function get_enclosures() {
diff --git a/classes/feeditem/common.php b/classes/feeditem/common.php
index 3193ed273..f208f4a48 100755
--- a/classes/feeditem/common.php
+++ b/classes/feeditem/common.php
@@ -162,4 +162,35 @@ abstract class FeedItem_Common extends FeedItem {
}
}
+ static function normalize_categories($cats) {
+
+ $tmp = [];
+
+ foreach ($cats as $rawcat) {
+ $tmp = array_merge($tmp, explode(",", $rawcat));
+ }
+
+ $tmp = array_map(function($srccat) {
+ $cat = clean(trim(mb_strtolower($srccat)));
+
+ // we don't support numeric tags
+ if (is_numeric($cat))
+ $cat = 't:' . $cat;
+
+ $cat = preg_replace('/[,\'\"]/', "", $cat);
+
+ if (DB_TYPE == "mysql") {
+ $cat = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $cat);
+ }
+
+ if (mb_strlen($cat) > 250)
+ $cat = mb_substr($cat, 0, 250);
+
+ return $cat;
+ }, $tmp);
+
+ asort($tmp);
+
+ return array_unique($tmp);
+ }
}
diff --git a/classes/feeditem/rss.php b/classes/feeditem/rss.php
index 916c73ec4..1f7953c51 100755
--- a/classes/feeditem/rss.php
+++ b/classes/feeditem/rss.php
@@ -97,19 +97,19 @@ class FeedItem_RSS extends FeedItem_Common {
function get_categories() {
$categories = $this->elem->getElementsByTagName("category");
- $cats = array();
+ $cats = [];
foreach ($categories as $cat) {
- array_push($cats, trim($cat->nodeValue));
+ array_push($cats, $cat->nodeValue);
}
$categories = $this->xpath->query("dc:subject", $this->elem);
foreach ($categories as $cat) {
- array_push($cats, clean(trim($cat->nodeValue)));
+ array_push($cats, $cat->nodeValue);
}
- return $cats;
+ return $this->normalize_categories($cats);
}
function get_enclosures() {
diff --git a/classes/feeds.php b/classes/feeds.php
index b89f4e4ca..e1478a696 100755
--- a/classes/feeds.php
+++ b/classes/feeds.php
@@ -2,6 +2,8 @@
require_once "colors.php";
class Feeds extends Handler_Protected {
+ const NEVER_GROUP_FEEDS = [ -6, 0 ];
+ const NEVER_GROUP_BY_DATE = [ -2, -1, -3 ];
private $params;
@@ -49,7 +51,9 @@ class Feeds extends Handler_Protected {
if ($error)
$reply .= " <i title=\"" . htmlspecialchars($error) . "\" class='material-icons icon-error'>error</i>";
- $reply .= "</span></span>";
+ $reply .= "</span>";
+ $reply .= "<span id='feed_current_unread' style='display: none'></span>";
+ $reply .= "</span>";
$reply .= "<span class=\"right\">";
$reply .= "<span id='selected_prompt'></span>";
@@ -199,7 +203,8 @@ class Feeds extends Handler_Protected {
$qfh_ret = $this->queryFeedHeadlines($params);
}
- $vfeed_group_enabled = get_pref("VFEED_GROUP_BY_FEED") && $feed != -6;
+ $vfeed_group_enabled = get_pref("VFEED_GROUP_BY_FEED") &&
+ !(in_array($feed, Feeds::NEVER_GROUP_FEEDS) && !$cat_view);
$result = $qfh_ret[0]; // this could be either a PDO query result or a -1 if first id changed
$feed_title = $qfh_ret[1];
@@ -1438,7 +1443,7 @@ class Feeds extends Handler_Protected {
$start_ts = isset($params["start_ts"]) ? $params["start_ts"] : false;
$check_first_id = isset($params["check_first_id"]) ? $params["check_first_id"] : false;
$skip_first_id_check = isset($params["skip_first_id_check"]) ? $params["skip_first_id_check"] : false;
- $order_by = isset($params["order_by"]) ? $params["order_by"] : false;
+ //$order_by = isset($params["order_by"]) ? $params["order_by"] : false;
$ext_tables_part = "";
$limit_query_part = "";
@@ -1693,12 +1698,18 @@ class Feeds extends Handler_Protected {
if (is_numeric($feed)) {
// proper override_order applied above
if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
- $yyiw_desc = $order_by == "date_reverse" ? "" : "desc";
+
+ if (!(in_array($feed, Feeds::NEVER_GROUP_BY_DATE) && !$cat_view)) {
+ $yyiw_desc = $order_by == "date_reverse" ? "" : "desc";
+ $yyiw_order_qpart = "yyiw $yyiw_desc, ";
+ } else {
+ $yyiw_order_qpart = "";
+ }
if (!$override_order) {
- $order_by = "yyiw $yyiw_desc, ttrss_feeds.title, ".$order_by;
+ $order_by = "$yyiw_order_qpart ttrss_feeds.title, $order_by";
} else {
- $order_by = "yyiw $yyiw_desc, ttrss_feeds.title, ".$override_order;
+ $order_by = "$yyiw_order_qpart ttrss_feeds.title, $override_order";
}
}
@@ -1915,7 +1926,7 @@ class Feeds extends Handler_Protected {
$sum = 0;
for ($i = 0; $i < strlen($name); $i++) {
- $sum += ord($name{$i});
+ $sum += ord($name[$i]);
}
$sum %= count($colormap);
diff --git a/classes/handler/public.php b/classes/handler/public.php
index 06c01df57..b81fb03b8 100755
--- a/classes/handler/public.php
+++ b/classes/handler/public.php
@@ -509,7 +509,7 @@ class Handler_Public extends Handler {
<!DOCTYPE html>
<html>
<head>
- <title><?php echo __("Share with Tiny Tiny RSS") ?> ?></title>
+ <title><?php echo __("Share with Tiny Tiny RSS") ?></title>
<?php
echo stylesheet_tag("css/default.css");
echo javascript_tag("lib/prototype.js");
@@ -996,6 +996,7 @@ class Handler_Public extends Handler {
$tpl->setVariable('LOGIN', $login);
$tpl->setVariable('RESETPASS_LINK', $resetpass_link);
+ $tpl->setVariable('TTRSS_HOST', SELF_URL_PATH);
$tpl->addBlock('message');
@@ -1203,27 +1204,30 @@ class Handler_Public extends Handler {
public function pluginhandler() {
$host = new PluginHost();
- $plugin = basename(clean($_REQUEST["plugin"]));
+ $plugin_name = clean_filename($_REQUEST["plugin"]);
$method = clean($_REQUEST["pmethod"]);
- $host->load($plugin, PluginHost::KIND_USER, 0);
+ $host->load($plugin_name, PluginHost::KIND_USER, 0);
$host->load_data();
- $pclass = $host->get_plugin($plugin);
+ $plugin = $host->get_plugin($plugin_name);
- if ($pclass) {
- if (method_exists($pclass, $method)) {
- if ($pclass->is_public_method($method)) {
- $pclass->$method();
+ if ($plugin) {
+ if (method_exists($plugin, $method)) {
+ if ($plugin->is_public_method($method)) {
+ $plugin->$method();
} else {
+ user_error("PluginHandler[PUBLIC]: Requested private method '$method' of plugin '$plugin_name'.", E_USER_WARNING);
header("Content-Type: text/json");
print error_json(6);
}
} else {
+ user_error("PluginHandler[PUBLIC]: Requested unknown method '$method' of plugin '$plugin_name'.", E_USER_WARNING);
header("Content-Type: text/json");
print error_json(13);
}
} else {
+ user_error("PluginHandler[PUBLIC]: Requested method '$method' of unknown plugin '$plugin_name'.", E_USER_WARNING);
header("Content-Type: text/json");
print error_json(14);
}
diff --git a/classes/iauthmodule.php b/classes/iauthmodule.php
index 9ec674078..2d0c98709 100644
--- a/classes/iauthmodule.php
+++ b/classes/iauthmodule.php
@@ -1,4 +1,4 @@
<?php
interface IAuthModule {
- function authenticate($login, $password);
+ function authenticate($login, $password); // + optional third parameter: $service
}
diff --git a/classes/logger/sql.php b/classes/logger/sql.php
index 989539e5d..1b44b1e5f 100755
--- a/classes/logger/sql.php
+++ b/classes/logger/sql.php
@@ -15,6 +15,17 @@ class Logger_SQL {
// limit context length, DOMDocument dumps entire XML in here sometimes, which may be huge
$context = mb_substr($context, 0, 8192);
+ $server_params = [
+ "IP" => "REMOTE_ADDR",
+ "Request URI" => "REQUEST_URI",
+ "User agent" => "HTTP_USER_AGENT",
+ ];
+
+ foreach ($server_params as $n => $p) {
+ if (isset($_SERVER[$p]))
+ $context .= "\n$n: " . $_SERVER[$p];
+ }
+
// passed error message may contain invalid unicode characters, failing to insert an error here
// would break the execution entirely by generating an actual fatal error instead of a E_WARNING etc
$errstr = UConverter::transcode($errstr, 'UTF-8', 'UTF-8');
diff --git a/classes/pluginhandler.php b/classes/pluginhandler.php
index d10343e09..9682e440f 100644
--- a/classes/pluginhandler.php
+++ b/classes/pluginhandler.php
@@ -5,15 +5,18 @@ class PluginHandler extends Handler_Protected {
}
function catchall($method) {
- $plugin = PluginHost::getInstance()->get_plugin(clean($_REQUEST["plugin"]));
+ $plugin_name = clean($_REQUEST["plugin"]);
+ $plugin = PluginHost::getInstance()->get_plugin($plugin_name);
if ($plugin) {
if (method_exists($plugin, $method)) {
$plugin->$method();
} else {
+ user_error("PluginHandler: Requested unknown method '$method' of plugin '$plugin_name'.", E_USER_WARNING);
print error_json(13);
}
} else {
+ user_error("PluginHandler: Requested method '$method' of unknown plugin '$plugin_name'.", E_USER_WARNING);
print error_json(14);
}
}
diff --git a/classes/pluginhost.php b/classes/pluginhost.php
index d09ecca17..6158880f2 100755
--- a/classes/pluginhost.php
+++ b/classes/pluginhost.php
@@ -60,6 +60,8 @@ class PluginHost {
const HOOK_FILTER_TRIGGERED = 40;
const HOOK_GET_FULL_TEXT = 41;
const HOOK_ARTICLE_IMAGE = 42;
+ const HOOK_FEED_TREE = 43;
+ const HOOK_IFRAME_WHITELISTED = 44;
const KIND_ALL = 1;
const KIND_SYSTEM = 2;
@@ -128,28 +130,44 @@ class PluginHost {
}
}
- function add_hook($type, $sender) {
+ function add_hook($type, $sender, $priority = 50) {
+ $priority = (int) $priority;
+
if (!is_array($this->hooks[$type])) {
- $this->hooks[$type] = array();
+ $this->hooks[$type] = [];
+ }
+
+ if (!is_array($this->hooks[$type][$priority])) {
+ $this->hooks[$type][$priority] = [];
}
- array_push($this->hooks[$type], $sender);
+ array_push($this->hooks[$type][$priority], $sender);
+ ksort($this->hooks[$type]);
}
function del_hook($type, $sender) {
if (is_array($this->hooks[$type])) {
- $key = array_Search($sender, $this->hooks[$type]);
- if ($key !== FALSE) {
- unset($this->hooks[$type][$key]);
+ foreach (array_keys($this->hooks[$type]) as $prio) {
+ $key = array_search($sender, $this->hooks[$type][$prio]);
+
+ if ($key !== FALSE) {
+ unset($this->hooks[$type][$prio][$key]);
+ }
}
}
}
function get_hooks($type) {
if (isset($this->hooks[$type])) {
- return $this->hooks[$type];
+ $tmp = [];
+
+ foreach (array_keys($this->hooks[$type]) as $prio) {
+ $tmp = array_merge($tmp, $this->hooks[$type][$prio]);
+ }
+
+ return $tmp;
} else {
- return array();
+ return [];
}
}
function load_all($kind, $owner_uid = false, $skip_init = false) {
@@ -170,7 +188,7 @@ class PluginHost {
foreach ($plugins as $class) {
$class = trim($class);
- $class_file = strtolower(basename($class));
+ $class_file = strtolower(clean_filename($class));
if (!is_dir(__DIR__."/../plugins/$class_file") &&
!is_dir(__DIR__."/../plugins.local/$class_file")) continue;
@@ -475,4 +493,34 @@ class PluginHost {
function get_owner_uid() {
return $this->owner_uid;
}
+
+ // handled by classes/pluginhandler.php, requires valid session
+ function get_method_url($sender, $method, $params) {
+ return get_self_url_prefix() . "/backend.php?" .
+ http_build_query(
+ array_merge(
+ [
+ "op" => "pluginhandler",
+ "plugin" => strtolower(get_class($sender)),
+ "method" => $method
+ ],
+ $params));
+ }
+
+ // WARNING: endpoint in public.php, exposed to unauthenticated users
+ function get_public_method_url($sender, $method, $params) {
+ if ($sender->is_public_method($method)) {
+ return get_self_url_prefix() . "/public.php?" .
+ http_build_query(
+ array_merge(
+ [
+ "op" => "pluginhandler",
+ "plugin" => strtolower(get_class($sender)),
+ "pmethod" => $method
+ ],
+ $params));
+ } else {
+ user_error("get_public_method_url: requested method '$method' of '" . get_class($sender) . "' is private.");
+ }
+ }
}
diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php
index c55affd77..f672a0375 100755
--- a/classes/pref/feeds.php
+++ b/classes/pref/feeds.php
@@ -312,7 +312,7 @@ class Pref_Feeds extends Handler_Protected {
array_push($root['items'], $feed);
}
- $root['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', count($cat['items'])), count($cat['items']));
+ $root['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', count($root['items'])), count($root['items']));
}
$fl = array();
diff --git a/classes/pref/prefs.php b/classes/pref/prefs.php
index e7e7a365e..4214ac6a8 100644
--- a/classes/pref/prefs.php
+++ b/classes/pref/prefs.php
@@ -118,10 +118,20 @@ class Pref_Prefs extends Handler_Protected {
function changepassword() {
+ if (defined('_TTRSS_DEMO_INSTANCE')) {
+ print "ERROR: ".format_error("Disabled in demo version.");
+ return;
+ }
+
$old_pw = clean($_POST["old_password"]);
$new_pw = clean($_POST["new_password"]);
$con_pw = clean($_POST["confirm_password"]);
+ if ($old_pw == $new_pw) {
+ print "ERROR: ".format_error("New password must be different from the old one.");
+ return;
+ }
+
if ($old_pw == "") {
print "ERROR: ".format_error("Old password cannot be blank.");
return;
@@ -194,6 +204,37 @@ class Pref_Prefs extends Handler_Protected {
$full_name = clean($_POST["full_name"]);
$active_uid = $_SESSION["uid"];
+ $sth = $this->pdo->prepare("SELECT email, login, full_name FROM ttrss_users WHERE id = ?");
+ $sth->execute([$active_uid]);
+
+ if ($row = $sth->fetch()) {
+ $old_email = $row["email"];
+
+ if ($old_email != $email) {
+ $mailer = new Mailer();
+
+ require_once "lib/MiniTemplator.class.php";
+
+ $tpl = new MiniTemplator;
+
+ $tpl->readTemplateFromFile("templates/mail_change_template.txt");
+
+ $tpl->setVariable('LOGIN', $row["login"]);
+ $tpl->setVariable('NEWMAIL', $email);
+ $tpl->setVariable('TTRSS_HOST', SELF_URL_PATH);
+
+ $tpl->addBlock('message');
+
+ $tpl->generateOutputToString($message);
+
+ $mailer->mail(["to_name" => $row["login"],
+ "to_address" => $row["email"],
+ "subject" => "[tt-rss] Mail address change notification",
+ "message" => $message]);
+
+ }
+ }
+
$sth = $this->pdo->prepare("UPDATE ttrss_users SET email = ?,
full_name = ? WHERE id = ?");
$sth->execute([$email, $full_name, $active_uid]);
@@ -359,6 +400,30 @@ class Pref_Prefs extends Handler_Protected {
print "</form>";
print "</div>"; # content pane
+
+ if ($_SESSION["auth_module"] == "auth_internal") {
+
+ print "<div dojoType='dijit.layout.ContentPane' title=\"" . __('App passwords') . "\">";
+
+ print_notice("You can create separate passwords for API clients. Using one is required if you enable OTP.");
+
+ print "<div id='app_passwords_holder'>";
+ $this->appPasswordList();
+ print "</div>";
+
+ print "<hr>";
+
+ print "<button style='float : left' class='alt-primary' dojoType='dijit.form.Button'
+ onclick=\"Helpers.AppPasswords.generate()\">" .
+ __('Generate new password') . "</button> ";
+
+ print "<button style='float : left' class='alt-danger' dojoType='dijit.form.Button'
+ onclick=\"Helpers.AppPasswords.removeSelected()\">" .
+ __('Remove selected passwords') . "</button>";
+
+ print "</div>"; # content pane
+ }
+
print "<div dojoType='dijit.layout.ContentPane' title=\"".__('One time passwords / Authenticator')."\">";
if ($_SESSION["auth_module"] == "auth_internal") {
@@ -403,17 +468,30 @@ class Pref_Prefs extends Handler_Protected {
print "</form>";
- } else if (function_exists("imagecreatefromstring")) {
+ } else {
print_warning("You will need a compatible Authenticator to use this. Changing your password would automatically disable OTP.");
- print_notice("Scan the following code by the Authenticator application:");
+ print_notice("You will need to generate app passwords for the API clients if you enable OTP.");
- $csrf_token = $_SESSION["csrf_token"];
+ if (function_exists("imagecreatefromstring")) {
+ print "<h3>" . __("Scan the following code by the Authenticator application or copy the key manually") . "</h3>";
- print "<img alt='otp qr-code' src='backend.php?op=pref-prefs&method=otpqrcode&csrf_token=$csrf_token'>";
+ $csrf_token = $_SESSION["csrf_token"];
+ print "<img alt='otp qr-code' src='backend.php?op=pref-prefs&method=otpqrcode&csrf_token=$csrf_token'>";
+ } else {
+ print_error("PHP GD functions are required to generate QR codes.");
+ print "<h3>" . __("Use the following OTP key with a compatible Authenticator application") . "</h3>";
+ }
print "<form dojoType='dijit.form.Form' id='changeOtpForm'>";
+ $otp_secret = $this->otpsecret();
+
+ print "<fieldset>";
+ print "<label>".__("OTP Key:")."</label>";
+ print "<input dojoType='dijit.form.ValidationTextBox' disabled='disabled' value='$otp_secret' size='32'>";
+ print "</fieldset>";
+
print_hidden("op", "pref-prefs");
print_hidden("method", "otpenable");
@@ -454,8 +532,6 @@ class Pref_Prefs extends Handler_Protected {
print "</form>";
- } else {
- print_notice("PHP GD functions are required for OTP support.");
}
}
@@ -773,6 +849,24 @@ class Pref_Prefs extends Handler_Protected {
print_warning("Your PHP configuration has open_basedir restrictions enabled. Some plugins relying on CURL for functionality may not work correctly.");
}
+ $feed_handler_whitelist = [ "Af_Comics" ];
+
+ $feed_handlers = array_merge(
+ PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FEED_FETCHED),
+ PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FEED_PARSED),
+ PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FETCH_FEED));
+
+ $feed_handlers = array_filter($feed_handlers, function($plugin) use ($feed_handler_whitelist) {
+ return in_array(get_class($plugin), $feed_handler_whitelist) === FALSE; });
+
+ if (count($feed_handlers) > 0) {
+ print_error(
+ T_sprintf("The following plugins use per-feed content hooks. This may cause excessive data usage and origin server load resulting in a ban of your instance: <b>%s</b>" ,
+ implode(", ", array_map(function($plugin) { return get_class($plugin); }, $feed_handlers))
+ ) . " (<a href='https://tt-rss.org/wiki/FeedHandlerPlugins' target='_blank'>".__("More info...")."</a>)"
+ );
+ }
+
print "<h2>".__("System plugins")."</h2>";
print_notice("System plugins are enabled in <strong>config.php</strong> for all users.");
@@ -886,27 +980,41 @@ class Pref_Prefs extends Handler_Protected {
$_SESSION["prefs_show_advanced"] = !$_SESSION["prefs_show_advanced"];
}
- function otpqrcode() {
- require_once "lib/phpqrcode/phpqrcode.php";
-
- $sth = $this->pdo->prepare("SELECT login,salt,otp_enabled
+ function otpsecret() {
+ $sth = $this->pdo->prepare("SELECT salt, otp_enabled
FROM ttrss_users
WHERE id = ?");
$sth->execute([$_SESSION['uid']]);
if ($row = $sth->fetch()) {
-
- $base32 = new \OTPHP\Base32();
-
- $login = $row["login"];
$otp_enabled = sql_bool_to_bool($row["otp_enabled"]);
if (!$otp_enabled) {
- $secret = $base32->encode(sha1($row["salt"]));
+ $base32 = new \OTPHP\Base32();
+ $secret = $base32->encode(mb_substr(sha1($row["salt"]), 0, 12), false);
+
+ return $secret;
+ }
+ }
+
+ return false;
+ }
+
+ function otpqrcode() {
+ require_once "lib/phpqrcode/phpqrcode.php";
+ $sth = $this->pdo->prepare("SELECT login
+ FROM ttrss_users
+ WHERE id = ?");
+ $sth->execute([$_SESSION['uid']]);
+
+ if ($row = $sth->fetch()) {
+ $secret = $this->otpsecret();
+ $login = $row['login'];
+
+ if ($secret) {
QRcode::png("otpauth://totp/".urlencode($login).
"?secret=$secret&issuer=".urlencode("Tiny Tiny RSS"));
-
}
}
}
@@ -920,16 +1028,12 @@ class Pref_Prefs extends Handler_Protected {
if ($authenticator->check_password($_SESSION["uid"], $password)) {
- $sth = $this->pdo->prepare("SELECT salt
- FROM ttrss_users
- WHERE id = ?");
- $sth->execute([$_SESSION['uid']]);
+ $secret = $this->otpsecret();
- if ($row = $sth->fetch()) {
+ if ($secret) {
$base32 = new \OTPHP\Base32();
- $secret = $base32->encode(sha1($row["salt"]));
$topt = new \OTPHP\TOTP($secret);
$otp_check = $topt->now();
@@ -972,6 +1076,31 @@ class Pref_Prefs extends Handler_Protected {
if ($authenticator->check_password($_SESSION["uid"], $password)) {
+ $sth = $this->pdo->prepare("SELECT email, login FROM ttrss_users WHERE id = ?");
+ $sth->execute([$_SESSION['uid']]);
+
+ if ($row = $sth->fetch()) {
+ $mailer = new Mailer();
+
+ require_once "lib/MiniTemplator.class.php";
+
+ $tpl = new MiniTemplator;
+
+ $tpl->readTemplateFromFile("templates/otp_disabled_template.txt");
+
+ $tpl->setVariable('LOGIN', $row["login"]);
+ $tpl->setVariable('TTRSS_HOST', SELF_URL_PATH);
+
+ $tpl->addBlock('message');
+
+ $tpl->generateOutputToString($message);
+
+ $mailer->mail(["to_name" => $row["login"],
+ "to_address" => $row["email"],
+ "subject" => "[tt-rss] OTP change notification",
+ "message" => $message]);
+ }
+
$sth = $this->pdo->prepare("UPDATE ttrss_users SET otp_enabled = false WHERE
id = ?");
$sth->execute([$_SESSION['uid']]);
@@ -1008,12 +1137,18 @@ class Pref_Prefs extends Handler_Protected {
print_hidden("method", "setpref");
print_hidden("key", "USER_STYLESHEET");
+ print "<div id='css_edit_apply_msg' style='display : none'>";
+ print_warning(__("User CSS has been applied, you might need to reload the page to see all changes."));
+ print "</div>";
+
print "<textarea class='panel user-css-editor' dojoType='dijit.form.SimpleTextarea'
style='font-size : 12px;' name='value'>$value</textarea>";
print "<footer>";
- print "<button dojoType='dijit.form.Button'
- onclick=\"dijit.byId('cssEditDlg').execute()\">".__('Save')."</button> ";
+ print "<button dojoType='dijit.form.Button' class='alt-success'
+ onclick=\"dijit.byId('cssEditDlg').apply()\">".__('Apply')."</button> ";
+ print "<button dojoType='dijit.form.Button' class='alt-primary'
+ onclick=\"dijit.byId('cssEditDlg').execute()\">".__('Save and reload')."</button> ";
print "<button dojoType='dijit.form.Button'
onclick=\"dijit.byId('cssEditDlg').hide()\">".__('Cancel')."</button>";
print "</footer>";
@@ -1130,4 +1265,87 @@ class Pref_Prefs extends Handler_Protected {
}
return "";
}
+
+ private function appPasswordList() {
+ print "<div dojoType='fox.Toolbar'>";
+ print "<div dojoType='fox.form.DropDownButton'>" .
+ "<span>" . __('Select') . "</span>";
+ print "<div dojoType='dijit.Menu' style='display: none'>";
+ print "<div onclick=\"Tables.select('app-password-list', true)\"
+ dojoType=\"dijit.MenuItem\">" . __('All') . "</div>";
+ print "<div onclick=\"Tables.select('app-password-list', false)\"
+ dojoType=\"dijit.MenuItem\">" . __('None') . "</div>";
+ print "</div></div>";
+ print "</div>"; #toolbar
+
+ print "<div class='panel panel-scrollable'>";
+ print "<table width='100%' id='app-password-list'>";
+ print "<tr>";
+ print "<th width='2%'></th>";
+ print "<th align='left'>".__("Description")."</th>";
+ print "<th align='right'>".__("Created")."</th>";
+ print "<th align='right'>".__("Last used")."</th>";
+ print "</tr>";
+
+ $sth = $this->pdo->prepare("SELECT id, title, created, last_used
+ FROM ttrss_app_passwords WHERE owner_uid = ?");
+ $sth->execute([$_SESSION['uid']]);
+
+ while ($row = $sth->fetch()) {
+
+ $row_id = $row["id"];
+
+ print "<tr data-row-id='$row_id'>";
+
+ print "<td align='center'>
+ <input onclick='Tables.onRowChecked(this)' dojoType='dijit.form.CheckBox' type='checkbox'></td>";
+ print "<td>" . htmlspecialchars($row["title"]) . "</td>";
+
+ print "<td align='right' class='text-muted'>";
+ print make_local_datetime($row['created'], false);
+ print "</td>";
+
+ print "<td align='right' class='text-muted'>";
+ print make_local_datetime($row['last_used'], false);
+ print "</td>";
+
+ print "</tr>";
+ }
+
+ print "</table>";
+ print "</div>";
+ }
+
+ private function encryptAppPassword($password) {
+ $salt = substr(bin2hex(get_random_bytes(24)), 0, 24);
+
+ return "SSHA-512:".hash('sha512', $salt . $password). ":$salt";
+ }
+
+ function deleteAppPassword() {
+ $ids = explode(",", clean($_REQUEST['ids']));
+ $ids_qmarks = arr_qmarks($ids);
+
+ $sth = $this->pdo->prepare("DELETE FROM ttrss_app_passwords WHERE id IN ($ids_qmarks) AND owner_uid = ?");
+ $sth->execute(array_merge($ids, [$_SESSION['uid']]));
+
+ $this->appPasswordList();
+ }
+
+ function generateAppPassword() {
+ $title = clean($_REQUEST['title']);
+ $new_password = make_password(16);
+ $new_password_hash = $this->encryptAppPassword($new_password);
+
+ print_warning(T_sprintf("Generated password <strong>%s</strong> for %s. Please remember it for future reference.", $new_password, $title));
+
+ $sth = $this->pdo->prepare("INSERT INTO ttrss_app_passwords
+ (title, pwd_hash, service, created, owner_uid)
+ VALUES
+ (?, ?, ?, NOW(), ?)");
+
+ $sth->execute([$title, $new_password_hash, Auth_Base::AUTH_SERVICE_API, $_SESSION['uid']]);
+
+ $this->appPasswordList();
+ }
}
diff --git a/classes/rpc.php b/classes/rpc.php
index 8736cbb65..af5bdf52c 100755
--- a/classes/rpc.php
+++ b/classes/rpc.php
@@ -572,7 +572,7 @@ class RPC extends Handler_Protected {
function log() {
$msg = clean($_REQUEST['msg']);
- $file = basename(clean($_REQUEST['file']));
+ $file = clean_filename($_REQUEST['file']);
$line = (int) clean($_REQUEST['line']);
$context = clean($_REQUEST['context']);
@@ -590,15 +590,20 @@ class RPC extends Handler_Protected {
function checkforupdates() {
$rv = [];
- if (CHECK_FOR_UPDATES && $_SESSION["access_level"] >= 10 && defined("GIT_VERSION_TIMESTAMP")) {
+ $git_timestamp = false;
+ $git_commit = false;
+
+ get_version($git_commit, $git_timestamp);
+
+ if (CHECK_FOR_UPDATES && $_SESSION["access_level"] >= 10 && $git_timestamp) {
$content = @fetch_file_contents(["url" => "https://srv.tt-rss.org/version.json"]);
if ($content) {
$content = json_decode($content, true);
if ($content && isset($content["changeset"])) {
- if ((int)GIT_VERSION_TIMESTAMP < (int)$content["changeset"]["timestamp"] &&
- GIT_VERSION_HEAD != $content["changeset"]["id"]) {
+ if ($git_timestamp < (int)$content["changeset"]["timestamp"] &&
+ $git_commit != $content["changeset"]["id"]) {
$rv = $content["changeset"];
}
diff --git a/classes/rssutils.php b/classes/rssutils.php
index fe4c0a8a3..66008899b 100755
--- a/classes/rssutils.php
+++ b/classes/rssutils.php
@@ -259,6 +259,8 @@ class RSSUtils {
*/
static function update_rss_feed($feed, $no_cache = false) {
+ reset_fetch_domain_quota();
+
Debug::log("start", Debug::$LOG_VERBOSE);
$pdo = Db::pdo();
@@ -335,8 +337,19 @@ class RSSUtils {
$force_refetch = isset($_REQUEST["force_refetch"]);
$feed_data = "";
+ Debug::log("running HOOK_FETCH_FEED handlers...", Debug::$LOG_VERBOSE);
+
foreach ($pluginhost->get_hooks(PluginHost::HOOK_FETCH_FEED) as $plugin) {
+ Debug::log("... " . get_class($plugin), Debug::$LOG_VERBOSE);
+ $start = microtime(true);
$feed_data = $plugin->hook_fetch_feed($feed_data, $fetch_url, $owner_uid, $feed, 0, $auth_login, $auth_pass);
+ Debug::log(sprintf("=== %.4f (sec)", microtime(true) - $start), Debug::$LOG_VERBOSE);
+ }
+
+ if ($feed_data) {
+ Debug::log("feed data has been modified by a plugin.", Debug::$LOG_VERBOSE);
+ } else {
+ Debug::log("feed data has not been modified by a plugin.", Debug::$LOG_VERBOSE);
}
// try cache
@@ -428,8 +441,20 @@ class RSSUtils {
return;
}
+ Debug::log("running HOOK_FEED_FETCHED handlers...", Debug::$LOG_VERBOSE);
+ $feed_data_checksum = md5($feed_data);
+
foreach ($pluginhost->get_hooks(PluginHost::HOOK_FEED_FETCHED) as $plugin) {
+ Debug::log("... " . get_class($plugin), Debug::$LOG_VERBOSE);
+ $start = microtime(true);
$feed_data = $plugin->hook_feed_fetched($feed_data, $fetch_url, $owner_uid, $feed);
+ Debug::log(sprintf("=== %.4f (sec)", microtime(true) - $start), Debug::$LOG_VERBOSE);
+ }
+
+ if (md5($feed_data) != $feed_data_checksum) {
+ Debug::log("feed data has been modified by a plugin.", Debug::$LOG_VERBOSE);
+ } else {
+ Debug::log("feed data has not been modified by a plugin.", Debug::$LOG_VERBOSE);
}
$rss = new FeedParser($feed_data);
@@ -437,8 +462,16 @@ class RSSUtils {
if (!$rss->error()) {
+ Debug::log("running HOOK_FEED_PARSED handlers...", Debug::$LOG_VERBOSE);
+
// We use local pluginhost here because we need to load different per-user feed plugins
- $pluginhost->run_hooks(PluginHost::HOOK_FEED_PARSED, "hook_feed_parsed", $rss);
+
+ foreach ($pluginhost->get_hooks(PluginHost::HOOK_FEED_PARSED) as $plugin) {
+ Debug::log("... " . get_class($plugin), Debug::$LOG_VERBOSE);
+ $start = microtime(true);
+ $plugin->hook_feed_parsed($rss);
+ Debug::log(sprintf("=== %.4f (sec)", microtime(true) - $start), Debug::$LOG_VERBOSE);
+ }
Debug::log("language: $feed_language", Debug::$LOG_VERBOSE);
Debug::log("processing feed data...", Debug::$LOG_VERBOSE);
@@ -559,18 +592,10 @@ class RSSUtils {
Debug::log("guid $entry_guid / $entry_guid_hashed", Debug::$LOG_VERBOSE);
- $entry_timestamp = strip_tags($item->get_date());
+ $entry_timestamp = (int)$item->get_date();
Debug::log("orig date: " . $item->get_date(), Debug::$LOG_VERBOSE);
- if ($entry_timestamp == -1 || !$entry_timestamp || $entry_timestamp > time()) {
- $entry_timestamp = time();
- }
-
- $entry_timestamp_fmt = strftime("%Y/%m/%d %H:%M:%S", $entry_timestamp);
-
- Debug::log("date $entry_timestamp [$entry_timestamp_fmt]", Debug::$LOG_VERBOSE);
-
$entry_title = strip_tags($item->get_title());
$entry_link = rewrite_relative_url($site_url, clean($item->get_link()));
@@ -599,31 +624,10 @@ class RSSUtils {
$entry_guid = mb_substr($entry_guid, 0, 245);
Debug::log("author $entry_author", Debug::$LOG_VERBOSE);
- Debug::log("num_comments: $num_comments", Debug::$LOG_VERBOSE);
Debug::log("looking for tags...", Debug::$LOG_VERBOSE);
- // parse <category> entries into tags
-
- $additional_tags = array();
-
- $additional_tags_src = $item->get_categories();
-
- if (is_array($additional_tags_src)) {
- foreach ($additional_tags_src as $tobj) {
- array_push($additional_tags, $tobj);
- }
- }
-
- $entry_tags = array_unique($additional_tags);
-
- for ($i = 0; $i < count($entry_tags); $i++) {
- $entry_tags[$i] = mb_strtolower($entry_tags[$i], 'utf-8');
-
- // we don't support numeric tags, let's prefix them
- if (is_numeric($entry_tags[$i])) $entry_tags[$i] = 't:' . $entry_tags[$i];
- }
-
- Debug::log("tags found: " . join(",", $entry_tags), Debug::$LOG_VERBOSE);
+ $entry_tags = $item->get_categories();
+ Debug::log("tags found: " . join(", ", $entry_tags), Debug::$LOG_VERBOSE);
Debug::log("done collecting data.", Debug::$LOG_VERBOSE);
@@ -656,7 +660,8 @@ class RSSUtils {
"force_catchup" => false, // ugly hack for the time being
"score_modifier" => 0, // no previous value, plugin should recalculate score modifier based on content if needed
"language" => $entry_language,
- "num_comments" => $num_comments, // read only
+ "timestamp" => $entry_timestamp,
+ "num_comments" => $num_comments,
"feed" => array("id" => $feed,
"fetch_url" => $fetch_url,
"site_url" => $site_url,
@@ -734,7 +739,7 @@ class RSSUtils {
if (count($matched_filter_ids) > 0) {
$filter_ids_qmarks = arr_qmarks($matched_filter_ids);
- $fsth = $pdo->prepare("UPDATE ttrss_filters2 SET last_triggered = NOW() WHERE
+ $fsth = $pdo->prepare("UPDATE ttrss_filters2 SET last_triggered = NOW() WHERE
id IN ($filter_ids_qmarks) AND owner_uid = ?");
$fsth->execute(array_merge($matched_filter_ids, [$owner_uid]));
@@ -797,6 +802,17 @@ class RSSUtils {
$article_labels = $article["labels"];
$entry_score_modifier = (int) $article["score_modifier"];
$entry_language = $article["language"];
+ $entry_timestamp = $article["timestamp"];
+ $num_comments = $article["num_comments"];
+
+ if ($entry_timestamp == -1 || !$entry_timestamp || $entry_timestamp > time()) {
+ $entry_timestamp = time();
+ }
+
+ $entry_timestamp_fmt = strftime("%Y/%m/%d %H:%M:%S", $entry_timestamp);
+
+ Debug::log("date $entry_timestamp [$entry_timestamp_fmt]", Debug::$LOG_VERBOSE);
+ Debug::log("num_comments: $num_comments", Debug::$LOG_VERBOSE);
if (Debug::get_loglevel() >= Debug::$LOG_EXTENDED) {
Debug::log("article labels:", Debug::$LOG_VERBOSE);
@@ -1071,9 +1087,7 @@ class RSSUtils {
$manual_tags = trim_array(explode(",", $f["param"]));
foreach ($manual_tags as $tag) {
- if (Article::tag_is_valid($tag)) {
- array_push($entry_tags, $tag);
- }
+ array_push($entry_tags, $tag);
}
}
}
@@ -1086,19 +1100,17 @@ class RSSUtils {
$filtered_tags = array();
$tags_to_cache = array();
- if ($entry_tags && is_array($entry_tags)) {
- foreach ($entry_tags as $tag) {
- if (array_search($tag, $boring_tags) === false) {
- array_push($filtered_tags, $tag);
- }
+ foreach ($entry_tags as $tag) {
+ if (array_search($tag, $boring_tags) === false) {
+ array_push($filtered_tags, $tag);
}
}
$filtered_tags = array_unique($filtered_tags);
- if (Debug::get_loglevel() >= Debug::$LOG_EXTENDED) {
- Debug::log("filtered article tags:", Debug::$LOG_VERBOSE);
- print_r($filtered_tags);
+ if (Debug::get_loglevel() >= Debug::$LOG_VERBOSE) {
+ Debug::log("filtered tags: " . implode(", ", $filtered_tags), Debug::$LOG_VERBOSE);
+
}
// Save article tags in the database
@@ -1113,12 +1125,9 @@ class RSSUtils {
(owner_uid,tag_name,post_int_id)
VALUES (?, ?, ?)");
- foreach ($filtered_tags as $tag) {
-
- $tag = Article::sanitize_tag($tag);
-
- if (!Article::tag_is_valid($tag)) continue;
+ $filtered_tags = FeedItem_Common::normalize_categories($filtered_tags);
+ foreach ($filtered_tags as $tag) {
$tsth->execute([$tag, $entry_int_id, $owner_uid]);
if (!$tsth->fetch()) {
@@ -1129,9 +1138,6 @@ class RSSUtils {
}
/* update the cache */
-
- $tags_to_cache = array_unique($tags_to_cache);
-
$tags_str = join(",", $tags_to_cache);
$tsth = $pdo->prepare("UPDATE ttrss_user_entries
@@ -1194,10 +1200,18 @@ class RSSUtils {
Debug::log("cache_enclosures: downloading: $src to $local_filename", Debug::$LOG_VERBOSE);
if (!$cache->exists($local_filename)) {
- $file_content = fetch_file_contents(array("url" => $src, "max_size" => MAX_CACHE_FILE_SIZE));
+
+ global $fetch_last_error_code;
+ global $fetch_last_error;
+
+ $file_content = fetch_file_contents(array("url" => $src,
+ "http_referrer" => $src,
+ "max_size" => MAX_CACHE_FILE_SIZE));
if ($file_content) {
$cache->put($local_filename, $file_content);
+ } else {
+ Debug::log("cache_enclosures: failed with $fetch_last_error_code: $fetch_last_error");
}
} else if (is_writable($local_filename)) {
$cache->touch($local_filename);
@@ -1228,10 +1242,17 @@ class RSSUtils {
if (!$cache->exists($local_filename)) {
Debug::log("cache_media: downloading: $src to $local_filename", Debug::$LOG_VERBOSE);
- $file_content = fetch_file_contents(array("url" => $src, "max_size" => MAX_CACHE_FILE_SIZE));
+ global $fetch_last_error_code;
+ global $fetch_last_error;
+
+ $file_content = fetch_file_contents(array("url" => $src,
+ "http_referrer" => $src,
+ "max_size" => MAX_CACHE_FILE_SIZE));
if ($file_content) {
$cache->put($local_filename, $file_content);
+ } else {
+ Debug::log("cache_media: failed with $fetch_last_error_code: $fetch_last_error");
}
} else if ($cache->isWritable($local_filename)) {
$cache->touch($local_filename);
@@ -1548,7 +1569,8 @@ class RSSUtils {
}
static function is_gzipped($feed_data) {
- return mb_strpos($feed_data, "\x1f" . "\x8b" . "\x08", 0, "US-ASCII") === 0;
+ return strpos(substr($feed_data, 0, 3),
+ "\x1f" . "\x8b" . "\x08", 0) === 0;
}
static function load_filters($feed_id, $owner_uid) {