public function nonceAction() { header('Content-Type: application/json; charset=UTF-8'); header('Last-Modified: ' . gmdate('D, d M Y H:i:s \\G\\M\\T')); header('Expires: 0'); header('Cache-Control: private, no-cache, no-store, must-revalidate'); header('Pragma: no-cache'); $user = isset($_GET['user']) ? $_GET['user'] : ''; if (ctype_alnum($user)) { try { $salt = FreshRSS_Context::$system_conf->salt; $conf = get_user_configuration($user); $s = $conf->passwordHash; if (strlen($s) >= 60) { $this->view->salt1 = substr($s, 0, 29); //CRYPT_BLOWFISH Salt: "$2a$", a two digit cost parameter, "$", and 22 characters from the alphabet "./0-9A-Za-z". $this->view->nonce = sha1($salt . uniqid(mt_rand(), true)); Minz_Session::_param('nonce', $this->view->nonce); return; //Success } } catch (Minz_Exception $me) { Minz_Log::warning('Nonce failure: ' . $me->getMessage()); } } else { Minz_Log::notice('Nonce failure due to invalid username!'); } $this->view->nonce = ''; //Failure $this->view->salt1 = ''; }
public function nonceAction() { header('Content-Type: application/json; charset=UTF-8'); header('Last-Modified: ' . gmdate('D, d M Y H:i:s \\G\\M\\T')); header('Expires: 0'); header('Cache-Control: private, no-cache, no-store, must-revalidate'); header('Pragma: no-cache'); $user = isset($_GET['user']) ? $_GET['user'] : ''; if (ctype_alnum($user)) { try { $salt = FreshRSS_Context::$system_conf->salt; $conf = get_user_configuration($user); $s = $conf->passwordHash; if (strlen($s) >= 60) { $this->view->salt1 = substr($s, 0, 29); //CRYPT_BLOWFISH Salt: "$2a$", a two digit cost parameter, "$", and 22 characters from the alphabet "./0-9A-Za-z". $this->view->nonce = sha1($salt . uniqid(mt_rand(), true)); Minz_Session::_param('nonce', $this->view->nonce); return; //Success } } catch (Minz_Exception $me) { Minz_Log::warning('Nonce failure: ' . $me->getMessage()); } } else { Minz_Log::notice('Nonce failure due to invalid username!'); } //Failure: Return random data. $this->view->salt1 = sprintf('$2a$%02d$', FreshRSS_user_Controller::BCRYPT_COST); $alphabet = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for ($i = 22; $i > 0; $i--) { $this->view->salt1 .= $alphabet[rand(0, 63)]; } $this->view->nonce = sha1(rand()); }
/** * This action actualizes entries from one or several feeds. * * Parameters are: * - id (default: false): Feed ID * - url (default: false): Feed URL * - force (default: false) * If id and url are not specified, all the feeds are actualized. But if force is * false, process stops at 10 feeds to avoid time execution problem. */ public function actualizeAction($simplePiePush = null) { @set_time_limit(300); $feedDAO = FreshRSS_Factory::createFeedDao(); $entryDAO = FreshRSS_Factory::createEntryDao(); Minz_Session::_param('actualize_feeds', false); $id = Minz_Request::param('id'); $url = Minz_Request::param('url'); $force = Minz_Request::param('force'); // Create a list of feeds to actualize. // If id is set and valid, corresponding feed is added to the list but // alone in order to automatize further process. $feeds = array(); if ($id || $url) { $feed = $id ? $feedDAO->searchById($id) : $feedDAO->searchByUrl($url); if ($feed) { $feeds[] = $feed; } } else { $feeds = $feedDAO->listFeedsOrderUpdate(FreshRSS_Context::$user_conf->ttl_default); } // Calculate date of oldest entries we accept in DB. $nb_month_old = max(FreshRSS_Context::$user_conf->old_entries, 1); $date_min = time() - 3600 * 24 * 30 * $nb_month_old; // PubSubHubbub support $pubsubhubbubEnabledGeneral = FreshRSS_Context::$system_conf->pubsubhubbub_enabled; $pshbMinAge = time() - 3600 * 24; //TODO: Make a configuration. $updated_feeds = 0; $is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0; foreach ($feeds as $feed) { $url = $feed->url(); //For detection of HTTP 301 $pubSubHubbubEnabled = $pubsubhubbubEnabledGeneral && $feed->pubSubHubbubEnabled(); if (!$simplePiePush && !$id && $pubSubHubbubEnabled && $feed->lastUpdate() > $pshbMinAge) { //$text = 'Skip pull of feed using PubSubHubbub: ' . $url; //Minz_Log::debug($text); //file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND); continue; //When PubSubHubbub is used, do not pull refresh so often } if (!$feed->lock()) { Minz_Log::notice('Feed already being actualized: ' . $feed->url()); continue; } try { if ($simplePiePush) { $feed->loadEntries($simplePiePush); //Used by PubSubHubbub } else { $feed->load(false); } } catch (FreshRSS_Feed_Exception $e) { Minz_Log::warning($e->getMessage()); $feedDAO->updateLastUpdate($feed->id(), true); $feed->unlock(); continue; } $feed_history = $feed->keepHistory(); if ($feed_history == -2) { // TODO: -2 must be a constant! // -2 means we take the default value from configuration $feed_history = FreshRSS_Context::$user_conf->keep_history_default; } // We want chronological order and SimplePie uses reverse order. $entries = array_reverse($feed->entries()); if (count($entries) > 0) { $newGuids = array(); foreach ($entries as $entry) { $newGuids[] = $entry->guid(); } // For this feed, check existing GUIDs already in database. $existingHashForGuids = $entryDAO->listHashForFeedGuids($feed->id(), $newGuids); unset($newGuids); $oldGuids = array(); // Add entries in database if possible. foreach ($entries as $entry) { $entry_date = $entry->date(true); if (isset($existingHashForGuids[$entry->guid()])) { $existingHash = $existingHashForGuids[$entry->guid()]; if (strcasecmp($existingHash, $entry->hash()) === 0 || $existingHash === '00000000000000000000000000000000') { //This entry already exists and is unchanged. TODO: Remove the test with the zero'ed hash in FreshRSS v1.3 $oldGuids[] = $entry->guid(); } else { //This entry already exists but has been updated Minz_Log::debug('Entry with GUID `' . $entry->guid() . '` updated in feed ' . $feed->id() . ', old hash ' . $existingHash . ', new hash ' . $entry->hash()); //TODO: Make an updated/is_read policy by feed, in addition to the global one. $entry->_isRead(FreshRSS_Context::$user_conf->mark_updated_article_unread ? false : null); //Change is_read according to policy. if (!$entryDAO->hasTransaction()) { $entryDAO->beginTransaction(); } $entryDAO->updateEntry($entry->toArray()); } } elseif ($feed_history == 0 && $entry_date < $date_min) { // This entry should not be added considering configuration and date. $oldGuids[] = $entry->guid(); } else { if ($entry_date < $date_min) { $id = min(time(), $entry_date) . uSecString(); $entry->_isRead(true); //Old article that was not in database. Probably an error, so mark as read } else { $id = uTimeString(); $entry->_isRead($is_read); } $entry->_id($id); $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry); if ($entry === null) { // An extension has returned a null value, there is nothing to insert. continue; } if ($pubSubHubbubEnabled && !$simplePiePush) { //We use push, but have discovered an article by pull! $text = 'An article was discovered by pull although we use PubSubHubbub!: Feed ' . $url . ' GUID ' . $entry->guid(); file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND); Minz_Log::warning($text); $pubSubHubbubEnabled = false; $feed->pubSubHubbubError(true); } if (!$entryDAO->hasTransaction()) { $entryDAO->beginTransaction(); } $entryDAO->addEntry($entry->toArray()); } } $entryDAO->updateLastSeen($feed->id(), $oldGuids); } if ($feed_history >= 0 && rand(0, 30) === 1) { // TODO: move this function in web cron when available (see entry::purge) // Remove old entries once in 30. if (!$entryDAO->hasTransaction()) { $entryDAO->beginTransaction(); } $nb = $feedDAO->cleanOldEntries($feed->id(), $date_min, max($feed_history, count($entries) + 10)); if ($nb > 0) { Minz_Log::debug($nb . ' old entries cleaned in feed [' . $feed->url() . ']'); } } $feedDAO->updateLastUpdate($feed->id(), 0, $entryDAO->hasTransaction()); if ($entryDAO->hasTransaction()) { $entryDAO->commit(); } if ($feed->hubUrl() && $feed->selfUrl()) { //selfUrl has priority for PubSubHubbub if ($feed->selfUrl() !== $url) { //https://code.google.com/p/pubsubhubbub/wiki/MovingFeedsOrChangingHubs $selfUrl = checkUrl($feed->selfUrl()); if ($selfUrl) { Minz_Log::debug('PubSubHubbub unsubscribe ' . $feed->url()); if (!$feed->pubSubHubbubSubscribe(false)) { //Unsubscribe Minz_Log::warning('Error while PubSubHubbub unsubscribing from ' . $feed->url()); } $feed->_url($selfUrl, false); Minz_Log::notice('Feed ' . $url . ' canonical address moved to ' . $feed->url()); $feedDAO->updateFeed($feed->id(), array('url' => $feed->url())); } } } elseif ($feed->url() !== $url) { // HTTP 301 Moved Permanently Minz_Log::notice('Feed ' . $url . ' moved permanently to ' . $feed->url()); $feedDAO->updateFeed($feed->id(), array('url' => $feed->url())); } $feed->faviconPrepare(); if ($pubsubhubbubEnabledGeneral && $feed->pubSubHubbubPrepare()) { Minz_Log::notice('PubSubHubbub subscribe ' . $feed->url()); if (!$feed->pubSubHubbubSubscribe(true)) { //Subscribe Minz_Log::warning('Error while PubSubHubbub subscribing to ' . $feed->url()); } } $feed->unlock(); $updated_feeds++; unset($feed); // No more than 10 feeds unless $force is true to avoid overloading // the server. if ($updated_feeds >= 10 && !$force) { break; } } if (Minz_Request::param('ajax')) { // Most of the time, ajax request is for only one feed. But since // there are several parallel requests, we should return that there // are several updated feeds. $notif = array('type' => 'good', 'content' => _t('feedback.sub.feed.actualizeds')); Minz_Session::_param('notification', $notif); // No layout in ajax request. $this->view->_useLayout(false); } else { // Redirect to the main page with correct notification. if ($updated_feeds === 1) { $feed = reset($feeds); Minz_Request::good(_t('feedback.sub.feed.actualized', $feed->name()), array('params' => array('get' => 'f_' . $feed->id()))); } elseif ($updated_feeds > 1) { Minz_Request::good(_t('feedback.sub.feed.n_actualized', $updated_feeds), array()); } else { Minz_Request::good(_t('feedback.sub.feed.no_refresh'), array()); } } return $updated_feeds; }
/** * This action displays the RSS feed of FreshRSS. */ public function rssAction() { $allow_anonymous = FreshRSS_Context::$system_conf->allow_anonymous; $token = FreshRSS_Context::$user_conf->token; $token_param = Minz_Request::param('token', ''); $token_is_ok = $token != '' && $token === $token_param; // Check if user has access. if (!FreshRSS_Auth::hasAccess() && !$allow_anonymous && !$token_is_ok) { Minz_Error::error(403); } try { $this->updateContext(); } catch (FreshRSS_Context_Exception $e) { Minz_Error::error(404); } try { $this->view->entries = $this->listEntriesByContext(); } catch (FreshRSS_EntriesGetter_Exception $e) { Minz_Log::notice($e->getMessage()); Minz_Error::error(404); } // No layout for RSS output. $this->view->url = empty($_SERVER['QUERY_STRING']) ? '' : '?' . $_SERVER['QUERY_STRING']; $this->view->rss_title = FreshRSS_Context::$name . ' | ' . Minz_View::title(); $this->view->_useLayout(false); header('Content-Type: application/rss+xml; charset=utf-8'); }
function get_content_by_parsing($url, $path) { require_once LIB_PATH . '/lib_phpQuery.php'; Minz_Log::notice('FreshRSS GET ' . url_remove_credentials($url)); $html = file_get_contents($url); if ($html) { $doc = phpQuery::newDocument($html); $content = $doc->find($path); return sanitizeHTML($content->__toString(), $url); } else { throw new Exception(); } }
if (defined('STDOUT')) { fwrite(STDOUT, 'Actualize ' . $user . "...\n"); //Unbuffered } echo $user, ' '; //Buffered Minz_Session::_param('currentUser', $user); new Minz_ModelPdo($user); //TODO: FIXME: Quick-fix while waiting for a better FreshRSS() constructor/init FreshRSS_Auth::giveAccess(); $app->init(); $app->run(); if (!invalidateHttpCache()) { Minz_Log::notice('FreshRSS write access problem in ' . join_path(USERS_PATH, $user, 'log.txt'), $log_file); if (defined('STDERR')) { fwrite(STDERR, 'Write access problem in ' . join_path(USERS_PATH, $user, 'log.txt') . "\n"); } } } Minz_Log::notice('FreshRSS actualize done.', $log_file); if (defined('STDOUT')) { fwrite(STDOUT, 'Done.' . "\n"); $end_date = date_create('now'); $duration = date_diff($end_date, $begin_date); fwrite(STDOUT, 'Ending feed actualization at ' . $end_date->format('c') . "\n"); //Unbuffered fwrite(STDOUT, 'Feed actualizations took ' . $duration->format('%a day(s), %h hour(s), %i minute(s) and %s seconds') . ' for ' . count($users) . " users\n"); //Unbuffered } echo 'End.', "\n"; ob_end_flush();
public function load($loadDetails = false) { if ($this->url !== null) { if (CACHE_PATH === false) { throw new Minz_FileNotExistException('CACHE_PATH', Minz_Exception::ERROR); } else { $url = htmlspecialchars_decode($this->url, ENT_QUOTES); if ($this->httpAuth != '') { $url = preg_replace('#((.+)://)(.+)#', '${1}' . $this->httpAuth . '@${3}', $url); } $feed = customSimplePie(); if (substr($url, -11) === '#force_feed') { $feed->force_feed(true); $url = substr($url, 0, -11); } $feed->set_feed_url($url); if (!$loadDetails) { //Only activates auto-discovery when adding a new feed $feed->set_autodiscovery_level(SIMPLEPIE_LOCATOR_NONE); } $mtime = $feed->init(); if (!$mtime || $feed->error()) { $errorMessage = $feed->error(); throw new FreshRSS_Feed_Exception(($errorMessage == '' ? 'Feed error' : $errorMessage) . ' [' . $url . ']'); } if ($loadDetails) { // si on a utilisé l'auto-discover, notre url va avoir changé $subscribe_url = $feed->subscribe_url(false); $title = strtr(html_only_entity_decode($feed->get_title()), array('<' => '<', '>' => '>', '"' => '"')); //HTML to HTML-PRE //ENT_COMPAT except & $this->_name($title == '' ? $url : $title); $this->_website(html_only_entity_decode($feed->get_link())); $this->_description(html_only_entity_decode($feed->get_description())); } else { //The case of HTTP 301 Moved Permanently $subscribe_url = $feed->subscribe_url(true); } $clean_url = url_remove_credentials($subscribe_url); if ($subscribe_url !== null && $subscribe_url !== $url) { $this->_url($clean_url); } if ($mtime === true || $mtime > $this->lastUpdate) { Minz_Log::notice('FreshRSS no cache ' . $mtime . ' > ' . $this->lastUpdate . ' for ' . $clean_url); $this->loadEntries($feed); // et on charge les articles du flux } else { Minz_Log::notice('FreshRSS use cache for ' . $clean_url); $this->entries = array(); } $feed->__destruct(); //http://simplepie.org/wiki/faq/i_m_getting_memory_leaks unset($feed); } } }
function get_content_by_parsing($url, $path) { require_once LIB_PATH . '/lib_phpQuery.php'; Minz_Log::notice('FreshRSS GET ' . SimplePie_Misc::url_remove_credentials($url)); $html = file_get_contents($url); if ($html) { $doc = phpQuery::newDocument($html); $content = $doc->find($path); foreach (pq('img[data-src]') as $img) { $imgP = pq($img); $dataSrc = $imgP->attr('data-src'); if (strlen($dataSrc) > 4) { $imgP->attr('src', $dataSrc); $imgP->removeAttr('data-src'); } } return sanitizeHTML($content->__toString(), $url); } else { throw new Exception(); } }
/** * Affiche la Vue en elle-même */ public function render() { if (!$this->includeFile($this->view_filename)) { Minz_Log::notice('File not found: `' . $this->view_filename . '`'); } }
/** * This action actualizes entries from one or several feeds. * * Parameters are: * - id (default: false) * - force (default: false) * If id is not specified, all the feeds are actualized. But if force is * false, process stops at 10 feeds to avoid time execution problem. */ public function actualizeAction() { @set_time_limit(300); $feedDAO = FreshRSS_Factory::createFeedDao(); $entryDAO = FreshRSS_Factory::createEntryDao(); Minz_Session::_param('actualize_feeds', false); $id = Minz_Request::param('id'); $force = Minz_Request::param('force'); // Create a list of feeds to actualize. // If id is set and valid, corresponding feed is added to the list but // alone in order to automatize further process. $feeds = array(); if ($id) { $feed = $feedDAO->searchById($id); if ($feed) { $feeds[] = $feed; } } else { $feeds = $feedDAO->listFeedsOrderUpdate(FreshRSS_Context::$user_conf->ttl_default); } // Calculate date of oldest entries we accept in DB. $nb_month_old = max(FreshRSS_Context::$user_conf->old_entries, 1); $date_min = time() - 3600 * 24 * 30 * $nb_month_old; $updated_feeds = 0; $is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0; foreach ($feeds as $feed) { if (!$feed->lock()) { Minz_Log::notice('Feed already being actualized: ' . $feed->url()); continue; } try { // Load entries $feed->load(false); } catch (FreshRSS_Feed_Exception $e) { Minz_Log::notice($e->getMessage()); $feedDAO->updateLastUpdate($feed->id(), 1); $feed->unlock(); continue; } $url = $feed->url(); $feed_history = $feed->keepHistory(); if ($feed_history == -2) { // TODO: -2 must be a constant! // -2 means we take the default value from configuration $feed_history = FreshRSS_Context::$user_conf->keep_history_default; } // We want chronological order and SimplePie uses reverse order. $entries = array_reverse($feed->entries()); if (count($entries) > 0) { // For this feed, check last n entry GUIDs already in database. $existing_guids = array_fill_keys($entryDAO->listLastGuidsByFeed($feed->id(), count($entries) + 10), 1); $use_declared_date = empty($existing_guids); // Add entries in database if possible. $prepared_statement = $entryDAO->addEntryPrepare(); $feedDAO->beginTransaction(); foreach ($entries as $entry) { $entry_date = $entry->date(true); if (isset($existing_guids[$entry->guid()]) || $feed_history == 0 && $entry_date < $date_min) { // This entry already exists in DB or should not be added // considering configuration and date. continue; } $id = uTimeString(); if ($use_declared_date || $entry_date < $date_min) { // Use declared date at first import. $id = min(time(), $entry_date) . uSecString(); } $entry->_id($id); $entry->_isRead($is_read); $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry); if (is_null($entry)) { // An extension has returned a null value, there is nothing to insert. continue; } $values = $entry->toArray(); $entryDAO->addEntry($values, $prepared_statement); } } if ($feed_history >= 0 && rand(0, 30) === 1) { // TODO: move this function in web cron when available (see entry::purge) // Remove old entries once in 30. if (!$feedDAO->hasTransaction()) { $feedDAO->beginTransaction(); } $nb = $feedDAO->cleanOldEntries($feed->id(), $date_min, max($feed_history, count($entries) + 10)); if ($nb > 0) { Minz_Log::debug($nb . ' old entries cleaned in feed [' . $feed->url() . ']'); } } $feedDAO->updateLastUpdate($feed->id(), 0, $feedDAO->hasTransaction()); if ($feedDAO->hasTransaction()) { $feedDAO->commit(); } if ($feed->url() !== $url) { // HTTP 301 Moved Permanently Minz_Log::notice('Feed ' . $url . ' moved permanently to ' . $feed->url()); $feedDAO->updateFeed($feed->id(), array('url' => $feed->url())); } $feed->faviconPrepare(); $feed->unlock(); $updated_feeds++; unset($feed); // No more than 10 feeds unless $force is true to avoid overloading // the server. if ($updated_feeds >= 10 && !$force) { break; } } if (Minz_Request::param('ajax')) { // Most of the time, ajax request is for only one feed. But since // there are several parallel requests, we should return that there // are several updated feeds. $notif = array('type' => 'good', 'content' => _t('feedback.sub.feed.actualizeds')); Minz_Session::_param('notification', $notif); // No layout in ajax request. $this->view->_useLayout(false); return; } // Redirect to the main page with correct notification. if ($updated_feeds === 1) { $feed = reset($feeds); Minz_Request::good(_t('feedback.sub.feed.actualized', $feed->name()), array('params' => array('get' => 'f_' . $feed->id()))); } elseif ($updated_feeds > 1) { Minz_Request::good(_t('feedback.sub.feed.n_actualized', $updated_feeds), array()); } else { Minz_Request::good(_t('feedback.sub.feed.no_refresh'), array()); } }