/** * Load and parse RSS file * * @global string $CONFIG['TIMEZONE'] * @param string $rss_url * @param int $timestamp unix timestamp * @return array|false */ private function parse($rss_url, $timestamp = null) { // Sanity check if (!filter_var($rss_url, FILTER_VALIDATE_URL)) { return false; } $timestamp = filter_var($timestamp, FILTER_VALIDATE_INT); // -------------------------------------------------------------------- // Extablish Conditional GET // -------------------------------------------------------------------- $modified = null; $opts = array(); if ($timestamp) { date_default_timezone_set('GMT'); $modified = date('D, d M Y H:i:s', $timestamp) . ' GMT'; date_default_timezone_set($GLOBALS['CONFIG']['TIMEZONE']); } if ($modified) { // file_get_contents() compatible headers for Conditional GET $opts = array('http' => array('header' => "If-Modified-Since: {$modified}\r\n")); } // -------------------------------------------------------------------- // Backtrack_limit is too restrictive for complex feeds, boost it // -------------------------------------------------------------------- ini_set('pcre.backtrack_limit', 999999); // -------------------------------------------------------------------- // Parse // -------------------------------------------------------------------- if (ini_get('allow_url_fopen')) { // file_get_contents ini_set('default_socket_timeout', 30); $ctx = stream_context_create($opts); $rss_content = @file_get_contents($rss_url, null, $ctx); } elseif (function_exists('curl_init')) { // cURL $ch = curl_init(); if ($modified) { curl_setopt($ch, CURLOPT_HTTPHEADER, $opts['http']['header']); } curl_setopt($ch, CURLOPT_URL, $rss_url); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $rss_content = curl_exec($ch); curl_close($ch); } else { throw new Exception('No way to retrieve RSS feeds'); } if ($rss_content) { // Parse document encoding $result['encoding'] = $this->myPregMatch("'encoding=[\\'\"](.*?)[\\'\"]'si", $rss_content); if ($result['encoding'] != '') { // if document codepage is specified, use it // This is used in myPregMatch() $this->rsscp = $result['encoding']; } else { // otherwise use UTF-8 // This is used in myPregMatch() $this->rsscp = 'UTF-8'; } // --------------------------------------------------------------- // Parse CHANNEL info // --------------------------------------------------------------- // Init some variables $is_atom = false; $out_channel = array(); preg_match("'<channel.*?>(.*?)</channel>'si", $rss_content, $out_channel); if (!count($out_channel)) { // Maybe this is an Atom feed? Parse FEED info preg_match("'<feed.*?>(.*?)</feed>'si", $rss_content, $out_channel); if (count($out_channel)) { $is_atom = true; } else { return false; } // This isn't an RSS/Atom feed, abort } foreach ($this->channeltags as $channeltag) { if ($is_atom && isset($this->channeltags_atom[$channeltag])) { // Atom specific tag if (is_array($this->channeltags_atom[$channeltag])) { foreach ($this->channeltags_atom[$channeltag] as $tmp_tag) { $temp = $this->myPregMatch("'<{$tmp_tag}.*?>(.*?)</{$tmp_tag}>'si", @$out_channel[1]); if (!empty($temp)) { break; } } } else { $temp = $this->myPregMatch("'<{$this->channeltags_atom[$channeltag]}.*?>(.*?)</{$this->channeltags_atom[$channeltag]}>'si", @$out_channel[1]); } } else { if ($is_atom && $channeltag == 'link') { // Yet more Atom tom-fuckery $temp = $this->myPregMatch('#<link[\\s]+[^>]*?href[\\s]?=[\\s"\']+(.*?)["\']+.*?/>#si', @$out_channel[1]); } else { // RSS compatible channel tag $temp = $this->myPregMatch("'<{$channeltag}.*?>(.*?)</{$channeltag}>'si", @$out_channel[1]); } } if (!empty($temp)) { $result[$channeltag] = $temp; } // Set only if not empty } // If date_format is specified and lastBuildDate is valid if ($this->date_format != '' && isset($result['lastBuildDate']) && ($timestamp = strtotime($result['lastBuildDate'])) !== -1) { // convert lastBuildDate to specified date format $result['lastBuildDate'] = date($this->date_format, $timestamp); } // --------------------------------------------------------------- // Parse TEXTINPUT info // --------------------------------------------------------------- $out_textinfo = array(); preg_match("'<textinput(|[^>]*[^/])>(.*?)</textinput>'si", $rss_content, $out_textinfo); // This a little strange regexp means: // Look for tag <textinput> with or without any attributes, but skip truncated version <textinput /> (it's not beggining tag) if (isset($out_textinfo[2])) { foreach ($this->textinputtags as $textinputtag) { $temp = $this->myPregMatch("'<{$textinputtag}.*?>(.*?)</{$textinputtag}>'si", $out_textinfo[2]); if (!empty($temp)) { $result['textinput_' . $textinputtag] = $temp; } // Set only if not empty } } // --------------------------------------------------------------- // Parse IMAGE info // --------------------------------------------------------------- $out_imageinfo = array(); preg_match("'<image.*?>(.*?)</image>'si", $rss_content, $out_imageinfo); if (isset($out_imageinfo[1])) { foreach ($this->imagetags as $imagetag) { $temp = $this->myPregMatch("'<{$imagetag}.*?>(.*?)</{$imagetag}>'si", $out_imageinfo[1]); if (!empty($temp)) { $result['image_' . $imagetag] = $temp; } // Set only if not empty } } // --------------------------------------------------------------- // Parse ITEMS // --------------------------------------------------------------- $items = array(); if ($is_atom) { preg_match_all("'<entry(| .*?)>(.*?)</entry>'si", $rss_content, $items); } else { preg_match_all("'<item(| .*?)>(.*?)</item>'si", $rss_content, $items); } // RSS $rss_items = $items[2]; $i = 0; $result['items'] = array(); // create array even if there are no items foreach ($rss_items as $rss_item) { if ($i < $this->items_limit || $this->items_limit == 0) { // --------------------------------------------------------------- // Go through each $itemtags and collect the data // --------------------------------------------------------------- foreach ($this->itemtags as $itemtag) { if ($itemtag == 'category') { $tmp_funct = 'myPregMatchAll'; } else { $tmp_funct = 'myPregMatch'; } if ($is_atom && isset($this->itemtags_atom[$itemtag])) { // Atom specific tag if (is_array($this->itemtags_atom[$itemtag])) { foreach ($this->itemtags_atom[$itemtag] as $tmp_tag) { $temp = $this->{$tmp_funct}("'<{$tmp_tag}.*?>(.*?)</{$tmp_tag}>'si", $rss_item); if (!empty($temp)) { break; } } } else { $temp = $this->{$tmp_funct}("'<{$this->itemtags_atom[$itemtag]}.*?>(.*?)</{$this->itemtags_atom[$itemtag]}>'si", $rss_item); } } else { if ($is_atom && $itemtag == 'link') { // Yet more Atom tom-fuckery $temp = $this->{$tmp_funct}('#<link[\\s]+[^>]*?href[\\s]?=[\\s"\']+(.*?)["\']+.*?/>#si', $rss_item); } else { // RSS compatible item tag $temp = $this->{$tmp_funct}("'<{$itemtag}.*?>(.*?)</{$itemtag}>'si", $rss_item); } } // Check if link is valid if ($itemtag == 'link' && !suxFunct::canonicalizeUrl($temp)) { // Seriously? An invalid URL? And I'm supposed to care? // Why do I feel like this is some sort abusive dad pushing // their kids to play hockey against their wishes thing? $pattern = '#<.*?xml:base[\\s]?=[\\s"\']+(.*?)["\']+#i'; $out_baseurl = array(); // Attempt 1) Look for xml:base in this node preg_match($pattern, $rss_item, $out_baseurl); if (!isset($out_baseurl[1]) || !suxFunct::canonicalizeUrl($out_baseurl[1])) { // Attempt 2) Look for xml:base anywhere, starting from the begining of the document preg_match($pattern, $rss_content, $out_baseurl); if (!isset($out_baseurl[1]) || !suxFunct::canonicalizeUrl($out_baseurl[1])) { // Attempt 3) Look for the channel <link> and see if that's a real url if (isset($result['link']) && suxFunct::canonicalizeUrl($result['link'])) { $out_baseurl[1] = $result['link']; } } } if (isset($out_baseurl[1])) { $temp = trim($temp); $temp = trim($temp, '/'); $temp2 = parse_url($out_baseurl[1]); if (isset($temp2['port'])) { $temp2 = "{$temp2['scheme']}://{$temp['host']}:{$temp2['port']}"; } else { $temp2 = "{$temp2['scheme']}://{$temp2['host']}"; } $temp = "{$temp2}/{$temp}"; $temp = suxFunct::canonicalizeUrl($temp); } } // Stack it if (!empty($temp)) { $result['items'][$i][$itemtag] = $temp; } } // --------------------------------------------------------------- // Make some adjustments // --------------------------------------------------------------- // If date_format is specified and pubDate is valid if ($this->date_format != '' && isset($result['items'][$i]['pubDate']) && ($timestamp = strtotime($result['items'][$i]['pubDate'])) !== -1) { // convert pubDate to specified date format $result['items'][$i]['pubDate'] = date($this->date_format, $timestamp); } else { unset($result['items'][$i]['pubDate']); } // Item counter $i++; } } // Don't trust data from external website, sanitize everything array_walk_recursive($result, array($this, 'sanitizeByReference')); $result['items_count'] = $i; // new dBug($result); // exit; return $result; } else { // Error in opening return False return false; } }
/** * for suxValidate, check if a duplicate openid url exists * * @return bool */ function isDuplicateOpenIDUrl($value, $empty, &$params, &$formvars) { if (empty($formvars['url'])) { return false; } $user = $this->user->getUserByOpenID(suxFunct::canonicalizeUrl($formvars['url'])); if ($user) { return false; } else { return true; } }
/** * trust a url * @param int $id user id * @param string $id url * @return bool */ private function trustUrl($id, $url) { if (!filter_var($id, FILTER_VALIDATE_INT) || $id < 1) { return false; } $url = suxFunct::canonicalizeUrl($url); $trusted = array('users_id' => $id, 'auth_url' => $url); $query = suxDB::prepareCountQuery($this->db_table_trust, $trusted); $st = $this->db->prepare($query); $st->execute($trusted); if (!$st->fetchColumn()) { $query = suxDB::prepareInsertQuery($this->db_table_trust, $trusted); $st = $this->db->prepare($query); $st->execute($trusted); } }
/** * Constructor * * @param string $key PDO dsn key */ function __construct($id) { // Declare objects $this->msg = new suxThreadedMessages(); $this->bookmarks = new suxBookmarks(); $this->r = new blogRenderer($this->module); // Renderer suxValidate::register_object('this', $this); // Register self to validator parent::__construct(); // Let the parent do the rest // Declare properties $this->id = $id; $this->msg->setPublished(null); $this->bookmarks->setPublished(null); // If feature is turned off, then redirect if ($GLOBALS['CONFIG']['FEATURE']['auto_bookmark'] == false) { suxFunct::redirect(suxFunct::getPreviousURL()); } // Redirect if not logged in if (empty($_SESSION['users_id'])) { suxFunct::redirect(suxFunct::makeUrl('/user/register')); } // -------------------------------------------------------------------- // Scan post for href links // -------------------------------------------------------------------- $msg = $this->msg->getByID($id); if (!$msg) { suxFunct::redirect(suxFunct::getPreviousURL()); } // No message, skip if ($msg['users_id'] != $_SESSION['users_id']) { suxFunct::redirect(suxFunct::getPreviousURL()); } // Not the user's message, skip $matches = array(); $pattern = '#<a[\\s]+[^>]*?href[\\s]?=[\\s"\']+(.*?)["\']+.*?>([^<]+|.*?)?</a>#si'; // href pattern preg_match_all($pattern, $msg['body_html'], $matches); $count = count($matches[1]); if (!$count) { suxFunct::redirect(suxFunct::getPreviousURL()); } // No links, skip // Limit the amount of time we wait for a connection to a remote server to 5 seconds ini_set('default_socket_timeout', 5); for ($i = 0; $i < $count; ++$i) { if (mb_substr($matches[1][$i], 0, 7) == 'http://' || mb_substr($matches[1][$i], 0, 8) == 'https://') { // Basic info $url = suxFunct::canonicalizeUrl($matches[1][$i]); if (!filter_var($url, FILTER_VALIDATE_URL) || $this->bookmarks->getByID($url)) { continue; } // skip it $title = strip_tags($matches[2][$i]); $body = null; if (!$this->r->detectPOST()) { $tmp = $this->bookmarks->fetchUrlInfo($url); if ($tmp) { $title = $tmp['title']; $body = $tmp['description']; } } // Add to array for use in template $this->arr['found_links'][$url] = array('title' => $title, 'body' => $body); } } $count = count(@$this->arr['found_links']); if (!$count) { suxFunct::redirect(suxFunct::getPreviousURL()); } // No links, skip }
/** * Saves a bookmark to the database * * @param int $users_id users_id * @param array $url required keys => (url, title, body) optional keys => (id, published_on, draft) * @param int $trusted passed on to sanitizeHtml() * @return int insert id */ function save($users_id, array $url, $trusted = -1) { // ------------------------------------------------------------------- // Sanitize // ------------------------------------------------------------------- if (!filter_var($users_id, FILTER_VALIDATE_INT) || $users_id < 1) { throw new Exception('Invalid user id'); } if (!isset($url['url']) || !isset($url['title']) || !isset($url['body'])) { throw new Exception('Invalid $url array'); } if (!filter_var($url['url'], FILTER_VALIDATE_URL)) { throw new Exception('Invalid url'); } // Users id $clean['users_id'] = $users_id; // Canonicalize Url $clean['url'] = suxFunct::canonicalizeUrl($url['url']); // No HTML in title $clean['title'] = strip_tags($url['title']); // Sanitize HTML in body $clean['body_html'] = suxFunct::sanitizeHtml($url['body'], $trusted); // Convert and copy body to UTF-8 plaintext $converter = new suxHtml2UTF8($clean['body_html']); $clean['body_plaintext'] = $converter->getText(); // Id if (isset($url['id'])) { if (!filter_var($url['id'], FILTER_VALIDATE_INT) || $url['id'] < 1) { throw new Exception('Invalid id'); } else { $clean['id'] = $url['id']; } } else { $query = "SELECT id FROM {$this->db_table} WHERE url = ? "; $st = $this->db->prepare($query); $st->execute(array($clean['url'])); $edit = $st->fetch(PDO::FETCH_ASSOC); if ($edit) { $clean['id'] = $edit['id']; } } // Publish date if (isset($url['published_on'])) { // ISO 8601 date format // regex must match '2008-06-18 16:53:29' or '2008-06-18T16:53:29-04:00' $regex = '/^(\\d{4})-(0[0-9]|1[0,1,2])-([0,1,2][0-9]|3[0,1]).+(\\d{2}):(\\d{2}):(\\d{2})/'; if (!preg_match($regex, $url['published_on'])) { throw new Exception('Invalid date'); } $clean['published_on'] = $url['published_on']; } else { $clean['published_on'] = date('Y-m-d H:i:s'); } // Draft, boolean / tinyint $clean['draft'] = false; if (isset($url['draft']) && $url['draft']) { $clean['draft'] = true; } // We now have the $clean[] array // -------------------------------------------------------------------- // Go! // -------------------------------------------------------------------- // http://bugs.php.net/bug.php?id=44597 // As of 5.2.6 you still can't use this function's $input_parameters to // pass a boolean to PostgreSQL. To do that, you'll have to call // bindParam() with explicit types for *each* parameter in the query. // Annoying much? This sucks more than you can imagine. if (isset($clean['id'])) { // UPDATE unset($clean['users_id']); // Don't override the original submitter $query = suxDB::prepareUpdateQuery($this->db_table, $clean); $st = $this->db->prepare($query); if ($this->db_driver == 'pgsql') { $st->bindParam(':id', $clean['id'], PDO::PARAM_INT); $st->bindParam(':url', $clean['url'], PDO::PARAM_STR); $st->bindParam(':title', $clean['title'], PDO::PARAM_STR); $st->bindParam(':body_html', $clean['body_html'], PDO::PARAM_STR); $st->bindParam(':body_plaintext', $clean['body_plaintext'], PDO::PARAM_STR); $st->bindParam(':published_on', $clean['published_on'], PDO::PARAM_STR); $st->bindParam(':draft', $clean['draft'], PDO::PARAM_BOOL); $st->execute(); } else { $st->execute($clean); } } else { // INSERT $query = suxDB::prepareInsertQuery($this->db_table, $clean); $st = $this->db->prepare($query); if ($this->db_driver == 'pgsql') { $st->bindParam(':users_id', $clean['users_id'], PDO::PARAM_INT); $st->bindParam(':url', $clean['url'], PDO::PARAM_STR); $st->bindParam(':title', $clean['title'], PDO::PARAM_STR); $st->bindParam(':body_html', $clean['body_html'], PDO::PARAM_STR); $st->bindParam(':body_plaintext', $clean['body_plaintext'], PDO::PARAM_STR); $st->bindParam(':published_on', $clean['published_on'], PDO::PARAM_STR); $st->bindParam(':draft', $clean['draft'], PDO::PARAM_BOOL); $st->execute(); } else { $st->execute($clean); } if ($this->db_driver == 'pgsql') { $clean['id'] = $this->db->lastInsertId("{$this->db_table}_id_seq"); } else { $clean['id'] = $this->db->lastInsertId(); } } return $clean['id']; }
/** * Constructs a widget * * @global string $CONFIG['PATH'] * @param string $title a title * @param string $content html content * @param string $url URL for the title * @param string $image path to image (http://) * @param string $caption caption for image * @param string $url2 another url, for image * @param string $floater class for image encapsulation * @return string the html code */ function widget($title, $content, $url = null, $image = null, $caption = null, $url2 = null, $floater = 'floatright') { // Sanitize / Filter if ($url) { $url = suxFunct::canonicalizeUrl($url); if (!filter_var($url, FILTER_VALIDATE_URL)) { $url = null; } } if ($image) { $image = suxFunct::canonicalizeUrl($image); if (!filter_var($image, FILTER_VALIDATE_URL)) { $image = null; } // The server can be crippled if getimagesize() recursively points // to itself (example: . $image = /index.php) so we enforce image // extensions to avoid this if (!preg_match('/\\.(jpe?g|gif|png)$/i', $image)) { $image = null; } } if ($url2) { $url2 = suxFunct::canonicalizeUrl($url2); if (!filter_var($url2, FILTER_VALIDATE_URL)) { $url2 = null; } } // Image manipulation $size = $image ? @getimagesize($image) : null; if ($size) { $image = "<img src='{$image}' alt='' {$size[3]} />"; } else { $image = null; } // Makeshift renderer object $r['arr']['size'] = $size; $r['text']['title'] = $title; $r['text']['image'] = $image; $r['text']['caption'] = $caption; $r['text']['content'] = $content; $r['text']['floater'] = $floater; $r['text']['url_title'] = $url; $r['text']['url_image'] = $url; if ($url2) { $r['text']['url_image'] = $url2; } $r = (object) $r; // Template $tpl = new suxTemplate('globals'); $tpl->assignByRef('r', $r); return $tpl->fetch('widget.tpl'); }
/** * Helper function called by preg_replace() on link replacement. * * Maintains an internal list of links to be displayed at the end of the * text, with numeric indices to the original point in the text they * appeared. Also makes an effort at identifying and handling absolute * and relative links. * * @param string $link URL of the link * @param string $display Part of the text to associate number with * @return string */ private function buildLinkList($link, $display) { if (mb_substr($link, 0, 7) == 'http://' || mb_substr($link, 0, 8) == 'https://' || mb_substr($link, 0, 7) == 'mailto:') { // Absolute href links $link = suxFunct::canonicalizeUrl($link); $this->link_count++; $this->link_list .= "[" . $this->link_count . "] {$link}\n"; $additional = ' [' . $this->link_count . ']'; } elseif (mb_substr($link, 0, 11) == 'javascript:') { // Ignore javascript links $additional = ''; } else { // Relative href links $this->link_count++; $this->link_list .= "[" . $this->link_count . "] "; if (mb_substr($link, 0, 1) != '/') { $link = '/' . $link; } $link = suxFunct::canonicalizeUrl($this->url . $link); $this->link_list .= "{$link}\n"; $additional = ' [' . $this->link_count . ']'; } return $display . $additional; }
/** * Detach an openid from system * * @param string $openid_url url */ function detachOpenID($openid_url) { // Canonicalize url $openid_url = suxFunct::canonicalizeUrl($openid_url); $query = "DELETE FROM {$this->db_table_openid} WHERE openid_url = ? "; $st = $this->db->prepare($query); $st->execute(array($openid_url)); }