remove a lot of stuff from global context (functions.php), add a few helper classes instead
This commit is contained in:
parent
d04ac399ff
commit
74568df4ff
|
@ -67,7 +67,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
load_user_plugins( $_SESSION["uid"]);
|
||||
UserHelper::load_user_plugins( $_SESSION["uid"]);
|
||||
}
|
||||
|
||||
$method = strtolower($_REQUEST["op"]);
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
}
|
||||
|
||||
if (SINGLE_USER_MODE) {
|
||||
authenticate_user( "admin", null);
|
||||
UserHelper::authenticate( "admin", null);
|
||||
}
|
||||
|
||||
if ($_SESSION["uid"]) {
|
||||
|
@ -50,7 +50,7 @@
|
|||
print error_json(6);
|
||||
return;
|
||||
}
|
||||
load_user_plugins( $_SESSION["uid"]);
|
||||
UserHelper::load_user_plugins($_SESSION["uid"]);
|
||||
}
|
||||
|
||||
$purge_intervals = array(
|
||||
|
|
|
@ -74,10 +74,10 @@ class API extends Handler {
|
|||
}
|
||||
|
||||
if (get_pref("ENABLE_API_ACCESS", $uid)) {
|
||||
if (authenticate_user($login, $password, false, Auth_Base::AUTH_SERVICE_API)) { // try login with normal password
|
||||
if (UserHelper::authenticate($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, false, Auth_Base::AUTH_SERVICE_API)) { // else try with base64_decoded password
|
||||
} else if (UserHelper::authenticate($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
|
||||
|
@ -91,7 +91,7 @@ class API extends Handler {
|
|||
}
|
||||
|
||||
function logout() {
|
||||
logout_user();
|
||||
Pref_Users::logout_user();
|
||||
$this->wrap(self::STATUS_OK, array("status" => "OK"));
|
||||
}
|
||||
|
||||
|
@ -343,7 +343,7 @@ class API extends Handler {
|
|||
);
|
||||
|
||||
if ($sanitize_content) {
|
||||
$article["content"] = sanitize(
|
||||
$article["content"] = Sanitizer::sanitize(
|
||||
$line["content"],
|
||||
API::param_to_bool($line['hide_images']),
|
||||
false, $line["site_url"], false, $line["id"]);
|
||||
|
@ -748,7 +748,7 @@ class API extends Handler {
|
|||
if ($show_content) {
|
||||
|
||||
if ($sanitize_content) {
|
||||
$headline_row["content"] = sanitize(
|
||||
$headline_row["content"] = Sanitizer::sanitize(
|
||||
$line["content"],
|
||||
API::param_to_bool($line['hide_images']),
|
||||
false, $line["site_url"], false, $line["id"]);
|
||||
|
|
|
@ -19,8 +19,8 @@ class Backend extends Handler_Protected {
|
|||
$topic = basename(clean($_REQUEST["topic"])); // only one for now
|
||||
|
||||
if ($topic == "main") {
|
||||
$info = get_hotkeys_info();
|
||||
$imap = get_hotkeys_map();
|
||||
$info = RPC::get_hotkeys_info();
|
||||
$imap = RPC::get_hotkeys_map();
|
||||
$omap = array();
|
||||
|
||||
foreach ($imap[1] as $sequence => $action) {
|
||||
|
|
|
@ -113,4 +113,13 @@ class Db
|
|||
|
||||
return self::$instance->pdo;
|
||||
}
|
||||
|
||||
public static function sql_random_function() {
|
||||
if (DB_TYPE == "mysql") {
|
||||
return "RAND()";
|
||||
} else {
|
||||
return "RANDOM()";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -269,7 +269,7 @@ class DiskCache {
|
|||
|
||||
header("Content-Disposition: inline; filename=\"${filename}${fake_extension}\"");
|
||||
|
||||
return send_local_file($this->getFullPath($filename));
|
||||
return $this->send_local_file($this->getFullPath($filename));
|
||||
}
|
||||
|
||||
public function getUrl($filename) {
|
||||
|
@ -359,4 +359,56 @@ class DiskCache {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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);
|
||||
|
||||
$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";
|
||||
|
||||
# block SVG because of possible embedded javascript (.....)
|
||||
$mimetype_blacklist = [ "image/svg+xml" ];
|
||||
|
||||
/* only serve video and images */
|
||||
if (!preg_match("/(image|video)\//", $mimetype) || in_array($mimetype, $mimetype_blacklist)) {
|
||||
http_response_code(400);
|
||||
header("Content-type: text/plain");
|
||||
|
||||
print "Stored file has disallowed content type ($mimetype)";
|
||||
return false;
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -305,7 +305,7 @@ class Feeds extends Handler_Protected {
|
|||
$line["buttons"] .= $p->hook_article_button($line);
|
||||
}
|
||||
|
||||
$line["content"] = sanitize($line["content"],
|
||||
$line["content"] = Sanitizer::sanitize($line["content"],
|
||||
$line['hide_images'], false, $line["site_url"], $highlight_words, $line["id"]);
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_CDM) as $p) {
|
||||
|
@ -550,7 +550,7 @@ class Feeds extends Handler_Protected {
|
|||
"disable_cache" => (bool) $disable_cache];
|
||||
|
||||
// this is parsed by handleRpcJson() on first viewfeed() to set cdm expanded, etc
|
||||
$reply['runtime-info'] = make_runtime_info();
|
||||
$reply['runtime-info'] = RPC::make_runtime_info();
|
||||
|
||||
$reply_json = json_encode($reply);
|
||||
|
||||
|
@ -1124,11 +1124,11 @@ class Feeds extends Handler_Protected {
|
|||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$url = validate_url($url);
|
||||
$url = UrlHelper::validate($url);
|
||||
|
||||
if (!$url) return array("code" => 2);
|
||||
|
||||
$contents = @fetch_file_contents($url, false, $auth_login, $auth_pass);
|
||||
$contents = @UrlHelper::fetch($url, false, $auth_login, $auth_pass);
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SUBSCRIBE_FEED) as $plugin) {
|
||||
$contents = $plugin->hook_subscribe_feed($contents, $url, $auth_login, $auth_pass);
|
||||
|
@ -1924,7 +1924,7 @@ class Feeds extends Handler_Protected {
|
|||
}
|
||||
|
||||
static function get_feeds_from_html($url, $content) {
|
||||
$url = validate_url($url);
|
||||
$url = UrlHelper::validate($url);
|
||||
$baseUrl = substr($url, 0, strrpos($url, '/') + 1);
|
||||
|
||||
$feedUrls = [];
|
||||
|
|
|
@ -81,7 +81,7 @@ class Handler_Public extends Handler {
|
|||
$tpl->setVariable('SELF_URL', htmlspecialchars(get_self_url_prefix()), true);
|
||||
while ($line = $result->fetch()) {
|
||||
|
||||
$line["content_preview"] = sanitize(truncate_string(strip_tags($line["content"]), 100, '...'));
|
||||
$line["content_preview"] = Sanitizer::sanitize(truncate_string(strip_tags($line["content"]), 100, '...'));
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
|
||||
$line = $p->hook_query_headlines($line);
|
||||
|
@ -98,7 +98,7 @@ class Handler_Public extends Handler {
|
|||
$tpl->setVariable('ARTICLE_TITLE', htmlspecialchars($line['title']), true);
|
||||
$tpl->setVariable('ARTICLE_EXCERPT', $line["content_preview"], true);
|
||||
|
||||
$content = sanitize($line["content"], false, $owner_uid,
|
||||
$content = Sanitizer::sanitize($line["content"], false, $owner_uid,
|
||||
$feed_site_url, false, $line["id"]);
|
||||
|
||||
$content = DiskCache::rewriteUrls($content);
|
||||
|
@ -180,7 +180,7 @@ class Handler_Public extends Handler {
|
|||
|
||||
while ($line = $result->fetch()) {
|
||||
|
||||
$line["content_preview"] = sanitize(truncate_string(strip_tags($line["content_preview"]), 100, '...'));
|
||||
$line["content_preview"] = Sanitizer::sanitize(truncate_string(strip_tags($line["content_preview"]), 100, '...'));
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
|
||||
$line = $p->hook_query_headlines($line, 100);
|
||||
|
@ -196,7 +196,7 @@ class Handler_Public extends Handler {
|
|||
$article['link'] = $line['link'];
|
||||
$article['title'] = $line['title'];
|
||||
$article['excerpt'] = $line["content_preview"];
|
||||
$article['content'] = sanitize($line["content"], false, $owner_uid, $feed_site_url, false, $line["id"]);
|
||||
$article['content'] = Sanitizer::sanitize($line["content"], false, $owner_uid, $feed_site_url, false, $line["id"]);
|
||||
$article['updated'] = date('c', strtotime($line["updated"]));
|
||||
|
||||
if ($line['note']) $article['note'] = $line['note'];
|
||||
|
@ -284,7 +284,7 @@ class Handler_Public extends Handler {
|
|||
|
||||
function logout() {
|
||||
if (validate_csrf($_POST["csrf_token"])) {
|
||||
logout_user();
|
||||
Pref_Users::logout_user();
|
||||
header("Location: index.php");
|
||||
} else {
|
||||
header("Content-Type: text/json");
|
||||
|
@ -343,7 +343,7 @@ class Handler_Public extends Handler {
|
|||
$line["tags"] = Article::get_article_tags($id, $owner_uid, $line["tag_cache"]);
|
||||
unset($line["tag_cache"]);
|
||||
|
||||
$line["content"] = sanitize($line["content"],
|
||||
$line["content"] = Sanitizer::sanitize($line["content"],
|
||||
$line['hide_images'],
|
||||
$owner_uid, $line["site_url"], false, $line["id"]);
|
||||
|
||||
|
@ -470,7 +470,7 @@ class Handler_Public extends Handler {
|
|||
if (!$format) $format = 'atom';
|
||||
|
||||
if (SINGLE_USER_MODE) {
|
||||
authenticate_user("admin", null);
|
||||
UserHelper::authenticate("admin", null);
|
||||
}
|
||||
|
||||
$owner_id = false;
|
||||
|
@ -508,7 +508,7 @@ class Handler_Public extends Handler {
|
|||
|
||||
function sharepopup() {
|
||||
if (SINGLE_USER_MODE) {
|
||||
login_sequence();
|
||||
UserHelper::login_sequence();
|
||||
}
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
|
@ -681,7 +681,7 @@ class Handler_Public extends Handler {
|
|||
@session_set_cookie_params(0);
|
||||
}
|
||||
|
||||
if (authenticate_user($login, $password)) {
|
||||
if (UserHelper::authenticate($login, $password)) {
|
||||
$_POST["password"] = "";
|
||||
|
||||
if (get_schema_version() >= 120) {
|
||||
|
@ -729,7 +729,7 @@ class Handler_Public extends Handler {
|
|||
|
||||
function subscribe() {
|
||||
if (SINGLE_USER_MODE) {
|
||||
login_sequence();
|
||||
UserHelper::login_sequence();
|
||||
}
|
||||
|
||||
if ($_SESSION["uid"]) {
|
||||
|
@ -878,7 +878,7 @@ class Handler_Public extends Handler {
|
|||
print "</div></div></body></html>";
|
||||
|
||||
} else {
|
||||
render_login_form();
|
||||
$this->render_login_form();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1092,7 +1092,7 @@ class Handler_Public extends Handler {
|
|||
|
||||
if (!SINGLE_USER_MODE && $_SESSION["access_level"] < 10) {
|
||||
$_SESSION["login_error_msg"] = __("Your access level is insufficient to run this script.");
|
||||
render_login_form();
|
||||
$this->render_login_form();
|
||||
exit;
|
||||
}
|
||||
|
||||
|
@ -1272,5 +1272,13 @@ class Handler_Public extends Handler {
|
|||
print error_json(14);
|
||||
}
|
||||
}
|
||||
|
||||
static function render_login_form() {
|
||||
header('Cache-Control: public');
|
||||
|
||||
require_once "login_form.php";
|
||||
exit;
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
|
|
|
@ -1703,7 +1703,7 @@ class Pref_Feeds extends Handler_Protected {
|
|||
foreach ($feeds as $feed) {
|
||||
$feed = trim($feed);
|
||||
|
||||
if (validate_url($feed)) {
|
||||
if (UrlHelper::validate($feed)) {
|
||||
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
|
|
|
@ -257,7 +257,7 @@ class Pref_Prefs extends Handler_Protected {
|
|||
AND owner_uid = :uid");
|
||||
$sth->execute([":profile" => $_SESSION['profile'], ":uid" => $_SESSION['uid']]);
|
||||
|
||||
initialize_user_prefs($_SESSION["uid"], $_SESSION["profile"]);
|
||||
$this->initialize_user_prefs($_SESSION["uid"], $_SESSION["profile"]);
|
||||
|
||||
echo __("Your preferences are now set to default values.");
|
||||
}
|
||||
|
@ -590,9 +590,9 @@ class Pref_Prefs extends Handler_Protected {
|
|||
if ($profile) {
|
||||
print_notice(__("Some preferences are only available in default profile."));
|
||||
|
||||
initialize_user_prefs($_SESSION["uid"], $profile);
|
||||
$this->initialize_user_prefs($_SESSION["uid"], $profile);
|
||||
} else {
|
||||
initialize_user_prefs($_SESSION["uid"]);
|
||||
$this->initialize_user_prefs($_SESSION["uid"]);
|
||||
}
|
||||
|
||||
$prefs_available = [];
|
||||
|
@ -1366,4 +1366,57 @@ class Pref_Prefs extends Handler_Protected {
|
|||
|
||||
$this->appPasswordList();
|
||||
}
|
||||
|
||||
static function initialize_user_prefs($uid, $profile = false) {
|
||||
|
||||
if (get_schema_version() < 63) $profile_qpart = "";
|
||||
|
||||
$pdo = Db::pdo();
|
||||
$in_nested_tr = false;
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
} catch (Exception $e) {
|
||||
$in_nested_tr = true;
|
||||
}
|
||||
|
||||
$sth = $pdo->query("SELECT pref_name,def_value FROM ttrss_prefs");
|
||||
|
||||
if (!is_numeric($profile) || !$profile || get_schema_version() < 63) $profile = null;
|
||||
|
||||
$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 = $u_sth->fetch()) {
|
||||
array_push($active_prefs, $line["pref_name"]);
|
||||
}
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
if (array_search($line["pref_name"], $active_prefs) === false) {
|
||||
// print "adding " . $line["pref_name"] . "<br>";
|
||||
|
||||
if (get_schema_version() < 63) {
|
||||
$i_sth = $pdo->prepare("INSERT INTO ttrss_user_prefs
|
||||
(owner_uid,pref_name,value) VALUES
|
||||
(?, ?, ?)");
|
||||
$i_sth->execute([$uid, $line["pref_name"], $line["def_value"]]);
|
||||
|
||||
} else {
|
||||
$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]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!$in_nested_tr) $pdo->commit();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -259,7 +259,7 @@ class Pref_Users extends Handler_Protected {
|
|||
print T_sprintf("Added user %s with password %s",
|
||||
$login, $tmp_user_pwd);
|
||||
|
||||
initialize_user($new_uid);
|
||||
$this->initialize_user($new_uid);
|
||||
|
||||
} else {
|
||||
|
||||
|
@ -443,4 +443,25 @@ class Pref_Users extends Handler_Protected {
|
|||
return $default;
|
||||
}
|
||||
|
||||
// this is called after user is created to initialize default feeds, labels
|
||||
// or whatever else
|
||||
// user preferences are checked on every login, not here
|
||||
static function initialize_user($uid) {
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("insert into ttrss_feeds (owner_uid,title,feed_url)
|
||||
values (?, 'Tiny Tiny RSS: Forum',
|
||||
'https://tt-rss.org/forum/rss.php')");
|
||||
$sth->execute([$uid]);
|
||||
}
|
||||
|
||||
static function logout_user() {
|
||||
@session_destroy();
|
||||
if (isset($_COOKIE[session_name()])) {
|
||||
setcookie(session_name(), '', time()-42000, '/');
|
||||
}
|
||||
session_commit();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
300
classes/rpc.php
300
classes/rpc.php
|
@ -52,7 +52,7 @@ class RPC extends Handler_Protected {
|
|||
$profile_id = $row['id'];
|
||||
|
||||
if ($profile_id) {
|
||||
initialize_user_prefs($_SESSION["uid"], $profile_id);
|
||||
Pref_Prefs::initialize_user_prefs($_SESSION["uid"], $profile_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -279,7 +279,7 @@ class RPC extends Handler_Protected {
|
|||
];
|
||||
|
||||
if ($seq % 2 == 0)
|
||||
$reply['runtime-info'] = make_runtime_info();
|
||||
$reply['runtime-info'] = $this->make_runtime_info();
|
||||
|
||||
print json_encode($reply);
|
||||
}
|
||||
|
@ -323,8 +323,8 @@ class RPC extends Handler_Protected {
|
|||
$reply['error'] = sanity_check();
|
||||
|
||||
if ($reply['error']['code'] == 0) {
|
||||
$reply['init-params'] = make_init_params();
|
||||
$reply['runtime-info'] = make_runtime_info();
|
||||
$reply['init-params'] = $this->make_init_params();
|
||||
$reply['runtime-info'] = $this->make_runtime_info();
|
||||
}
|
||||
|
||||
print json_encode($reply);
|
||||
|
@ -461,7 +461,7 @@ class RPC extends Handler_Protected {
|
|||
$updstart_thresh_qpart = "AND (ttrss_feeds.last_update_started IS NULL OR ttrss_feeds.last_update_started < DATE_SUB(NOW(), INTERVAL 5 MINUTE))";
|
||||
}
|
||||
|
||||
$random_qpart = sql_random_function();
|
||||
$random_qpart = Db::sql_random_function();
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
|
@ -602,7 +602,7 @@ class RPC extends Handler_Protected {
|
|||
get_version($git_commit, $git_timestamp);
|
||||
|
||||
if (defined('CHECK_FOR_UPDATES') && CHECK_FOR_UPDATES && $_SESSION["access_level"] >= 10 && $git_timestamp) {
|
||||
$content = @fetch_file_contents(["url" => "https://tt-rss.org/version.json"]);
|
||||
$content = @UrlHelper::fetch(["url" => "https://tt-rss.org/version.json"]);
|
||||
|
||||
if ($content) {
|
||||
$content = json_decode($content, true);
|
||||
|
@ -620,4 +620,292 @@ class RPC extends Handler_Protected {
|
|||
print json_encode($rv);
|
||||
}
|
||||
|
||||
private function make_init_params() {
|
||||
$params = array();
|
||||
|
||||
foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
|
||||
"ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
|
||||
"CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE",
|
||||
"HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
|
||||
|
||||
$params[strtolower($param)] = (int) get_pref($param);
|
||||
}
|
||||
|
||||
$params["check_for_updates"] = CHECK_FOR_UPDATES;
|
||||
$params["icons_url"] = ICONS_URL;
|
||||
$params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME;
|
||||
$params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE");
|
||||
$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);
|
||||
$params["theme"] = theme_exists($theme) ? $theme : "";
|
||||
|
||||
$params["plugins"] = implode(", ", PluginHost::getInstance()->get_plugin_names());
|
||||
|
||||
$params["php_platform"] = PHP_OS;
|
||||
$params["php_version"] = PHP_VERSION;
|
||||
|
||||
$params["sanity_checksum"] = sha1(file_get_contents("include/sanity_check.php"));
|
||||
|
||||
$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 = $row["mid"];
|
||||
$num_feeds = $row["nf"];
|
||||
|
||||
$params["self_url_prefix"] = get_self_url_prefix();
|
||||
$params["max_feed_id"] = (int) $max_feed_id;
|
||||
$params["num_feeds"] = (int) $num_feeds;
|
||||
|
||||
$params["hotkeys"] = $this->get_hotkeys_map();
|
||||
|
||||
$params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
|
||||
|
||||
$params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE;
|
||||
|
||||
$params["icon_indicator_white"] = $this->image_to_base64("images/indicator_white.gif");
|
||||
|
||||
$params["labels"] = Labels::get_all_labels($_SESSION["uid"]);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
private function image_to_base64($filename) {
|
||||
if (file_exists($filename)) {
|
||||
$ext = pathinfo($filename, PATHINFO_EXTENSION);
|
||||
|
||||
return "data:image/$ext;base64," . base64_encode(file_get_contents($filename));
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
static function make_runtime_info() {
|
||||
$data = array();
|
||||
|
||||
$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 = $row['mid'];
|
||||
$num_feeds = $row['nf'];
|
||||
|
||||
$data["max_feed_id"] = (int) $max_feed_id;
|
||||
$data["num_feeds"] = (int) $num_feeds;
|
||||
$data['cdm_expanded'] = get_pref('CDM_EXPANDED');
|
||||
$data["labels"] = Labels::get_all_labels($_SESSION["uid"]);
|
||||
|
||||
if (LOG_DESTINATION == 'sql' && $_SESSION['access_level'] >= 10) {
|
||||
if (DB_TYPE == 'pgsql') {
|
||||
$log_interval = "created_at > NOW() - interval '1 hour'";
|
||||
} else {
|
||||
$log_interval = "created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)";
|
||||
}
|
||||
|
||||
$sth = $pdo->prepare("SELECT COUNT(id) AS cid FROM ttrss_error_log WHERE $log_interval");
|
||||
$sth->execute();
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$data['recent_log_events'] = $row['cid'];
|
||||
}
|
||||
}
|
||||
|
||||
if (file_exists(LOCK_DIRECTORY . "/update_daemon.lock")) {
|
||||
|
||||
$data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
|
||||
|
||||
if (time() - $_SESSION["daemon_stamp_check"] > 30) {
|
||||
|
||||
$stamp = (int) @file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
|
||||
|
||||
if ($stamp) {
|
||||
$stamp_delta = time() - $stamp;
|
||||
|
||||
if ($stamp_delta > 1800) {
|
||||
$stamp_check = 0;
|
||||
} else {
|
||||
$stamp_check = 1;
|
||||
$_SESSION["daemon_stamp_check"] = time();
|
||||
}
|
||||
|
||||
$data['daemon_stamp_ok'] = $stamp_check;
|
||||
|
||||
$stamp_fmt = date("Y.m.d, G:i", $stamp);
|
||||
|
||||
$data['daemon_stamp'] = $stamp_fmt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
static function get_hotkeys_info() {
|
||||
$hotkeys = array(
|
||||
__("Navigation") => array(
|
||||
"next_feed" => __("Open next feed"),
|
||||
"prev_feed" => __("Open previous feed"),
|
||||
"next_article_or_scroll" => __("Open next article (in combined mode, scroll down)"),
|
||||
"prev_article_or_scroll" => __("Open previous article (in combined mode, scroll up)"),
|
||||
"next_headlines_page" => __("Scroll headlines by one page down"),
|
||||
"prev_headlines_page" => __("Scroll headlines by one page up"),
|
||||
"next_article_noscroll" => __("Open next article"),
|
||||
"prev_article_noscroll" => __("Open previous article"),
|
||||
"next_article_noexpand" => __("Move to next article (don't expand)"),
|
||||
"prev_article_noexpand" => __("Move to previous article (don't expand)"),
|
||||
"search_dialog" => __("Show search dialog"),
|
||||
"cancel_search" => __("Cancel active search")),
|
||||
__("Article") => array(
|
||||
"toggle_mark" => __("Toggle starred"),
|
||||
"toggle_publ" => __("Toggle published"),
|
||||
"toggle_unread" => __("Toggle unread"),
|
||||
"edit_tags" => __("Edit tags"),
|
||||
"open_in_new_window" => __("Open in new window"),
|
||||
"catchup_below" => __("Mark below as read"),
|
||||
"catchup_above" => __("Mark above as read"),
|
||||
"article_scroll_down" => __("Scroll down"),
|
||||
"article_scroll_up" => __("Scroll up"),
|
||||
"article_page_down" => __("Scroll down page"),
|
||||
"article_page_up" => __("Scroll up page"),
|
||||
"select_article_cursor" => __("Select article under cursor"),
|
||||
"email_article" => __("Email article"),
|
||||
"close_article" => __("Close/collapse article"),
|
||||
"toggle_expand" => __("Toggle article expansion (combined mode)"),
|
||||
"toggle_widescreen" => __("Toggle widescreen mode"),
|
||||
"toggle_full_text" => __("Toggle full article text via Readability")),
|
||||
__("Article selection") => array(
|
||||
"select_all" => __("Select all articles"),
|
||||
"select_unread" => __("Select unread"),
|
||||
"select_marked" => __("Select starred"),
|
||||
"select_published" => __("Select published"),
|
||||
"select_invert" => __("Invert selection"),
|
||||
"select_none" => __("Deselect everything")),
|
||||
__("Feed") => array(
|
||||
"feed_refresh" => __("Refresh current feed"),
|
||||
"feed_unhide_read" => __("Un/hide read feeds"),
|
||||
"feed_subscribe" => __("Subscribe to feed"),
|
||||
"feed_edit" => __("Edit feed"),
|
||||
"feed_catchup" => __("Mark as read"),
|
||||
"feed_reverse" => __("Reverse headlines"),
|
||||
"feed_toggle_vgroup" => __("Toggle headline grouping"),
|
||||
"feed_debug_update" => __("Debug feed update"),
|
||||
"feed_debug_viewfeed" => __("Debug viewfeed()"),
|
||||
"catchup_all" => __("Mark all feeds as read"),
|
||||
"cat_toggle_collapse" => __("Un/collapse current category"),
|
||||
"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"),
|
||||
"goto_marked" => __("Starred"),
|
||||
"goto_published" => __("Published"),
|
||||
"goto_read" => __("Recently read"),
|
||||
"goto_tagcloud" => __("Tag cloud"),
|
||||
"goto_prefs" => __("Preferences")),
|
||||
__("Other") => array(
|
||||
"create_label" => __("Create label"),
|
||||
"create_filter" => __("Create filter"),
|
||||
"collapse_sidebar" => __("Un/collapse sidebar"),
|
||||
"help_dialog" => __("Show help dialog"))
|
||||
);
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_INFO) as $plugin) {
|
||||
$hotkeys = $plugin->hook_hotkey_info($hotkeys);
|
||||
}
|
||||
|
||||
return $hotkeys;
|
||||
}
|
||||
|
||||
// {3} - 3 panel mode only
|
||||
// {C} - combined mode only
|
||||
static function get_hotkeys_map() {
|
||||
$hotkeys = array(
|
||||
"k" => "next_feed",
|
||||
"j" => "prev_feed",
|
||||
"n" => "next_article_noscroll",
|
||||
"p" => "prev_article_noscroll",
|
||||
"N" => "article_page_down",
|
||||
"P" => "article_page_up",
|
||||
"*(33)|Shift+PgUp" => "article_page_up",
|
||||
"*(34)|Shift+PgDn" => "article_page_down",
|
||||
"{3}(38)|Up" => "prev_article_or_scroll",
|
||||
"{3}(40)|Down" => "next_article_or_scroll",
|
||||
"*(38)|Shift+Up" => "article_scroll_up",
|
||||
"*(40)|Shift+Down" => "article_scroll_down",
|
||||
"^(38)|Ctrl+Up" => "prev_article_noscroll",
|
||||
"^(40)|Ctrl+Down" => "next_article_noscroll",
|
||||
"/" => "search_dialog",
|
||||
"\\" => "cancel_search",
|
||||
"s" => "toggle_mark",
|
||||
"S" => "toggle_publ",
|
||||
"u" => "toggle_unread",
|
||||
"T" => "edit_tags",
|
||||
"o" => "open_in_new_window",
|
||||
"c p" => "catchup_below",
|
||||
"c n" => "catchup_above",
|
||||
"a W" => "toggle_widescreen",
|
||||
"a e" => "toggle_full_text",
|
||||
"e" => "email_article",
|
||||
"a q" => "close_article",
|
||||
"a a" => "select_all",
|
||||
"a u" => "select_unread",
|
||||
"a U" => "select_marked",
|
||||
"a p" => "select_published",
|
||||
"a i" => "select_invert",
|
||||
"a n" => "select_none",
|
||||
"f r" => "feed_refresh",
|
||||
"f a" => "feed_unhide_read",
|
||||
"f s" => "feed_subscribe",
|
||||
"f e" => "feed_edit",
|
||||
"f q" => "feed_catchup",
|
||||
"f x" => "feed_reverse",
|
||||
"f g" => "feed_toggle_vgroup",
|
||||
"f D" => "feed_debug_update",
|
||||
"f G" => "feed_debug_viewfeed",
|
||||
"f C" => "toggle_combined_mode",
|
||||
"f c" => "toggle_cdm_expanded",
|
||||
"Q" => "catchup_all",
|
||||
"x" => "cat_toggle_collapse",
|
||||
"g a" => "goto_all",
|
||||
"g f" => "goto_fresh",
|
||||
"g s" => "goto_marked",
|
||||
"g p" => "goto_published",
|
||||
"g r" => "goto_read",
|
||||
"g t" => "goto_tagcloud",
|
||||
"g P" => "goto_prefs",
|
||||
"r" => "select_article_cursor",
|
||||
"c l" => "create_label",
|
||||
"c f" => "create_filter",
|
||||
"c s" => "collapse_sidebar",
|
||||
"?" => "help_dialog",
|
||||
);
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) {
|
||||
$hotkeys = $plugin->hook_hotkey_map($hotkeys);
|
||||
}
|
||||
|
||||
$prefixes = array();
|
||||
|
||||
foreach (array_keys($hotkeys) as $hotkey) {
|
||||
$pair = explode(" ", $hotkey, 2);
|
||||
|
||||
if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
|
||||
array_push($prefixes, $pair[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return array($prefixes, $hotkeys);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -218,7 +218,7 @@ class RSSUtils {
|
|||
}
|
||||
|
||||
if (!$basic_info) {
|
||||
$feed_data = fetch_file_contents($fetch_url, false,
|
||||
$feed_data = UrlHelper::fetch($fetch_url, false,
|
||||
$auth_login, $auth_pass, false,
|
||||
FEED_FETCH_TIMEOUT,
|
||||
0);
|
||||
|
@ -268,8 +268,6 @@ class RSSUtils {
|
|||
*/
|
||||
static function update_rss_feed($feed, $no_cache = false) {
|
||||
|
||||
reset_fetch_domain_quota();
|
||||
|
||||
Debug::log("start", Debug::$LOG_VERBOSE);
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
@ -400,7 +398,7 @@ class RSSUtils {
|
|||
|
||||
Debug::log("fetching [$fetch_url] (force_refetch: $force_refetch)...", Debug::$LOG_VERBOSE);
|
||||
|
||||
$feed_data = fetch_file_contents([
|
||||
$feed_data = UrlHelper::fetch([
|
||||
"url" => $fetch_url,
|
||||
"login" => $auth_login,
|
||||
"pass" => $auth_pass,
|
||||
|
@ -1225,7 +1223,7 @@ class RSSUtils {
|
|||
global $fetch_last_error_code;
|
||||
global $fetch_last_error;
|
||||
|
||||
$file_content = fetch_file_contents(array("url" => $src,
|
||||
$file_content = UrlHelper::fetch(array("url" => $src,
|
||||
"http_referrer" => $src,
|
||||
"max_size" => MAX_CACHE_FILE_SIZE));
|
||||
|
||||
|
@ -1255,7 +1253,7 @@ class RSSUtils {
|
|||
global $fetch_last_error_code;
|
||||
global $fetch_last_error;
|
||||
|
||||
$file_content = fetch_file_contents(array("url" => $url,
|
||||
$file_content = UrlHelper::fetch(array("url" => $url,
|
||||
"http_referrer" => $url,
|
||||
"max_size" => MAX_CACHE_FILE_SIZE));
|
||||
|
||||
|
@ -1517,7 +1515,7 @@ class RSSUtils {
|
|||
static function housekeeping_user($owner_uid) {
|
||||
$tmph = new PluginHost();
|
||||
|
||||
load_user_plugins($owner_uid, $tmph);
|
||||
UserHelper::load_user_plugins($owner_uid, $tmph);
|
||||
|
||||
$tmph->run_hooks(PluginHost::HOOK_HOUSE_KEEPING, "hook_house_keeping", "");
|
||||
}
|
||||
|
@ -1546,7 +1544,7 @@ class RSSUtils {
|
|||
|
||||
if ($favicon_url) {
|
||||
// Limiting to "image" type misses those served with text/plain
|
||||
$contents = fetch_file_contents($favicon_url); // , "image");
|
||||
$contents = UrlHelper::fetch($favicon_url); // , "image");
|
||||
|
||||
if ($contents) {
|
||||
// Crude image type matching.
|
||||
|
@ -1719,7 +1717,7 @@ class RSSUtils {
|
|||
|
||||
$favicon_url = false;
|
||||
|
||||
if ($html = @fetch_file_contents($url)) {
|
||||
if ($html = @UrlHelper::fetch($url)) {
|
||||
|
||||
$doc = new DOMDocument();
|
||||
if ($doc->loadHTML($html)) {
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
<?php
|
||||
class Sanitizer {
|
||||
private static function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
|
||||
$xpath = new DOMXPath($doc);
|
||||
$entries = $xpath->query('//*');
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if (!in_array($entry->nodeName, $allowed_elements)) {
|
||||
$entry->parentNode->removeChild($entry);
|
||||
}
|
||||
|
||||
if ($entry->hasAttributes()) {
|
||||
$attrs_to_remove = array();
|
||||
|
||||
foreach ($entry->attributes as $attr) {
|
||||
|
||||
if (strpos($attr->nodeName, 'on') === 0) {
|
||||
array_push($attrs_to_remove, $attr);
|
||||
}
|
||||
|
||||
if (strpos($attr->nodeName, "data-") === 0) {
|
||||
array_push($attrs_to_remove, $attr);
|
||||
}
|
||||
|
||||
if ($attr->nodeName == 'href' && stripos($attr->value, 'javascript:') === 0) {
|
||||
array_push($attrs_to_remove, $attr);
|
||||
}
|
||||
|
||||
if (in_array($attr->nodeName, $disallowed_attributes)) {
|
||||
array_push($attrs_to_remove, $attr);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($attrs_to_remove as $attr) {
|
||||
$entry->removeAttributeNode($attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $doc;
|
||||
}
|
||||
|
||||
public static function iframe_whitelisted($entry) {
|
||||
@$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST);
|
||||
|
||||
if ($src) {
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_IFRAME_WHITELISTED) as $plugin) {
|
||||
if ($plugin->hook_iframe_whitelisted($src))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
|
||||
if (!$owner) $owner = $_SESSION["uid"];
|
||||
|
||||
$res = trim($str); if (!$res) return '';
|
||||
|
||||
$doc = new DOMDocument();
|
||||
$doc->loadHTML('<?xml encoding="UTF-8">' . $res);
|
||||
$xpath = new DOMXPath($doc);
|
||||
|
||||
$rewrite_base_url = $site_url ? $site_url : get_self_url_prefix();
|
||||
|
||||
$entries = $xpath->query('(//a[@href]|//img[@src]|//source[@srcset|@src])');
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
|
||||
if ($entry->hasAttribute('href')) {
|
||||
$entry->setAttribute('href',
|
||||
rewrite_relative_url($rewrite_base_url, $entry->getAttribute('href')));
|
||||
|
||||
$entry->setAttribute('rel', 'noopener noreferrer');
|
||||
$entry->setAttribute("target", "_blank");
|
||||
}
|
||||
|
||||
if ($entry->hasAttribute('src')) {
|
||||
$entry->setAttribute('src',
|
||||
rewrite_relative_url($rewrite_base_url, $entry->getAttribute('src')));
|
||||
}
|
||||
|
||||
if ($entry->nodeName == 'img') {
|
||||
$entry->setAttribute('referrerpolicy', 'no-referrer');
|
||||
$entry->setAttribute('loading', 'lazy');
|
||||
}
|
||||
|
||||
if ($entry->hasAttribute('srcset')) {
|
||||
$matches = RSSUtils::decode_srcset($entry->getAttribute('srcset'));
|
||||
|
||||
for ($i = 0; $i < count($matches); $i++) {
|
||||
$matches[$i]["url"] = rewrite_relative_url($rewrite_base_url, $matches[$i]["url"]);
|
||||
}
|
||||
|
||||
$entry->setAttribute("srcset", RSSUtils::encode_srcset($matches));
|
||||
}
|
||||
|
||||
if ($entry->hasAttribute('src') &&
|
||||
($owner && get_pref("STRIP_IMAGES", $owner)) || $force_remove_images || $_SESSION["bw_limit"]) {
|
||||
|
||||
$p = $doc->createElement('p');
|
||||
|
||||
$a = $doc->createElement('a');
|
||||
$a->setAttribute('href', $entry->getAttribute('src'));
|
||||
|
||||
$a->appendChild(new DOMText($entry->getAttribute('src')));
|
||||
$a->setAttribute('target', '_blank');
|
||||
$a->setAttribute('rel', 'noopener noreferrer');
|
||||
|
||||
$p->appendChild($a);
|
||||
|
||||
if ($entry->nodeName == 'source') {
|
||||
|
||||
if ($entry->parentNode && $entry->parentNode->parentNode)
|
||||
$entry->parentNode->parentNode->replaceChild($p, $entry->parentNode);
|
||||
|
||||
} else if ($entry->nodeName == 'img') {
|
||||
if ($entry->parentNode)
|
||||
$entry->parentNode->replaceChild($p, $entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$entries = $xpath->query('//iframe');
|
||||
foreach ($entries as $entry) {
|
||||
if (!Sanitizer::iframe_whitelisted($entry)) {
|
||||
$entry->setAttribute('sandbox', 'allow-scripts');
|
||||
} else {
|
||||
if (is_prefix_https()) {
|
||||
$entry->setAttribute("src",
|
||||
str_replace("http://", "https://",
|
||||
$entry->getAttribute("src")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$allowed_elements = array('a', 'abbr', 'address', 'acronym', 'audio', 'article', 'aside',
|
||||
'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
|
||||
'caption', 'cite', 'center', 'code', 'col', 'colgroup',
|
||||
'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font',
|
||||
'dt', 'em', 'footer', 'figure', 'figcaption',
|
||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hr', 'html', 'i',
|
||||
'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
|
||||
'ol', 'p', 'picture', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
|
||||
'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
|
||||
'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
|
||||
'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
|
||||
|
||||
if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
|
||||
|
||||
$disallowed_attributes = array('id', 'style', 'class', 'width', 'height', 'allow');
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) {
|
||||
$retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
|
||||
if (is_array($retval)) {
|
||||
$doc = $retval[0];
|
||||
$allowed_elements = $retval[1];
|
||||
$disallowed_attributes = $retval[2];
|
||||
} else {
|
||||
$doc = $retval;
|
||||
}
|
||||
}
|
||||
|
||||
$doc->removeChild($doc->firstChild); //remove doctype
|
||||
$doc = Sanitizer::strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
|
||||
|
||||
$entries = $xpath->query('//iframe');
|
||||
foreach ($entries as $entry) {
|
||||
$div = $doc->createElement('div');
|
||||
$div->setAttribute('class', 'embed-responsive');
|
||||
$entry->parentNode->replaceChild($div, $entry);
|
||||
$div->appendChild($entry);
|
||||
}
|
||||
|
||||
if ($highlight_words && is_array($highlight_words)) {
|
||||
foreach ($highlight_words as $word) {
|
||||
|
||||
// http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
|
||||
|
||||
$elements = $xpath->query("//*/text()");
|
||||
|
||||
foreach ($elements as $child) {
|
||||
|
||||
$fragment = $doc->createDocumentFragment();
|
||||
$text = $child->textContent;
|
||||
|
||||
while (($pos = mb_stripos($text, $word)) !== false) {
|
||||
$fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
|
||||
$word = mb_substr($text, $pos, mb_strlen($word));
|
||||
$highlight = $doc->createElement('span');
|
||||
$highlight->appendChild(new DomText($word));
|
||||
$highlight->setAttribute('class', 'highlight');
|
||||
$fragment->appendChild($highlight);
|
||||
$text = mb_substr($text, $pos + mb_strlen($word));
|
||||
}
|
||||
|
||||
if (!empty($text)) $fragment->appendChild(new DomText($text));
|
||||
|
||||
$child->parentNode->replaceChild($fragment, $child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$res = $doc->saveHTML();
|
||||
|
||||
/* strip everything outside of <body>...</body> */
|
||||
|
||||
$res_frag = array();
|
||||
if (preg_match('/<body>(.*)<\/body>/is', $res, $res_frag)) {
|
||||
return $res_frag[1];
|
||||
} else {
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,474 @@
|
|||
<?php
|
||||
class UrlHelper {
|
||||
static function build_url($parts) {
|
||||
$tmp = $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
|
||||
|
||||
if (isset($parts['query'])) $tmp .= '?' . $parts['query'];
|
||||
if (isset($parts['fragment'])) $tmp .= '#' . $parts['fragment'];
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a (possibly) relative URL to a absolute one.
|
||||
*
|
||||
* @param string $url Base URL (i.e. from where the document is)
|
||||
* @param string $rel_url Possibly relative URL in the document
|
||||
*
|
||||
* @return string Absolute URL
|
||||
*/
|
||||
public static function rewrite_relative($url, $rel_url) {
|
||||
|
||||
$rel_parts = parse_url($rel_url);
|
||||
|
||||
if ($rel_parts['host'] && $rel_parts['scheme']) {
|
||||
return UrlHelper::validate($rel_url);
|
||||
} else if (strpos($rel_url, "//") === 0) {
|
||||
# protocol-relative URL (rare but they exist)
|
||||
return UrlHelper::validate("https:" . $rel_url);
|
||||
} else if (strpos($rel_url, "magnet:") === 0) {
|
||||
# allow magnet links
|
||||
return $rel_url;
|
||||
} else {
|
||||
$parts = parse_url($url);
|
||||
|
||||
$rel_parts['host'] = $parts['host'];
|
||||
$rel_parts['scheme'] = $parts['scheme'];
|
||||
|
||||
if (strpos($rel_parts['path'], '/') !== 0)
|
||||
$rel_parts['path'] = '/' . $rel_parts['path'];
|
||||
|
||||
$rel_parts['path'] = str_replace("/./", "/", $rel_parts['path']);
|
||||
$rel_parts['path'] = str_replace("//", "/", $rel_parts['path']);
|
||||
|
||||
return UrlHelper::validate(UrlHelper::build_url($rel_parts));
|
||||
}
|
||||
}
|
||||
|
||||
// extended filtering involves validation for safe ports and loopback
|
||||
static function validate($url, $extended_filtering = false) {
|
||||
|
||||
$url = clean($url);
|
||||
|
||||
# fix protocol-relative URLs
|
||||
if (strpos($url, "//") === 0)
|
||||
$url = "https:" . $url;
|
||||
|
||||
if (filter_var($url, FILTER_VALIDATE_URL) === false)
|
||||
return false;
|
||||
|
||||
$tokens = parse_url($url);
|
||||
|
||||
if (!$tokens['host'])
|
||||
return false;
|
||||
|
||||
if (!in_array(strtolower($tokens['scheme']), ['http', 'https']))
|
||||
return false;
|
||||
|
||||
if ($extended_filtering) {
|
||||
if (!in_array($tokens['port'], [80, 443, '']))
|
||||
return false;
|
||||
|
||||
if (strtolower($tokens['host']) == 'localhost' || $tokens['host'] == '::1' || strpos($tokens['host'], '127.') === 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
//convert IDNA hostname to punycode if possible
|
||||
if (function_exists("idn_to_ascii")) {
|
||||
if (mb_detect_encoding($tokens['host']) != 'ASCII') {
|
||||
$parts['host'] = idn_to_ascii($tokens['host']);
|
||||
$url = UrlHelper::build_url($tokens);
|
||||
}
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
static function resolve_redirects($url, $timeout, $nest = 0) {
|
||||
|
||||
// too many redirects
|
||||
if ($nest > 10)
|
||||
return false;
|
||||
|
||||
if (version_compare(PHP_VERSION, '7.1.0', '>=')) {
|
||||
$context_options = array(
|
||||
'http' => array(
|
||||
'header' => array(
|
||||
'Connection: close'
|
||||
),
|
||||
'method' => 'HEAD',
|
||||
'timeout' => $timeout,
|
||||
'protocol_version'=> 1.1)
|
||||
);
|
||||
|
||||
if (defined('_HTTP_PROXY')) {
|
||||
$context_options['http']['request_fulluri'] = true;
|
||||
$context_options['http']['proxy'] = _HTTP_PROXY;
|
||||
}
|
||||
|
||||
$context = stream_context_create($context_options);
|
||||
|
||||
$headers = get_headers($url, 0, $context);
|
||||
} else {
|
||||
$headers = get_headers($url, 0);
|
||||
}
|
||||
|
||||
if (is_array($headers)) {
|
||||
$headers = array_reverse($headers); // last one is the correct one
|
||||
|
||||
foreach($headers as $header) {
|
||||
if (stripos($header, 'Location:') === 0) {
|
||||
$url = UrlHelper::rewrite_relative($url, trim(substr($header, strlen('Location:'))));
|
||||
|
||||
return resolve_redirects($url, $timeout, $nest + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
// request failed?
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: max_size currently only works for CURL transfers
|
||||
// TODO: multiple-argument way is deprecated, first parameter is a hash now
|
||||
public static function fetch($options /* previously: 0: $url , 1: $type = false, 2: $login = false, 3: $pass = false,
|
||||
4: $post_query = false, 5: $timeout = false, 6: $timestamp = 0, 7: $useragent = false*/) {
|
||||
|
||||
global $fetch_last_error;
|
||||
global $fetch_last_error_code;
|
||||
global $fetch_last_error_content;
|
||||
global $fetch_last_content_type;
|
||||
global $fetch_last_modified;
|
||||
global $fetch_effective_url;
|
||||
global $fetch_effective_ip_addr;
|
||||
global $fetch_curl_used;
|
||||
global $fetch_domain_hits;
|
||||
|
||||
$fetch_last_error = false;
|
||||
$fetch_last_error_code = -1;
|
||||
$fetch_last_error_content = "";
|
||||
$fetch_last_content_type = "";
|
||||
$fetch_curl_used = false;
|
||||
$fetch_last_modified = "";
|
||||
$fetch_effective_url = "";
|
||||
$fetch_effective_ip_addr = "";
|
||||
|
||||
if (!is_array($fetch_domain_hits))
|
||||
$fetch_domain_hits = [];
|
||||
|
||||
if (!is_array($options)) {
|
||||
|
||||
// falling back on compatibility shim
|
||||
$option_names = [ "url", "type", "login", "pass", "post_query", "timeout", "last_modified", "useragent" ];
|
||||
$tmp = [];
|
||||
|
||||
for ($i = 0; $i < func_num_args(); $i++) {
|
||||
$tmp[$option_names[$i]] = func_get_arg($i);
|
||||
}
|
||||
|
||||
$options = $tmp;
|
||||
|
||||
/*$options = array(
|
||||
"url" => func_get_arg(0),
|
||||
"type" => @func_get_arg(1),
|
||||
"login" => @func_get_arg(2),
|
||||
"pass" => @func_get_arg(3),
|
||||
"post_query" => @func_get_arg(4),
|
||||
"timeout" => @func_get_arg(5),
|
||||
"timestamp" => @func_get_arg(6),
|
||||
"useragent" => @func_get_arg(7)
|
||||
); */
|
||||
}
|
||||
|
||||
$url = $options["url"];
|
||||
$type = isset($options["type"]) ? $options["type"] : false;
|
||||
$login = isset($options["login"]) ? $options["login"] : false;
|
||||
$pass = isset($options["pass"]) ? $options["pass"] : false;
|
||||
$post_query = isset($options["post_query"]) ? $options["post_query"] : false;
|
||||
$timeout = isset($options["timeout"]) ? $options["timeout"] : false;
|
||||
$last_modified = isset($options["last_modified"]) ? $options["last_modified"] : "";
|
||||
$useragent = isset($options["useragent"]) ? $options["useragent"] : false;
|
||||
$followlocation = isset($options["followlocation"]) ? $options["followlocation"] : true;
|
||||
$max_size = isset($options["max_size"]) ? $options["max_size"] : MAX_DOWNLOAD_FILE_SIZE; // in bytes
|
||||
$http_accept = isset($options["http_accept"]) ? $options["http_accept"] : false;
|
||||
$http_referrer = isset($options["http_referrer"]) ? $options["http_referrer"] : false;
|
||||
|
||||
$url = ltrim($url, ' ');
|
||||
$url = str_replace(' ', '%20', $url);
|
||||
|
||||
$url = UrlHelper::validate($url, true);
|
||||
|
||||
if (!$url) {
|
||||
$fetch_last_error = "Requested URL failed extended validation.";
|
||||
return false;
|
||||
}
|
||||
|
||||
$url_host = parse_url($url, PHP_URL_HOST);
|
||||
$ip_addr = gethostbyname($url_host);
|
||||
|
||||
if (!$ip_addr || strpos($ip_addr, "127.") === 0) {
|
||||
$fetch_last_error = "URL hostname failed to resolve or resolved to a loopback address ($ip_addr)";
|
||||
return false;
|
||||
}
|
||||
|
||||
$fetch_domain_hits[$url_host] += 1;
|
||||
|
||||
/*if ($fetch_domain_hits[$url_host] > MAX_FETCH_REQUESTS_PER_HOST) {
|
||||
user_error("Exceeded fetch request quota for $url_host: " . $fetch_domain_hits[$url_host], E_USER_WARNING);
|
||||
#return false;
|
||||
}*/
|
||||
|
||||
if (!defined('NO_CURL') && function_exists('curl_init') && !ini_get("open_basedir")) {
|
||||
|
||||
$fetch_curl_used = true;
|
||||
|
||||
$ch = curl_init($url);
|
||||
|
||||
$curl_http_headers = [];
|
||||
|
||||
if ($last_modified && !$post_query)
|
||||
array_push($curl_http_headers, "If-Modified-Since: $last_modified");
|
||||
|
||||
if ($http_accept)
|
||||
array_push($curl_http_headers, "Accept: " . $http_accept);
|
||||
|
||||
if (count($curl_http_headers) > 0)
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_http_headers);
|
||||
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout ? $timeout : FILE_FETCH_CONNECT_TIMEOUT);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout ? $timeout : FILE_FETCH_TIMEOUT);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, !ini_get("open_basedir") && $followlocation);
|
||||
curl_setopt($ch, CURLOPT_MAXREDIRS, 20);
|
||||
curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, $useragent ? $useragent :
|
||||
SELF_USER_AGENT);
|
||||
curl_setopt($ch, CURLOPT_ENCODING, "");
|
||||
|
||||
if ($http_referrer)
|
||||
curl_setopt($ch, CURLOPT_REFERER, $http_referrer);
|
||||
|
||||
if ($max_size) {
|
||||
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
|
||||
curl_setopt($ch, CURLOPT_BUFFERSIZE, 16384); // needed to get 5 arguments in progress function?
|
||||
|
||||
// holy shit closures in php
|
||||
// download & upload are *expected* sizes respectively, could be zero
|
||||
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function($curl_handle, $download_size, $downloaded, $upload_size, $uploaded) use( &$max_size) {
|
||||
Debug::log("[curl progressfunction] $downloaded $max_size", Debug::$LOG_EXTENDED);
|
||||
|
||||
return ($downloaded > $max_size) ? 1 : 0; // if max size is set, abort when exceeding it
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if (!ini_get("open_basedir")) {
|
||||
curl_setopt($ch, CURLOPT_COOKIEJAR, "/dev/null");
|
||||
}
|
||||
|
||||
if (defined('_HTTP_PROXY')) {
|
||||
curl_setopt($ch, CURLOPT_PROXY, _HTTP_PROXY);
|
||||
}
|
||||
|
||||
if ($post_query) {
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_query);
|
||||
}
|
||||
|
||||
if ($login && $pass)
|
||||
curl_setopt($ch, CURLOPT_USERPWD, "$login:$pass");
|
||||
|
||||
$ret = @curl_exec($ch);
|
||||
|
||||
$headers_length = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$headers = explode("\r\n", substr($ret, 0, $headers_length));
|
||||
$contents = substr($ret, $headers_length);
|
||||
|
||||
foreach ($headers as $header) {
|
||||
if (strstr($header, ": ") !== false) {
|
||||
list ($key, $value) = explode(": ", $header);
|
||||
|
||||
if (strtolower($key) == "last-modified") {
|
||||
$fetch_last_modified = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (substr(strtolower($header), 0, 7) == 'http/1.') {
|
||||
$fetch_last_error_code = (int) substr($header, 9, 3);
|
||||
$fetch_last_error = $header;
|
||||
}
|
||||
}
|
||||
|
||||
if (curl_errno($ch) === 23 || curl_errno($ch) === 61) {
|
||||
curl_setopt($ch, CURLOPT_ENCODING, 'none');
|
||||
$contents = @curl_exec($ch);
|
||||
}
|
||||
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$fetch_last_content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||
|
||||
$fetch_effective_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
|
||||
|
||||
if (!UrlHelper::validate($fetch_effective_url, true)) {
|
||||
$fetch_last_error = "URL received after redirection failed extended validation.";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$fetch_effective_ip_addr = gethostbyname(parse_url($fetch_effective_url, PHP_URL_HOST));
|
||||
|
||||
if (!$fetch_effective_ip_addr || strpos($fetch_effective_ip_addr, "127.") === 0) {
|
||||
$fetch_last_error = "URL hostname received after redirection failed to resolve or resolved to a loopback address ($fetch_effective_ip_addr)";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$fetch_last_error_code = $http_code;
|
||||
|
||||
if ($http_code != 200 || $type && strpos($fetch_last_content_type, "$type") === false) {
|
||||
|
||||
if (curl_errno($ch) != 0) {
|
||||
$fetch_last_error .= "; " . curl_errno($ch) . " " . curl_error($ch);
|
||||
}
|
||||
|
||||
$fetch_last_error_content = $contents;
|
||||
curl_close($ch);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$contents) {
|
||||
$fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
|
||||
curl_close($ch);
|
||||
return false;
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
$is_gzipped = RSSUtils::is_gzipped($contents);
|
||||
|
||||
if ($is_gzipped) {
|
||||
$tmp = @gzdecode($contents);
|
||||
|
||||
if ($tmp) $contents = $tmp;
|
||||
}
|
||||
|
||||
return $contents;
|
||||
} else {
|
||||
|
||||
$fetch_curl_used = false;
|
||||
|
||||
if ($login && $pass){
|
||||
$url_parts = array();
|
||||
|
||||
preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
|
||||
|
||||
$pass = urlencode($pass);
|
||||
|
||||
if ($url_parts[1] && $url_parts[2]) {
|
||||
$url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: should this support POST requests or not? idk
|
||||
|
||||
$context_options = array(
|
||||
'http' => array(
|
||||
'header' => array(
|
||||
'Connection: close'
|
||||
),
|
||||
'method' => 'GET',
|
||||
'ignore_errors' => true,
|
||||
'timeout' => $timeout ? $timeout : FILE_FETCH_TIMEOUT,
|
||||
'protocol_version'=> 1.1)
|
||||
);
|
||||
|
||||
if (!$post_query && $last_modified)
|
||||
array_push($context_options['http']['header'], "If-Modified-Since: $last_modified");
|
||||
|
||||
if ($http_accept)
|
||||
array_push($context_options['http']['header'], "Accept: $http_accept");
|
||||
|
||||
if ($http_referrer)
|
||||
array_push($context_options['http']['header'], "Referer: $http_referrer");
|
||||
|
||||
if (defined('_HTTP_PROXY')) {
|
||||
$context_options['http']['request_fulluri'] = true;
|
||||
$context_options['http']['proxy'] = _HTTP_PROXY;
|
||||
}
|
||||
|
||||
$context = stream_context_create($context_options);
|
||||
|
||||
$old_error = error_get_last();
|
||||
|
||||
$fetch_effective_url = resolve_redirects($url, $timeout ? $timeout : FILE_FETCH_CONNECT_TIMEOUT);
|
||||
|
||||
if (!UrlHelper::validate($fetch_effective_url, true)) {
|
||||
$fetch_last_error = "URL received after redirection failed extended validation.";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$fetch_effective_ip_addr = gethostbyname(parse_url($fetch_effective_url, PHP_URL_HOST));
|
||||
|
||||
if (!$fetch_effective_ip_addr || strpos($fetch_effective_ip_addr, "127.") === 0) {
|
||||
$fetch_last_error = "URL hostname received after redirection failed to resolve or resolved to a loopback address ($fetch_effective_ip_addr)";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = @file_get_contents($url, false, $context);
|
||||
|
||||
if (isset($http_response_header) && is_array($http_response_header)) {
|
||||
foreach ($http_response_header as $header) {
|
||||
if (strstr($header, ": ") !== false) {
|
||||
list ($key, $value) = explode(": ", $header);
|
||||
|
||||
$key = strtolower($key);
|
||||
|
||||
if ($key == 'content-type') {
|
||||
$fetch_last_content_type = $value;
|
||||
// don't abort here b/c there might be more than one
|
||||
// e.g. if we were being redirected -- last one is the right one
|
||||
} else if ($key == 'last-modified') {
|
||||
$fetch_last_modified = $value;
|
||||
} else if ($key == 'location') {
|
||||
$fetch_effective_url = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (substr(strtolower($header), 0, 7) == 'http/1.') {
|
||||
$fetch_last_error_code = (int) substr($header, 9, 3);
|
||||
$fetch_last_error = $header;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($fetch_last_error_code != 200) {
|
||||
$error = error_get_last();
|
||||
|
||||
if ($error['message'] != $old_error['message']) {
|
||||
$fetch_last_error .= "; " . $error["message"];
|
||||
}
|
||||
|
||||
$fetch_last_error_content = $data;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$is_gzipped = RSSUtils::is_gzipped($data);
|
||||
|
||||
if ($is_gzipped) {
|
||||
$tmp = @gzdecode($data);
|
||||
|
||||
if ($tmp) $data = $tmp;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
class UserHelper {
|
||||
|
||||
static function authenticate($login, $password, $check_only = false, $service = false) {
|
||||
|
||||
if (!SINGLE_USER_MODE) {
|
||||
$user_id = false;
|
||||
$auth_module = false;
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_AUTH_USER) as $plugin) {
|
||||
|
||||
$user_id = (int) $plugin->authenticate($login, $password, $service);
|
||||
|
||||
if ($user_id) {
|
||||
$auth_module = strtolower(get_class($plugin));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($user_id && !$check_only) {
|
||||
|
||||
session_start();
|
||||
session_regenerate_id(true);
|
||||
|
||||
$_SESSION["uid"] = $user_id;
|
||||
$_SESSION["auth_module"] = $auth_module;
|
||||
|
||||
$pdo = Db::pdo();
|
||||
$sth = $pdo->prepare("SELECT login,access_level,pwd_hash FROM ttrss_users
|
||||
WHERE id = ?");
|
||||
$sth->execute([$user_id]);
|
||||
$row = $sth->fetch();
|
||||
|
||||
$_SESSION["name"] = $row["login"];
|
||||
$_SESSION["access_level"] = $row["access_level"];
|
||||
$_SESSION["csrf_token"] = bin2hex(get_random_bytes(16));
|
||||
|
||||
$usth = $pdo->prepare("UPDATE ttrss_users SET last_login = NOW() WHERE id = ?");
|
||||
$usth->execute([$user_id]);
|
||||
|
||||
$_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
|
||||
$_SESSION["user_agent"] = sha1($_SERVER['HTTP_USER_AGENT']);
|
||||
$_SESSION["pwd_hash"] = $row["pwd_hash"];
|
||||
|
||||
Pref_Prefs::initialize_user_prefs($_SESSION["uid"]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
} else {
|
||||
|
||||
$_SESSION["uid"] = 1;
|
||||
$_SESSION["name"] = "admin";
|
||||
$_SESSION["access_level"] = 10;
|
||||
|
||||
$_SESSION["hide_hello"] = true;
|
||||
$_SESSION["hide_logout"] = true;
|
||||
|
||||
$_SESSION["auth_module"] = false;
|
||||
|
||||
if (!$_SESSION["csrf_token"])
|
||||
$_SESSION["csrf_token"] = bin2hex(get_random_bytes(16));
|
||||
|
||||
$_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
|
||||
|
||||
Pref_Prefs::initialize_user_prefs($_SESSION["uid"]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static function load_user_plugins($owner_uid, $pluginhost = false) {
|
||||
|
||||
if (!$pluginhost) $pluginhost = PluginHost::getInstance();
|
||||
|
||||
if ($owner_uid && SCHEMA_VERSION >= 100 && !$_SESSION["safe_mode"]) {
|
||||
$plugins = get_pref("_ENABLED_PLUGINS", $owner_uid);
|
||||
|
||||
$pluginhost->load($plugins, PluginHost::KIND_USER, $owner_uid);
|
||||
|
||||
if (get_schema_version() > 100) {
|
||||
$pluginhost->load_data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function login_sequence() {
|
||||
$pdo = Db::pdo();
|
||||
|
||||
if (SINGLE_USER_MODE) {
|
||||
@session_start();
|
||||
UserHelper::authenticate("admin", null);
|
||||
startup_gettext();
|
||||
UserHelper::load_user_plugins($_SESSION["uid"]);
|
||||
} else {
|
||||
if (!validate_session()) $_SESSION["uid"] = false;
|
||||
|
||||
if (!$_SESSION["uid"]) {
|
||||
|
||||
if (AUTH_AUTO_LOGIN && UserHelper::authenticate(null, null)) {
|
||||
$_SESSION["ref_schema_version"] = get_schema_version(true);
|
||||
} else {
|
||||
UserHelper::authenticate(null, null, true);
|
||||
}
|
||||
|
||||
if (!$_SESSION["uid"]) {
|
||||
Pref_Users::logout_user();
|
||||
|
||||
Handler_Public::render_login_form();
|
||||
exit;
|
||||
}
|
||||
|
||||
} else {
|
||||
/* bump login timestamp */
|
||||
$sth = $pdo->prepare("UPDATE ttrss_users SET last_login = NOW() WHERE id = ?");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
|
||||
$_SESSION["last_login_update"] = time();
|
||||
}
|
||||
|
||||
if ($_SESSION["uid"]) {
|
||||
startup_gettext();
|
||||
UserHelper::load_user_plugins($_SESSION["uid"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function print_user_stylesheet() {
|
||||
$value = get_pref('USER_STYLESHEET');
|
||||
|
||||
if ($value) {
|
||||
print "<style type='text/css' id='user_css_style'>";
|
||||
print str_replace("<br/>", "\n", $value);
|
||||
print "</style>";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -28,7 +28,7 @@
|
|||
|
||||
if (!init_plugins()) return;
|
||||
|
||||
login_sequence();
|
||||
UserHelper::login_sequence();
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
|
||||
|
@ -51,7 +51,7 @@
|
|||
const __csrf_token = "<?php echo $_SESSION["csrf_token"]; ?>";
|
||||
</script>
|
||||
|
||||
<?php print_user_stylesheet() ?>
|
||||
<?php UserHelper::print_user_stylesheet() ?>
|
||||
|
||||
<style type="text/css">
|
||||
<?php
|
||||
|
|
|
@ -13,7 +13,7 @@ class Af_Comics_Cad extends Af_ComicFilter {
|
|||
|
||||
$doc = new DOMDocument();
|
||||
|
||||
$res = fetch_file_contents($article["link"], false, false, false,
|
||||
$res = UrlHelper::fetch($article["link"], false, false, false,
|
||||
false, false, 0,
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0");
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ class Af_Comics_ComicClass extends Af_ComicFilter {
|
|||
// lol at people who block clients by user agent
|
||||
// oh noes my ad revenue Q_Q
|
||||
|
||||
$res = fetch_file_contents($article["link"], false, false, false,
|
||||
$res = UrlHelper::fetch($article["link"], false, false, false,
|
||||
false, false, 0,
|
||||
"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)");
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ class Af_Comics_ComicPress extends Af_ComicFilter {
|
|||
// lol at people who block clients by user agent
|
||||
// oh noes my ad revenue Q_Q
|
||||
|
||||
$res = fetch_file_contents(["url" => $article["link"],
|
||||
$res = UrlHelper::fetch(["url" => $article["link"],
|
||||
"useragent" => "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)"]);
|
||||
|
||||
$doc = new DOMDocument();
|
||||
|
@ -37,7 +37,7 @@ class Af_Comics_ComicPress extends Af_ComicFilter {
|
|||
|
||||
if ($webtoon_link) {
|
||||
|
||||
$res = fetch_file_contents(["url" => $webtoon_link->getAttribute("href"),
|
||||
$res = UrlHelper::fetch(["url" => $webtoon_link->getAttribute("href"),
|
||||
"useragent" => "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)"]);
|
||||
|
||||
if (@$doc->loadHTML($res)) {
|
||||
|
|
|
@ -9,7 +9,7 @@ class Af_Comics_DarkLegacy extends Af_ComicFilter {
|
|||
|
||||
if (strpos($article["guid"], "darklegacycomics.com") !== false) {
|
||||
|
||||
$res = fetch_file_contents($article["link"], false, false, false,
|
||||
$res = UrlHelper::fetch($article["link"], false, false, false,
|
||||
false, false, 0,
|
||||
"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)");
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ class Af_Comics_Dilbert extends Af_ComicFilter {
|
|||
if (strpos($article["link"], "dilbert.com") !== false ||
|
||||
strpos($article["link"], "/DilbertDailyStrip") !== false) {
|
||||
|
||||
$res = fetch_file_contents($article["link"], false, false, false,
|
||||
$res = UrlHelper::fetch($article["link"], false, false, false,
|
||||
false, false, 0,
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0");
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ class Af_Comics_Explosm extends Af_ComicFilter {
|
|||
|
||||
$doc = new DOMDocument();
|
||||
|
||||
if (@$doc->loadHTML(fetch_file_contents($article["link"]))) {
|
||||
if (@$doc->loadHTML(UrlHelper::fetch($article["link"]))) {
|
||||
$xpath = new DOMXPath($doc);
|
||||
$basenode = $xpath->query('(//img[@id="main-comic"])')->item(0);
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ class Af_Comics_Gocomics extends Af_ComicFilter {
|
|||
|
||||
$article_link = $site_url . date('/Y/m/d');
|
||||
|
||||
$body = fetch_file_contents(array('url' => $article_link, 'type' => 'text/html', 'followlocation' => false));
|
||||
$body = UrlHelper::fetch(array('url' => $article_link, 'type' => 'text/html', 'followlocation' => false));
|
||||
|
||||
$feed_title = htmlspecialchars($comic[1]);
|
||||
$site_url = htmlspecialchars($site_url);
|
||||
|
|
|
@ -37,7 +37,7 @@ class Af_Comics_Gocomics_FarSide extends Af_ComicFilter {
|
|||
$tpl->setVariable('FEED_URL', htmlspecialchars($url), true);
|
||||
$tpl->setVariable('SELF_URL', htmlspecialchars($url), true);
|
||||
|
||||
$body = fetch_file_contents(['url' => $article_link, 'type' => 'text/html', 'followlocation' => false]);
|
||||
$body = UrlHelper::fetch(['url' => $article_link, 'type' => 'text/html', 'followlocation' => false]);
|
||||
|
||||
if ($body) {
|
||||
$doc = new DOMDocument();
|
||||
|
|
|
@ -10,7 +10,7 @@ class Af_Comics_Pa extends Af_ComicFilter {
|
|||
|
||||
$doc = new DOMDocument();
|
||||
|
||||
if ($doc->loadHTML(fetch_file_contents($article["link"]))) {
|
||||
if ($doc->loadHTML(UrlHelper::fetch($article["link"]))) {
|
||||
$xpath = new DOMXPath($doc);
|
||||
$basenode = $xpath->query('(//div[@id="comicFrame"])')->item(0);
|
||||
|
||||
|
@ -25,7 +25,7 @@ class Af_Comics_Pa extends Af_ComicFilter {
|
|||
if (strpos($article["link"], "penny-arcade.com") !== false && strpos($article["title"], "News Post:") !== false) {
|
||||
$doc = new DOMDocument();
|
||||
|
||||
if ($doc->loadHTML(fetch_file_contents($article["link"]))) {
|
||||
if ($doc->loadHTML(UrlHelper::fetch($article["link"]))) {
|
||||
$xpath = new DOMXPath($doc);
|
||||
$entries = $xpath->query('(//div[@class="post"])');
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ class Af_Comics_Pvp extends Af_ComicFilter {
|
|||
function process(&$article) {
|
||||
if (strpos($article["guid"], "pvponline.com") !== false) {
|
||||
|
||||
$res = fetch_file_contents($article["link"], false, false, false,
|
||||
$res = UrlHelper::fetch($article["link"], false, false, false,
|
||||
false, false, 0,
|
||||
"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)");
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ class Af_Comics_Tfd extends Af_ComicFilter {
|
|||
function process(&$article) {
|
||||
if (strpos($article["link"], "toothpastefordinner.com") !== false ||
|
||||
strpos($article["link"], "marriedtothesea.com") !== false) {
|
||||
$res = fetch_file_contents($article["link"], false, false, false,
|
||||
$res = UrlHelper::fetch($article["link"], false, false, false,
|
||||
false, false, 0,
|
||||
"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)");
|
||||
|
||||
|
@ -16,7 +16,7 @@ class Af_Comics_Tfd extends Af_ComicFilter {
|
|||
|
||||
$doc = new DOMDocument();
|
||||
|
||||
if (@$doc->loadHTML(fetch_file_contents($article["link"]))) {
|
||||
if (@$doc->loadHTML(UrlHelper::fetch($article["link"]))) {
|
||||
$xpath = new DOMXPath($doc);
|
||||
$basenode = $xpath->query('//img[contains(@src, ".gif")]')->item(0);
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ class Af_Comics_Twp extends Af_ComicFilter {
|
|||
|
||||
$doc = new DOMDocument();
|
||||
|
||||
if (@$doc->loadHTML(fetch_file_contents($article["link"]))) {
|
||||
if (@$doc->loadHTML(UrlHelper::fetch($article["link"]))) {
|
||||
$xpath = new DOMXpath($doc);
|
||||
|
||||
$basenode = $xpath->query("//td/center/img")->item(0);
|
||||
|
|
|
@ -8,7 +8,7 @@ class Af_Comics_Whomp extends Af_ComicFilter {
|
|||
function process(&$article) {
|
||||
if (strpos($article["guid"], "whompcomic.com") !== false) {
|
||||
|
||||
$res = fetch_file_contents($article["link"], false, false, false,
|
||||
$res = UrlHelper::fetch($article["link"], false, false, false,
|
||||
false, false, 0,
|
||||
"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)");
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ class Af_Proxy_Http extends Plugin {
|
|||
}
|
||||
|
||||
public function imgproxy() {
|
||||
$url = validate_url(clean($_REQUEST["url"]));
|
||||
$url = UrlHelper::validate(clean($_REQUEST["url"]));
|
||||
|
||||
// called without user context, let's just redirect to original URL
|
||||
if (!$_SESSION["uid"] || $_REQUEST['af_proxy_http_token'] != $_SESSION['af_proxy_http_token']) {
|
||||
|
@ -62,7 +62,7 @@ class Af_Proxy_Http extends Plugin {
|
|||
header("Location: " . $this->cache->getUrl($local_filename));
|
||||
return;
|
||||
} else {
|
||||
$data = fetch_file_contents(["url" => $url, "max_size" => MAX_CACHE_FILE_SIZE]);
|
||||
$data = UrlHelper::fetch(["url" => $url, "max_size" => MAX_CACHE_FILE_SIZE]);
|
||||
|
||||
if ($data) {
|
||||
if ($this->cache->put($local_filename, $data)) {
|
||||
|
@ -125,7 +125,7 @@ class Af_Proxy_Http extends Plugin {
|
|||
foreach (explode(" " , $this->ssl_known_whitelist) as $host) {
|
||||
if (substr(strtolower($parts['host']), -strlen($host)) === strtolower($host)) {
|
||||
$parts['scheme'] = 'https';
|
||||
$url = build_url($parts);
|
||||
$url = UrlHelper::build_url($parts);
|
||||
if ($all_remote && $is_remote) {
|
||||
break;
|
||||
} else {
|
||||
|
|
|
@ -176,7 +176,7 @@ class Af_Readability extends Plugin {
|
|||
|
||||
global $fetch_effective_url;
|
||||
|
||||
$tmp = fetch_file_contents([
|
||||
$tmp = UrlHelper::fetch([
|
||||
"url" => $url,
|
||||
"http_accept" => "text/*",
|
||||
"type" => "text/html"]);
|
||||
|
@ -235,7 +235,7 @@ class Af_Readability extends Plugin {
|
|||
$extracted_content = $this->extract_content($article["link"]);
|
||||
|
||||
# let's see if there's anything of value in there
|
||||
$content_test = trim(strip_tags(sanitize($extracted_content)));
|
||||
$content_test = trim(strip_tags(Sanitizer::sanitize($extracted_content)));
|
||||
|
||||
if ($content_test) {
|
||||
$article["content"] = $extracted_content;
|
||||
|
@ -264,7 +264,7 @@ class Af_Readability extends Plugin {
|
|||
$extracted_content = $this->extract_content($link);
|
||||
|
||||
# let's see if there's anything of value in there
|
||||
$content_test = trim(strip_tags(sanitize($extracted_content)));
|
||||
$content_test = trim(strip_tags(Sanitizer::sanitize($extracted_content)));
|
||||
|
||||
if ($content_test) {
|
||||
return $extracted_content;
|
||||
|
@ -303,7 +303,7 @@ class Af_Readability extends Plugin {
|
|||
$ret = [];
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$ret["content"] = sanitize($this->extract_content($row["link"]));
|
||||
$ret["content"] = Sanitizer::sanitize($this->extract_content($row["link"]));
|
||||
}
|
||||
|
||||
print json_encode($ret);
|
||||
|
|
|
@ -103,7 +103,7 @@ class Af_RedditImgur extends Plugin {
|
|||
if (!$found && preg_match("/^https?:\/\/twitter.com\/(.*?)\/status\/(.*)/", $entry->getAttribute("href"), $matches)) {
|
||||
Debug::log("handling as twitter: " . $matches[1] . " " . $matches[2], Debug::$LOG_VERBOSE);
|
||||
|
||||
$oembed_result = fetch_file_contents("https://publish.twitter.com/oembed?url=" . urlencode($entry->getAttribute("href")));
|
||||
$oembed_result = UrlHelper::fetch("https://publish.twitter.com/oembed?url=" . urlencode($entry->getAttribute("href")));
|
||||
|
||||
if ($oembed_result) {
|
||||
$oembed_result = json_decode($oembed_result, true);
|
||||
|
@ -165,7 +165,7 @@ class Af_RedditImgur extends Plugin {
|
|||
$source_stream = false;
|
||||
|
||||
if ($source_article_url) {
|
||||
$j = json_decode(fetch_file_contents($source_article_url.".json"), true);
|
||||
$j = json_decode(UrlHelper::fetch($source_article_url.".json"), true);
|
||||
|
||||
if ($j) {
|
||||
foreach ($j as $listing) {
|
||||
|
@ -195,7 +195,7 @@ class Af_RedditImgur extends Plugin {
|
|||
|
||||
Debug::log("Handling as Streamable", Debug::$LOG_VERBOSE);
|
||||
|
||||
$tmp = fetch_file_contents($entry->getAttribute("href"));
|
||||
$tmp = UrlHelper::fetch($entry->getAttribute("href"));
|
||||
|
||||
if ($tmp) {
|
||||
$tmpdoc = new DOMDocument();
|
||||
|
@ -285,7 +285,7 @@ class Af_RedditImgur extends Plugin {
|
|||
|
||||
Debug::log("handling as imgur page/whatever", Debug::$LOG_VERBOSE);
|
||||
|
||||
$content = fetch_file_contents(["url" => $entry->getAttribute("href"),
|
||||
$content = UrlHelper::fetch(["url" => $entry->getAttribute("href"),
|
||||
"http_accept" => "text/*"]);
|
||||
|
||||
if ($content) {
|
||||
|
@ -331,7 +331,7 @@ class Af_RedditImgur extends Plugin {
|
|||
if (!$found) {
|
||||
Debug::log("looking for meta og:image", Debug::$LOG_VERBOSE);
|
||||
|
||||
$content = fetch_file_contents(["url" => $entry->getAttribute("href"),
|
||||
$content = UrlHelper::fetch(["url" => $entry->getAttribute("href"),
|
||||
"http_accept" => "text/*"]);
|
||||
|
||||
if ($content) {
|
||||
|
|
|
@ -40,16 +40,16 @@ class Cache_Starred_Images extends Plugin {
|
|||
|
||||
Debug::log("caching media of starred articles for user " . $this->host->get_owner_uid() . "...");
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT content, ttrss_entries.title,
|
||||
$sth = $this->pdo->prepare("SELECT content, ttrss_entries.title,
|
||||
ttrss_user_entries.owner_uid, link, site_url, ttrss_entries.id, plugin_data
|
||||
FROM ttrss_entries, ttrss_user_entries LEFT JOIN ttrss_feeds ON
|
||||
(ttrss_user_entries.feed_id = ttrss_feeds.id)
|
||||
WHERE ref_id = ttrss_entries.id AND
|
||||
marked = true AND
|
||||
site_url != '' AND
|
||||
site_url != '' AND
|
||||
ttrss_user_entries.owner_uid = ? AND
|
||||
plugin_data NOT LIKE '%starred_cache_images%'
|
||||
ORDER BY ".sql_random_function()." LIMIT 100");
|
||||
ORDER BY ".Db::sql_random_function()." LIMIT 100");
|
||||
|
||||
if ($sth->execute([$this->host->get_owner_uid()])) {
|
||||
|
||||
|
@ -139,7 +139,7 @@ class Cache_Starred_Images extends Plugin {
|
|||
if (!$this->cache->exists($local_filename)) {
|
||||
Debug::log("cache_images: downloading: $url to $local_filename", Debug::$LOG_VERBOSE);
|
||||
|
||||
$data = fetch_file_contents(["url" => $url, "max_size" => MAX_CACHE_FILE_SIZE]);
|
||||
$data = UrlHelper::fetch(["url" => $url, "max_size" => MAX_CACHE_FILE_SIZE]);
|
||||
|
||||
if ($data)
|
||||
return $this->cache->put($local_filename, $data);;
|
||||
|
|
|
@ -23,7 +23,7 @@ class No_Iframes extends Plugin {
|
|||
$entries = $xpath->query('//iframe');
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if (!iframe_whitelisted($entry))
|
||||
if (!Sanitizer::iframe_whitelisted($entry))
|
||||
$entry->parentNode->removeChild($entry);
|
||||
}
|
||||
|
||||
|
@ -34,4 +34,4 @@ class No_Iframes extends Plugin {
|
|||
return 2;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
if (!init_plugins()) return;
|
||||
|
||||
login_sequence();
|
||||
UserHelper::login_sequence();
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
?>
|
||||
|
@ -43,7 +43,7 @@
|
|||
const __csrf_token = "<?php echo $_SESSION["csrf_token"]; ?>";
|
||||
</script>
|
||||
|
||||
<?php print_user_stylesheet() ?>
|
||||
<?php UserHelper::print_user_stylesheet() ?>
|
||||
|
||||
<link rel="shortcut icon" type="image/png" href="images/favicon.png"/>
|
||||
<link rel="icon" type="image/png" sizes="72x72" href="images/favicon-72px.png" />
|
||||
|
|
|
@ -288,7 +288,7 @@
|
|||
|
||||
$new_uid = db_fetch_result($result, 0, "id");
|
||||
|
||||
initialize_user( $new_uid);
|
||||
Pref_Users::initialize_user($new_uid);
|
||||
|
||||
$reg_text = "Hi!\n".
|
||||
"\n".
|
||||
|
|
13
update.php
13
update.php
|
@ -14,6 +14,19 @@
|
|||
require_once "db.php";
|
||||
require_once "db-prefs.php";
|
||||
|
||||
function make_stampfile($filename) {
|
||||
$fp = fopen(LOCK_DIRECTORY . "/$filename", "w");
|
||||
|
||||
if (flock($fp, LOCK_EX | LOCK_NB)) {
|
||||
fwrite($fp, time() . "\n");
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function cleanup_tags($days = 14, $limit = 1000) {
|
||||
|
||||
$days = (int) $days;
|
||||
|
|
Loading…
Reference in New Issue