1<?php 2 3declare(strict_types=1); 4/** 5 * SimplePie 6 * 7 * A PHP-Based RSS and Atom Feed Framework. 8 * Takes the hard work out of managing a complete RSS/Atom solution. 9 * 10 * Copyright (c) 2004-2022, Ryan Parman, Sam Sneddon, Ryan McCue, and contributors 11 * All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without modification, are 14 * permitted provided that the following conditions are met: 15 * 16 * * Redistributions of source code must retain the above copyright notice, this list of 17 * conditions and the following disclaimer. 18 * 19 * * Redistributions in binary form must reproduce the above copyright notice, this list 20 * of conditions and the following disclaimer in the documentation and/or other materials 21 * provided with the distribution. 22 * 23 * * Neither the name of the SimplePie Team nor the names of its contributors may be used 24 * to endorse or promote products derived from this software without specific prior 25 * written permission. 26 * 27 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 28 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 29 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 30 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 34 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 * POSSIBILITY OF SUCH DAMAGE. 36 * 37 * @package SimplePie 38 * @copyright 2004-2016 Ryan Parman, Sam Sneddon, Ryan McCue 39 * @author Ryan Parman 40 * @author Sam Sneddon 41 * @author Ryan McCue 42 * @link http://simplepie.org/ SimplePie 43 * @license http://www.opensource.org/licenses/bsd-license.php BSD License 44 */ 45 46namespace SimplePie; 47 48/** 49 * Used for fetching remote files and reading local files 50 * 51 * Supports HTTP 1.0 via cURL or fsockopen, with spotty HTTP 1.1 support 52 * 53 * This class can be overloaded with {@see \SimplePie\SimplePie::set_file_class()} 54 * 55 * @package SimplePie 56 * @subpackage HTTP 57 * @todo Move to properly supporting RFC2616 (HTTP/1.1) 58 */ 59class File 60{ 61 public $url; 62 public $useragent; 63 public $success = true; 64 public $headers = []; 65 public $body; 66 public $status_code = 0; 67 public $redirects = 0; 68 public $error; 69 public $method = \SimplePie\SimplePie::FILE_SOURCE_NONE; 70 public $permanent_url; 71 72 public function __construct($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false, $curl_options = []) 73 { 74 if (class_exists('idna_convert')) { 75 $idn = new \idna_convert(); 76 $parsed = \SimplePie\Misc::parse_url($url); 77 $url = \SimplePie\Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], null); 78 } 79 $this->url = $url; 80 $this->permanent_url = $url; 81 $this->useragent = $useragent; 82 if (preg_match('/^http(s)?:\/\//i', $url)) { 83 if ($useragent === null) { 84 $useragent = ini_get('user_agent'); 85 $this->useragent = $useragent; 86 } 87 if (!is_array($headers)) { 88 $headers = []; 89 } 90 if (!$force_fsockopen && function_exists('curl_exec')) { 91 $this->method = \SimplePie\SimplePie::FILE_SOURCE_REMOTE | \SimplePie\SimplePie::FILE_SOURCE_CURL; 92 $fp = curl_init(); 93 $headers2 = []; 94 foreach ($headers as $key => $value) { 95 $headers2[] = "$key: $value"; 96 } 97 if (version_compare(\SimplePie\Misc::get_curl_version(), '7.10.5', '>=')) { 98 curl_setopt($fp, CURLOPT_ENCODING, ''); 99 } 100 curl_setopt($fp, CURLOPT_URL, $url); 101 curl_setopt($fp, CURLOPT_HEADER, 1); 102 curl_setopt($fp, CURLOPT_RETURNTRANSFER, 1); 103 curl_setopt($fp, CURLOPT_FAILONERROR, 1); 104 curl_setopt($fp, CURLOPT_TIMEOUT, $timeout); 105 curl_setopt($fp, CURLOPT_CONNECTTIMEOUT, $timeout); 106 curl_setopt($fp, CURLOPT_REFERER, \SimplePie\Misc::url_remove_credentials($url)); 107 curl_setopt($fp, CURLOPT_USERAGENT, $useragent); 108 curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2); 109 foreach ($curl_options as $curl_param => $curl_value) { 110 curl_setopt($fp, $curl_param, $curl_value); 111 } 112 113 $this->headers = curl_exec($fp); 114 if (curl_errno($fp) === 23 || curl_errno($fp) === 61) { 115 curl_setopt($fp, CURLOPT_ENCODING, 'none'); 116 $this->headers = curl_exec($fp); 117 } 118 $this->status_code = curl_getinfo($fp, CURLINFO_HTTP_CODE); 119 if (curl_errno($fp)) { 120 $this->error = 'cURL error ' . curl_errno($fp) . ': ' . curl_error($fp); 121 $this->success = false; 122 } else { 123 // Use the updated url provided by curl_getinfo after any redirects. 124 if ($info = curl_getinfo($fp)) { 125 $this->url = $info['url']; 126 } 127 curl_close($fp); 128 $this->headers = \SimplePie\HTTP\Parser::prepareHeaders($this->headers, $info['redirect_count'] + 1); 129 $parser = new \SimplePie\HTTP\Parser($this->headers); 130 if ($parser->parse()) { 131 $this->headers = $parser->headers; 132 $this->body = trim($parser->body); 133 $this->status_code = $parser->status_code; 134 if ((in_array($this->status_code, [300, 301, 302, 303, 307]) || $this->status_code > 307 && $this->status_code < 400) && isset($this->headers['location']) && $this->redirects < $redirects) { 135 $this->redirects++; 136 $location = \SimplePie\Misc::absolutize_url($this->headers['location'], $url); 137 $previousStatusCode = $this->status_code; 138 $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen, $curl_options); 139 $this->permanent_url = ($previousStatusCode == 301) ? $location : $url; 140 return; 141 } 142 } 143 } 144 } else { 145 $this->method = \SimplePie\SimplePie::FILE_SOURCE_REMOTE | \SimplePie\SimplePie::FILE_SOURCE_FSOCKOPEN; 146 $url_parts = parse_url($url); 147 $socket_host = $url_parts['host']; 148 if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') { 149 $socket_host = "ssl://$url_parts[host]"; 150 $url_parts['port'] = 443; 151 } 152 if (!isset($url_parts['port'])) { 153 $url_parts['port'] = 80; 154 } 155 $fp = @fsockopen($socket_host, $url_parts['port'], $errno, $errstr, $timeout); 156 if (!$fp) { 157 $this->error = 'fsockopen error: ' . $errstr; 158 $this->success = false; 159 } else { 160 stream_set_timeout($fp, $timeout); 161 if (isset($url_parts['path'])) { 162 if (isset($url_parts['query'])) { 163 $get = "$url_parts[path]?$url_parts[query]"; 164 } else { 165 $get = $url_parts['path']; 166 } 167 } else { 168 $get = '/'; 169 } 170 $out = "GET $get HTTP/1.1\r\n"; 171 $out .= "Host: $url_parts[host]\r\n"; 172 $out .= "User-Agent: $useragent\r\n"; 173 if (extension_loaded('zlib')) { 174 $out .= "Accept-Encoding: x-gzip,gzip,deflate\r\n"; 175 } 176 177 if (isset($url_parts['user']) && isset($url_parts['pass'])) { 178 $out .= "Authorization: Basic " . base64_encode("$url_parts[user]:$url_parts[pass]") . "\r\n"; 179 } 180 foreach ($headers as $key => $value) { 181 $out .= "$key: $value\r\n"; 182 } 183 $out .= "Connection: Close\r\n\r\n"; 184 fwrite($fp, $out); 185 186 $info = stream_get_meta_data($fp); 187 188 $this->headers = ''; 189 while (!$info['eof'] && !$info['timed_out']) { 190 $this->headers .= fread($fp, 1160); 191 $info = stream_get_meta_data($fp); 192 } 193 if (!$info['timed_out']) { 194 $parser = new \SimplePie\HTTP\Parser($this->headers); 195 if ($parser->parse()) { 196 $this->headers = $parser->headers; 197 $this->body = $parser->body; 198 $this->status_code = $parser->status_code; 199 if ((in_array($this->status_code, [300, 301, 302, 303, 307]) || $this->status_code > 307 && $this->status_code < 400) && isset($this->headers['location']) && $this->redirects < $redirects) { 200 $this->redirects++; 201 $location = \SimplePie\Misc::absolutize_url($this->headers['location'], $url); 202 $previousStatusCode = $this->status_code; 203 $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen, $curl_options); 204 $this->permanent_url = ($previousStatusCode == 301) ? $location : $url; 205 return; 206 } 207 if (isset($this->headers['content-encoding'])) { 208 // Hey, we act dumb elsewhere, so let's do that here too 209 switch (strtolower(trim($this->headers['content-encoding'], "\x09\x0A\x0D\x20"))) { 210 case 'gzip': 211 case 'x-gzip': 212 $decoder = new \SimplePie\Gzdecode($this->body); 213 if (!$decoder->parse()) { 214 $this->error = 'Unable to decode HTTP "gzip" stream'; 215 $this->success = false; 216 } else { 217 $this->body = trim($decoder->data); 218 } 219 break; 220 221 case 'deflate': 222 if (($decompressed = gzinflate($this->body)) !== false) { 223 $this->body = $decompressed; 224 } elseif (($decompressed = gzuncompress($this->body)) !== false) { 225 $this->body = $decompressed; 226 } elseif (function_exists('gzdecode') && ($decompressed = gzdecode($this->body)) !== false) { 227 $this->body = $decompressed; 228 } else { 229 $this->error = 'Unable to decode HTTP "deflate" stream'; 230 $this->success = false; 231 } 232 break; 233 234 default: 235 $this->error = 'Unknown content coding'; 236 $this->success = false; 237 } 238 } 239 } 240 } else { 241 $this->error = 'fsocket timed out'; 242 $this->success = false; 243 } 244 fclose($fp); 245 } 246 } 247 } else { 248 $this->method = \SimplePie\SimplePie::FILE_SOURCE_LOCAL | \SimplePie\SimplePie::FILE_SOURCE_FILE_GET_CONTENTS; 249 if (empty($url) || !($this->body = trim(file_get_contents($url)))) { 250 $this->error = 'file_get_contents could not read the file'; 251 $this->success = false; 252 } 253 } 254 } 255} 256 257class_alias('SimplePie\File', 'SimplePie_File'); 258