diff options
Diffstat (limited to 'vendor/fguillot/picofeed/lib/PicoFeed')
11 files changed, 318 insertions, 278 deletions
diff --git a/vendor/fguillot/picofeed/lib/PicoFeed/Client/Client.php b/vendor/fguillot/picofeed/lib/PicoFeed/Client/Client.php index 55d2c562f..84a5cf296 100644 --- a/vendor/fguillot/picofeed/lib/PicoFeed/Client/Client.php +++ b/vendor/fguillot/picofeed/lib/PicoFeed/Client/Client.php @@ -158,46 +158,21 @@ abstract class Client protected $status_code = 0; /** - * HTTP response body + * Enables direct passthrough to requesting client * * @access protected - * @var string - */ - protected $body = ''; - - /** - * Body size - * - * @access protected - * @var integer - */ - protected $body_length = 0; - - /** - * HTTP response headers - * - * @access protected - * @var array - */ - protected $headers = array(); - - /** - * Counter on the number of header received - * - * @access protected - * @var integer + * @var bool */ - protected $headers_counter = 0; + protected $passthrough = false; /** * Do the HTTP request * * @abstract * @access public - * @param bool $follow_location Flag used when there is an open_basedir restriction * @return array */ - abstract public function doRequest($follow_location = true); + abstract public function doRequest(); /** * Get client instance: curl or stream driver @@ -295,48 +270,6 @@ abstract class Client } } - /** - * Handle manually redirections when there is an open base dir restriction - * - * @access private - * @param string $location Redirected URL - * @return array - */ - public function handleRedirection($location) - { - $nb_redirects = 0; - $result = array(); - $this->url = Url::resolve($location, $this->url); - $this->body = ''; - $this->body_length = 0; - $this->headers = array(); - $this->headers_counter = 0; - - while (true) { - - $nb_redirects++; - - if ($nb_redirects >= $this->max_redirects) { - throw new MaxRedirectException('Maximum number of redirections reached'); - } - - $result = $this->doRequest(false); - - if ($result['status'] == 301 || $result['status'] == 302) { - $this->url = $result['headers']['Location']; - $this->body = ''; - $this->body_length = 0; - $this->headers = array(); - $this->headers_counter = 0; - } - else { - break; - } - } - - return $result; - } - /** * Check if a request has been modified according to the parameters * @@ -538,6 +471,17 @@ abstract class Client } /** + * return true if passthrough mode is enabled + * + * @access public + * @return bool + */ + public function isPassthroughEnabled() + { + return $this->passthrough; + } + + /** * Set connection timeout * * @access public @@ -668,6 +612,30 @@ abstract class Client } /** + * Enable the passthrough mode + * + * @access public + * @return \PicoFeed\Client\Client + */ + public function enablePassthroughMode() + { + $this->passthrough = true; + return $this; + } + + /** + * Disable the passthrough mode + * + * @access public + * @return \PicoFeed\Client\Client + */ + public function disablePassthroughMode() + { + $this->passthrough = false; + return $this; + } + + /** * Set config object * * @access public diff --git a/vendor/fguillot/picofeed/lib/PicoFeed/Client/Curl.php b/vendor/fguillot/picofeed/lib/PicoFeed/Client/Curl.php index d45773d2d..5e5514f52 100644 --- a/vendor/fguillot/picofeed/lib/PicoFeed/Client/Curl.php +++ b/vendor/fguillot/picofeed/lib/PicoFeed/Client/Curl.php @@ -13,6 +13,38 @@ use PicoFeed\Logging\Logger; class Curl extends Client { /** + * HTTP response body + * + * @access private + * @var string + */ + private $body = ''; + + /** + * Body size + * + * @access private + * @var integer + */ + private $body_length = 0; + + /** + * HTTP response headers + * + * @access private + * @var array + */ + private $headers = array(); + + /** + * Counter on the number of header received + * + * @access private + * @var integer + */ + private $headers_counter = 0; + + /** * cURL callback to read the HTTP body * * If the function return -1, curl stop to read the HTTP response @@ -64,6 +96,44 @@ class Curl extends Client } /** + * cURL callback to passthrough the HTTP status header to the client + * + * @access public + * @param resource $ch cURL handler + * @param string $buffer Header line + * @return integer Length of the buffer + */ + public function passthroughHeaders($ch, $buffer) + { + list($status, $headers) = HttpHeaders::parse(array($buffer)); + + if ($status !== 0) { + header(':', true, $status); + } + elseif (isset($headers['Content-Type'])) { + header($buffer); + } + + return $this->readHeaders($ch, $buffer); + } + + /** + * cURL callback to passthrough the HTTP body to the client + * + * If the function return -1, curl stop to read the HTTP response + * + * @access public + * @param resource $ch cURL handler + * @param string $buffer Chunk of data + * @return integer Length of the buffer + */ + public function passthroughBody($ch, $buffer) + { + echo $buffer; + return strlen($buffer); + } + + /** * Prepare HTTP headers * * @access private @@ -131,6 +201,29 @@ class Curl extends Client } /** + * Set write/header functions + * + * @access private + * @return resource $ch + */ + private function prepareDownloadMode($ch) + { + $write_function = 'readBody'; + $header_function = 'readHeaders'; + + if ($this->isPassthroughEnabled()) { + $write_function = 'passthroughBody'; + $header_function = 'passthroughHeaders'; + + } + + curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($this, $write_function)); + curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, $header_function)); + + return $ch; + } + + /** * Prepare curl context * * @access private @@ -147,12 +240,11 @@ class Curl extends Client curl_setopt($ch, CURLOPT_FOLLOWLOCATION, ini_get('open_basedir') === ''); curl_setopt($ch, CURLOPT_MAXREDIRS, $this->max_redirects); curl_setopt($ch, CURLOPT_ENCODING, ''); - curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($this, 'readBody')); - curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'readHeaders')); curl_setopt($ch, CURLOPT_COOKIEJAR, 'php://memory'); curl_setopt($ch, CURLOPT_COOKIEFILE, 'php://memory'); curl_setopt($ch, CURLOPT_SSLVERSION, 1); // Enforce TLS v1 + $ch = $this->prepareDownloadMode($ch); $ch = $this->prepareProxyContext($ch); $ch = $this->prepareAuthContext($ch); @@ -229,6 +321,48 @@ class Curl extends Client } /** + * Handle manually redirections when there is an open base dir restriction + * + * @access private + * @param string $location Redirected URL + * @return array + */ + private function handleRedirection($location) + { + $nb_redirects = 0; + $result = array(); + $this->url = Url::resolve($location, $this->url); + $this->body = ''; + $this->body_length = 0; + $this->headers = array(); + $this->headers_counter = 0; + + while (true) { + + $nb_redirects++; + + if ($nb_redirects >= $this->max_redirects) { + throw new MaxRedirectException('Maximum number of redirections reached'); + } + + $result = $this->doRequest(false); + + if ($result['status'] == 301 || $result['status'] == 302) { + $this->url = Url::resolve($result['headers']['Location'], $this->url); + $this->body = ''; + $this->body_length = 0; + $this->headers = array(); + $this->headers_counter = 0; + } + else { + break; + } + } + + return $result; + } + + /** * Handle cURL errors (throw individual exceptions) * * We don't use constants because they are not necessary always available diff --git a/vendor/fguillot/picofeed/lib/PicoFeed/Client/HttpHeaders.php b/vendor/fguillot/picofeed/lib/PicoFeed/Client/HttpHeaders.php index cde8f757c..ccced5f8e 100644 --- a/vendor/fguillot/picofeed/lib/PicoFeed/Client/HttpHeaders.php +++ b/vendor/fguillot/picofeed/lib/PicoFeed/Client/HttpHeaders.php @@ -53,12 +53,13 @@ class HttpHeaders implements ArrayAccess */ public static function parse(array $lines) { - $status = 200; + $status = 0; $headers = array(); foreach ($lines as $line) { if (strpos($line, 'HTTP') === 0) { + $headers = array(); $status = (int) substr($line, 9, 3); } else if (strpos($line, ':') !== false) { diff --git a/vendor/fguillot/picofeed/lib/PicoFeed/Client/Stream.php b/vendor/fguillot/picofeed/lib/PicoFeed/Client/Stream.php index b80e731d6..1e539b106 100644 --- a/vendor/fguillot/picofeed/lib/PicoFeed/Client/Stream.php +++ b/vendor/fguillot/picofeed/lib/PicoFeed/Client/Stream.php @@ -25,7 +25,9 @@ class Stream extends Client 'User-Agent: '.$this->user_agent, ); - if (function_exists('gzdecode')) { + // disable compression in passthrough mode. It could result in double + // compressed content which isn't decodeable by browsers + if (function_exists('gzdecode') && ! $this->isPassthroughEnabled()) { $headers[] = 'Accept-Encoding: gzip'; } @@ -49,6 +51,23 @@ class Stream extends Client } /** + * Construct the final URL from location headers + * + * @access private + * @param array $headers List of HTTP response header + */ + private function setEffectiveUrl($headers) + { + foreach($headers as $header) { + if (stripos($header, 'Location') === 0) { + list($name, $value) = explode(': ', $header); + + $this->url = Url::resolve($value, $this->url); + } + } + } + + /** * Prepare stream context * * @access private @@ -61,7 +80,7 @@ class Stream extends Client 'method' => 'GET', 'protocol_version' => 1.1, 'timeout' => $this->timeout, - 'follow_location' => 0, + 'max_redirects' => $this->max_redirects, ) ); @@ -89,11 +108,12 @@ class Stream extends Client * Do the HTTP request * * @access public - * @param bool $follow_location Flag used when there is an open_basedir restriction - * @return array HTTP response ['body' => ..., 'status' => ..., 'headers' => ...] + * @return array HTTP response ['body' => ..., 'status' => ..., 'headers' => ...] */ - public function doRequest($follow_location = false) + public function doRequest() { + $body = ''; + // Create context $context = stream_context_create($this->prepareContext()); @@ -103,30 +123,36 @@ class Stream extends Client throw new InvalidUrlException('Unable to establish a connection'); } - // Get the entire body until the max size - $body = stream_get_contents($stream, $this->max_body_size + 1); - - // If the body size is too large abort everything - if (strlen($body) > $this->max_body_size) { - throw new MaxSizeException('Content size too large'); - } - // Get HTTP headers response $metadata = stream_get_meta_data($stream); + list($status, $headers) = HttpHeaders::parse($metadata['wrapper_data']); + + if ($this->isPassthroughEnabled()) { + header(':', true, $status); + + if (isset($headers['Content-Type'])) { + header('Content-Type: '.$headers['Content-Type']); + } - if ($metadata['timed_out']) { - throw new TimeoutException('Operation timeout'); + fpassthru($stream); } + else { + // Get the entire body until the max size + $body = stream_get_contents($stream, $this->max_body_size + 1); - list($status, $headers) = HttpHeaders::parse($metadata['wrapper_data']); + // If the body size is too large abort everything + if (strlen($body) > $this->max_body_size) { + throw new MaxSizeException('Content size too large'); + } + + if ($metadata['timed_out']) { + throw new TimeoutException('Operation timeout'); + } + } fclose($stream); - // Do redirect manual to get only the headers of the last request and - // the final url - if ($status == 301 || $status == 302) { - return $this->handleRedirection($headers['Location']); - } + $this->setEffectiveUrl($metadata['wrapper_data']); return array( 'status' => $status, diff --git a/vendor/fguillot/picofeed/lib/PicoFeed/Config/Config.php b/vendor/fguillot/picofeed/lib/PicoFeed/Config/Config.php index 2ee3718eb..181da03b6 100644 --- a/vendor/fguillot/picofeed/lib/PicoFeed/Config/Config.php +++ b/vendor/fguillot/picofeed/lib/PicoFeed/Config/Config.php @@ -32,6 +32,7 @@ namespace PicoFeed\Config; * @method \PicoFeed\Config\Config setFilterBlacklistedTags(array $value) * @method \PicoFeed\Config\Config setFilterImageProxyUrl($value) * @method \PicoFeed\Config\Config setFilterImageProxyCallback($closure) + * @method \PicoFeed\Config\Config setFilterImageProxyProtocol($value) * * @method integer getClientTimeout() * @method string getClientUserAgent() @@ -57,6 +58,7 @@ namespace PicoFeed\Config; * @method array getFilterBlacklistedTags(array $default_value) * @method string getFilterImageProxyUrl() * @method \Closure getFilterImageProxyCallback() + * @method string getFilterImageProxyProtocol() */ class Config { diff --git a/vendor/fguillot/picofeed/lib/PicoFeed/Encoding/Encoding.php b/vendor/fguillot/picofeed/lib/PicoFeed/Encoding/Encoding.php index 7739def5f..0590c47b1 100644 --- a/vendor/fguillot/picofeed/lib/PicoFeed/Encoding/Encoding.php +++ b/vendor/fguillot/picofeed/lib/PicoFeed/Encoding/Encoding.php @@ -3,165 +3,19 @@ namespace PicoFeed\Encoding; /** - * @author "Sebastián Grignoli" <grignoli@framework2.com.ar> - * @package Encoding - * @version 1.2 - * @link https://github.com/neitanod/forceutf8 - * @example https://github.com/neitanod/forceutf8 - * @license Revised BSD + * Encoding class + * + * @package Encoding */ class Encoding { - protected static $win1252ToUtf8 = array( - 128 => "\xe2\x82\xac", - 130 => "\xe2\x80\x9a", - 131 => "\xc6\x92", - 132 => "\xe2\x80\x9e", - 133 => "\xe2\x80\xa6", - 134 => "\xe2\x80\xa0", - 135 => "\xe2\x80\xa1", - 136 => "\xcb\x86", - 137 => "\xe2\x80\xb0", - 138 => "\xc5\xa0", - 139 => "\xe2\x80\xb9", - 140 => "\xc5\x92", - 142 => "\xc5\xbd", - 145 => "\xe2\x80\x98", - 146 => "\xe2\x80\x99", - 147 => "\xe2\x80\x9c", - 148 => "\xe2\x80\x9d", - 149 => "\xe2\x80\xa2", - 150 => "\xe2\x80\x93", - 151 => "\xe2\x80\x94", - 152 => "\xcb\x9c", - 153 => "\xe2\x84\xa2", - 154 => "\xc5\xa1", - 155 => "\xe2\x80\xba", - 156 => "\xc5\x93", - 158 => "\xc5\xbe", - 159 => "\xc5\xb8" - ); - - /** - * Function Encoding::toUTF8 - * - * This function leaves UTF8 characters alone, while converting almost all non-UTF8 to UTF8. - * - * It assumes that the encoding of the original string is either Windows-1252 or ISO 8859-1. - * - * It may fail to convert characters to UTF-8 if they fall into one of these scenarios: - * - * 1) when any of these characters: ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß - * are followed by any of these: ("group B") - * ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶•¸¹º»¼½¾¿ - * For example: %ABREPRESENT%C9%BB. «REPRESENTÉ» - * The "«" (%AB) character will be converted, but the "É" followed by "»" (%C9%BB) - * is also a valid unicode character, and will be left unchanged. - * - * 2) when any of these: àáâãäåæçèéêëìíîï are followed by TWO chars from group B, - * 3) when any of these: ðñòó are followed by THREE chars from group B. - * - * @name toUTF8 - * @param string $text Any string. - * @return string The same string, UTF8 encoded - * - */ - public static function toUTF8($text) - { - if (is_array($text)) { - foreach ($text as $k => $v) { - $text[$k] = self::toUTF8($v); - } - - return $text; - } - elseif (is_string($text)) { - - $max = strlen($text); - $buf = ""; - - for ($i = 0; $i < $max; $i++) { - - $c1 = $text{$i}; - - if ($c1>="\xc0") { //Should be converted to UTF8, if it's not UTF8 already - - $c2 = $i+1 >= $max? "\x00" : $text{$i+1}; - $c3 = $i+2 >= $max? "\x00" : $text{$i+2}; - $c4 = $i+3 >= $max? "\x00" : $text{$i+3}; - - if ($c1 >= "\xc0" & $c1 <= "\xdf") { //looks like 2 bytes UTF8 - - if ($c2 >= "\x80" && $c2 <= "\xbf") { //yeah, almost sure it's UTF8 already - $buf .= $c1 . $c2; - $i++; - } - else { //not valid UTF8. Convert it. - $buf .= self::convertInvalidCharacter($c1); - } - } - else if ($c1 >= "\xe0" & $c1 <= "\xef") { //looks like 3 bytes UTF8 - - if ($c2 >= "\x80" && $c2 <= "\xbf" && $c3 >= "\x80" && $c3 <= "\xbf") { //yeah, almost sure it's UTF8 already - $buf .= $c1 . $c2 . $c3; - $i = $i + 2; - } - else { //not valid UTF8. Convert it. - $buf .= self::convertInvalidCharacter($c1); - } - } - else if ($c1 >= "\xf0" & $c1 <= "\xf7") { //looks like 4 bytes UTF8 - - if ($c2 >= "\x80" && $c2 <= "\xbf" && $c3 >= "\x80" && $c3 <= "\xbf" && $c4 >= "\x80" && $c4 <= "\xbf") { //yeah, almost sure it's UTF8 already - $buf .= $c1 . $c2 . $c3; - $i = $i + 2; - } - else { //not valid UTF8. Convert it. - $buf .= self::convertInvalidCharacter($c1); - } - } - else { //doesn't look like UTF8, but should be converted - $buf .= self::convertInvalidCharacter($c1); - } - } - elseif (($c1 & "\xc0") == "\x80") { // needs conversion - - if (isset(self::$win1252ToUtf8[ord($c1)])) { //found in Windows-1252 special cases - $buf .= self::$win1252ToUtf8[ord($c1)]; - } - else { - $buf .= self::convertInvalidCharacter($c1); - } - } - else { // it doesn't need conversion - $buf .= $c1; - } - } - - return $buf; - } - else { - return $text; - } - } - - public static function convertInvalidCharacter($c1) - { - $cc1 = chr(ord($c1) / 64) | "\xc0"; - $cc2 = ($c1 & "\x3f") | "\x80"; - return $cc1.$cc2; - } - public static function convert($input, $encoding) { - switch ($encoding) { - case 'utf-8': - return $input; - case 'windows-1251': - case 'windows-1255': - return iconv($encoding, 'UTF-8//TRANSLIT', $input); - default: - return self::toUTF8($input); + if ($encoding === 'utf-8' || $encoding === '') { + return $input; } + + // convert input to utf-8; ignore malformed characters + return iconv($encoding, 'UTF-8//IGNORE', $input); } } diff --git a/vendor/fguillot/picofeed/lib/PicoFeed/Filter/Attribute.php b/vendor/fguillot/picofeed/lib/PicoFeed/Filter/Attribute.php index 02126d208..e8012dd98 100644 --- a/vendor/fguillot/picofeed/lib/PicoFeed/Filter/Attribute.php +++ b/vendor/fguillot/picofeed/lib/PicoFeed/Filter/Attribute.php @@ -2,7 +2,7 @@ namespace PicoFeed\Filter; -use \PicoFeed\Client\Url; +use PicoFeed\Client\Url; /** * Attribute Filter class @@ -29,6 +29,14 @@ class Attribute private $image_proxy_callback = null; /** + * limits the image proxy usage to this protocol + * + * @access private + * @var string + */ + private $image_proxy_limit_protocol = ''; + + /** * Tags and attribute whitelist * * @access private @@ -225,6 +233,7 @@ class Attribute 'filterBlacklistResourceAttribute', 'filterProtocolUrlAttribute', 'rewriteImageProxyUrl', + 'secureIframeSrc', ); /** @@ -273,8 +282,8 @@ class Attribute * * @access public * @param string $tag Tag name - * @param string $attribute Atttribute name - * @param string $value Atttribute value + * @param string $attribute Attribute name + * @param string $value Attribute value * @return boolean */ public function filterEmptyAttribute($tag, $attribute, $value) @@ -287,8 +296,8 @@ class Attribute * * @access public * @param string $tag Tag name - * @param string $attribute Atttribute name - * @param string $value Atttribute value + * @param string $attribute Attribute name + * @param string $value Attribute value * @return boolean */ public function filterAllowedAttribute($tag, $attribute, $value) @@ -301,8 +310,8 @@ class Attribute * * @access public * @param string $tag Tag name - * @param string $attribute Atttribute name - * @param string $value Atttribute value + * @param string $attribute Attribute name + * @param string $value Attribute value * @return boolean */ public function filterIntegerAttribute($tag, $attribute, $value) @@ -319,8 +328,8 @@ class Attribute * * @access public * @param string $tag Tag name - * @param string $attribute Atttribute name - * @param string $value Atttribute value + * @param string $attribute Attribute name + * @param string $value Attribute value * @return boolean */ public function filterIframeAttribute($tag, $attribute, $value) @@ -344,8 +353,8 @@ class Attribute * * @access public * @param string $tag Tag name - * @param string $attribute Atttribute name - * @param string $value Atttribute value + * @param string $attribute Attribute name + * @param string $value Attribute value * @return boolean */ public function filterBlacklistResourceAttribute($tag, $attribute, $value) @@ -362,8 +371,8 @@ class Attribute * * @access public * @param string $tag Tag name - * @param string $attribute Atttribute name - * @param string $value Atttribute value + * @param string $attribute Attribute name + * @param string $value Attribute value * @return boolean */ public function rewriteAbsoluteUrl($tag, $attribute, &$value) @@ -376,17 +385,37 @@ class Attribute } /** - * Rewrite image url to use with a proxy (HTTPS resource are ignored) + * Turns iframes' src attribute from http to https to prevent + * mixed active content + * + * @access public + * @param string $tag Tag name + * @param array $attribute Atttributes name + * @param string $value Attribute value + * @return boolean + */ + public function secureIframeSrc($tag, $attribute, &$value) + { + if ($tag === 'iframe' && $attribute === 'src' && strpos($value, 'http://') === 0) { + $value = substr_replace($value, 's', 4, 0); + } + + return true; + } + + /** + * Rewrite image url to use with a proxy * * @access public * @param string $tag Tag name - * @param string $attribute Atttribute name - * @param string $value Atttribute value + * @param string $attribute Attribute name + * @param string $value Attribute value * @return boolean */ public function rewriteImageProxyUrl($tag, $attribute, &$value) { - if ($tag === 'img' && $attribute === 'src' && strpos($value, 'http:') === 0) { + if ($tag === 'img' && $attribute === 'src' + && ! ($this->image_proxy_limit_protocol !== '' && stripos($value, $this->image_proxy_limit_protocol.':') !== 0)) { if ($this->image_proxy_url) { $value = sprintf($this->image_proxy_url, rawurlencode($value)); @@ -404,8 +433,8 @@ class Attribute * * @access public * @param string $tag Tag name - * @param string $attribute Atttribute name - * @param string $value Atttribute value + |