├── composer.json ├── README.md ├── vendor ├── autoload.php ├── composer │ ├── autoload_classmap.php │ ├── autoload_namespaces.php │ ├── autoload_psr4.php │ ├── autoload_static.php │ ├── LICENSE │ ├── autoload_real.php │ ├── installed.json │ └── ClassLoader.php └── php-curl-class │ └── php-curl-class │ ├── src │ └── Curl │ │ ├── StrUtil.php │ │ ├── Decoder.php │ │ ├── ArrayUtil.php │ │ ├── CaseInsensitiveArray.php │ │ ├── Url.php │ │ ├── MultiCurl.php │ │ └── Curl.php │ ├── composer.json │ ├── LICENSE │ ├── SECURITY.md │ └── README.md ├── ServerChan.php ├── LICENSE └── index.php /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "php-curl-class/php-curl-class": "^8.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xingbianli-sale-spider 2 | 猩便利无人货架特价促销信息爬虫&推送到微信 3 | 4 | 详见文章:[绕过微信爬取猩便利特价信息并推送到微信](https://www.mokeyjay.com/archives/2051) 5 | 很久没脱离框架写代码了,写的很随意,求不吐槽 6 | 因为不想造轮子所以引入了一个curl包,体积不大所以干脆整个vendor丢上来了 -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | array($vendorDir . '/php-curl-class/php-curl-class/src/Curl'), 10 | ); 11 | -------------------------------------------------------------------------------- /vendor/php-curl-class/php-curl-class/src/Curl/StrUtil.php: -------------------------------------------------------------------------------- 1 | get(SC_URL, [ 20 | 'desp' => $message, 21 | 'text' => '炉石科技猩便利特价推送', 22 | ]); 23 | $json = json_decode($json, TRUE); 24 | if ($json === NULL){ 25 | return 'Server酱返回格式不正确'; 26 | } else if ($json['errno'] != 0){ 27 | return 'Server酱返回错误:' . $json['errmsg'] . ' - ' . $json['dataset']; 28 | } else { 29 | return TRUE; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /vendor/php-curl-class/php-curl-class/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "php-curl-class/php-curl-class", 3 | "description": "PHP Curl Class makes it easy to send HTTP requests and integrate with web APIs.", 4 | "homepage": "https://github.com/php-curl-class/php-curl-class", 5 | "license": "Unlicense", 6 | "keywords": [ 7 | "php", "curl", "class", "api", "client", "framework", "http client", 8 | "http", "json", "requests", "rest", "restful", "web service", "xml" 9 | ], 10 | "authors": [ 11 | { 12 | "name": "Zach Borboa" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=5.3", 17 | "ext-curl": "*" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "*", 21 | "squizlabs/php_codesniffer": "*" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Curl\\": "src/Curl/" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'Curl\\' => 5, 13 | ), 14 | ); 15 | 16 | public static $prefixDirsPsr4 = array ( 17 | 'Curl\\' => 18 | array ( 19 | 0 => __DIR__ . '/..' . '/php-curl-class/php-curl-class/src/Curl', 20 | ), 21 | ); 22 | 23 | public static function getInitializer(ClassLoader $loader) 24 | { 25 | return \Closure::bind(function () use ($loader) { 26 | $loader->prefixLengthsPsr4 = ComposerStaticInit3f918b6b978b64fc6c98433220ebe54b::$prefixLengthsPsr4; 27 | $loader->prefixDirsPsr4 = ComposerStaticInit3f918b6b978b64fc6c98433220ebe54b::$prefixDirsPsr4; 28 | 29 | }, null, ClassLoader::class); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 mokeyjay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /vendor/php-curl-class/php-curl-class/src/Curl/Decoder.php: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); 27 | if ($useStaticLoader) { 28 | require_once __DIR__ . '/autoload_static.php'; 29 | 30 | call_user_func(\Composer\Autoload\ComposerStaticInit3f918b6b978b64fc6c98433220ebe54b::getInitializer($loader)); 31 | } else { 32 | $map = require __DIR__ . '/autoload_namespaces.php'; 33 | foreach ($map as $namespace => $path) { 34 | $loader->set($namespace, $path); 35 | } 36 | 37 | $map = require __DIR__ . '/autoload_psr4.php'; 38 | foreach ($map as $namespace => $path) { 39 | $loader->setPsr4($namespace, $path); 40 | } 41 | 42 | $classMap = require __DIR__ . '/autoload_classmap.php'; 43 | if ($classMap) { 44 | $loader->addClassMap($classMap); 45 | } 46 | } 47 | 48 | $loader->register(true); 49 | 50 | return $loader; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "php-curl-class/php-curl-class", 4 | "version": "8.0.0", 5 | "version_normalized": "8.0.0.0", 6 | "source": { 7 | "type": "git", 8 | "url": "https://github.com/php-curl-class/php-curl-class.git", 9 | "reference": "dc8282d086362d946520781c8908e0b084f01438" 10 | }, 11 | "dist": { 12 | "type": "zip", 13 | "url": "https://api.github.com/repos/php-curl-class/php-curl-class/zipball/dc8282d086362d946520781c8908e0b084f01438", 14 | "reference": "dc8282d086362d946520781c8908e0b084f01438", 15 | "shasum": "" 16 | }, 17 | "require": { 18 | "ext-curl": "*", 19 | "php": ">=5.3" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "*", 23 | "squizlabs/php_codesniffer": "*" 24 | }, 25 | "time": "2017-11-02T06:07:25+00:00", 26 | "type": "library", 27 | "installation-source": "dist", 28 | "autoload": { 29 | "psr-4": { 30 | "Curl\\": "src/Curl/" 31 | } 32 | }, 33 | "notification-url": "https://packagist.org/downloads/", 34 | "license": [ 35 | "Unlicense" 36 | ], 37 | "authors": [ 38 | { 39 | "name": "Zach Borboa" 40 | } 41 | ], 42 | "description": "PHP Curl Class makes it easy to send HTTP requests and integrate with web APIs.", 43 | "homepage": "https://github.com/php-curl-class/php-curl-class", 44 | "keywords": [ 45 | "api", 46 | "class", 47 | "client", 48 | "curl", 49 | "framework", 50 | "http", 51 | "http client", 52 | "json", 53 | "php", 54 | "requests", 55 | "rest", 56 | "restful", 57 | "web service", 58 | "xml" 59 | ] 60 | } 61 | ] 62 | -------------------------------------------------------------------------------- /vendor/php-curl-class/php-curl-class/src/Curl/ArrayUtil.php: -------------------------------------------------------------------------------- 1 | $value) { 54 | if (is_scalar($value)) { 55 | if ($prefix) { 56 | $return[$prefix . '[' . $key . ']'] = $value; 57 | } else { 58 | $return[$key] = $value; 59 | } 60 | } else { 61 | if ($value instanceof \CURLFile) { 62 | $return[$key] = $value; 63 | } else { 64 | $return = array_merge( 65 | $return, 66 | self::array_flatten_multidim( 67 | $value, 68 | $prefix ? $prefix . '[' . $key . ']' : $key 69 | ) 70 | ); 71 | } 72 | } 73 | } 74 | } 75 | } elseif ($array === null) { 76 | $return[$prefix] = $array; 77 | } 78 | return $return; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /vendor/php-curl-class/php-curl-class/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Considerations 2 | 3 | ### Url may point to system files 4 | 5 | * Don't blindly accept urls from users as they may point to system files. Curl supports many protocols including `FILE`. 6 | The following would show the contents of `file:///etc/passwd`. 7 | 8 | ```bash 9 | # Attacker. 10 | $ curl https://www.example.com/display_webpage.php?url=file%3A%2F%2F%2Fetc%2Fpasswd 11 | ``` 12 | 13 | ```php 14 | // display_webpage.php 15 | $url = $_GET['url']; // DANGER! 16 | $curl = new Curl(); 17 | $curl->get($url); 18 | echo $curl->response; 19 | ``` 20 | 21 | Safer: 22 | 23 | ```php 24 | function is_website_url($url, $allowed_schemes = array('http', 'https')) { 25 | $validate_url = !(filter_var($url, FILTER_VALIDATE_URL) === false); 26 | $scheme = parse_url($url, PHP_URL_SCHEME); 27 | return $validate_url && in_array($scheme, $allowed_schemes, true); 28 | } 29 | 30 | $url = $_GET['url']; 31 | if (!is_website_url($url)) { 32 | die('Unsafe url detected.'); 33 | } 34 | ``` 35 | 36 | ### Url may point to internal urls 37 | 38 | * Url may point to internal urls including those behind a firewall (e.g. http://192.168.0.1/ or ftp://192.168.0.1/). Use 39 | a whitelist to allow certain urls rather than a blacklist. 40 | 41 | ### Request data may refer to system files 42 | 43 | * Request data prefixed with the `@` character may have special interpretation and read from system files. 44 | 45 | ```bash 46 | # Attacker. 47 | $ curl https://www.example.com/upload_photo.php --data "photo=@/etc/passwd" 48 | ``` 49 | 50 | ```php 51 | // upload_photo.php 52 | $curl = new Curl(); 53 | $curl->post('http://www.anotherwebsite.com/', array( 54 | 'photo' => $_POST['photo'], // DANGER! 55 | )); 56 | ``` 57 | 58 | ### Unsafe response with redirection enabled 59 | 60 | * Requests with redirection enabled may return responses from unexpected sources. 61 | Downloading https://www.example.com/image.png may redirect and download https://www.evil.com/virus.exe 62 | 63 | ```php 64 | $curl = new Curl(); 65 | $curl->setOpt(CURLOPT_FOLLOWLOCATION, true); // DANGER! 66 | $curl->download('https://www.example.com/image.png', 'my_image.png'); 67 | ``` 68 | 69 | ```php 70 | $curl = new Curl(); 71 | $curl->setOpt(CURLOPT_FOLLOWLOCATION, true); // DANGER! 72 | $curl->get('https://www.example.com/image.png'); 73 | ``` 74 | 75 | ### Keep SSL protections enabled 76 | 77 | * Do not disable SSL protections. 78 | 79 | ```php 80 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // DANGER! 81 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // DANGER! 82 | ``` 83 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | =此值时触发推送 4 | // 猩便利接口所需数据配置 5 | define('XBL_ID', 'abcd9e80-ab4d-a66d-a7cd-abcd377abcdb'); // 货架id 6 | define('TOKEN', ''); // Cookie里的token值 7 | define('XBL_UUID', ''); // Cookie里的UUID值 8 | // Server酱微信推送url 9 | define('SC_URL', 'http://sc.ftqq.com/XXX.send'); 10 | 11 | require __DIR__ . '/vendor/autoload.php'; 12 | require __DIR__ . '/ServerChan.php'; 13 | use Curl\Curl; 14 | 15 | // 初始化Curl 16 | $curl = new Curl(); 17 | $curl->setUserAgent('Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 MicroMessenger/6.5.2.501 NetType/WIFI WindowsWechat QBCore/3.43.691.400 QQBrowser/9.0.2524.400'); // 微信PC版浏览器UA 18 | $curl->setCookies([ 19 | 'token' => TOKEN, 20 | 'xbl_uuid' => XBL_UUID, 21 | ]); 22 | $curl->get('https://www.xingbianli.com/openrack/'. XBL_ID .'/commodityList'); 23 | 24 | // 解析接口返回值 25 | $error = ''; 26 | if ($curl->error) { 27 | $error = 'cURL错误: #' . $curl->errorCode . ' - ' . $curl->errorMessage; 28 | } else { 29 | if($curl->response->code != 200){ 30 | $error = '接口错误: #' . $curl->response->code . ' - ' . $curl->response->msg; 31 | } else { 32 | $data = $curl->response->data; 33 | $curl->close(); 34 | } 35 | } 36 | if($error){ 37 | ServerChan::send($error); 38 | die($error); 39 | } 40 | 41 | /** 42 | * 分析特价商品降幅 43 | */ 44 | $discount = []; // [特价商品id => 特价价格] 数组 45 | foreach ($data->discount as $v){ 46 | $discount[$v->commodityId] = $v->currentPrice; 47 | } 48 | 49 | $commodity_list = []; // [特价商品id => [商品信息]] 数组 50 | $diff_list = []; // [特价商品id => 降幅] 数组 51 | 52 | foreach ($data->commodityList as $commodity){ 53 | if(isset($discount[$commodity->commodityId])){ 54 | $commodity = (array)$commodity; 55 | $diff_price = $commodity['basicPrice'] - $discount[$commodity['commodityId']]; 56 | 57 | // 只收满足阈值的特价商品 58 | if($diff_list >= DECLINE){ 59 | $commodity_list[$commodity['commodityId']] = $commodity; 60 | $diff_list[$commodity['commodityId']] = $diff_price; 61 | } 62 | } 63 | } 64 | 65 | // 重新排序,降幅越大越靠前 66 | arsort($diff_list); 67 | $push_list = []; 68 | foreach ($diff_list as $cid => $diff){ 69 | $push_list[$cid] = $commodity_list[$cid]; 70 | } 71 | 72 | /** 73 | * 推送 74 | */ 75 | if(!empty($push_list)){ 76 | $msg = ''; 77 | foreach ($push_list as $cid => $commodity){ 78 | $msg .= '![](' . $commodity['picUrl'] . ')' . "\n\n"; 79 | $msg .= '#### ¥' . $discount[$cid] . ' - ' . $commodity['name'] . "\n\n"; 80 | $msg .= '-' . $diff_list[$cid] . '元 ~~原价' . $commodity['basicPrice'] .'~~'; 81 | $msg .= "\n\n" . '-----' . "\n\n"; 82 | } 83 | echo $msg; 84 | ServerChan::send($msg); 85 | } -------------------------------------------------------------------------------- /vendor/php-curl-class/php-curl-class/src/Curl/CaseInsensitiveArray.php: -------------------------------------------------------------------------------- 1 | $value) { 46 | $this->offsetSet($key, $value); 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * Offset Set 53 | * 54 | * Set data at a specified Offset. Converts the offset to lower-case, and 55 | * stores the Case-Sensitive Offset and the Data at the lower-case indexes 56 | * in $this->keys and @this->data. 57 | * 58 | * @see https://secure.php.net/manual/en/arrayaccess.offseteset.php 59 | * 60 | * @param string $offset The offset to store the data at (case-insensitive). 61 | * @param mixed $value The data to store at the specified offset. 62 | * 63 | * @return void 64 | * 65 | * @access public 66 | */ 67 | public function offsetSet($offset, $value) 68 | { 69 | if ($offset === null) { 70 | $this->data[] = $value; 71 | } else { 72 | $offsetlower = strtolower($offset); 73 | $this->data[$offsetlower] = $value; 74 | $this->keys[$offsetlower] = $offset; 75 | } 76 | } 77 | 78 | /** 79 | * Offset Exists 80 | * 81 | * Checks if the Offset exists in data storage. The index is looked up with 82 | * the lower-case version of the provided offset. 83 | * 84 | * @see https://secure.php.net/manual/en/arrayaccess.offsetexists.php 85 | * 86 | * @param string $offset Offset to check 87 | * 88 | * @return bool If the offset exists. 89 | * 90 | * @access public 91 | */ 92 | public function offsetExists($offset) 93 | { 94 | return (bool) array_key_exists(strtolower($offset), $this->data); 95 | } 96 | 97 | /** 98 | * Offset Unset 99 | * 100 | * Unsets the specified offset. Converts the provided offset to lowercase, 101 | * and unsets the Case-Sensitive Key, as well as the stored data. 102 | * 103 | * @see https://secure.php.net/manual/en/arrayaccess.offsetunset.php 104 | * 105 | * @param string $offset The offset to unset. 106 | * 107 | * @return void 108 | * 109 | * @access public 110 | */ 111 | public function offsetUnset($offset) 112 | { 113 | $offsetlower = strtolower($offset); 114 | unset($this->data[$offsetlower]); 115 | unset($this->keys[$offsetlower]); 116 | } 117 | 118 | /** 119 | * Offset Get 120 | * 121 | * Return the stored data at the provided offset. The offset is converted to 122 | * lowercase and the lookup is done on the Data store directly. 123 | * 124 | * @see https://secure.php.net/manual/en/arrayaccess.offsetget.php 125 | * 126 | * @param string $offset Offset to lookup. 127 | * 128 | * @return mixed The data stored at the offset. 129 | * 130 | * @access public 131 | */ 132 | public function offsetGet($offset) 133 | { 134 | $offsetlower = strtolower($offset); 135 | return isset($this->data[$offsetlower]) ? $this->data[$offsetlower] : null; 136 | } 137 | 138 | /** 139 | * Count 140 | * 141 | * @see https://secure.php.net/manual/en/countable.count.php 142 | * 143 | * @param void 144 | * 145 | * @return int The number of elements stored in the Array. 146 | * 147 | * @access public 148 | */ 149 | public function count() 150 | { 151 | return (int) count($this->data); 152 | } 153 | 154 | /** 155 | * Current 156 | * 157 | * @see https://secure.php.net/manual/en/iterator.current.php 158 | * 159 | * @param void 160 | * 161 | * @return mixed Data at the current position. 162 | * 163 | * @access public 164 | */ 165 | public function current() 166 | { 167 | return current($this->data); 168 | } 169 | 170 | /** 171 | * Next 172 | * 173 | * @see https://secure.php.net/manual/en/iterator.next.php 174 | * 175 | * @param void 176 | * 177 | * @return void 178 | * 179 | * @access public 180 | */ 181 | public function next() 182 | { 183 | next($this->data); 184 | } 185 | 186 | /** 187 | * Key 188 | * 189 | * @see https://secure.php.net/manual/en/iterator.key.php 190 | * 191 | * @param void 192 | * 193 | * @return mixed Case-Sensitive key at current position. 194 | * 195 | * @access public 196 | */ 197 | public function key() 198 | { 199 | $key = key($this->data); 200 | return isset($this->keys[$key]) ? $this->keys[$key] : $key; 201 | } 202 | 203 | /** 204 | * Valid 205 | * 206 | * @see https://secure.php.net/manual/en/iterator.valid.php 207 | * 208 | * @return bool If the current position is valid. 209 | * 210 | * @access public 211 | */ 212 | public function valid() 213 | { 214 | return (bool) !(key($this->data) === null); 215 | } 216 | 217 | /** 218 | * Rewind 219 | * 220 | * @see https://secure.php.net/manual/en/iterator.rewind.php 221 | * 222 | * @param void 223 | * 224 | * @return void 225 | * 226 | * @access public 227 | */ 228 | public function rewind() 229 | { 230 | reset($this->data); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /vendor/php-curl-class/php-curl-class/src/Curl/Url.php: -------------------------------------------------------------------------------- 1 | baseUrl = $base_url; 15 | $this->relativeUrl = $relative_url; 16 | } 17 | 18 | public function __toString() 19 | { 20 | return $this->absolutizeUrl(); 21 | } 22 | 23 | /** 24 | * Remove dot segments. 25 | * 26 | * Interpret and remove the special "." and ".." path segments from a referenced path. 27 | */ 28 | public static function removeDotSegments($input) 29 | { 30 | // 1. The input buffer is initialized with the now-appended path 31 | // components and the output buffer is initialized to the empty 32 | // string. 33 | $output = ''; 34 | 35 | // 2. While the input buffer is not empty, loop as follows: 36 | while (!empty($input)) { 37 | // A. If the input buffer begins with a prefix of "../" or "./", 38 | // then remove that prefix from the input buffer; otherwise, 39 | if (StrUtil::startsWith($input, '../')) { 40 | $input = substr($input, 3); 41 | } elseif (StrUtil::startsWith($input, './')) { 42 | $input = substr($input, 2); 43 | 44 | // B. if the input buffer begins with a prefix of "/./" or "/.", 45 | // where "." is a complete path segment, then replace that 46 | // prefix with "/" in the input buffer; otherwise, 47 | } elseif (StrUtil::startsWith($input, '/./')) { 48 | $input = substr($input, 2); 49 | } elseif ($input === '/.') { 50 | $input = '/'; 51 | 52 | // C. if the input buffer begins with a prefix of "/../" or "/..", 53 | // where ".." is a complete path segment, then replace that 54 | // prefix with "/" in the input buffer and remove the last 55 | // segment and its preceding "/" (if any) from the output 56 | // buffer; otherwise, 57 | } elseif (StrUtil::startsWith($input, '/../')) { 58 | $input = substr($input, 3); 59 | $output = substr_replace($output, '', mb_strrpos($output, '/')); 60 | } elseif ($input === '/..') { 61 | $input = '/'; 62 | $output = substr_replace($output, '', mb_strrpos($output, '/')); 63 | 64 | // D. if the input buffer consists only of "." or "..", then remove 65 | // that from the input buffer; otherwise, 66 | } elseif ($input === '.' || $input === '..') { 67 | $input = ''; 68 | 69 | // E. move the first path segment in the input buffer to the end of 70 | // the output buffer, including the initial "/" character (if 71 | // any) and any subsequent characters up to, but not including, 72 | // the next "/" character or the end of the input buffer. 73 | } elseif (!(($pos = mb_strpos($input, '/', 1)) === false)) { 74 | $output .= substr($input, 0, $pos); 75 | $input = substr_replace($input, '', 0, $pos); 76 | } else { 77 | $output .= $input; 78 | $input = ''; 79 | } 80 | } 81 | 82 | // 3. Finally, the output buffer is returned as the result of 83 | // remove_dot_segments. 84 | return $output . $input; 85 | } 86 | 87 | /** 88 | * Absolutize url. 89 | * 90 | * Combine the base and relative url into an absolute url. 91 | */ 92 | private function absolutizeUrl() 93 | { 94 | $b = $this->parseUrl($this->baseUrl); 95 | 96 | if (!($this->relativeUrl === null)) { 97 | $r = $this->parseUrl($this->relativeUrl); 98 | 99 | // Copy relative parts to base url. 100 | if (isset($r['scheme'])) { 101 | $b['scheme'] = $r['scheme']; 102 | } 103 | if (isset($r['host'])) { 104 | $b['host'] = $r['host']; 105 | } 106 | if (isset($r['port'])) { 107 | $b['port'] = $r['port']; 108 | } 109 | if (isset($r['user'])) { 110 | $b['user'] = $r['user']; 111 | } 112 | if (isset($r['pass'])) { 113 | $b['pass'] = $r['pass']; 114 | } 115 | 116 | if (!isset($r['path']) || $r['path'] === '') { 117 | $r['path'] = '/'; 118 | } 119 | // Merge relative url with base when relative url's path doesn't start with a slash. 120 | if (!(StrUtil::startsWith($r['path'], '/'))) { 121 | $base = mb_strrchr($b['path'], '/', true); 122 | if ($base === false) { 123 | $base = ''; 124 | } 125 | $r['path'] = $base . '/' . $r['path']; 126 | } 127 | $b['path'] = $r['path']; 128 | $b['path'] = $this->removeDotSegments($b['path']); 129 | 130 | if (isset($r['query'])) { 131 | $b['query'] = $r['query']; 132 | } 133 | if (isset($r['fragment'])) { 134 | $b['fragment'] = $r['fragment']; 135 | } 136 | } 137 | 138 | if (!isset($b['path'])) { 139 | $b['path'] = '/'; 140 | } 141 | 142 | $absolutized_url = $this->unparseUrl($b); 143 | return $absolutized_url; 144 | } 145 | 146 | /** 147 | * Parse url. 148 | * 149 | * Parse url into components of a URI as specified by RFC 3986. 150 | */ 151 | private function parseUrl($url) 152 | { 153 | return parse_url($url); 154 | } 155 | 156 | /** 157 | * Unparse url. 158 | * 159 | * Combine url components into a url. 160 | */ 161 | private function unparseUrl($parsed_url) { 162 | $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; 163 | $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; 164 | $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; 165 | $user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; 166 | $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; 167 | $pass = ($user || $pass) ? $pass . '@' : ''; 168 | $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; 169 | $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; 170 | $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; 171 | $unparsed_url = $scheme . $user . $pass . $host . $port . $path . $query . $fragment; 172 | return $unparsed_url; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /vendor/php-curl-class/php-curl-class/README.md: -------------------------------------------------------------------------------- 1 | # PHP Curl Class: HTTP requests made easy 2 | 3 | [![](https://img.shields.io/github/release/php-curl-class/php-curl-class.svg)](https://github.com/php-curl-class/php-curl-class/releases/) 4 | [![](https://img.shields.io/github/license/php-curl-class/php-curl-class.svg)](https://github.com/php-curl-class/php-curl-class/blob/master/LICENSE) 5 | [![](https://img.shields.io/travis/php-curl-class/php-curl-class.svg)](https://travis-ci.org/php-curl-class/php-curl-class/) 6 | [![](https://img.shields.io/packagist/dt/php-curl-class/php-curl-class.svg)](https://github.com/php-curl-class/php-curl-class/releases/) 7 | 8 | PHP Curl Class makes it easy to send HTTP requests and integrate with web APIs. 9 | 10 | ![PHP Curl Class screencast](www/img/screencast.gif) 11 | 12 | --- 13 | 14 | - [Installation](#installation) 15 | - [Requirements](#requirements) 16 | - [Quick Start and Examples](#quick-start-and-examples) 17 | - [Available Methods](#available-methods) 18 | - [Security](#security) 19 | - [Troubleshooting](#troubleshooting) 20 | - [Run Tests](#run-tests) 21 | - [Contribute](#contribute) 22 | 23 | --- 24 | 25 | ### Installation 26 | 27 | To install PHP Curl Class, simply: 28 | 29 | $ composer require php-curl-class/php-curl-class 30 | 31 | For latest commit version: 32 | 33 | $ composer require php-curl-class/php-curl-class @dev 34 | 35 | ### Requirements 36 | 37 | PHP Curl Class works with PHP 5.3, 5.4, 5.5, 5.6, 7.0, 7.1, and HHVM. 38 | 39 | ### Quick Start and Examples 40 | 41 | More examples are available under [/examples](https://github.com/php-curl-class/php-curl-class/tree/master/examples). 42 | 43 | ```php 44 | require __DIR__ . '/vendor/autoload.php'; 45 | 46 | use \Curl\Curl; 47 | 48 | $curl = new Curl(); 49 | $curl->get('https://www.example.com/'); 50 | 51 | if ($curl->error) { 52 | echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; 53 | } else { 54 | echo 'Response:' . "\n"; 55 | var_dump($curl->response); 56 | } 57 | ``` 58 | 59 | ```php 60 | // https://www.example.com/search?q=keyword 61 | $curl = new Curl(); 62 | $curl->get('https://www.example.com/search', array( 63 | 'q' => 'keyword', 64 | )); 65 | ``` 66 | 67 | ```php 68 | $curl = new Curl(); 69 | $curl->post('https://www.example.com/login/', array( 70 | 'username' => 'myusername', 71 | 'password' => 'mypassword', 72 | )); 73 | ``` 74 | 75 | ```php 76 | $curl = new Curl(); 77 | $curl->setBasicAuthentication('username', 'password'); 78 | $curl->setUserAgent('MyUserAgent/0.0.1 (+https://www.example.com/bot.html)'); 79 | $curl->setReferrer('https://www.example.com/url?url=https%3A%2F%2Fwww.example.com%2F'); 80 | $curl->setHeader('X-Requested-With', 'XMLHttpRequest'); 81 | $curl->setCookie('key', 'value'); 82 | $curl->get('https://www.example.com/'); 83 | 84 | if ($curl->error) { 85 | echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; 86 | } else { 87 | echo 'Response:' . "\n"; 88 | var_dump($curl->response); 89 | } 90 | 91 | var_dump($curl->requestHeaders); 92 | var_dump($curl->responseHeaders); 93 | ``` 94 | 95 | ```php 96 | $curl = new Curl(); 97 | $curl->setOpt(CURLOPT_FOLLOWLOCATION, true); 98 | $curl->get('https://shortn.example.com/bHbVsP'); 99 | ``` 100 | 101 | ```php 102 | $curl = new Curl(); 103 | $curl->put('https://api.example.com/user/', array( 104 | 'first_name' => 'Zach', 105 | 'last_name' => 'Borboa', 106 | )); 107 | ``` 108 | 109 | ```php 110 | $curl = new Curl(); 111 | $curl->patch('https://api.example.com/profile/', array( 112 | 'image' => '@path/to/file.jpg', 113 | )); 114 | ``` 115 | 116 | ```php 117 | $curl = new Curl(); 118 | $curl->patch('https://api.example.com/profile/', array( 119 | 'image' => new CURLFile('path/to/file.jpg'), 120 | )); 121 | ``` 122 | 123 | ```php 124 | $curl = new Curl(); 125 | $curl->delete('https://api.example.com/user/', array( 126 | 'id' => '1234', 127 | )); 128 | ``` 129 | 130 | ```php 131 | // Enable all supported encoding types and download a file. 132 | $curl = new Curl(); 133 | $curl->setOpt(CURLOPT_ENCODING , ''); 134 | $curl->download('https://www.example.com/file.bin', '/tmp/myfile.bin'); 135 | ``` 136 | 137 | ```php 138 | // Case-insensitive access to headers. 139 | $curl = new Curl(); 140 | $curl->download('https://www.example.com/image.png', '/tmp/myimage.png'); 141 | echo $curl->responseHeaders['Content-Type'] . "\n"; // image/png 142 | echo $curl->responseHeaders['CoNTeNT-TyPE'] . "\n"; // image/png 143 | ``` 144 | 145 | ```php 146 | // Clean up. 147 | $curl->close(); 148 | ``` 149 | 150 | ```php 151 | // Example access to curl object. 152 | curl_set_opt($curl->curl, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1'); 153 | curl_close($curl->curl); 154 | ``` 155 | 156 | ```php 157 | require __DIR__ . '/vendor/autoload.php'; 158 | 159 | use \Curl\MultiCurl; 160 | 161 | // Requests in parallel with callback functions. 162 | $multi_curl = new MultiCurl(); 163 | 164 | $multi_curl->success(function($instance) { 165 | echo 'call to "' . $instance->url . '" was successful.' . "\n"; 166 | echo 'response:' . "\n"; 167 | var_dump($instance->response); 168 | }); 169 | $multi_curl->error(function($instance) { 170 | echo 'call to "' . $instance->url . '" was unsuccessful.' . "\n"; 171 | echo 'error code: ' . $instance->errorCode . "\n"; 172 | echo 'error message: ' . $instance->errorMessage . "\n"; 173 | }); 174 | $multi_curl->complete(function($instance) { 175 | echo 'call completed' . "\n"; 176 | }); 177 | 178 | $multi_curl->addGet('https://www.google.com/search', array( 179 | 'q' => 'hello world', 180 | )); 181 | $multi_curl->addGet('https://duckduckgo.com/', array( 182 | 'q' => 'hello world', 183 | )); 184 | $multi_curl->addGet('https://www.bing.com/search', array( 185 | 'q' => 'hello world', 186 | )); 187 | 188 | $multi_curl->start(); // Blocks until all items in the queue have been processed. 189 | ``` 190 | 191 | More examples are available under [/examples](https://github.com/php-curl-class/php-curl-class/tree/master/examples). 192 | 193 | ### Available Methods 194 | ```php 195 | Curl::__construct($base_url = null) 196 | Curl::__destruct() 197 | Curl::__get($name) 198 | Curl::attemptRetry() 199 | Curl::beforeSend($callback) 200 | Curl::buildPostData($data) 201 | Curl::call() 202 | Curl::close() 203 | Curl::complete($callback) 204 | Curl::delete($url, $query_parameters = array(), $data = array()) 205 | Curl::download($url, $mixed_filename) 206 | Curl::error($callback) 207 | Curl::exec($ch = null) 208 | Curl::execDone() 209 | Curl::get($url, $data = array()) 210 | Curl::getCookie($key) 211 | Curl::getInfo($opt = null) 212 | Curl::getOpt($option) 213 | Curl::getResponseCookie($key) 214 | Curl::getResponseCookies() 215 | Curl::head($url, $data = array()) 216 | Curl::options($url, $data = array()) 217 | Curl::patch($url, $data = array()) 218 | Curl::post($url, $data = array(), $follow_303_with_post = false) 219 | Curl::progress($callback) 220 | Curl::put($url, $data = array()) 221 | Curl::removeHeader($key) 222 | Curl::search($url, $data = array()) 223 | Curl::setBasicAuthentication($username, $password = '') 224 | Curl::setConnectTimeout($seconds) 225 | Curl::setCookie($key, $value) 226 | Curl::setCookieFile($cookie_file) 227 | Curl::setCookieJar($cookie_jar) 228 | Curl::setCookieString($string) 229 | Curl::setCookies($cookies) 230 | Curl::setDefaultDecoder($mixed = 'json') 231 | Curl::setDefaultJsonDecoder() 232 | Curl::setDefaultTimeout() 233 | Curl::setDefaultUserAgent() 234 | Curl::setDefaultXmlDecoder() 235 | Curl::setDigestAuthentication($username, $password = '') 236 | Curl::setHeader($key, $value) 237 | Curl::setHeaders($headers) 238 | Curl::setJsonDecoder($mixed) 239 | Curl::setMaxFilesize($bytes) 240 | Curl::setOpt($option, $value) 241 | Curl::setOpts($options) 242 | Curl::setPort($port) 243 | Curl::setReferer($referer) 244 | Curl::setReferrer($referrer) 245 | Curl::setRetry($mixed) 246 | Curl::setTimeout($seconds) 247 | Curl::setUrl($url, $mixed_data = '') 248 | Curl::setUserAgent($user_agent) 249 | Curl::setXmlDecoder($mixed) 250 | Curl::success($callback) 251 | Curl::unsetHeader($key) 252 | Curl::verbose($on = true, $output = STDERR) 253 | MultiCurl::__construct($base_url = null) 254 | MultiCurl::__destruct() 255 | MultiCurl::addCurl(Curl $curl) 256 | MultiCurl::addDelete($url, $query_parameters = array(), $data = array()) 257 | MultiCurl::addDownload($url, $mixed_filename) 258 | MultiCurl::addGet($url, $data = array()) 259 | MultiCurl::addHead($url, $data = array()) 260 | MultiCurl::addOptions($url, $data = array()) 261 | MultiCurl::addPatch($url, $data = array()) 262 | MultiCurl::addPost($url, $data = array(), $follow_303_with_post = false) 263 | MultiCurl::addPut($url, $data = array()) 264 | MultiCurl::addSearch($url, $data = array()) 265 | MultiCurl::beforeSend($callback) 266 | MultiCurl::close() 267 | MultiCurl::complete($callback) 268 | MultiCurl::error($callback) 269 | MultiCurl::getOpt($option) 270 | MultiCurl::removeHeader($key) 271 | MultiCurl::setBasicAuthentication($username, $password = '') 272 | MultiCurl::setConcurrency($concurrency) 273 | MultiCurl::setConnectTimeout($seconds) 274 | MultiCurl::setCookie($key, $value) 275 | MultiCurl::setCookieFile($cookie_file) 276 | MultiCurl::setCookieJar($cookie_jar) 277 | MultiCurl::setCookieString($string) 278 | MultiCurl::setCookies($cookies) 279 | MultiCurl::setDigestAuthentication($username, $password = '') 280 | MultiCurl::setHeader($key, $value) 281 | MultiCurl::setHeaders($headers) 282 | MultiCurl::setJsonDecoder($mixed) 283 | MultiCurl::setOpt($option, $value) 284 | MultiCurl::setOpts($options) 285 | MultiCurl::setPort($port) 286 | MultiCurl::setReferer($referer) 287 | MultiCurl::setReferrer($referrer) 288 | MultiCurl::setRetry($mixed) 289 | MultiCurl::setTimeout($seconds) 290 | MultiCurl::setUrl($url) 291 | MultiCurl::setUserAgent($user_agent) 292 | MultiCurl::setXmlDecoder($mixed) 293 | MultiCurl::start() 294 | MultiCurl::success($callback) 295 | MultiCurl::unsetHeader($key) 296 | MultiCurl::verbose($on = true, $output = STDERR) 297 | ``` 298 | 299 | ### Security 300 | 301 | See [SECURITY](https://github.com/php-curl-class/php-curl-class/blob/master/SECURITY.md) for security considerations. 302 | 303 | ### Troubleshooting 304 | 305 | See [TROUBLESHOOTING](https://github.com/php-curl-class/php-curl-class/blob/master/TROUBLESHOOTING.md) for troubleshooting. 306 | 307 | ### Run Tests 308 | 309 | To run tests: 310 | 311 | $ git clone https://github.com/php-curl-class/php-curl-class.git 312 | $ cd php-curl-class/ 313 | $ composer update 314 | $ ./tests/run.sh 315 | 316 | ### Contribute 317 | 1. Check for open issues or open a new issue to start a discussion around a bug or feature. 318 | 1. Fork the repository on GitHub to start making your changes. 319 | 1. Write one or more tests for the new feature or that expose the bug. 320 | 1. Make code changes to implement the feature or fix the bug. 321 | 1. Send a pull request to get your changes merged and published. 322 | -------------------------------------------------------------------------------- /vendor/composer/ClassLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see http://www.php-fig.org/psr/psr-0/ 41 | * @see http://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | // PSR-4 46 | private $prefixLengthsPsr4 = array(); 47 | private $prefixDirsPsr4 = array(); 48 | private $fallbackDirsPsr4 = array(); 49 | 50 | // PSR-0 51 | private $prefixesPsr0 = array(); 52 | private $fallbackDirsPsr0 = array(); 53 | 54 | private $useIncludePath = false; 55 | private $classMap = array(); 56 | private $classMapAuthoritative = false; 57 | private $missingClasses = array(); 58 | private $apcuPrefix; 59 | 60 | public function getPrefixes() 61 | { 62 | if (!empty($this->prefixesPsr0)) { 63 | return call_user_func_array('array_merge', $this->prefixesPsr0); 64 | } 65 | 66 | return array(); 67 | } 68 | 69 | public function getPrefixesPsr4() 70 | { 71 | return $this->prefixDirsPsr4; 72 | } 73 | 74 | public function getFallbackDirs() 75 | { 76 | return $this->fallbackDirsPsr0; 77 | } 78 | 79 | public function getFallbackDirsPsr4() 80 | { 81 | return $this->fallbackDirsPsr4; 82 | } 83 | 84 | public function getClassMap() 85 | { 86 | return $this->classMap; 87 | } 88 | 89 | /** 90 | * @param array $classMap Class to filename map 91 | */ 92 | public function addClassMap(array $classMap) 93 | { 94 | if ($this->classMap) { 95 | $this->classMap = array_merge($this->classMap, $classMap); 96 | } else { 97 | $this->classMap = $classMap; 98 | } 99 | } 100 | 101 | /** 102 | * Registers a set of PSR-0 directories for a given prefix, either 103 | * appending or prepending to the ones previously set for this prefix. 104 | * 105 | * @param string $prefix The prefix 106 | * @param array|string $paths The PSR-0 root directories 107 | * @param bool $prepend Whether to prepend the directories 108 | */ 109 | public function add($prefix, $paths, $prepend = false) 110 | { 111 | if (!$prefix) { 112 | if ($prepend) { 113 | $this->fallbackDirsPsr0 = array_merge( 114 | (array) $paths, 115 | $this->fallbackDirsPsr0 116 | ); 117 | } else { 118 | $this->fallbackDirsPsr0 = array_merge( 119 | $this->fallbackDirsPsr0, 120 | (array) $paths 121 | ); 122 | } 123 | 124 | return; 125 | } 126 | 127 | $first = $prefix[0]; 128 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 129 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 130 | 131 | return; 132 | } 133 | if ($prepend) { 134 | $this->prefixesPsr0[$first][$prefix] = array_merge( 135 | (array) $paths, 136 | $this->prefixesPsr0[$first][$prefix] 137 | ); 138 | } else { 139 | $this->prefixesPsr0[$first][$prefix] = array_merge( 140 | $this->prefixesPsr0[$first][$prefix], 141 | (array) $paths 142 | ); 143 | } 144 | } 145 | 146 | /** 147 | * Registers a set of PSR-4 directories for a given namespace, either 148 | * appending or prepending to the ones previously set for this namespace. 149 | * 150 | * @param string $prefix The prefix/namespace, with trailing '\\' 151 | * @param array|string $paths The PSR-4 base directories 152 | * @param bool $prepend Whether to prepend the directories 153 | * 154 | * @throws \InvalidArgumentException 155 | */ 156 | public function addPsr4($prefix, $paths, $prepend = false) 157 | { 158 | if (!$prefix) { 159 | // Register directories for the root namespace. 160 | if ($prepend) { 161 | $this->fallbackDirsPsr4 = array_merge( 162 | (array) $paths, 163 | $this->fallbackDirsPsr4 164 | ); 165 | } else { 166 | $this->fallbackDirsPsr4 = array_merge( 167 | $this->fallbackDirsPsr4, 168 | (array) $paths 169 | ); 170 | } 171 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 172 | // Register directories for a new namespace. 173 | $length = strlen($prefix); 174 | if ('\\' !== $prefix[$length - 1]) { 175 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 176 | } 177 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 178 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 179 | } elseif ($prepend) { 180 | // Prepend directories for an already registered namespace. 181 | $this->prefixDirsPsr4[$prefix] = array_merge( 182 | (array) $paths, 183 | $this->prefixDirsPsr4[$prefix] 184 | ); 185 | } else { 186 | // Append directories for an already registered namespace. 187 | $this->prefixDirsPsr4[$prefix] = array_merge( 188 | $this->prefixDirsPsr4[$prefix], 189 | (array) $paths 190 | ); 191 | } 192 | } 193 | 194 | /** 195 | * Registers a set of PSR-0 directories for a given prefix, 196 | * replacing any others previously set for this prefix. 197 | * 198 | * @param string $prefix The prefix 199 | * @param array|string $paths The PSR-0 base directories 200 | */ 201 | public function set($prefix, $paths) 202 | { 203 | if (!$prefix) { 204 | $this->fallbackDirsPsr0 = (array) $paths; 205 | } else { 206 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 207 | } 208 | } 209 | 210 | /** 211 | * Registers a set of PSR-4 directories for a given namespace, 212 | * replacing any others previously set for this namespace. 213 | * 214 | * @param string $prefix The prefix/namespace, with trailing '\\' 215 | * @param array|string $paths The PSR-4 base directories 216 | * 217 | * @throws \InvalidArgumentException 218 | */ 219 | public function setPsr4($prefix, $paths) 220 | { 221 | if (!$prefix) { 222 | $this->fallbackDirsPsr4 = (array) $paths; 223 | } else { 224 | $length = strlen($prefix); 225 | if ('\\' !== $prefix[$length - 1]) { 226 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 227 | } 228 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 229 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 230 | } 231 | } 232 | 233 | /** 234 | * Turns on searching the include path for class files. 235 | * 236 | * @param bool $useIncludePath 237 | */ 238 | public function setUseIncludePath($useIncludePath) 239 | { 240 | $this->useIncludePath = $useIncludePath; 241 | } 242 | 243 | /** 244 | * Can be used to check if the autoloader uses the include path to check 245 | * for classes. 246 | * 247 | * @return bool 248 | */ 249 | public function getUseIncludePath() 250 | { 251 | return $this->useIncludePath; 252 | } 253 | 254 | /** 255 | * Turns off searching the prefix and fallback directories for classes 256 | * that have not been registered with the class map. 257 | * 258 | * @param bool $classMapAuthoritative 259 | */ 260 | public function setClassMapAuthoritative($classMapAuthoritative) 261 | { 262 | $this->classMapAuthoritative = $classMapAuthoritative; 263 | } 264 | 265 | /** 266 | * Should class lookup fail if not found in the current class map? 267 | * 268 | * @return bool 269 | */ 270 | public function isClassMapAuthoritative() 271 | { 272 | return $this->classMapAuthoritative; 273 | } 274 | 275 | /** 276 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 277 | * 278 | * @param string|null $apcuPrefix 279 | */ 280 | public function setApcuPrefix($apcuPrefix) 281 | { 282 | $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; 283 | } 284 | 285 | /** 286 | * The APCu prefix in use, or null if APCu caching is not enabled. 287 | * 288 | * @return string|null 289 | */ 290 | public function getApcuPrefix() 291 | { 292 | return $this->apcuPrefix; 293 | } 294 | 295 | /** 296 | * Registers this instance as an autoloader. 297 | * 298 | * @param bool $prepend Whether to prepend the autoloader or not 299 | */ 300 | public function register($prepend = false) 301 | { 302 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 303 | } 304 | 305 | /** 306 | * Unregisters this instance as an autoloader. 307 | */ 308 | public function unregister() 309 | { 310 | spl_autoload_unregister(array($this, 'loadClass')); 311 | } 312 | 313 | /** 314 | * Loads the given class or interface. 315 | * 316 | * @param string $class The name of the class 317 | * @return bool|null True if loaded, null otherwise 318 | */ 319 | public function loadClass($class) 320 | { 321 | if ($file = $this->findFile($class)) { 322 | includeFile($file); 323 | 324 | return true; 325 | } 326 | } 327 | 328 | /** 329 | * Finds the path to the file where the class is defined. 330 | * 331 | * @param string $class The name of the class 332 | * 333 | * @return string|false The path if found, false otherwise 334 | */ 335 | public function findFile($class) 336 | { 337 | // class map lookup 338 | if (isset($this->classMap[$class])) { 339 | return $this->classMap[$class]; 340 | } 341 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 342 | return false; 343 | } 344 | if (null !== $this->apcuPrefix) { 345 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 346 | if ($hit) { 347 | return $file; 348 | } 349 | } 350 | 351 | $file = $this->findFileWithExtension($class, '.php'); 352 | 353 | // Search for Hack files if we are running on HHVM 354 | if (false === $file && defined('HHVM_VERSION')) { 355 | $file = $this->findFileWithExtension($class, '.hh'); 356 | } 357 | 358 | if (null !== $this->apcuPrefix) { 359 | apcu_add($this->apcuPrefix.$class, $file); 360 | } 361 | 362 | if (false === $file) { 363 | // Remember that this class does not exist. 364 | $this->missingClasses[$class] = true; 365 | } 366 | 367 | return $file; 368 | } 369 | 370 | private function findFileWithExtension($class, $ext) 371 | { 372 | // PSR-4 lookup 373 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 374 | 375 | $first = $class[0]; 376 | if (isset($this->prefixLengthsPsr4[$first])) { 377 | $subPath = $class; 378 | while (false !== $lastPos = strrpos($subPath, '\\')) { 379 | $subPath = substr($subPath, 0, $lastPos); 380 | $search = $subPath.'\\'; 381 | if (isset($this->prefixDirsPsr4[$search])) { 382 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 383 | $length = $this->prefixLengthsPsr4[$first][$search]; 384 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { 385 | return $file; 386 | } 387 | } 388 | } 389 | } 390 | } 391 | 392 | // PSR-4 fallback dirs 393 | foreach ($this->fallbackDirsPsr4 as $dir) { 394 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 395 | return $file; 396 | } 397 | } 398 | 399 | // PSR-0 lookup 400 | if (false !== $pos = strrpos($class, '\\')) { 401 | // namespaced class name 402 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 403 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 404 | } else { 405 | // PEAR-like class name 406 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 407 | } 408 | 409 | if (isset($this->prefixesPsr0[$first])) { 410 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 411 | if (0 === strpos($class, $prefix)) { 412 | foreach ($dirs as $dir) { 413 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 414 | return $file; 415 | } 416 | } 417 | } 418 | } 419 | } 420 | 421 | // PSR-0 fallback dirs 422 | foreach ($this->fallbackDirsPsr0 as $dir) { 423 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 424 | return $file; 425 | } 426 | } 427 | 428 | // PSR-0 include paths. 429 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 430 | return $file; 431 | } 432 | 433 | return false; 434 | } 435 | } 436 | 437 | /** 438 | * Scope isolated include. 439 | * 440 | * Prevents access to $this/self from included files. 441 | */ 442 | function includeFile($file) 443 | { 444 | include $file; 445 | } 446 | -------------------------------------------------------------------------------- /vendor/php-curl-class/php-curl-class/src/Curl/MultiCurl.php: -------------------------------------------------------------------------------- 1 | multiCurl = curl_multi_init(); 39 | $this->headers = new CaseInsensitiveArray(); 40 | $this->setUrl($base_url); 41 | } 42 | 43 | /** 44 | * Add Delete 45 | * 46 | * @access public 47 | * @param $url 48 | * @param $query_parameters 49 | * @param $data 50 | * 51 | * @return object 52 | */ 53 | public function addDelete($url, $query_parameters = array(), $data = array()) 54 | { 55 | if (is_array($url)) { 56 | $data = $query_parameters; 57 | $query_parameters = $url; 58 | $url = $this->baseUrl; 59 | } 60 | $curl = new Curl(); 61 | $curl->setUrl($url, $query_parameters); 62 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'DELETE'); 63 | $curl->setOpt(CURLOPT_POSTFIELDS, $curl->buildPostData($data)); 64 | $this->queueHandle($curl); 65 | return $curl; 66 | } 67 | 68 | /** 69 | * Add Download 70 | * 71 | * @access public 72 | * @param $url 73 | * @param $mixed_filename 74 | * 75 | * @return object 76 | */ 77 | public function addDownload($url, $mixed_filename) 78 | { 79 | $curl = new Curl(); 80 | $curl->setUrl($url); 81 | 82 | // Use tmpfile() or php://temp to avoid "Too many open files" error. 83 | if (is_callable($mixed_filename)) { 84 | $callback = $mixed_filename; 85 | $curl->downloadCompleteFunction = $callback; 86 | $curl->fileHandle = tmpfile(); 87 | } else { 88 | $filename = $mixed_filename; 89 | $curl->downloadCompleteFunction = function ($instance, $fh) use ($filename) { 90 | file_put_contents($filename, stream_get_contents($fh)); 91 | }; 92 | $curl->fileHandle = fopen('php://temp', 'wb'); 93 | } 94 | 95 | $curl->setOpt(CURLOPT_FILE, $curl->fileHandle); 96 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'GET'); 97 | $curl->setOpt(CURLOPT_HTTPGET, true); 98 | $this->queueHandle($curl); 99 | return $curl; 100 | } 101 | 102 | /** 103 | * Add Get 104 | * 105 | * @access public 106 | * @param $url 107 | * @param $data 108 | * 109 | * @return object 110 | */ 111 | public function addGet($url, $data = array()) 112 | { 113 | if (is_array($url)) { 114 | $data = $url; 115 | $url = $this->baseUrl; 116 | } 117 | $curl = new Curl(); 118 | $curl->setUrl($url, $data); 119 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'GET'); 120 | $curl->setOpt(CURLOPT_HTTPGET, true); 121 | $this->queueHandle($curl); 122 | return $curl; 123 | } 124 | 125 | /** 126 | * Add Head 127 | * 128 | * @access public 129 | * @param $url 130 | * @param $data 131 | * 132 | * @return object 133 | */ 134 | public function addHead($url, $data = array()) 135 | { 136 | if (is_array($url)) { 137 | $data = $url; 138 | $url = $this->baseUrl; 139 | } 140 | $curl = new Curl(); 141 | $curl->setUrl($url, $data); 142 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'HEAD'); 143 | $curl->setOpt(CURLOPT_NOBODY, true); 144 | $this->queueHandle($curl); 145 | return $curl; 146 | } 147 | 148 | /** 149 | * Add Options 150 | * 151 | * @access public 152 | * @param $url 153 | * @param $data 154 | * 155 | * @return object 156 | */ 157 | public function addOptions($url, $data = array()) 158 | { 159 | if (is_array($url)) { 160 | $data = $url; 161 | $url = $this->baseUrl; 162 | } 163 | $curl = new Curl(); 164 | $curl->setUrl($url, $data); 165 | $curl->removeHeader('Content-Length'); 166 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'OPTIONS'); 167 | $this->queueHandle($curl); 168 | return $curl; 169 | } 170 | 171 | /** 172 | * Add Patch 173 | * 174 | * @access public 175 | * @param $url 176 | * @param $data 177 | * 178 | * @return object 179 | */ 180 | public function addPatch($url, $data = array()) 181 | { 182 | if (is_array($url)) { 183 | $data = $url; 184 | $url = $this->baseUrl; 185 | } 186 | $curl = new Curl(); 187 | $curl->setUrl($url); 188 | $curl->removeHeader('Content-Length'); 189 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'PATCH'); 190 | $curl->setOpt(CURLOPT_POSTFIELDS, $data); 191 | $this->queueHandle($curl); 192 | return $curl; 193 | } 194 | 195 | /** 196 | * Add Post 197 | * 198 | * @access public 199 | * @param $url 200 | * @param $data 201 | * @param $follow_303_with_post 202 | * If true, will cause 303 redirections to be followed using GET requests (default: false). 203 | * Note: Redirections are only followed if the CURLOPT_FOLLOWLOCATION option is set to true. 204 | * 205 | * @return object 206 | */ 207 | public function addPost($url, $data = array(), $follow_303_with_post = false) 208 | { 209 | if (is_array($url)) { 210 | $follow_303_with_post = (bool)$data; 211 | $data = $url; 212 | $url = $this->baseUrl; 213 | } 214 | 215 | $curl = new Curl(); 216 | 217 | if (is_array($data) && empty($data)) { 218 | $curl->removeHeader('Content-Length'); 219 | } 220 | 221 | $curl->setUrl($url); 222 | 223 | /* 224 | * For post-redirect-get requests, the CURLOPT_CUSTOMREQUEST option must not 225 | * be set, otherwise cURL will perform POST requests for redirections. 226 | */ 227 | if (!$follow_303_with_post) { 228 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'POST'); 229 | } 230 | 231 | $curl->setOpt(CURLOPT_POST, true); 232 | $curl->setOpt(CURLOPT_POSTFIELDS, $curl->buildPostData($data)); 233 | $this->queueHandle($curl); 234 | return $curl; 235 | } 236 | 237 | /** 238 | * Add Put 239 | * 240 | * @access public 241 | * @param $url 242 | * @param $data 243 | * 244 | * @return object 245 | */ 246 | public function addPut($url, $data = array()) 247 | { 248 | if (is_array($url)) { 249 | $data = $url; 250 | $url = $this->baseUrl; 251 | } 252 | $curl = new Curl(); 253 | $curl->setUrl($url); 254 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'PUT'); 255 | $put_data = $curl->buildPostData($data); 256 | if (is_string($put_data)) { 257 | $curl->setHeader('Content-Length', strlen($put_data)); 258 | } 259 | $curl->setOpt(CURLOPT_POSTFIELDS, $put_data); 260 | $this->queueHandle($curl); 261 | return $curl; 262 | } 263 | 264 | /** 265 | * Add Search 266 | * 267 | * @access public 268 | * @param $url 269 | * @param $data 270 | * 271 | * @return object 272 | */ 273 | public function addSearch($url, $data = array()) 274 | { 275 | if (is_array($url)) { 276 | $data = $url; 277 | $url = $this->baseUrl; 278 | } 279 | $curl = new Curl(); 280 | $curl->setUrl($url); 281 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'SEARCH'); 282 | $put_data = $curl->buildPostData($data); 283 | if (is_string($put_data)) { 284 | $curl->setHeader('Content-Length', strlen($put_data)); 285 | } 286 | $curl->setOpt(CURLOPT_POSTFIELDS, $put_data); 287 | $this->queueHandle($curl); 288 | return $curl; 289 | } 290 | 291 | /** 292 | * Add Curl 293 | * 294 | * Add a Curl instance to the handle queue. 295 | * 296 | * @access public 297 | * @param $curl 298 | * 299 | * @return object 300 | */ 301 | public function addCurl(Curl $curl) 302 | { 303 | $this->queueHandle($curl); 304 | return $curl; 305 | } 306 | 307 | /** 308 | * Before Send 309 | * 310 | * @access public 311 | * @param $callback 312 | */ 313 | public function beforeSend($callback) 314 | { 315 | $this->beforeSendFunction = $callback; 316 | } 317 | 318 | /** 319 | * Close 320 | * 321 | * @access public 322 | */ 323 | public function close() 324 | { 325 | foreach ($this->curls as $curl) { 326 | $curl->close(); 327 | } 328 | 329 | if (is_resource($this->multiCurl)) { 330 | curl_multi_close($this->multiCurl); 331 | } 332 | } 333 | 334 | /** 335 | * Complete 336 | * 337 | * @access public 338 | * @param $callback 339 | */ 340 | public function complete($callback) 341 | { 342 | $this->completeFunction = $callback; 343 | } 344 | 345 | /** 346 | * Error 347 | * 348 | * @access public 349 | * @param $callback 350 | */ 351 | public function error($callback) 352 | { 353 | $this->errorFunction = $callback; 354 | } 355 | 356 | /** 357 | * Get Opt 358 | * 359 | * @access public 360 | * @param $option 361 | * 362 | * @return mixed 363 | */ 364 | public function getOpt($option) 365 | { 366 | return isset($this->options[$option]) ? $this->options[$option] : null; 367 | } 368 | 369 | /** 370 | * Set Basic Authentication 371 | * 372 | * @access public 373 | * @param $username 374 | * @param $password 375 | */ 376 | public function setBasicAuthentication($username, $password = '') 377 | { 378 | $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_BASIC); 379 | $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); 380 | } 381 | 382 | /** 383 | * Set Concurrency 384 | * 385 | * @access public 386 | * @param $concurrency 387 | */ 388 | public function setConcurrency($concurrency) 389 | { 390 | $this->concurrency = $concurrency; 391 | } 392 | 393 | /** 394 | * Set Digest Authentication 395 | * 396 | * @access public 397 | * @param $username 398 | * @param $password 399 | */ 400 | public function setDigestAuthentication($username, $password = '') 401 | { 402 | $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); 403 | $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); 404 | } 405 | 406 | /** 407 | * Set Cookie 408 | * 409 | * @access public 410 | * @param $key 411 | * @param $value 412 | */ 413 | public function setCookie($key, $value) 414 | { 415 | $this->cookies[$key] = $value; 416 | } 417 | 418 | /** 419 | * Set Cookies 420 | * 421 | * @access public 422 | * @param $cookies 423 | */ 424 | public function setCookies($cookies) 425 | { 426 | foreach ($cookies as $key => $value) { 427 | $this->cookies[$key] = $value; 428 | } 429 | } 430 | 431 | /** 432 | * Set Port 433 | * 434 | * @access public 435 | * @param $port 436 | */ 437 | public function setPort($port) 438 | { 439 | $this->setOpt(CURLOPT_PORT, intval($port)); 440 | } 441 | 442 | /** 443 | * Set Connect Timeout 444 | * 445 | * @access public 446 | * @param $seconds 447 | */ 448 | public function setConnectTimeout($seconds) 449 | { 450 | $this->setOpt(CURLOPT_CONNECTTIMEOUT, $seconds); 451 | } 452 | 453 | /** 454 | * Set Cookie String 455 | * 456 | * @access public 457 | * @param $string 458 | */ 459 | public function setCookieString($string) 460 | { 461 | $this->setOpt(CURLOPT_COOKIE, $string); 462 | } 463 | 464 | /** 465 | * Set Cookie File 466 | * 467 | * @access public 468 | * @param $cookie_file 469 | */ 470 | public function setCookieFile($cookie_file) 471 | { 472 | $this->setOpt(CURLOPT_COOKIEFILE, $cookie_file); 473 | } 474 | 475 | /** 476 | * Set Cookie Jar 477 | * 478 | * @access public 479 | * @param $cookie_jar 480 | */ 481 | public function setCookieJar($cookie_jar) 482 | { 483 | $this->setOpt(CURLOPT_COOKIEJAR, $cookie_jar); 484 | } 485 | 486 | /** 487 | * Set Header 488 | * 489 | * Add extra header to include in the request. 490 | * 491 | * @access public 492 | * @param $key 493 | * @param $value 494 | */ 495 | public function setHeader($key, $value) 496 | { 497 | $this->headers[$key] = $value; 498 | } 499 | 500 | /** 501 | * Set Headers 502 | * 503 | * Add extra headers to include in the request. 504 | * 505 | * @access public 506 | * @param $headers 507 | */ 508 | public function setHeaders($headers) 509 | { 510 | foreach ($headers as $key => $value) { 511 | $this->headers[$key] = $value; 512 | } 513 | } 514 | 515 | /** 516 | * Set JSON Decoder 517 | * 518 | * @access public 519 | * @param $mixed boolean|callable 520 | */ 521 | public function setJsonDecoder($mixed) 522 | { 523 | if ($mixed === false) { 524 | $this->jsonDecoder = false; 525 | } elseif (is_callable($mixed)) { 526 | $this->jsonDecoder = $mixed; 527 | } 528 | } 529 | 530 | /** 531 | * Set XML Decoder 532 | * 533 | * @access public 534 | * @param $mixed boolean|callable 535 | */ 536 | public function setXmlDecoder($mixed) 537 | { 538 | if ($mixed === false) { 539 | $this->xmlDecoder = false; 540 | } elseif (is_callable($mixed)) { 541 | $this->xmlDecoder = $mixed; 542 | } 543 | } 544 | 545 | /** 546 | * Set Opt 547 | * 548 | * @access public 549 | * @param $option 550 | * @param $value 551 | */ 552 | public function setOpt($option, $value) 553 | { 554 | $this->options[$option] = $value; 555 | } 556 | 557 | /** 558 | * Set Opts 559 | * 560 | * @access public 561 | * @param $options 562 | */ 563 | public function setOpts($options) 564 | { 565 | foreach ($options as $option => $value) { 566 | $this->setOpt($option, $value); 567 | } 568 | } 569 | 570 | /** 571 | * Set Referer 572 | * 573 | * @access public 574 | * @param $referer 575 | */ 576 | public function setReferer($referer) 577 | { 578 | $this->setReferrer($referer); 579 | } 580 | 581 | /** 582 | * Set Referrer 583 | * 584 | * @access public 585 | * @param $referrer 586 | */ 587 | public function setReferrer($referrer) 588 | { 589 | $this->setOpt(CURLOPT_REFERER, $referrer); 590 | } 591 | 592 | /** 593 | * Set Retry 594 | * 595 | * Number of retries to attempt or decider callable. Maximum number of 596 | * attempts is $maximum_number_of_retries + 1. 597 | * 598 | * @access public 599 | * @param $mixed 600 | */ 601 | public function setRetry($mixed) 602 | { 603 | $this->retry = $mixed; 604 | } 605 | 606 | /** 607 | * Set Timeout 608 | * 609 | * @access public 610 | * @param $seconds 611 | */ 612 | public function setTimeout($seconds) 613 | { 614 | $this->setOpt(CURLOPT_TIMEOUT, $seconds); 615 | } 616 | 617 | /** 618 | * Set Url 619 | * 620 | * @access public 621 | * @param $url 622 | */ 623 | public function setUrl($url) 624 | { 625 | $this->baseUrl = $url; 626 | } 627 | 628 | /** 629 | * Set User Agent 630 | * 631 | * @access public 632 | * @param $user_agent 633 | */ 634 | public function setUserAgent($user_agent) 635 | { 636 | $this->setOpt(CURLOPT_USERAGENT, $user_agent); 637 | } 638 | 639 | /** 640 | * Start 641 | * 642 | * @access public 643 | */ 644 | public function start() 645 | { 646 | if ($this->isStarted) { 647 | return; 648 | } 649 | 650 | $this->isStarted = true; 651 | 652 | $concurrency = $this->concurrency; 653 | if ($concurrency > count($this->curls)) { 654 | $concurrency = count($this->curls); 655 | } 656 | 657 | for ($i = 0; $i < $concurrency; $i++) { 658 | $this->initHandle(array_shift($this->curls)); 659 | } 660 | 661 | do { 662 | // Wait for activity on any curl_multi connection when curl_multi_select (libcurl) fails to correctly block. 663 | // https://bugs.php.net/bug.php?id=63411 664 | if (curl_multi_select($this->multiCurl) === -1) { 665 | usleep(100000); 666 | } 667 | 668 | curl_multi_exec($this->multiCurl, $active); 669 | 670 | while (!($info_array = curl_multi_info_read($this->multiCurl)) === false) { 671 | if ($info_array['msg'] === CURLMSG_DONE) { 672 | foreach ($this->activeCurls as $key => $ch) { 673 | if ($ch->curl === $info_array['handle']) { 674 | // Set the error code for multi handles using the "result" key in the array returned by 675 | // curl_multi_info_read(). Using curl_errno() on a multi handle will incorrectly return 0 676 | // for errors. 677 | $ch->curlErrorCode = $info_array['result']; 678 | $ch->exec($ch->curl); 679 | 680 | if ($ch->attemptRetry()) { 681 | // Remove completed handle before adding again in order to retry request. 682 | curl_multi_remove_handle($this->multiCurl, $ch->curl); 683 | 684 | $curlm_error_code = curl_multi_add_handle($this->multiCurl, $ch->curl); 685 | if (!($curlm_error_code === CURLM_OK)) { 686 | throw new \ErrorException( 687 | 'cURL multi add handle error: ' . curl_multi_strerror($curlm_error_code) 688 | ); 689 | } 690 | } else { 691 | $ch->execDone(); 692 | 693 | // Remove completed instance from active curls. 694 | unset($this->activeCurls[$key]); 695 | 696 | // Start a new request before removing the handle of the completed one. 697 | if (count($this->curls) >= 1) { 698 | $this->initHandle(array_shift($this->curls)); 699 | } 700 | curl_multi_remove_handle($this->multiCurl, $ch->curl); 701 | 702 | // Clean up completed instance. 703 | $ch->close(); 704 | } 705 | 706 | break; 707 | } 708 | } 709 | } 710 | } 711 | 712 | if (!$active) { 713 | $active = count($this->activeCurls); 714 | } 715 | } while ($active > 0); 716 | 717 | $this->isStarted = false; 718 | } 719 | 720 | /** 721 | * Success 722 | * 723 | * @access public 724 | * @param $callback 725 | */ 726 | public function success($callback) 727 | { 728 | $this->successFunction = $callback; 729 | } 730 | 731 | /** 732 | * Unset Header 733 | * 734 | * Remove extra header previously set using Curl::setHeader(). 735 | * 736 | * @access public 737 | * @param $key 738 | */ 739 | public function unsetHeader($key) 740 | { 741 | unset($this->headers[$key]); 742 | } 743 | 744 | /** 745 | * Remove Header 746 | * 747 | * Remove an internal header from the request. 748 | * Using `curl -H "Host:" ...' is equivalent to $curl->removeHeader('Host');. 749 | * 750 | * @access public 751 | * @param $key 752 | */ 753 | public function removeHeader($key) 754 | { 755 | $this->setHeader($key, ''); 756 | } 757 | 758 | /** 759 | * Verbose 760 | * 761 | * @access public 762 | * @param bool $on 763 | * @param resource $output 764 | */ 765 | public function verbose($on = true, $output = STDERR) 766 | { 767 | // Turn off CURLINFO_HEADER_OUT for verbose to work. This has the side 768 | // effect of causing Curl::requestHeaders to be empty. 769 | if ($on) { 770 | $this->setOpt(CURLINFO_HEADER_OUT, false); 771 | } 772 | $this->setOpt(CURLOPT_VERBOSE, $on); 773 | $this->setOpt(CURLOPT_STDERR, $output); 774 | } 775 | 776 | /** 777 | * Destruct 778 | * 779 | * @access public 780 | */ 781 | public function __destruct() 782 | { 783 | $this->close(); 784 | } 785 | 786 | /** 787 | * Queue Handle 788 | * 789 | * @access private 790 | * @param $curl 791 | */ 792 | private function queueHandle($curl) 793 | { 794 | // Use sequential ids to allow for ordered post processing. 795 | $curl->id = $this->nextCurlId++; 796 | $curl->isChildOfMultiCurl = true; 797 | $this->curls[$curl->id] = $curl; 798 | } 799 | 800 | /** 801 | * Init Handle 802 | * 803 | * @access private 804 | * @param $curl 805 | * @throws \ErrorException 806 | */ 807 | private function initHandle($curl) 808 | { 809 | // Set callbacks if not already individually set. 810 | if ($curl->beforeSendFunction === null) { 811 | $curl->beforeSend($this->beforeSendFunction); 812 | } 813 | if ($curl->successFunction === null) { 814 | $curl->success($this->successFunction); 815 | } 816 | if ($curl->errorFunction === null) { 817 | $curl->error($this->errorFunction); 818 | } 819 | if ($curl->completeFunction === null) { 820 | $curl->complete($this->completeFunction); 821 | } 822 | 823 | $curl->setOpts($this->options); 824 | $curl->setHeaders($this->headers); 825 | $curl->setRetry($this->retry); 826 | 827 | foreach ($this->cookies as $key => $value) { 828 | $curl->setCookie($key, $value); 829 | } 830 | 831 | $curl->setJsonDecoder($this->jsonDecoder); 832 | $curl->setXmlDecoder($this->xmlDecoder); 833 | 834 | $curlm_error_code = curl_multi_add_handle($this->multiCurl, $curl->curl); 835 | if (!($curlm_error_code === CURLM_OK)) { 836 | throw new \ErrorException('cURL multi add handle error: ' . curl_multi_strerror($curlm_error_code)); 837 | } 838 | 839 | $this->activeCurls[$curl->id] = $curl; 840 | $curl->call($curl->beforeSendFunction); 841 | } 842 | } 843 | -------------------------------------------------------------------------------- /vendor/php-curl-class/php-curl-class/src/Curl/Curl.php: -------------------------------------------------------------------------------- 1 | 63 | // CTL = 65 | // separators = "(" | ")" | "<" | ">" | "@" 66 | // | "," | ";" | ":" | "\" | <"> 67 | // | "/" | "[" | "]" | "?" | "=" 68 | // | "{" | "}" | SP | HT 69 | // SP = 70 | // HT = 71 | // <"> = 72 | '!', '#', '$', '%', '&', "'", '*', '+', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 73 | 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 74 | 'Y', 'Z', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 75 | 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '|', '~', 76 | ); 77 | public static $RFC6265 = array( 78 | // RFC 6265: "US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash". 79 | // %x21 80 | '!', 81 | // %x23-2B 82 | '#', '$', '%', '&', "'", '(', ')', '*', '+', 83 | // %x2D-3A 84 | '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', 85 | // %x3C-5B 86 | '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 87 | 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', 88 | // %x5D-7E 89 | ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 90 | 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', 91 | ); 92 | 93 | private static $deferredProperties = array( 94 | 'effectiveUrl', 95 | 'rfc2616', 96 | 'rfc6265', 97 | 'totalTime', 98 | ); 99 | 100 | /** 101 | * Construct 102 | * 103 | * @access public 104 | * @param $base_url 105 | * @throws \ErrorException 106 | */ 107 | public function __construct($base_url = null) 108 | { 109 | if (!extension_loaded('curl')) { 110 | throw new \ErrorException('cURL library is not loaded'); 111 | } 112 | 113 | $this->curl = curl_init(); 114 | $this->id = uniqid('', true); 115 | $this->setDefaultUserAgent(); 116 | $this->setDefaultTimeout(); 117 | $this->setOpt(CURLINFO_HEADER_OUT, true); 118 | 119 | // Create a placeholder to temporarily store the header callback data. 120 | $header_callback_data = new \stdClass(); 121 | $header_callback_data->rawResponseHeaders = ''; 122 | $header_callback_data->responseCookies = array(); 123 | $this->headerCallbackData = $header_callback_data; 124 | $this->setOpt(CURLOPT_HEADERFUNCTION, $this->createHeaderCallback($header_callback_data)); 125 | 126 | $this->setOpt(CURLOPT_RETURNTRANSFER, true); 127 | $this->headers = new CaseInsensitiveArray(); 128 | $this->setUrl($base_url); 129 | } 130 | 131 | /** 132 | * Before Send 133 | * 134 | * @access public 135 | * @param $callback 136 | */ 137 | public function beforeSend($callback) 138 | { 139 | $this->beforeSendFunction = $callback; 140 | } 141 | 142 | /** 143 | * Build Post Data 144 | * 145 | * @access public 146 | * @param $data 147 | * 148 | * @return array|string 149 | */ 150 | public function buildPostData($data) 151 | { 152 | $binary_data = false; 153 | if (is_array($data)) { 154 | // Return JSON-encoded string when the request's content-type is JSON. 155 | if (isset($this->headers['Content-Type']) && 156 | preg_match($this->jsonPattern, $this->headers['Content-Type'])) { 157 | $json_str = json_encode($data); 158 | if (!($json_str === false)) { 159 | $data = $json_str; 160 | } 161 | } else { 162 | // Manually build a single-dimensional array from a multi-dimensional array as using curl_setopt($ch, 163 | // CURLOPT_POSTFIELDS, $data) doesn't correctly handle multi-dimensional arrays when files are 164 | // referenced. 165 | if (ArrayUtil::is_array_multidim($data)) { 166 | $data = ArrayUtil::array_flatten_multidim($data); 167 | } 168 | 169 | // Modify array values to ensure any referenced files are properly handled depending on the support of 170 | // the @filename API or CURLFile usage. This also fixes the warning "curl_setopt(): The usage of the 171 | // @filename API for file uploading is deprecated. Please use the CURLFile class instead". Ignore 172 | // non-file values prefixed with the @ character. 173 | foreach ($data as $key => $value) { 174 | if (is_string($value) && strpos($value, '@') === 0 && is_file(substr($value, 1))) { 175 | $binary_data = true; 176 | if (class_exists('CURLFile')) { 177 | $data[$key] = new \CURLFile(substr($value, 1)); 178 | } 179 | } elseif ($value instanceof \CURLFile) { 180 | $binary_data = true; 181 | } 182 | } 183 | } 184 | } 185 | 186 | if (!$binary_data && (is_array($data) || is_object($data))) { 187 | $data = http_build_query($data, '', '&'); 188 | } 189 | 190 | return $data; 191 | } 192 | 193 | /** 194 | * Call 195 | * 196 | * @access public 197 | */ 198 | public function call() 199 | { 200 | $args = func_get_args(); 201 | $function = array_shift($args); 202 | if (is_callable($function)) { 203 | array_unshift($args, $this); 204 | call_user_func_array($function, $args); 205 | } 206 | } 207 | 208 | /** 209 | * Close 210 | * 211 | * @access public 212 | */ 213 | public function close() 214 | { 215 | if (is_resource($this->curl)) { 216 | curl_close($this->curl); 217 | } 218 | $this->options = null; 219 | $this->jsonDecoder = null; 220 | $this->jsonDecoderArgs = null; 221 | $this->xmlDecoder = null; 222 | $this->defaultDecoder = null; 223 | } 224 | 225 | /** 226 | * Complete 227 | * 228 | * @access public 229 | * @param $callback 230 | */ 231 | public function complete($callback) 232 | { 233 | $this->completeFunction = $callback; 234 | } 235 | 236 | /** 237 | * Progress 238 | * 239 | * @access public 240 | * @param $callback 241 | */ 242 | public function progress($callback) 243 | { 244 | $this->setOpt(CURLOPT_PROGRESSFUNCTION, $callback); 245 | $this->setOpt(CURLOPT_NOPROGRESS, false); 246 | } 247 | 248 | /** 249 | * Delete 250 | * 251 | * @access public 252 | * @param $url 253 | * @param $query_parameters 254 | * @param $data 255 | * 256 | * @return mixed 257 | */ 258 | public function delete($url, $query_parameters = array(), $data = array()) 259 | { 260 | if (is_array($url)) { 261 | $data = $query_parameters; 262 | $query_parameters = $url; 263 | $url = (string)$this->url; 264 | } 265 | 266 | $this->setUrl($url, $query_parameters); 267 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'DELETE'); 268 | $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data)); 269 | return $this->exec(); 270 | } 271 | 272 | /** 273 | * Download 274 | * 275 | * @access public 276 | * @param $url 277 | * @param $mixed_filename 278 | * 279 | * @return boolean 280 | */ 281 | public function download($url, $mixed_filename) 282 | { 283 | if (is_callable($mixed_filename)) { 284 | $this->downloadCompleteFunction = $mixed_filename; 285 | $this->fileHandle = tmpfile(); 286 | } else { 287 | $filename = $mixed_filename; 288 | 289 | // Use a temporary file when downloading. Not using a temporary file can cause an error when an existing 290 | // file has already fully completed downloading and a new download is started with the same destination save 291 | // path. The download request will include header "Range: bytes=$filesize-" which is syntactically valid, 292 | // but unsatisfiable. 293 | $download_filename = $filename . '.pccdownload'; 294 | 295 | $mode = 'wb'; 296 | // Attempt to resume download only when a temporary download file exists and is not empty. 297 | if (file_exists($download_filename) && $filesize = filesize($download_filename)) { 298 | $mode = 'ab'; 299 | $first_byte_position = $filesize; 300 | $range = $first_byte_position . '-'; 301 | $this->setOpt(CURLOPT_RANGE, $range); 302 | } 303 | $this->fileHandle = fopen($download_filename, $mode); 304 | 305 | // Move the downloaded temporary file to the destination save path. 306 | $this->downloadCompleteFunction = function ($instance, $fh) use ($download_filename, $filename) { 307 | // Close the open file handle before renaming the file. 308 | if (is_resource($fh)) { 309 | fclose($fh); 310 | } 311 | 312 | rename($download_filename, $filename); 313 | }; 314 | } 315 | 316 | $this->setOpt(CURLOPT_FILE, $this->fileHandle); 317 | $this->get($url); 318 | 319 | return ! $this->error; 320 | } 321 | 322 | /** 323 | * Error 324 | * 325 | * @access public 326 | * @param $callback 327 | */ 328 | public function error($callback) 329 | { 330 | $this->errorFunction = $callback; 331 | } 332 | 333 | /** 334 | * Exec 335 | * 336 | * @access public 337 | * @param $ch 338 | * 339 | * @return mixed Returns the value provided by parseResponse. 340 | */ 341 | public function exec($ch = null) 342 | { 343 | $this->attempts += 1; 344 | 345 | if ($ch === null) { 346 | $this->responseCookies = array(); 347 | $this->call($this->beforeSendFunction); 348 | $this->rawResponse = curl_exec($this->curl); 349 | $this->curlErrorCode = curl_errno($this->curl); 350 | $this->curlErrorMessage = curl_error($this->curl); 351 | } else { 352 | $this->rawResponse = curl_multi_getcontent($ch); 353 | $this->curlErrorMessage = curl_error($ch); 354 | } 355 | $this->curlError = !($this->curlErrorCode === 0); 356 | 357 | // Transfer the header callback data and release the temporary store to avoid memory leak. 358 | $this->rawResponseHeaders = $this->headerCallbackData->rawResponseHeaders; 359 | $this->responseCookies = $this->headerCallbackData->responseCookies; 360 | $this->headerCallbackData->rawResponseHeaders = null; 361 | $this->headerCallbackData->responseCookies = null; 362 | 363 | // Include additional error code information in error message when possible. 364 | if ($this->curlError && function_exists('curl_strerror')) { 365 | $this->curlErrorMessage = 366 | curl_strerror($this->curlErrorCode) . ( 367 | empty($this->curlErrorMessage) ? '' : ': ' . $this->curlErrorMessage 368 | ); 369 | } 370 | 371 | $this->httpStatusCode = $this->getInfo(CURLINFO_HTTP_CODE); 372 | $this->httpError = in_array(floor($this->httpStatusCode / 100), array(4, 5)); 373 | $this->error = $this->curlError || $this->httpError; 374 | $this->errorCode = $this->error ? ($this->curlError ? $this->curlErrorCode : $this->httpStatusCode) : 0; 375 | 376 | // NOTE: CURLINFO_HEADER_OUT set to true is required for requestHeaders 377 | // to not be empty (e.g. $curl->setOpt(CURLINFO_HEADER_OUT, true);). 378 | if ($this->getOpt(CURLINFO_HEADER_OUT) === true) { 379 | $this->requestHeaders = $this->parseRequestHeaders($this->getInfo(CURLINFO_HEADER_OUT)); 380 | } 381 | $this->responseHeaders = $this->parseResponseHeaders($this->rawResponseHeaders); 382 | $this->response = $this->parseResponse($this->responseHeaders, $this->rawResponse); 383 | 384 | $this->httpErrorMessage = ''; 385 | if ($this->error) { 386 | if (isset($this->responseHeaders['Status-Line'])) { 387 | $this->httpErrorMessage = $this->responseHeaders['Status-Line']; 388 | } 389 | } 390 | $this->errorMessage = $this->curlError ? $this->curlErrorMessage : $this->httpErrorMessage; 391 | 392 | // Reset select deferred properties so that they may be recalculated. 393 | unset($this->effectiveUrl); 394 | unset($this->totalTime); 395 | 396 | // Reset content-length possibly set from a PUT or SEARCH request. 397 | $this->unsetHeader('Content-Length'); 398 | 399 | // Reset nobody setting possibly set from a HEAD request. 400 | $this->setOpt(CURLOPT_NOBODY, false); 401 | 402 | // Allow multicurl to attempt retry as needed. 403 | if ($this->isChildOfMultiCurl) { 404 | return; 405 | } 406 | 407 | if ($this->attemptRetry()) { 408 | return $this->exec($ch); 409 | } 410 | 411 | $this->execDone(); 412 | 413 | return $this->response; 414 | } 415 | 416 | public function execDone() 417 | { 418 | if ($this->error) { 419 | $this->call($this->errorFunction); 420 | } else { 421 | $this->call($this->successFunction); 422 | } 423 | 424 | $this->call($this->completeFunction); 425 | 426 | // Close open file handles and reset the curl instance. 427 | if (!($this->fileHandle === null)) { 428 | $this->downloadComplete($this->fileHandle); 429 | } 430 | } 431 | 432 | /** 433 | * Get 434 | * 435 | * @access public 436 | * @param $url 437 | * @param $data 438 | * 439 | * @return mixed Returns the value provided by exec. 440 | */ 441 | public function get($url, $data = array()) 442 | { 443 | if (is_array($url)) { 444 | $data = $url; 445 | $url = (string)$this->url; 446 | } 447 | $this->setUrl($url, $data); 448 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'GET'); 449 | $this->setOpt(CURLOPT_HTTPGET, true); 450 | return $this->exec(); 451 | } 452 | 453 | /** 454 | * Get Info 455 | * 456 | * @access public 457 | * @param $opt 458 | * 459 | * @return mixed 460 | */ 461 | public function getInfo($opt = null) 462 | { 463 | $args = array(); 464 | $args[] = $this->curl; 465 | 466 | if (func_num_args()) { 467 | $args[] = $opt; 468 | } 469 | 470 | return call_user_func_array('curl_getinfo', $args); 471 | } 472 | 473 | /** 474 | * Get Opt 475 | * 476 | * @access public 477 | * @param $option 478 | * 479 | * @return mixed 480 | */ 481 | public function getOpt($option) 482 | { 483 | return isset($this->options[$option]) ? $this->options[$option] : null; 484 | } 485 | 486 | /** 487 | * Head 488 | * 489 | * @access public 490 | * @param $url 491 | * @param $data 492 | * 493 | * @return mixed 494 | */ 495 | public function head($url, $data = array()) 496 | { 497 | if (is_array($url)) { 498 | $data = $url; 499 | $url = (string)$this->url; 500 | } 501 | $this->setUrl($url, $data); 502 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'HEAD'); 503 | $this->setOpt(CURLOPT_NOBODY, true); 504 | return $this->exec(); 505 | } 506 | 507 | /** 508 | * Options 509 | * 510 | * @access public 511 | * @param $url 512 | * @param $data 513 | * 514 | * @return mixed 515 | */ 516 | public function options($url, $data = array()) 517 | { 518 | if (is_array($url)) { 519 | $data = $url; 520 | $url = (string)$this->url; 521 | } 522 | $this->setUrl($url, $data); 523 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'OPTIONS'); 524 | return $this->exec(); 525 | } 526 | 527 | /** 528 | * Patch 529 | * 530 | * @access public 531 | * @param $url 532 | * @param $data 533 | * 534 | * @return mixed 535 | */ 536 | public function patch($url, $data = array()) 537 | { 538 | if (is_array($url)) { 539 | $data = $url; 540 | $url = (string)$this->url; 541 | } 542 | 543 | if (is_array($data) && empty($data)) { 544 | $this->removeHeader('Content-Length'); 545 | } 546 | 547 | $this->setUrl($url); 548 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PATCH'); 549 | $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data)); 550 | return $this->exec(); 551 | } 552 | 553 | /** 554 | * Post 555 | * 556 | * @access public 557 | * @param $url 558 | * @param $data 559 | * @param $follow_303_with_post 560 | * If true, will cause 303 redirections to be followed using a POST request (default: false). 561 | * Notes: 562 | * - Redirections are only followed if the CURLOPT_FOLLOWLOCATION option is set to true. 563 | * - According to the HTTP specs (see [1]), a 303 redirection should be followed using 564 | * the GET method. 301 and 302 must not. 565 | * - In order to force a 303 redirection to be performed using the same method, the 566 | * underlying cURL object must be set in a special state (the CURLOPT_CURSTOMREQUEST 567 | * option must be set to the method to use after the redirection). Due to a limitation 568 | * of the cURL extension of PHP < 5.5.11 ([2], [3]) and of HHVM, it is not possible 569 | * to reset this option. Using these PHP engines, it is therefore impossible to 570 | * restore this behavior on an existing php-curl-class Curl object. 571 | * 572 | * @return mixed 573 | * 574 | * [1] https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2 575 | * [2] https://github.com/php/php-src/pull/531 576 | * [3] http://php.net/ChangeLog-5.php#5.5.11 577 | */ 578 | public function post($url, $data = array(), $follow_303_with_post = false) 579 | { 580 | if (is_array($url)) { 581 | $follow_303_with_post = (bool)$data; 582 | $data = $url; 583 | $url = (string)$this->url; 584 | } 585 | 586 | $this->setUrl($url); 587 | 588 | if ($follow_303_with_post) { 589 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'POST'); 590 | } else { 591 | if (isset($this->options[CURLOPT_CUSTOMREQUEST])) { 592 | if ((version_compare(PHP_VERSION, '5.5.11') < 0) || defined('HHVM_VERSION')) { 593 | trigger_error( 594 | 'Due to technical limitations of PHP <= 5.5.11 and HHVM, it is not possible to ' 595 | . 'perform a post-redirect-get request using a php-curl-class Curl object that ' 596 | . 'has already been used to perform other types of requests. Either use a new ' 597 | . 'php-curl-class Curl object or upgrade your PHP engine.', 598 | E_USER_ERROR 599 | ); 600 | } else { 601 | $this->setOpt(CURLOPT_CUSTOMREQUEST, null); 602 | } 603 | } 604 | } 605 | 606 | $this->setOpt(CURLOPT_POST, true); 607 | $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data)); 608 | return $this->exec(); 609 | } 610 | 611 | /** 612 | * Put 613 | * 614 | * @access public 615 | * @param $url 616 | * @param $data 617 | * 618 | * @return mixed 619 | */ 620 | public function put($url, $data = array()) 621 | { 622 | if (is_array($url)) { 623 | $data = $url; 624 | $url = (string)$this->url; 625 | } 626 | $this->setUrl($url); 627 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PUT'); 628 | $put_data = $this->buildPostData($data); 629 | if (empty($this->options[CURLOPT_INFILE]) && empty($this->options[CURLOPT_INFILESIZE])) { 630 | if (is_string($put_data)) { 631 | $this->setHeader('Content-Length', strlen($put_data)); 632 | } 633 | } 634 | if (!empty($put_data)) { 635 | $this->setOpt(CURLOPT_POSTFIELDS, $put_data); 636 | } 637 | return $this->exec(); 638 | } 639 | 640 | /** 641 | * Search 642 | * 643 | * @access public 644 | * @param $url 645 | * @param $data 646 | * 647 | * @return mixed 648 | */ 649 | public function search($url, $data = array()) 650 | { 651 | if (is_array($url)) { 652 | $data = $url; 653 | $url = (string)$this->url; 654 | } 655 | $this->setUrl($url); 656 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'SEARCH'); 657 | $put_data = $this->buildPostData($data); 658 | if (empty($this->options[CURLOPT_INFILE]) && empty($this->options[CURLOPT_INFILESIZE])) { 659 | if (is_string($put_data)) { 660 | $this->setHeader('Content-Length', strlen($put_data)); 661 | } 662 | } 663 | if (!empty($put_data)) { 664 | $this->setOpt(CURLOPT_POSTFIELDS, $put_data); 665 | } 666 | return $this->exec(); 667 | } 668 | 669 | /** 670 | * Set Basic Authentication 671 | * 672 | * @access public 673 | * @param $username 674 | * @param $password 675 | */ 676 | public function setBasicAuthentication($username, $password = '') 677 | { 678 | $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_BASIC); 679 | $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); 680 | } 681 | 682 | /** 683 | * Set Digest Authentication 684 | * 685 | * @access public 686 | * @param $username 687 | * @param $password 688 | */ 689 | public function setDigestAuthentication($username, $password = '') 690 | { 691 | $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); 692 | $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); 693 | } 694 | 695 | /** 696 | * Set Cookie 697 | * 698 | * @access public 699 | * @param $key 700 | * @param $value 701 | */ 702 | public function setCookie($key, $value) 703 | { 704 | $this->setEncodedCookie($key, $value); 705 | $this->buildCookies(); 706 | } 707 | 708 | /** 709 | * Set Cookies 710 | * 711 | * @access public 712 | * @param $cookies 713 | */ 714 | public function setCookies($cookies) 715 | { 716 | foreach ($cookies as $key => $value) { 717 | $this->setEncodedCookie($key, $value); 718 | } 719 | $this->buildCookies(); 720 | } 721 | 722 | /** 723 | * Get Cookie 724 | * 725 | * @access public 726 | * @param $key 727 | * 728 | * @return mixed 729 | */ 730 | public function getCookie($key) 731 | { 732 | return $this->getResponseCookie($key); 733 | } 734 | 735 | /** 736 | * Get Response Cookie 737 | * 738 | * @access public 739 | * @param $key 740 | * 741 | * @return mixed 742 | */ 743 | public function getResponseCookie($key) 744 | { 745 | return isset($this->responseCookies[$key]) ? $this->responseCookies[$key] : null; 746 | } 747 | 748 | /** 749 | * Get Response Cookies 750 | * 751 | * @access public 752 | * 753 | * @return array 754 | */ 755 | public function getResponseCookies() 756 | { 757 | return $this->responseCookies; 758 | } 759 | 760 | /** 761 | * Set Max Filesize 762 | * 763 | * @access public 764 | * @param $bytes 765 | */ 766 | public function setMaxFilesize($bytes) 767 | { 768 | // Make compatible with PHP version both before and after 5.5.0. PHP 5.5.0 added the cURL resource as the first 769 | // argument to the CURLOPT_PROGRESSFUNCTION callback. 770 | $gte_v550 = version_compare(PHP_VERSION, '5.5.0') >= 0; 771 | if ($gte_v550) { 772 | $callback = function ($resource, $download_size, $downloaded, $upload_size, $uploaded) use ($bytes) { 773 | // Abort the transfer when $downloaded bytes exceeds maximum $bytes by returning a non-zero value. 774 | return $downloaded > $bytes ? 1 : 0; 775 | }; 776 | } else { 777 | $callback = function ($download_size, $downloaded, $upload_size, $uploaded) use ($bytes) { 778 | return $downloaded > $bytes ? 1 : 0; 779 | }; 780 | } 781 | 782 | $this->progress($callback); 783 | } 784 | 785 | /** 786 | * Set Port 787 | * 788 | * @access public 789 | * @param $port 790 | */ 791 | public function setPort($port) 792 | { 793 | $this->setOpt(CURLOPT_PORT, intval($port)); 794 | } 795 | 796 | /** 797 | * Set Connect Timeout 798 | * 799 | * @access public 800 | * @param $seconds 801 | */ 802 | public function setConnectTimeout($seconds) 803 | { 804 | $this->setOpt(CURLOPT_CONNECTTIMEOUT, $seconds); 805 | } 806 | 807 | /** 808 | * Set Cookie String 809 | * 810 | * @access public 811 | * @param $string 812 | * 813 | * @return bool 814 | */ 815 | public function setCookieString($string) 816 | { 817 | return $this->setOpt(CURLOPT_COOKIE, $string); 818 | } 819 | 820 | /** 821 | * Set Cookie File 822 | * 823 | * @access public 824 | * @param $cookie_file 825 | * 826 | * @return boolean 827 | */ 828 | public function setCookieFile($cookie_file) 829 | { 830 | return $this->setOpt(CURLOPT_COOKIEFILE, $cookie_file); 831 | } 832 | 833 | /** 834 | * Set Cookie Jar 835 | * 836 | * @access public 837 | * @param $cookie_jar 838 | * 839 | * @return boolean 840 | */ 841 | public function setCookieJar($cookie_jar) 842 | { 843 | return $this->setOpt(CURLOPT_COOKIEJAR, $cookie_jar); 844 | } 845 | 846 | /** 847 | * Set Default JSON Decoder 848 | * 849 | * @access public 850 | * @param $assoc 851 | * @param $depth 852 | * @param $options 853 | */ 854 | public function setDefaultJsonDecoder() 855 | { 856 | $this->jsonDecoder = '\Curl\Decoder::decodeJson'; 857 | $this->jsonDecoderArgs = func_get_args(); 858 | } 859 | 860 | /** 861 | * Set Default XML Decoder 862 | * 863 | * @access public 864 | */ 865 | public function setDefaultXmlDecoder() 866 | { 867 | $this->xmlDecoder = '\Curl\Decoder::decodeXml'; 868 | } 869 | 870 | /** 871 | * Set Default Decoder 872 | * 873 | * @access public 874 | * @param $mixed boolean|callable|string 875 | */ 876 | public function setDefaultDecoder($mixed = 'json') 877 | { 878 | if ($mixed === false) { 879 | $this->defaultDecoder = false; 880 | } elseif (is_callable($mixed)) { 881 | $this->defaultDecoder = $mixed; 882 | } else { 883 | if ($mixed === 'json') { 884 | $this->defaultDecoder = $this->jsonDecoder; 885 | } elseif ($mixed === 'xml') { 886 | $this->defaultDecoder = $this->xmlDecoder; 887 | } 888 | } 889 | } 890 | 891 | /** 892 | * Set Default Timeout 893 | * 894 | * @access public 895 | */ 896 | public function setDefaultTimeout() 897 | { 898 | $this->setTimeout(self::DEFAULT_TIMEOUT); 899 | } 900 | 901 | /** 902 | * Set Default User Agent 903 | * 904 | * @access public 905 | */ 906 | public function setDefaultUserAgent() 907 | { 908 | $user_agent = 'PHP-Curl-Class/' . self::VERSION . ' (+https://github.com/php-curl-class/php-curl-class)'; 909 | $user_agent .= ' PHP/' . PHP_VERSION; 910 | $curl_version = curl_version(); 911 | $user_agent .= ' curl/' . $curl_version['version']; 912 | $this->setUserAgent($user_agent); 913 | } 914 | 915 | /** 916 | * Set Header 917 | * 918 | * Add extra header to include in the request. 919 | * 920 | * @access public 921 | * @param $key 922 | * @param $value 923 | */ 924 | public function setHeader($key, $value) 925 | { 926 | $this->headers[$key] = $value; 927 | $headers = array(); 928 | foreach ($this->headers as $key => $value) { 929 | $headers[] = $key . ': ' . $value; 930 | } 931 | $this->setOpt(CURLOPT_HTTPHEADER, $headers); 932 | } 933 | 934 | /** 935 | * Set Headers 936 | * 937 | * Add extra headers to include in the request. 938 | * 939 | * @access public 940 | * @param $headers 941 | */ 942 | public function setHeaders($headers) 943 | { 944 | foreach ($headers as $key => $value) { 945 | $this->headers[$key] = $value; 946 | } 947 | 948 | $headers = array(); 949 | foreach ($this->headers as $key => $value) { 950 | $headers[] = $key . ': ' . $value; 951 | } 952 | $this->setOpt(CURLOPT_HTTPHEADER, $headers); 953 | } 954 | 955 | /** 956 | * Set JSON Decoder 957 | * 958 | * @access public 959 | * @param $mixed boolean|callable 960 | */ 961 | public function setJsonDecoder($mixed) 962 | { 963 | if ($mixed === false) { 964 | $this->jsonDecoder = false; 965 | $this->jsonDecoderArgs = array(); 966 | } elseif (is_callable($mixed)) { 967 | $this->jsonDecoder = $mixed; 968 | $this->jsonDecoderArgs = array(); 969 | } 970 | } 971 | 972 | /** 973 | * Set XML Decoder 974 | * 975 | * @access public 976 | * @param $mixed boolean|callable 977 | */ 978 | public function setXmlDecoder($mixed) 979 | { 980 | if ($mixed === false) { 981 | $this->xmlDecoder = false; 982 | } elseif (is_callable($mixed)) { 983 | $this->xmlDecoder = $mixed; 984 | } 985 | } 986 | 987 | /** 988 | * Set Opt 989 | * 990 | * @access public 991 | * @param $option 992 | * @param $value 993 | * 994 | * @return boolean 995 | */ 996 | public function setOpt($option, $value) 997 | { 998 | $required_options = array( 999 | CURLOPT_RETURNTRANSFER => 'CURLOPT_RETURNTRANSFER', 1000 | ); 1001 | 1002 | if (in_array($option, array_keys($required_options), true) && !($value === true)) { 1003 | trigger_error($required_options[$option] . ' is a required option', E_USER_WARNING); 1004 | } 1005 | 1006 | $success = curl_setopt($this->curl, $option, $value); 1007 | if ($success) { 1008 | $this->options[$option] = $value; 1009 | } 1010 | return $success; 1011 | } 1012 | 1013 | /** 1014 | * Set Opts 1015 | * 1016 | * @access public 1017 | * @param $options 1018 | * 1019 | * @return boolean 1020 | * Returns true if all options were successfully set. If an option could not be successfully set, false is 1021 | * immediately returned, ignoring any future options in the options array. Similar to curl_setopt_array(). 1022 | */ 1023 | public function setOpts($options) 1024 | { 1025 | foreach ($options as $option => $value) { 1026 | if (!$this->setOpt($option, $value)) { 1027 | return false; 1028 | } 1029 | } 1030 | return true; 1031 | } 1032 | 1033 | /** 1034 | * Set Referer 1035 | * 1036 | * @access public 1037 | * @param $referer 1038 | */ 1039 | public function setReferer($referer) 1040 | { 1041 | $this->setReferrer($referer); 1042 | } 1043 | 1044 | /** 1045 | * Set Referrer 1046 | * 1047 | * @access public 1048 | * @param $referrer 1049 | */ 1050 | public function setReferrer($referrer) 1051 | { 1052 | $this->setOpt(CURLOPT_REFERER, $referrer); 1053 | } 1054 | 1055 | /** 1056 | * Set Retry 1057 | * 1058 | * Number of retries to attempt or decider callable. Maximum number of 1059 | * attempts is $maximum_number_of_retries + 1. 1060 | * 1061 | * @access public 1062 | * @param $mixed 1063 | */ 1064 | public function setRetry($mixed) 1065 | { 1066 | if (is_callable($mixed)) { 1067 | $this->retryDecider = $mixed; 1068 | } elseif (is_int($mixed)) { 1069 | $maximum_number_of_retries = $mixed; 1070 | $this->remainingRetries = $maximum_number_of_retries; 1071 | } 1072 | } 1073 | 1074 | /** 1075 | * Set Timeout 1076 | * 1077 | * @access public 1078 | * @param $seconds 1079 | */ 1080 | public function setTimeout($seconds) 1081 | { 1082 | $this->setOpt(CURLOPT_TIMEOUT, $seconds); 1083 | } 1084 | 1085 | /** 1086 | * Set Url 1087 | * 1088 | * @access public 1089 | * @param $url 1090 | * @param $mixed_data 1091 | */ 1092 | public function setUrl($url, $mixed_data = '') 1093 | { 1094 | $built_url = $this->buildUrl($url, $mixed_data); 1095 | 1096 | if ($this->url === null) { 1097 | $this->url = (string)new Url($built_url); 1098 | } else { 1099 | $this->url = (string)new Url($this->url, $built_url); 1100 | } 1101 | 1102 | $this->setOpt(CURLOPT_URL, $this->url); 1103 | } 1104 | 1105 | /** 1106 | * Set User Agent 1107 | * 1108 | * @access public 1109 | * @param $user_agent 1110 | */ 1111 | public function setUserAgent($user_agent) 1112 | { 1113 | $this->setOpt(CURLOPT_USERAGENT, $user_agent); 1114 | } 1115 | 1116 | /** 1117 | * Attempt Retry 1118 | * 1119 | * @access public 1120 | */ 1121 | public function attemptRetry() 1122 | { 1123 | $attempt_retry = false; 1124 | if ($this->error) { 1125 | if ($this->retryDecider === null) { 1126 | $attempt_retry = $this->remainingRetries >= 1; 1127 | } else { 1128 | $attempt_retry = call_user_func($this->retryDecider, $this); 1129 | } 1130 | if ($attempt_retry) { 1131 | $this->retries += 1; 1132 | if ($this->remainingRetries) { 1133 | $this->remainingRetries -= 1; 1134 | } 1135 | } 1136 | } 1137 | return $attempt_retry; 1138 | } 1139 | 1140 | /** 1141 | * Success 1142 | * 1143 | * @access public 1144 | * @param $callback 1145 | */ 1146 | public function success($callback) 1147 | { 1148 | $this->successFunction = $callback; 1149 | } 1150 | 1151 | /** 1152 | * Unset Header 1153 | * 1154 | * Remove extra header previously set using Curl::setHeader(). 1155 | * 1156 | * @access public 1157 | * @param $key 1158 | */ 1159 | public function unsetHeader($key) 1160 | { 1161 | unset($this->headers[$key]); 1162 | $headers = array(); 1163 | foreach ($this->headers as $key => $value) { 1164 | $headers[] = $key . ': ' . $value; 1165 | } 1166 | $this->setOpt(CURLOPT_HTTPHEADER, $headers); 1167 | } 1168 | 1169 | /** 1170 | * Remove Header 1171 | * 1172 | * Remove an internal header from the request. 1173 | * Using `curl -H "Host:" ...' is equivalent to $curl->removeHeader('Host');. 1174 | * 1175 | * @access public 1176 | * @param $key 1177 | */ 1178 | public function removeHeader($key) 1179 | { 1180 | $this->setHeader($key, ''); 1181 | } 1182 | 1183 | /** 1184 | * Verbose 1185 | * 1186 | * @access public 1187 | * @param bool $on 1188 | * @param resource $output 1189 | */ 1190 | public function verbose($on = true, $output = STDERR) 1191 | { 1192 | // Turn off CURLINFO_HEADER_OUT for verbose to work. This has the side 1193 | // effect of causing Curl::requestHeaders to be empty. 1194 | if ($on) { 1195 | $this->setOpt(CURLINFO_HEADER_OUT, false); 1196 | } 1197 | $this->setOpt(CURLOPT_VERBOSE, $on); 1198 | $this->setOpt(CURLOPT_STDERR, $output); 1199 | } 1200 | 1201 | /** 1202 | * Destruct 1203 | * 1204 | * @access public 1205 | */ 1206 | public function __destruct() 1207 | { 1208 | $this->close(); 1209 | } 1210 | 1211 | public function __get($name) 1212 | { 1213 | $return = null; 1214 | if (in_array($name, self::$deferredProperties) && is_callable(array($this, $getter = '__get_' . $name))) { 1215 | $return = $this->$name = $this->$getter(); 1216 | } 1217 | return $return; 1218 | } 1219 | 1220 | /** 1221 | * Get Effective Url 1222 | * 1223 | * @access private 1224 | */ 1225 | private function __get_effectiveUrl() 1226 | { 1227 | return $this->getInfo(CURLINFO_EFFECTIVE_URL); 1228 | } 1229 | 1230 | /** 1231 | * Get RFC 2616 1232 | * 1233 | * @access private 1234 | */ 1235 | private function __get_rfc2616() 1236 | { 1237 | return array_fill_keys(self::$RFC2616, true); 1238 | } 1239 | 1240 | /** 1241 | * Get RFC 6265 1242 | * 1243 | * @access private 1244 | */ 1245 | private function __get_rfc6265() 1246 | { 1247 | return array_fill_keys(self::$RFC6265, true); 1248 | } 1249 | 1250 | /** 1251 | * Get Total Time 1252 | * 1253 | * @access private 1254 | */ 1255 | private function __get_totalTime() 1256 | { 1257 | return $this->getInfo(CURLINFO_TOTAL_TIME); 1258 | } 1259 | 1260 | /** 1261 | * Build Cookies 1262 | * 1263 | * @access private 1264 | */ 1265 | private function buildCookies() 1266 | { 1267 | // Avoid using http_build_query() as unnecessary encoding is performed. 1268 | // http_build_query($this->cookies, '', '; '); 1269 | $this->setOpt(CURLOPT_COOKIE, implode('; ', array_map(function ($k, $v) { 1270 | return $k . '=' . $v; 1271 | }, array_keys($this->cookies), array_values($this->cookies)))); 1272 | } 1273 | 1274 | /** 1275 | * Build Url 1276 | * 1277 | * @access private 1278 | * @param $url 1279 | * @param $mixed_data 1280 | * 1281 | * @return string 1282 | */ 1283 | private function buildUrl($url, $mixed_data = '') 1284 | { 1285 | $query_string = ''; 1286 | if (!empty($mixed_data)) { 1287 | $query_mark = strpos($url, '?') > 0 ? '&' : '?'; 1288 | if (is_string($mixed_data)) { 1289 | $query_string .= $query_mark . $mixed_data; 1290 | } elseif (is_array($mixed_data)) { 1291 | $query_string .= $query_mark . http_build_query($mixed_data, '', '&'); 1292 | } 1293 | } 1294 | return $url . $query_string; 1295 | } 1296 | 1297 | /** 1298 | * Create Header Callback 1299 | * 1300 | * @access private 1301 | * @param $header_callback_data 1302 | * 1303 | * @return callable 1304 | */ 1305 | private function createHeaderCallback($header_callback_data) 1306 | { 1307 | return function ($ch, $header) use ($header_callback_data) { 1308 | if (preg_match('/^Set-Cookie:\s*([^=]+)=([^;]+)/mi', $header, $cookie) === 1) { 1309 | $header_callback_data->responseCookies[$cookie[1]] = trim($cookie[2], " \n\r\t\0\x0B"); 1310 | } 1311 | $header_callback_data->rawResponseHeaders .= $header; 1312 | return strlen($header); 1313 | }; 1314 | } 1315 | 1316 | /** 1317 | * Download Complete 1318 | * 1319 | * @access private 1320 | * @param $fh 1321 | */ 1322 | private function downloadComplete($fh) 1323 | { 1324 | if (!$this->error && $this->downloadCompleteFunction) { 1325 | rewind($fh); 1326 | $this->call($this->downloadCompleteFunction, $fh); 1327 | $this->downloadCompleteFunction = null; 1328 | } 1329 | 1330 | if (is_resource($fh)) { 1331 | fclose($fh); 1332 | } 1333 | 1334 | // Fix "PHP Notice: Use of undefined constant STDOUT" when reading the 1335 | // PHP script from stdin. Using null causes "Warning: curl_setopt(): 1336 | // supplied argument is not a valid File-Handle resource". 1337 | if (!defined('STDOUT')) { 1338 | define('STDOUT', fopen('php://stdout', 'w')); 1339 | } 1340 | 1341 | // Reset CURLOPT_FILE with STDOUT to avoid: "curl_exec(): CURLOPT_FILE 1342 | // resource has gone away, resetting to default". 1343 | $this->setOpt(CURLOPT_FILE, STDOUT); 1344 | 1345 | // Reset CURLOPT_RETURNTRANSFER to tell cURL to return subsequent 1346 | // responses as the return value of curl_exec(). Without this, 1347 | // curl_exec() will revert to returning boolean values. 1348 | $this->setOpt(CURLOPT_RETURNTRANSFER, true); 1349 | } 1350 | 1351 | /** 1352 | * Parse Headers 1353 | * 1354 | * @access private 1355 | * @param $raw_headers 1356 | * 1357 | * @return array 1358 | */ 1359 | private function parseHeaders($raw_headers) 1360 | { 1361 | $raw_headers = preg_split('/\r\n/', $raw_headers, null, PREG_SPLIT_NO_EMPTY); 1362 | $http_headers = new CaseInsensitiveArray(); 1363 | 1364 | $raw_headers_count = count($raw_headers); 1365 | for ($i = 1; $i < $raw_headers_count; $i++) { 1366 | list($key, $value) = explode(':', $raw_headers[$i], 2); 1367 | $key = trim($key); 1368 | $value = trim($value); 1369 | // Use isset() as array_key_exists() and ArrayAccess are not compatible. 1370 | if (isset($http_headers[$key])) { 1371 | $http_headers[$key] .= ',' . $value; 1372 | } else { 1373 | $http_headers[$key] = $value; 1374 | } 1375 | } 1376 | 1377 | return array(isset($raw_headers['0']) ? $raw_headers['0'] : '', $http_headers); 1378 | } 1379 | 1380 | /** 1381 | * Parse Request Headers 1382 | * 1383 | * @access private 1384 | * @param $raw_headers 1385 | * 1386 | * @return \Curl\CaseInsensitiveArray 1387 | */ 1388 | private function parseRequestHeaders($raw_headers) 1389 | { 1390 | $request_headers = new CaseInsensitiveArray(); 1391 | list($first_line, $headers) = $this->parseHeaders($raw_headers); 1392 | $request_headers['Request-Line'] = $first_line; 1393 | foreach ($headers as $key => $value) { 1394 | $request_headers[$key] = $value; 1395 | } 1396 | return $request_headers; 1397 | } 1398 | 1399 | /** 1400 | * Parse Response 1401 | * 1402 | * @access private 1403 | * @param $response_headers 1404 | * @param $raw_response 1405 | * 1406 | * @return mixed 1407 | * Provided the content-type is determined to be json or xml: 1408 | * Returns stdClass object when the default json decoder is used and the content-type is json. 1409 | * Returns SimpleXMLElement object when the default xml decoder is used and the content-type is xml. 1410 | */ 1411 | private function parseResponse($response_headers, $raw_response) 1412 | { 1413 | $response = $raw_response; 1414 | if (isset($response_headers['Content-Type'])) { 1415 | if (preg_match($this->jsonPattern, $response_headers['Content-Type'])) { 1416 | if ($this->jsonDecoder) { 1417 | $args = $this->jsonDecoderArgs; 1418 | array_unshift($args, $response); 1419 | $response = call_user_func_array($this->jsonDecoder, $args); 1420 | } 1421 | } elseif (preg_match($this->xmlPattern, $response_headers['Content-Type'])) { 1422 | if ($this->xmlDecoder) { 1423 | $response = call_user_func($this->xmlDecoder, $response); 1424 | } 1425 | } else { 1426 | if ($this->defaultDecoder) { 1427 | $response = call_user_func($this->defaultDecoder, $response); 1428 | } 1429 | } 1430 | } 1431 | 1432 | return $response; 1433 | } 1434 | 1435 | /** 1436 | * Parse Response Headers 1437 | * 1438 | * @access private 1439 | * @param $raw_response_headers 1440 | * 1441 | * @return \Curl\CaseInsensitiveArray 1442 | */ 1443 | private function parseResponseHeaders($raw_response_headers) 1444 | { 1445 | $response_header_array = explode("\r\n\r\n", $raw_response_headers); 1446 | $response_header = ''; 1447 | for ($i = count($response_header_array) - 1; $i >= 0; $i--) { 1448 | if (stripos($response_header_array[$i], 'HTTP/') === 0) { 1449 | $response_header = $response_header_array[$i]; 1450 | break; 1451 | } 1452 | } 1453 | 1454 | $response_headers = new CaseInsensitiveArray(); 1455 | list($first_line, $headers) = $this->parseHeaders($response_header); 1456 | $response_headers['Status-Line'] = $first_line; 1457 | foreach ($headers as $key => $value) { 1458 | $response_headers[$key] = $value; 1459 | } 1460 | return $response_headers; 1461 | } 1462 | 1463 | /** 1464 | * Set Encoded Cookie 1465 | * 1466 | * @access private 1467 | * @param $key 1468 | * @param $value 1469 | */ 1470 | private function setEncodedCookie($key, $value) 1471 | { 1472 | $name_chars = array(); 1473 | foreach (str_split($key) as $name_char) { 1474 | if (isset($this->rfc2616[$name_char])) { 1475 | $name_chars[] = $name_char; 1476 | } else { 1477 | $name_chars[] = rawurlencode($name_char); 1478 | } 1479 | } 1480 | 1481 | $value_chars = array(); 1482 | foreach (str_split($value) as $value_char) { 1483 | if (isset($this->rfc6265[$value_char])) { 1484 | $value_chars[] = $value_char; 1485 | } else { 1486 | $value_chars[] = rawurlencode($value_char); 1487 | } 1488 | } 1489 | 1490 | $this->cookies[implode('', $name_chars)] = implode('', $value_chars); 1491 | } 1492 | } 1493 | --------------------------------------------------------------------------------