├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── config.php ├── index.php ├── serverchan.php └── vendor ├── autoload.php ├── composer ├── ClassLoader.php ├── LICENSE ├── autoload_classmap.php ├── autoload_namespaces.php ├── autoload_psr4.php ├── autoload_real.php ├── autoload_static.php └── installed.json └── php-curl-class └── php-curl-class ├── LICENSE ├── README.md ├── SECURITY.md ├── composer.json └── src └── Curl ├── ArrayUtil.php ├── CaseInsensitiveArray.php ├── Curl.php ├── Decoder.php ├── MultiCurl.php ├── StrUtil.php └── Url.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bwg:搬瓦工补货自动提醒,写此项目的初衷是想抢GIA CN2乞丐版。 2 | ### 前言 3 | 前两天搬瓦工偷偷补货GIA CN2乞丐版(限量款),三网直连,简直是为我量身定做的, 4 | 但我没有第一时间得到消息,等我到购买页面的时候发现已经卖完了卖完了……所以就有了这个项目。 5 | 6 | ### 效果 7 | *每当搬瓦工补货,都能收到微信消息。消息内容包括VPS的基本信息和最新的优惠码。* 8 | 9 | ![推送酱](https://ws1.sinaimg.cn/mw690/a4d9cbc6ly1fvu3f6bqc5j20v91jltd4.jpg) 10 | 11 | ![推送酱](https://ws1.sinaimg.cn/mw690/a4d9cbc6gy1fvu34xnpepj20v91jldju.jpg) 12 | 13 | ![推送酱](https://ws1.sinaimg.cn/mw690/a4d9cbc6gy1fvu35oyddnj20v91jl4n5.jpg) 14 | 15 | ### 需求 16 | - serverChan的SendKey 17 | - 一台VPS 18 | - php版本 => 5.6(推荐使用php7以上版本,性能很不错) 19 | 20 | ### 食用方法 21 | 为了能第一时间在微信收到补货消息,我选用了serverChan的推送服务。 22 | #### 申请SendKey 23 | - 点击访问[serverChan](http://pushbear.ftqq.com/admin/#/) 24 | - 点击右上角的“注册&登入”,接着用微信扫码登录 25 | - 登录成功后,来到了通道管理界面,点击“新增通道”,然后填写通道信息并新增 26 | - 新增完成后回到通道管理界面,点击通道的“设置”按钮,就可以看到“本通道的订阅二维码”,扫码关注(通知时所有关注了此通道二维码的人都会收到通知消息) 27 | - 回到通道管理界面,点击通道的“发送消息”按钮,便能看到SendKey 28 | 29 | #### 修改config.php 30 | ok,现在有了SendKey。修改本项目的配置文件config.php。可以看到sendKey项对应着两个通道,`public_notice`用于向公众推送补货提醒, 31 | `report_errors`用于向开发者报告程序错误,这两个通道实际上就是两个sendKey,如果你搭建只为自己个人使用,建议将两个sendKey通道配置为同一个值。 32 | 配置多个sendKey通道,是为了将用户和部署者区分开来,这样方便向部署者推送程序错误和补货通知,向公众只推送补货通知。接着,将products对应的pid改为你希望关注的pid, 33 | 这个pid是搬瓦工的商品代号,我已经在配置里写了几个,按已有格式来写就好。至于每个商品对应的别名,随便取一个都成。 34 | 35 | #### Demo 36 | 37 | ![推送酱](https://ws1.sinaimg.cn/large/a4d9cbc6ly1fvu38k7enhj204e042gnu.jpg) 38 | 39 | (此Demo只监听搬瓦工 CN2 GIA 乞丐版和香港月付9.9刀版本,建议你们自己搭建,按需关注) 40 | 41 | #### VPS 42 | *在vps上安装git和lamp环境之类的我就不多赘述了,相信玩域名和vps的人都会,不会的可以去找一键脚本。以下操作使用的是Centos7,其它操作系统命令大同小异。* 43 | #### clone本仓库源码 44 | ```bash 45 | $ git clone https://github.com/luolongfei/bwg.git ./ 46 | ``` 47 | #### 安装crontabs以及cronie 48 | ```bash 49 | $ yum -y install cronie crontabs 50 | ``` 51 | #### 验证 52 | ##### 验证crond是否安装及启动 53 | ```bash 54 | $ yum list cronie && systemctl status crond 55 | ``` 56 | ##### 验证crontab是否安装 57 | ```bash 58 | $ yum list crontabs $$ which crontab && crontab -l 59 | ``` 60 | #### 添加计划任务 61 | ##### 打开任务表单,并编辑 62 | ```bash 63 | $ crontab -e 64 | 65 | # 任务内容如下 66 | # 此任务的含义是在每分钟执行一次/data/www/bwg.feifei.ooo/路径下的index.php文件 67 | # 注意将/data/www/bwg.feifei.ooo/替换为你自己index.php所在路径 68 | * * * * * cd /data/www/bwg.feifei.ooo/; php index.php >/dev/null 2>&1 69 | ``` 70 | ##### 重启crond守护进程 71 | ```bash 72 | $ systemctl restart crond 73 | ``` 74 | ##### 查看当前crond状态 75 | ```bash 76 | $ systemctl status crond 77 | ``` 78 | ##### 查看当前计划任务列表 79 | ```bash 80 | $ crontab -l 81 | ``` 82 | 到这里就配置好了,have fun. 83 | 有任何问题欢迎提[issues](https://github.com/luolongfei/bwg/issues),有帮到你的话给个star如何~ 84 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "php-curl-class/php-curl-class": "^8.1" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "1c3338b5532c6689f8d432dd44d06770", 8 | "packages": [ 9 | { 10 | "name": "php-curl-class/php-curl-class", 11 | "version": "8.1.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/php-curl-class/php-curl-class.git", 15 | "reference": "0090c30b4348028cabf1d03427423460ca7dc4a4" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/php-curl-class/php-curl-class/zipball/0090c30b4348028cabf1d03427423460ca7dc4a4", 20 | "reference": "0090c30b4348028cabf1d03427423460ca7dc4a4", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-curl": "*", 25 | "ext-mbstring": "*", 26 | "php": ">=5.3" 27 | }, 28 | "require-dev": { 29 | "ext-gd": "*", 30 | "phpunit/phpunit": "*", 31 | "squizlabs/php_codesniffer": "*" 32 | }, 33 | "type": "library", 34 | "autoload": { 35 | "psr-4": { 36 | "Curl\\": "src/Curl/" 37 | } 38 | }, 39 | "notification-url": "https://packagist.org/downloads/", 40 | "license": [ 41 | "Unlicense" 42 | ], 43 | "authors": [ 44 | { 45 | "name": "Zach Borboa" 46 | } 47 | ], 48 | "description": "PHP Curl Class makes it easy to send HTTP requests and integrate with web APIs.", 49 | "homepage": "https://github.com/php-curl-class/php-curl-class", 50 | "keywords": [ 51 | "api", 52 | "class", 53 | "client", 54 | "curl", 55 | "framework", 56 | "http", 57 | "http client", 58 | "json", 59 | "php", 60 | "requests", 61 | "rest", 62 | "restful", 63 | "web service", 64 | "xml" 65 | ], 66 | "time": "2018-06-06T05:01:44+00:00" 67 | } 68 | ], 69 | "packages-dev": [], 70 | "aliases": [], 71 | "minimum-stability": "stable", 72 | "stability-flags": [], 73 | "prefer-stable": false, 74 | "prefer-lowest": false, 75 | "platform": [], 76 | "platform-dev": [] 77 | } 78 | -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | 5 | * @date 2018/7/28 6 | * @time 17:40 7 | */ 8 | 9 | return [ 10 | 'sendKey' => [ // 微信推送通道 11 | 'public_notice' => '5939-xxx', // 用于向公众推送补货提醒 12 | 'report_errors' => '5666-xxx', // 用于向开发者报告程序错误 13 | ], 14 | 'products' => [ // pid => 商品别名 15 | 71 => 'CN2 GIA限量乞丐版', 16 | 61 => '香港月付9.9刀', 17 | // 43 => '年付19.99刀可转CN2', 18 | // 56 => 'CN2 年付29.99', 19 | ], 20 | 'aff' => 24499, 21 | 'maxAttemptsNum' => 4, // 出错自动重试次数,建议最大不超过5 22 | ]; -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 5 | * @date 2018/7/25 6 | * @time 13:40 7 | */ 8 | 9 | error_reporting(E_ERROR); 10 | ini_set('display_errors', 1); 11 | set_time_limit(0); 12 | 13 | define('IS_CLI', PHP_SAPI === 'cli' ? true : false); 14 | define('DS', DIRECTORY_SEPARATOR); 15 | define('VENDOR_PATH', realpath('vendor') . DS); 16 | 17 | date_default_timezone_set('Asia/Shanghai'); 18 | 19 | // Server酱微信推送url 20 | define('SC_URL', 'https://pushbear.ftqq.com/sub'); 21 | 22 | /** 23 | * 定制错误处理 24 | */ 25 | register_shutdown_function('customize_error_handler'); 26 | function customize_error_handler() 27 | { 28 | if (!is_null($error = error_get_last())) { 29 | system_log($error); 30 | 31 | $response = [ 32 | 'STATUS' => 9, 33 | 'MESSAGE_ARRAY' => array( 34 | array( 35 | 'MESSAGE' => '程序执行出错,请稍后再试。' 36 | ) 37 | ), 38 | 'SYSTEM_DATE' => date('Y-m-d H:i:s') 39 | ]; 40 | 41 | header('Content-Type: application/json'); 42 | 43 | echo json_encode($response); 44 | } 45 | } 46 | 47 | /** 48 | * 记录程序日志 49 | * @param array|string $logContent 日志内容 50 | * @param string 文件名(不含后缀) 51 | * @param string $mark LOG | ERROR | WARNING 日志标志 52 | */ 53 | function system_log($logContent, $logFile = '', $mark = 'ERROR') 54 | { 55 | try { 56 | $logPath = __DIR__ . '/logs/' . date('Y/m/') . ($logFile ? date('d/') : ''); 57 | $logFile = $logPath . ($logFile ?: date('d')) . '.php'; 58 | 59 | if (!is_dir($logPath)) { 60 | mkdir($logPath, 0777, true); 61 | chmod($logPath, 0777); 62 | } 63 | 64 | $handle = fopen($logFile, 'a'); // 文件不存在则自动创建 65 | 66 | if (!filesize($logFile)) { 67 | fwrite($handle, "" . PHP_EOL . PHP_EOL); 68 | chmod($logFile, 0666); 69 | } 70 | 71 | fwrite($handle, $mark . ' - ' . date('Y-m-d H:i:s') . ' --> ' . (IS_CLI ? 'CLI' : 'URI: ' . $_SERVER['REQUEST_URI'] . PHP_EOL . 'REMOTE_ADDR: ' . $_SERVER['REMOTE_ADDR'] . PHP_EOL . 'SERVER_ADDR: ' . $_SERVER['SERVER_ADDR']) . PHP_EOL . (is_string($logContent) ? $logContent : var_export($logContent, true)) . PHP_EOL); // CLI模式下,$_SERVER中几乎无可用值 72 | 73 | fclose($handle); 74 | } catch (\Exception $e) { 75 | // do nothing 76 | } 77 | } 78 | 79 | require VENDOR_PATH . 'autoload.php'; 80 | require __DIR__ . DS . 'serverchan.php'; 81 | 82 | use Curl\Curl; 83 | 84 | class BWG 85 | { 86 | /** 87 | * @var BWG 88 | */ 89 | protected static $instance; 90 | 91 | /** 92 | * @var int curl超时秒数 93 | */ 94 | protected static $timeOut = 10; 95 | 96 | /** 97 | * 匹配VPS基础信息 98 | * @var string 99 | */ 100 | protected static $baseInfoRegex = '/SSD:\s*(?P[^<]+)[^:]+:\s*(?P[^<]+)[^:]+:\s*(?P[^<]+)[^:]+:\s*(?P[^<]+)[^:]+:\s*(?P[^<]+)[^:]+(?:(?<=Location):\s*[^:]+|):\s*(?P[^<]+)/i'; 101 | 102 | /** 103 | * 匹配优惠码 104 | * @var string 105 | */ 106 | protected static $promoCodeRegex = '/promo\s*code:\s*([^\n]+)/i'; 107 | 108 | /** 109 | * 匹配价格 110 | * @var string 111 | */ 112 | protected static $priceRegex = '/]+>.*?(?=\$)([^<]+)<\/option>/i'; 113 | 114 | /** 115 | * 匹配机房位置 116 | * @var string 117 | */ 118 | protected static $locationRegex = '/]+>([^<\$]+)<\/option>/i'; 119 | 120 | /** 121 | * 日志路径 122 | * @var string 123 | */ 124 | protected static $logPath; 125 | 126 | public function __construct() 127 | { 128 | static::$logPath = __DIR__ . '/logs/' . date('Y/m/d/'); 129 | } 130 | 131 | public static function instance() 132 | { 133 | if (static::$instance === null) { 134 | static::$instance = new static(); 135 | } 136 | 137 | return static::$instance; 138 | } 139 | 140 | /** 141 | * 补货提醒 142 | * @param array $sendKey 微信推送通道 143 | * @param integer $pid 商品id 144 | * @param string $pName 商品别名 145 | * @param integer $aff aff 146 | * @return null 147 | * @throws ErrorException 148 | */ 149 | public function notice($sendKey, $pid, $pName, $maxAttemptsNum, $aff = '') 150 | { 151 | if (file_exists(static::$logPath . 'today_notified_pid_' . $pid . '.php')) { // 防止同一天内重复提醒 152 | return false; 153 | } 154 | 155 | $curl = new Curl(); 156 | $curl->setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'); 157 | $curl->setReferrer('https://bwh1.net/cart.php'); 158 | $curl->setHeaders([ 159 | 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 160 | ]); 161 | $curl->setTimeout(static::$timeOut); 162 | $curl->setOpts([ 163 | CURLOPT_FOLLOWLOCATION => true, 164 | CURLOPT_MAXREDIRS => 3, 165 | CURLOPT_COOKIEFILE => '', 166 | CURLOPT_AUTOREFERER => true 167 | ]); 168 | 169 | $attempts = 0; // 已尝试次数 170 | while (true) { 171 | $curl->get('https://bwh1.net/cart.php', [ 172 | 'a' => 'add', 173 | 'pid' => $pid, 174 | ]); 175 | 176 | if (!$curl->error || $curl->httpStatusCode === 200 || $attempts >= $maxAttemptsNum) { 177 | break; 178 | } 179 | 180 | $attempts += 1; 181 | 182 | usleep(1000); 183 | } 184 | if ($attempts) { 185 | system_log(sprintf('此刻由于搬瓦工服务器没有正常响应,我一共尝试了%d次。', $attempts)); 186 | } 187 | 188 | if ($curl->error) { 189 | if (file_exists(static::$logPath . 'curl_error_code_' . $curl->errorCode . '.php')) { // 同一类型Curl错误一天仅推送一次 190 | return false; 191 | } 192 | 193 | ServerChan::send($sendKey['report_errors'], '小主,Curl 请求页面出错', "详情:\n\n" . $curl->errorCode . ' - ' . $curl->errorMessage); 194 | system_log( 195 | sprintf( 196 | "在%s这个时刻,Curl请求出错:%s\n写这条内容是为了防止在同一天内重复提醒。今次取得的页面信息为:\n%s", 197 | date('Y-m-d H:i:s'), 198 | $curl->errorMessage, 199 | $curl->response 200 | ), 201 | 'curl_error_code_' . $curl->errorCode, 202 | 'ERROR' 203 | ); 204 | 205 | return false; 206 | } 207 | 208 | $errors = ''; 209 | if ($curl->response && stripos($curl->response, 'Out of Stock') === false && stripos($curl->response, 'Critical Error') === false) { // 有货了 210 | if (!preg_match(self::$baseInfoRegex, $curl->response, $vps_base_info)) { 211 | $errors .= "匹配基础信息出错\n"; 212 | } 213 | if (!preg_match_all(self::$priceRegex, $curl->response, $price)) { 214 | $errors .= "匹配价格出错\n"; 215 | } 216 | if (!preg_match(self::$promoCodeRegex, $curl->response, $promo_code)) { 217 | $errors .= "匹配优惠码出错\n"; 218 | } 219 | if (!preg_match_all(self::$locationRegex, $curl->response, $location)) { 220 | $errors .= "匹配机房位置信息出错\n"; 221 | } 222 | if ($errors) { 223 | ServerChan::send( 224 | $sendKey['report_errors'], 225 | sprintf('小主,「%s」疑似有货了,但正则匹配出了一点小状况', $pName), 226 | sprintf("我取得的不寻常的情况如下:\n%s\n这可能是因为搬瓦工页面改版导致无法正确匹配,可能需要修改正则表达式,应该**不是真的补货**。请小主[亲自前往确定是否补货](https://bwh1.net/" . ($aff ? 'aff.php?aff=' . $aff : 'cart.php?a=add') . "&pid=" . $pid . ")。", $errors) 227 | ); 228 | } 229 | 230 | $notice_content = ''; 231 | if ($vps_base_info) { 232 | $notice_content = $pName . '的详情如下所述'; 233 | $notice_content .= sprintf( 234 | "\n\n#### 硬盘:%s\n#### 运存:%s\n#### CPU:%s\n#### 流量:%s\n#### 带宽:%s\n#### 虚拟技术:%s\n\n", 235 | $vps_base_info['ssd'], 236 | $vps_base_info['ram'], 237 | $vps_base_info['cpu'], 238 | $vps_base_info['transfer'], 239 | $vps_base_info['link_speed'], 240 | $vps_base_info['vr_type'] 241 | ); 242 | } 243 | if ($price[1]) { 244 | $notice_content .= sprintf("#### 可选价格为:\n%s\n\n", implode("\n", $price[1])); 245 | } 246 | if ($promo_code) { 247 | $notice_content .= sprintf("#### 优惠码:\n%s\n\n", $promo_code[1]); 248 | } 249 | if ($location[1]) { 250 | $notice_content .= sprintf("#### 可选机房为:\n%s\n\n", implode("\n", $location[1])); 251 | } 252 | $notice_content .= "\n[立即前往查看](https://bwh1.net/" . ($aff ? 'aff.php?aff=' . $aff : 'cart.php?a=add') . "&pid=" . $pid . ")\n\n![通讯酱](http://wx4.sinaimg.cn/mw690/0060lm7Tly1fvtvodr7ijj30ia0lkagm.jpg)\n笨笨的机器人敬上"; 253 | 254 | $errors || ServerChan::send($sendKey['public_notice'], sprintf('小主,「%s」补货了,赶快去抢购吧~', $pName), $notice_content); 255 | system_log(sprintf('在%s这个时刻,「%s」' . ($errors ? '疑似' : '') . "补货了,我通知了所有人,写这条内容是为了防止在同一天内重复提醒。今次取得的页面信息为:\n%s", date('Y-m-d H:i:s'), $pName, $curl->response), 'today_notified_pid_' . $pid, 'NOTICE'); 256 | } 257 | 258 | $curl->close(); 259 | 260 | return $curl->response; 261 | } 262 | } 263 | 264 | try { 265 | $config = require __DIR__ . DS . 'config.php'; 266 | foreach ($config['products'] as $pid => $pName) { // 多个VPS 267 | BWG::instance()->notice( 268 | $config['sendKey'], 269 | $pid, 270 | $pName, 271 | $config['maxAttemptsNum'], 272 | $config['aff'] 273 | ); 274 | 275 | usleep(600); 276 | } 277 | 278 | echo '执行成功。'; 279 | } catch (\Exception $e) { 280 | system_log($e->getMessage()); 281 | } -------------------------------------------------------------------------------- /serverchan.php: -------------------------------------------------------------------------------- 1 | 5 | * @date 2018/7/25 6 | * @time 13:40 7 | */ 8 | 9 | use Curl\Curl; 10 | 11 | class ServerChan 12 | { 13 | /** 14 | * 微信推送 15 | * @param string $sendKey 不同的$sendKey对应不同的人,不同的人对应不同的通道 16 | * @param string $title 17 | * @param string $content 18 | * @return object 19 | * @throws \ErrorException 20 | * @throws \Exception 21 | */ 22 | public static function send($sendKey, $title, $content) 23 | { 24 | $pushInfo = [ 25 | 'sendkey' => $sendKey, 26 | 'text' => $title, 27 | 'desp' => str_replace("\n", "\n\n", $content) // Server酱接口限定,两个\n等于一个换行 28 | ]; 29 | 30 | $curl = new Curl(); 31 | $curl->get(SC_URL, $pushInfo); 32 | 33 | if ($curl->error) { 34 | throw new \Exception('Curl 微信推送错误 #' . $curl->errorCode . ' - ' . $curl->errorMessage . "\n"); 35 | } 36 | 37 | $curl->close(); 38 | 39 | if ($curl->response->code !== 0) { 40 | system_log(array_merge(['error' => 'Server酱微信推送接口没有正确响应,本次推送可能不成功', 'errorReason' => $curl->response->message], $pushInfo)); 41 | } 42 | 43 | return $curl->response; 44 | } 45 | } -------------------------------------------------------------------------------- /vendor/autoload.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 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 383 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 384 | if (file_exists($file = $dir . $pathEnd)) { 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/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/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | array($vendorDir . '/php-curl-class/php-curl-class/src/Curl'), 10 | ); 11 | -------------------------------------------------------------------------------- /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\ComposerStaticInit440baba244423f1f0761f44507d2f641::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/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 = ComposerStaticInit440baba244423f1f0761f44507d2f641::$prefixLengthsPsr4; 27 | $loader->prefixDirsPsr4 = ComposerStaticInit440baba244423f1f0761f44507d2f641::$prefixDirsPsr4; 28 | 29 | }, null, ClassLoader::class); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "php-curl-class/php-curl-class", 4 | "version": "8.1.0", 5 | "version_normalized": "8.1.0.0", 6 | "source": { 7 | "type": "git", 8 | "url": "https://github.com/php-curl-class/php-curl-class.git", 9 | "reference": "0090c30b4348028cabf1d03427423460ca7dc4a4" 10 | }, 11 | "dist": { 12 | "type": "zip", 13 | "url": "https://api.github.com/repos/php-curl-class/php-curl-class/zipball/0090c30b4348028cabf1d03427423460ca7dc4a4", 14 | "reference": "0090c30b4348028cabf1d03427423460ca7dc4a4", 15 | "shasum": "" 16 | }, 17 | "require": { 18 | "ext-curl": "*", 19 | "ext-mbstring": "*", 20 | "php": ">=5.3" 21 | }, 22 | "require-dev": { 23 | "ext-gd": "*", 24 | "phpunit/phpunit": "*", 25 | "squizlabs/php_codesniffer": "*" 26 | }, 27 | "time": "2018-06-06T05:01:44+00:00", 28 | "type": "library", 29 | "installation-source": "dist", 30 | "autoload": { 31 | "psr-4": { 32 | "Curl\\": "src/Curl/" 33 | } 34 | }, 35 | "notification-url": "https://packagist.org/downloads/", 36 | "license": [ 37 | "Unlicense" 38 | ], 39 | "authors": [ 40 | { 41 | "name": "Zach Borboa" 42 | } 43 | ], 44 | "description": "PHP Curl Class makes it easy to send HTTP requests and integrate with web APIs.", 45 | "homepage": "https://github.com/php-curl-class/php-curl-class", 46 | "keywords": [ 47 | "api", 48 | "class", 49 | "client", 50 | "curl", 51 | "framework", 52 | "http", 53 | "http client", 54 | "json", 55 | "php", 56 | "requests", 57 | "rest", 58 | "restful", 59 | "web service", 60 | "xml" 61 | ] 62 | } 63 | ] 64 | -------------------------------------------------------------------------------- /vendor/php-curl-class/php-curl-class/LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /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, 7.2, 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 = '', $follow_303_with_post = false) 219 | Curl::progress($callback) 220 | Curl::put($url, $data = array()) 221 | Curl::removeHeader($key) 222 | Curl::reset() 223 | Curl::search($url, $data = array()) 224 | Curl::setBasicAuthentication($username, $password = '') 225 | Curl::setConnectTimeout($seconds) 226 | Curl::setCookie($key, $value) 227 | Curl::setCookieFile($cookie_file) 228 | Curl::setCookieJar($cookie_jar) 229 | Curl::setCookieString($string) 230 | Curl::setCookies($cookies) 231 | Curl::setDefaultDecoder($mixed = 'json') 232 | Curl::setDefaultJsonDecoder() 233 | Curl::setDefaultTimeout() 234 | Curl::setDefaultUserAgent() 235 | Curl::setDefaultXmlDecoder() 236 | Curl::setDigestAuthentication($username, $password = '') 237 | Curl::setHeader($key, $value) 238 | Curl::setHeaders($headers) 239 | Curl::setJsonDecoder($mixed) 240 | Curl::setMaxFilesize($bytes) 241 | Curl::setOpt($option, $value) 242 | Curl::setOpts($options) 243 | Curl::setPort($port) 244 | Curl::setReferer($referer) 245 | Curl::setReferrer($referrer) 246 | Curl::setRetry($mixed) 247 | Curl::setTimeout($seconds) 248 | Curl::setUrl($url, $mixed_data = '') 249 | Curl::setUserAgent($user_agent) 250 | Curl::setXmlDecoder($mixed) 251 | Curl::success($callback) 252 | Curl::unsetHeader($key) 253 | Curl::verbose($on = true, $output = STDERR) 254 | MultiCurl::__construct($base_url = null) 255 | MultiCurl::__destruct() 256 | MultiCurl::addCurl(Curl $curl) 257 | MultiCurl::addDelete($url, $query_parameters = array(), $data = array()) 258 | MultiCurl::addDownload($url, $mixed_filename) 259 | MultiCurl::addGet($url, $data = array()) 260 | MultiCurl::addHead($url, $data = array()) 261 | MultiCurl::addOptions($url, $data = array()) 262 | MultiCurl::addPatch($url, $data = array()) 263 | MultiCurl::addPost($url, $data = '', $follow_303_with_post = false) 264 | MultiCurl::addPut($url, $data = array()) 265 | MultiCurl::addSearch($url, $data = array()) 266 | MultiCurl::beforeSend($callback) 267 | MultiCurl::close() 268 | MultiCurl::complete($callback) 269 | MultiCurl::error($callback) 270 | MultiCurl::getOpt($option) 271 | MultiCurl::removeHeader($key) 272 | MultiCurl::setBasicAuthentication($username, $password = '') 273 | MultiCurl::setConcurrency($concurrency) 274 | MultiCurl::setConnectTimeout($seconds) 275 | MultiCurl::setCookie($key, $value) 276 | MultiCurl::setCookieFile($cookie_file) 277 | MultiCurl::setCookieJar($cookie_jar) 278 | MultiCurl::setCookieString($string) 279 | MultiCurl::setCookies($cookies) 280 | MultiCurl::setDigestAuthentication($username, $password = '') 281 | MultiCurl::setHeader($key, $value) 282 | MultiCurl::setHeaders($headers) 283 | MultiCurl::setJsonDecoder($mixed) 284 | MultiCurl::setOpt($option, $value) 285 | MultiCurl::setOpts($options) 286 | MultiCurl::setPort($port) 287 | MultiCurl::setReferer($referer) 288 | MultiCurl::setReferrer($referrer) 289 | MultiCurl::setRetry($mixed) 290 | MultiCurl::setTimeout($seconds) 291 | MultiCurl::setUrl($url) 292 | MultiCurl::setUserAgent($user_agent) 293 | MultiCurl::setXmlDecoder($mixed) 294 | MultiCurl::start() 295 | MultiCurl::success($callback) 296 | MultiCurl::unsetHeader($key) 297 | MultiCurl::verbose($on = true, $output = STDERR) 298 | ``` 299 | 300 | ### Security 301 | 302 | See [SECURITY](https://github.com/php-curl-class/php-curl-class/blob/master/SECURITY.md) for security considerations. 303 | 304 | ### Troubleshooting 305 | 306 | See [TROUBLESHOOTING](https://github.com/php-curl-class/php-curl-class/blob/master/TROUBLESHOOTING.md) for troubleshooting. 307 | 308 | ### Run Tests 309 | 310 | To run tests: 311 | 312 | $ git clone https://github.com/php-curl-class/php-curl-class.git 313 | $ cd php-curl-class/ 314 | $ composer update 315 | $ ./tests/run.sh 316 | 317 | ### Contribute 318 | 1. Check for open issues or open a new issue to start a discussion around a bug or feature. 319 | 1. Fork the repository on GitHub to start making your changes. 320 | 1. Write one or more tests for the new feature or that expose the bug. 321 | 1. Make code changes to implement the feature or fix the bug. 322 | 1. Send a pull request to get your changes merged and published. 323 | -------------------------------------------------------------------------------- /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_allowed_url($url, $allowed_url_schemes = array('http', 'https')) { 25 | $valid_url = filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED) !== false; 26 | if ($valid_url) { 27 | $scheme = parse_url($url, PHP_URL_SCHEME); 28 | return in_array($scheme, $allowed_url_schemes, true); 29 | } 30 | $valid_ip = filter_var($url, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false; 31 | return $valid_ip; 32 | } 33 | 34 | $url = $_GET['url']; 35 | if (!is_allowed_url($url)) { 36 | die('Unsafe url detected.'); 37 | } 38 | ``` 39 | 40 | ### Url may point to internal urls 41 | 42 | * 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 43 | a whitelist to allow certain urls rather than a blacklist. 44 | 45 | ### Request data may refer to system files 46 | 47 | * Request data prefixed with the `@` character may have special interpretation and read from system files. 48 | 49 | ```bash 50 | # Attacker. 51 | $ curl https://www.example.com/upload_photo.php --data "photo=@/etc/passwd" 52 | ``` 53 | 54 | ```php 55 | // upload_photo.php 56 | $curl = new Curl(); 57 | $curl->post('http://www.anotherwebsite.com/', array( 58 | 'photo' => $_POST['photo'], // DANGER! 59 | )); 60 | ``` 61 | 62 | ### Unsafe response with redirection enabled 63 | 64 | * Requests with redirection enabled may return responses from unexpected sources. 65 | Downloading https://www.example.com/image.png may redirect and download https://www.evil.com/virus.exe 66 | 67 | ```php 68 | $curl = new Curl(); 69 | $curl->setOpt(CURLOPT_FOLLOWLOCATION, true); // DANGER! 70 | $curl->download('https://www.example.com/image.png', 'my_image.png'); 71 | ``` 72 | 73 | ```php 74 | $curl = new Curl(); 75 | $curl->setOpt(CURLOPT_FOLLOWLOCATION, true); // DANGER! 76 | $curl->get('https://www.example.com/image.png'); 77 | ``` 78 | 79 | ### Keep SSL protections enabled 80 | 81 | * Do not disable SSL protections. 82 | 83 | ```php 84 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // DANGER! 85 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // DANGER! 86 | ``` 87 | -------------------------------------------------------------------------------- /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 | "ext-mbstring": "*" 19 | }, 20 | "require-dev": { 21 | "ext-gd": "*", 22 | "phpunit/phpunit": "*", 23 | "squizlabs/php_codesniffer": "*" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Curl\\": "src/Curl/" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /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/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/Curl.php: -------------------------------------------------------------------------------- 1 | 64 | // CTL = 66 | // separators = "(" | ")" | "<" | ">" | "@" 67 | // | "," | ";" | ":" | "\" | <"> 68 | // | "/" | "[" | "]" | "?" | "=" 69 | // | "{" | "}" | SP | HT 70 | // SP = 71 | // HT = 72 | // <"> = 73 | '!', '#', '$', '%', '&', "'", '*', '+', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 74 | 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 75 | 'Y', 'Z', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 76 | 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '|', '~', 77 | ); 78 | public static $RFC6265 = array( 79 | // RFC 6265: "US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash". 80 | // %x21 81 | '!', 82 | // %x23-2B 83 | '#', '$', '%', '&', "'", '(', ')', '*', '+', 84 | // %x2D-3A 85 | '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', 86 | // %x3C-5B 87 | '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 88 | 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', 89 | // %x5D-7E 90 | ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 91 | 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', 92 | ); 93 | 94 | private static $deferredProperties = array( 95 | 'effectiveUrl', 96 | 'rfc2616', 97 | 'rfc6265', 98 | 'totalTime', 99 | ); 100 | 101 | /** 102 | * Construct 103 | * 104 | * @access public 105 | * @param $base_url 106 | * @throws \ErrorException 107 | */ 108 | public function __construct($base_url = null) 109 | { 110 | if (!extension_loaded('curl')) { 111 | throw new \ErrorException('cURL library is not loaded'); 112 | } 113 | 114 | $this->curl = curl_init(); 115 | $this->initialize($base_url); 116 | } 117 | 118 | /** 119 | * Before Send 120 | * 121 | * @access public 122 | * @param $callback 123 | */ 124 | public function beforeSend($callback) 125 | { 126 | $this->beforeSendCallback = $callback; 127 | } 128 | 129 | /** 130 | * Build Post Data 131 | * 132 | * @access public 133 | * @param $data 134 | * 135 | * @return array|string 136 | */ 137 | public function buildPostData($data) 138 | { 139 | $binary_data = false; 140 | if (is_array($data)) { 141 | // Return JSON-encoded string when the request's content-type is JSON. 142 | if (isset($this->headers['Content-Type']) && 143 | preg_match($this->jsonPattern, $this->headers['Content-Type'])) { 144 | $json_str = json_encode($data); 145 | if (!($json_str === false)) { 146 | $data = $json_str; 147 | } 148 | } else { 149 | // Manually build a single-dimensional array from a multi-dimensional array as using curl_setopt($ch, 150 | // CURLOPT_POSTFIELDS, $data) doesn't correctly handle multi-dimensional arrays when files are 151 | // referenced. 152 | if (ArrayUtil::is_array_multidim($data)) { 153 | $data = ArrayUtil::array_flatten_multidim($data); 154 | } 155 | 156 | // Modify array values to ensure any referenced files are properly handled depending on the support of 157 | // the @filename API or CURLFile usage. This also fixes the warning "curl_setopt(): The usage of the 158 | // @filename API for file uploading is deprecated. Please use the CURLFile class instead". Ignore 159 | // non-file values prefixed with the @ character. 160 | foreach ($data as $key => $value) { 161 | if (is_string($value) && strpos($value, '@') === 0 && is_file(substr($value, 1))) { 162 | $binary_data = true; 163 | if (class_exists('CURLFile')) { 164 | $data[$key] = new \CURLFile(substr($value, 1)); 165 | } 166 | } elseif ($value instanceof \CURLFile) { 167 | $binary_data = true; 168 | } 169 | } 170 | } 171 | } 172 | 173 | if (!$binary_data && (is_array($data) || is_object($data))) { 174 | $data = http_build_query($data, '', '&'); 175 | } 176 | 177 | return $data; 178 | } 179 | 180 | /** 181 | * Call 182 | * 183 | * @access public 184 | */ 185 | public function call() 186 | { 187 | $args = func_get_args(); 188 | $function = array_shift($args); 189 | if (is_callable($function)) { 190 | array_unshift($args, $this); 191 | call_user_func_array($function, $args); 192 | } 193 | } 194 | 195 | /** 196 | * Close 197 | * 198 | * @access public 199 | */ 200 | public function close() 201 | { 202 | if (is_resource($this->curl)) { 203 | curl_close($this->curl); 204 | } 205 | $this->options = null; 206 | $this->jsonDecoder = null; 207 | $this->jsonDecoderArgs = null; 208 | $this->xmlDecoder = null; 209 | $this->defaultDecoder = null; 210 | } 211 | 212 | /** 213 | * Complete 214 | * 215 | * @access public 216 | * @param $callback 217 | */ 218 | public function complete($callback) 219 | { 220 | $this->completeCallback = $callback; 221 | } 222 | 223 | /** 224 | * Progress 225 | * 226 | * @access public 227 | * @param $callback 228 | */ 229 | public function progress($callback) 230 | { 231 | $this->setOpt(CURLOPT_PROGRESSFUNCTION, $callback); 232 | $this->setOpt(CURLOPT_NOPROGRESS, false); 233 | } 234 | 235 | /** 236 | * Delete 237 | * 238 | * @access public 239 | * @param $url 240 | * @param $query_parameters 241 | * @param $data 242 | * 243 | * @return mixed Returns the value provided by exec. 244 | */ 245 | public function delete($url, $query_parameters = array(), $data = array()) 246 | { 247 | if (is_array($url)) { 248 | $data = $query_parameters; 249 | $query_parameters = $url; 250 | $url = (string)$this->url; 251 | } 252 | 253 | $this->setUrl($url, $query_parameters); 254 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'DELETE'); 255 | $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data)); 256 | return $this->exec(); 257 | } 258 | 259 | /** 260 | * Download 261 | * 262 | * @access public 263 | * @param $url 264 | * @param $mixed_filename 265 | * 266 | * @return boolean 267 | */ 268 | public function download($url, $mixed_filename) 269 | { 270 | if (is_callable($mixed_filename)) { 271 | $this->downloadCompleteCallback = $mixed_filename; 272 | $this->fileHandle = tmpfile(); 273 | } else { 274 | $filename = $mixed_filename; 275 | 276 | // Use a temporary file when downloading. Not using a temporary file can cause an error when an existing 277 | // file has already fully completed downloading and a new download is started with the same destination save 278 | // path. The download request will include header "Range: bytes=$filesize-" which is syntactically valid, 279 | // but unsatisfiable. 280 | $download_filename = $filename . '.pccdownload'; 281 | 282 | $mode = 'wb'; 283 | // Attempt to resume download only when a temporary download file exists and is not empty. 284 | if (file_exists($download_filename) && $filesize = filesize($download_filename)) { 285 | $mode = 'ab'; 286 | $first_byte_position = $filesize; 287 | $range = $first_byte_position . '-'; 288 | $this->setOpt(CURLOPT_RANGE, $range); 289 | } 290 | $this->fileHandle = fopen($download_filename, $mode); 291 | 292 | // Move the downloaded temporary file to the destination save path. 293 | $this->downloadCompleteCallback = function ($instance, $fh) use ($download_filename, $filename) { 294 | // Close the open file handle before renaming the file. 295 | if (is_resource($fh)) { 296 | fclose($fh); 297 | } 298 | 299 | rename($download_filename, $filename); 300 | }; 301 | } 302 | 303 | $this->setOpt(CURLOPT_FILE, $this->fileHandle); 304 | $this->get($url); 305 | 306 | return ! $this->error; 307 | } 308 | 309 | /** 310 | * Error 311 | * 312 | * @access public 313 | * @param $callback 314 | */ 315 | public function error($callback) 316 | { 317 | $this->errorCallback = $callback; 318 | } 319 | 320 | /** 321 | * Exec 322 | * 323 | * @access public 324 | * @param $ch 325 | * 326 | * @return mixed Returns the value provided by parseResponse. 327 | */ 328 | public function exec($ch = null) 329 | { 330 | $this->attempts += 1; 331 | 332 | if ($this->jsonDecoder === null) { 333 | $this->setDefaultJsonDecoder(); 334 | } 335 | if ($this->xmlDecoder === null) { 336 | $this->setDefaultXmlDecoder(); 337 | } 338 | 339 | if ($ch === null) { 340 | $this->responseCookies = array(); 341 | $this->call($this->beforeSendCallback); 342 | $this->rawResponse = curl_exec($this->curl); 343 | $this->curlErrorCode = curl_errno($this->curl); 344 | $this->curlErrorMessage = curl_error($this->curl); 345 | } else { 346 | $this->rawResponse = curl_multi_getcontent($ch); 347 | $this->curlErrorMessage = curl_error($ch); 348 | } 349 | $this->curlError = !($this->curlErrorCode === 0); 350 | 351 | // Transfer the header callback data and release the temporary store to avoid memory leak. 352 | $this->rawResponseHeaders = $this->headerCallbackData->rawResponseHeaders; 353 | $this->responseCookies = $this->headerCallbackData->responseCookies; 354 | $this->headerCallbackData->rawResponseHeaders = null; 355 | $this->headerCallbackData->responseCookies = null; 356 | 357 | // Include additional error code information in error message when possible. 358 | if ($this->curlError && function_exists('curl_strerror')) { 359 | $this->curlErrorMessage = 360 | curl_strerror($this->curlErrorCode) . ( 361 | empty($this->curlErrorMessage) ? '' : ': ' . $this->curlErrorMessage 362 | ); 363 | } 364 | 365 | $this->httpStatusCode = $this->getInfo(CURLINFO_HTTP_CODE); 366 | $this->httpError = in_array(floor($this->httpStatusCode / 100), array(4, 5)); 367 | $this->error = $this->curlError || $this->httpError; 368 | $this->errorCode = $this->error ? ($this->curlError ? $this->curlErrorCode : $this->httpStatusCode) : 0; 369 | 370 | // NOTE: CURLINFO_HEADER_OUT set to true is required for requestHeaders 371 | // to not be empty (e.g. $curl->setOpt(CURLINFO_HEADER_OUT, true);). 372 | if ($this->getOpt(CURLINFO_HEADER_OUT) === true) { 373 | $this->requestHeaders = $this->parseRequestHeaders($this->getInfo(CURLINFO_HEADER_OUT)); 374 | } 375 | $this->responseHeaders = $this->parseResponseHeaders($this->rawResponseHeaders); 376 | $this->response = $this->parseResponse($this->responseHeaders, $this->rawResponse); 377 | 378 | $this->httpErrorMessage = ''; 379 | if ($this->error) { 380 | if (isset($this->responseHeaders['Status-Line'])) { 381 | $this->httpErrorMessage = $this->responseHeaders['Status-Line']; 382 | } 383 | } 384 | $this->errorMessage = $this->curlError ? $this->curlErrorMessage : $this->httpErrorMessage; 385 | 386 | // Reset select deferred properties so that they may be recalculated. 387 | unset($this->effectiveUrl); 388 | unset($this->totalTime); 389 | 390 | // Reset content-length header possibly set from a PUT or SEARCH request. 391 | $this->unsetHeader('Content-Length'); 392 | 393 | // Reset nobody setting possibly set from a HEAD request. 394 | $this->setOpt(CURLOPT_NOBODY, false); 395 | 396 | // Allow multicurl to attempt retry as needed. 397 | if ($this->isChildOfMultiCurl) { 398 | return; 399 | } 400 | 401 | if ($this->attemptRetry()) { 402 | return $this->exec($ch); 403 | } 404 | 405 | $this->execDone(); 406 | 407 | return $this->response; 408 | } 409 | 410 | public function execDone() 411 | { 412 | if ($this->error) { 413 | $this->call($this->errorCallback); 414 | } else { 415 | $this->call($this->successCallback); 416 | } 417 | 418 | $this->call($this->completeCallback); 419 | 420 | // Close open file handles and reset the curl instance. 421 | if (!($this->fileHandle === null)) { 422 | $this->downloadComplete($this->fileHandle); 423 | } 424 | } 425 | 426 | /** 427 | * Get 428 | * 429 | * @access public 430 | * @param $url 431 | * @param $data 432 | * 433 | * @return mixed Returns the value provided by exec. 434 | */ 435 | public function get($url, $data = array()) 436 | { 437 | if (is_array($url)) { 438 | $data = $url; 439 | $url = (string)$this->url; 440 | } 441 | $this->setUrl($url, $data); 442 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'GET'); 443 | $this->setOpt(CURLOPT_HTTPGET, true); 444 | return $this->exec(); 445 | } 446 | 447 | /** 448 | * Get Info 449 | * 450 | * @access public 451 | * @param $opt 452 | * 453 | * @return mixed 454 | */ 455 | public function getInfo($opt = null) 456 | { 457 | $args = array(); 458 | $args[] = $this->curl; 459 | 460 | if (func_num_args()) { 461 | $args[] = $opt; 462 | } 463 | 464 | return call_user_func_array('curl_getinfo', $args); 465 | } 466 | 467 | /** 468 | * Get Opt 469 | * 470 | * @access public 471 | * @param $option 472 | * 473 | * @return mixed 474 | */ 475 | public function getOpt($option) 476 | { 477 | return isset($this->options[$option]) ? $this->options[$option] : null; 478 | } 479 | 480 | /** 481 | * Head 482 | * 483 | * @access public 484 | * @param $url 485 | * @param $data 486 | * 487 | * @return mixed Returns the value provided by exec. 488 | */ 489 | public function head($url, $data = array()) 490 | { 491 | if (is_array($url)) { 492 | $data = $url; 493 | $url = (string)$this->url; 494 | } 495 | $this->setUrl($url, $data); 496 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'HEAD'); 497 | $this->setOpt(CURLOPT_NOBODY, true); 498 | return $this->exec(); 499 | } 500 | 501 | /** 502 | * Options 503 | * 504 | * @access public 505 | * @param $url 506 | * @param $data 507 | * 508 | * @return mixed Returns the value provided by exec. 509 | */ 510 | public function options($url, $data = array()) 511 | { 512 | if (is_array($url)) { 513 | $data = $url; 514 | $url = (string)$this->url; 515 | } 516 | $this->setUrl($url, $data); 517 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'OPTIONS'); 518 | return $this->exec(); 519 | } 520 | 521 | /** 522 | * Patch 523 | * 524 | * @access public 525 | * @param $url 526 | * @param $data 527 | * 528 | * @return mixed Returns the value provided by exec. 529 | */ 530 | public function patch($url, $data = array()) 531 | { 532 | if (is_array($url)) { 533 | $data = $url; 534 | $url = (string)$this->url; 535 | } 536 | 537 | if (is_array($data) && empty($data)) { 538 | $this->removeHeader('Content-Length'); 539 | } 540 | 541 | $this->setUrl($url); 542 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PATCH'); 543 | $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data)); 544 | return $this->exec(); 545 | } 546 | 547 | /** 548 | * Post 549 | * 550 | * @access public 551 | * @param $url 552 | * @param $data 553 | * @param $follow_303_with_post 554 | * If true, will cause 303 redirections to be followed using a POST request (default: false). 555 | * Notes: 556 | * - Redirections are only followed if the CURLOPT_FOLLOWLOCATION option is set to true. 557 | * - According to the HTTP specs (see [1]), a 303 redirection should be followed using 558 | * the GET method. 301 and 302 must not. 559 | * - In order to force a 303 redirection to be performed using the same method, the 560 | * underlying cURL object must be set in a special state (the CURLOPT_CURSTOMREQUEST 561 | * option must be set to the method to use after the redirection). Due to a limitation 562 | * of the cURL extension of PHP < 5.5.11 ([2], [3]) and of HHVM, it is not possible 563 | * to reset this option. Using these PHP engines, it is therefore impossible to 564 | * restore this behavior on an existing php-curl-class Curl object. 565 | * 566 | * @return mixed Returns the value provided by exec. 567 | * 568 | * [1] https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2 569 | * [2] https://github.com/php/php-src/pull/531 570 | * [3] http://php.net/ChangeLog-5.php#5.5.11 571 | */ 572 | public function post($url, $data = '', $follow_303_with_post = false) 573 | { 574 | if (is_array($url)) { 575 | $follow_303_with_post = (bool)$data; 576 | $data = $url; 577 | $url = (string)$this->url; 578 | } 579 | 580 | $this->setUrl($url); 581 | 582 | if ($follow_303_with_post) { 583 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'POST'); 584 | } else { 585 | if (isset($this->options[CURLOPT_CUSTOMREQUEST])) { 586 | if ((version_compare(PHP_VERSION, '5.5.11') < 0) || defined('HHVM_VERSION')) { 587 | trigger_error( 588 | 'Due to technical limitations of PHP <= 5.5.11 and HHVM, it is not possible to ' 589 | . 'perform a post-redirect-get request using a php-curl-class Curl object that ' 590 | . 'has already been used to perform other types of requests. Either use a new ' 591 | . 'php-curl-class Curl object or upgrade your PHP engine.', 592 | E_USER_ERROR 593 | ); 594 | } else { 595 | $this->setOpt(CURLOPT_CUSTOMREQUEST, null); 596 | } 597 | } 598 | } 599 | 600 | $this->setOpt(CURLOPT_POST, true); 601 | $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data)); 602 | return $this->exec(); 603 | } 604 | 605 | /** 606 | * Put 607 | * 608 | * @access public 609 | * @param $url 610 | * @param $data 611 | * 612 | * @return mixed Returns the value provided by exec. 613 | */ 614 | public function put($url, $data = array()) 615 | { 616 | if (is_array($url)) { 617 | $data = $url; 618 | $url = (string)$this->url; 619 | } 620 | $this->setUrl($url); 621 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PUT'); 622 | $put_data = $this->buildPostData($data); 623 | if (empty($this->options[CURLOPT_INFILE]) && empty($this->options[CURLOPT_INFILESIZE])) { 624 | if (is_string($put_data)) { 625 | $this->setHeader('Content-Length', strlen($put_data)); 626 | } 627 | } 628 | if (!empty($put_data)) { 629 | $this->setOpt(CURLOPT_POSTFIELDS, $put_data); 630 | } 631 | return $this->exec(); 632 | } 633 | 634 | /** 635 | * Search 636 | * 637 | * @access public 638 | * @param $url 639 | * @param $data 640 | * 641 | * @return mixed Returns the value provided by exec. 642 | */ 643 | public function search($url, $data = array()) 644 | { 645 | if (is_array($url)) { 646 | $data = $url; 647 | $url = (string)$this->url; 648 | } 649 | $this->setUrl($url); 650 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'SEARCH'); 651 | $put_data = $this->buildPostData($data); 652 | if (empty($this->options[CURLOPT_INFILE]) && empty($this->options[CURLOPT_INFILESIZE])) { 653 | if (is_string($put_data)) { 654 | $this->setHeader('Content-Length', strlen($put_data)); 655 | } 656 | } 657 | if (!empty($put_data)) { 658 | $this->setOpt(CURLOPT_POSTFIELDS, $put_data); 659 | } 660 | return $this->exec(); 661 | } 662 | 663 | /** 664 | * Set Basic Authentication 665 | * 666 | * @access public 667 | * @param $username 668 | * @param $password 669 | */ 670 | public function setBasicAuthentication($username, $password = '') 671 | { 672 | $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_BASIC); 673 | $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); 674 | } 675 | 676 | /** 677 | * Set Digest Authentication 678 | * 679 | * @access public 680 | * @param $username 681 | * @param $password 682 | */ 683 | public function setDigestAuthentication($username, $password = '') 684 | { 685 | $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); 686 | $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); 687 | } 688 | 689 | /** 690 | * Set Cookie 691 | * 692 | * @access public 693 | * @param $key 694 | * @param $value 695 | */ 696 | public function setCookie($key, $value) 697 | { 698 | $this->setEncodedCookie($key, $value); 699 | $this->buildCookies(); 700 | } 701 | 702 | /** 703 | * Set Cookies 704 | * 705 | * @access public 706 | * @param $cookies 707 | */ 708 | public function setCookies($cookies) 709 | { 710 | foreach ($cookies as $key => $value) { 711 | $this->setEncodedCookie($key, $value); 712 | } 713 | $this->buildCookies(); 714 | } 715 | 716 | /** 717 | * Get Cookie 718 | * 719 | * @access public 720 | * @param $key 721 | * 722 | * @return mixed 723 | */ 724 | public function getCookie($key) 725 | { 726 | return $this->getResponseCookie($key); 727 | } 728 | 729 | /** 730 | * Get Response Cookie 731 | * 732 | * @access public 733 | * @param $key 734 | * 735 | * @return mixed 736 | */ 737 | public function getResponseCookie($key) 738 | { 739 | return isset($this->responseCookies[$key]) ? $this->responseCookies[$key] : null; 740 | } 741 | 742 | /** 743 | * Get Response Cookies 744 | * 745 | * @access public 746 | * 747 | * @return array 748 | */ 749 | public function getResponseCookies() 750 | { 751 | return $this->responseCookies; 752 | } 753 | 754 | /** 755 | * Set Max Filesize 756 | * 757 | * @access public 758 | * @param $bytes 759 | */ 760 | public function setMaxFilesize($bytes) 761 | { 762 | // Make compatible with PHP version both before and after 5.5.0. PHP 5.5.0 added the cURL resource as the first 763 | // argument to the CURLOPT_PROGRESSFUNCTION callback. 764 | $gte_v550 = version_compare(PHP_VERSION, '5.5.0') >= 0; 765 | if ($gte_v550) { 766 | $callback = function ($resource, $download_size, $downloaded, $upload_size, $uploaded) use ($bytes) { 767 | // Abort the transfer when $downloaded bytes exceeds maximum $bytes by returning a non-zero value. 768 | return $downloaded > $bytes ? 1 : 0; 769 | }; 770 | } else { 771 | $callback = function ($download_size, $downloaded, $upload_size, $uploaded) use ($bytes) { 772 | return $downloaded > $bytes ? 1 : 0; 773 | }; 774 | } 775 | 776 | $this->progress($callback); 777 | } 778 | 779 | /** 780 | * Set Port 781 | * 782 | * @access public 783 | * @param $port 784 | */ 785 | public function setPort($port) 786 | { 787 | $this->setOpt(CURLOPT_PORT, intval($port)); 788 | } 789 | 790 | /** 791 | * Set Connect Timeout 792 | * 793 | * @access public 794 | * @param $seconds 795 | */ 796 | public function setConnectTimeout($seconds) 797 | { 798 | $this->setOpt(CURLOPT_CONNECTTIMEOUT, $seconds); 799 | } 800 | 801 | /** 802 | * Set Cookie String 803 | * 804 | * @access public 805 | * @param $string 806 | * 807 | * @return bool 808 | */ 809 | public function setCookieString($string) 810 | { 811 | return $this->setOpt(CURLOPT_COOKIE, $string); 812 | } 813 | 814 | /** 815 | * Set Cookie File 816 | * 817 | * @access public 818 | * @param $cookie_file 819 | * 820 | * @return boolean 821 | */ 822 | public function setCookieFile($cookie_file) 823 | { 824 | return $this->setOpt(CURLOPT_COOKIEFILE, $cookie_file); 825 | } 826 | 827 | /** 828 | * Set Cookie Jar 829 | * 830 | * @access public 831 | * @param $cookie_jar 832 | * 833 | * @return boolean 834 | */ 835 | public function setCookieJar($cookie_jar) 836 | { 837 | return $this->setOpt(CURLOPT_COOKIEJAR, $cookie_jar); 838 | } 839 | 840 | /** 841 | * Set Default JSON Decoder 842 | * 843 | * @access public 844 | * @param $assoc 845 | * @param $depth 846 | * @param $options 847 | */ 848 | public function setDefaultJsonDecoder() 849 | { 850 | $this->jsonDecoder = '\Curl\Decoder::decodeJson'; 851 | $this->jsonDecoderArgs = func_get_args(); 852 | } 853 | 854 | /** 855 | * Set Default XML Decoder 856 | * 857 | * @access public 858 | */ 859 | public function setDefaultXmlDecoder() 860 | { 861 | $this->xmlDecoder = '\Curl\Decoder::decodeXml'; 862 | } 863 | 864 | /** 865 | * Set Default Decoder 866 | * 867 | * @access public 868 | * @param $mixed boolean|callable|string 869 | */ 870 | public function setDefaultDecoder($mixed = 'json') 871 | { 872 | if ($mixed === false) { 873 | $this->defaultDecoder = false; 874 | } elseif (is_callable($mixed)) { 875 | $this->defaultDecoder = $mixed; 876 | } else { 877 | if ($mixed === 'json') { 878 | $this->defaultDecoder = '\Curl\Decoder::decodeJson'; 879 | } elseif ($mixed === 'xml') { 880 | $this->defaultDecoder = '\Curl\Decoder::decodeXml'; 881 | } 882 | } 883 | } 884 | 885 | /** 886 | * Set Default Timeout 887 | * 888 | * @access public 889 | */ 890 | public function setDefaultTimeout() 891 | { 892 | $this->setTimeout(self::DEFAULT_TIMEOUT); 893 | } 894 | 895 | /** 896 | * Set Default User Agent 897 | * 898 | * @access public 899 | */ 900 | public function setDefaultUserAgent() 901 | { 902 | $user_agent = 'PHP-Curl-Class/' . self::VERSION . ' (+https://github.com/php-curl-class/php-curl-class)'; 903 | $user_agent .= ' PHP/' . PHP_VERSION; 904 | $curl_version = curl_version(); 905 | $user_agent .= ' curl/' . $curl_version['version']; 906 | $this->setUserAgent($user_agent); 907 | } 908 | 909 | /** 910 | * Set Header 911 | * 912 | * Add extra header to include in the request. 913 | * 914 | * @access public 915 | * @param $key 916 | * @param $value 917 | */ 918 | public function setHeader($key, $value) 919 | { 920 | $this->headers[$key] = $value; 921 | $headers = array(); 922 | foreach ($this->headers as $key => $value) { 923 | $headers[] = $key . ': ' . $value; 924 | } 925 | $this->setOpt(CURLOPT_HTTPHEADER, $headers); 926 | } 927 | 928 | /** 929 | * Set Headers 930 | * 931 | * Add extra headers to include in the request. 932 | * 933 | * @access public 934 | * @param $headers 935 | */ 936 | public function setHeaders($headers) 937 | { 938 | foreach ($headers as $key => $value) { 939 | $this->headers[$key] = $value; 940 | } 941 | 942 | $headers = array(); 943 | foreach ($this->headers as $key => $value) { 944 | $headers[] = $key . ': ' . $value; 945 | } 946 | $this->setOpt(CURLOPT_HTTPHEADER, $headers); 947 | } 948 | 949 | /** 950 | * Set JSON Decoder 951 | * 952 | * @access public 953 | * @param $mixed boolean|callable 954 | */ 955 | public function setJsonDecoder($mixed) 956 | { 957 | if ($mixed === false) { 958 | $this->jsonDecoder = false; 959 | $this->jsonDecoderArgs = array(); 960 | } elseif (is_callable($mixed)) { 961 | $this->jsonDecoder = $mixed; 962 | $this->jsonDecoderArgs = array(); 963 | } 964 | } 965 | 966 | /** 967 | * Set XML Decoder 968 | * 969 | * @access public 970 | * @param $mixed boolean|callable 971 | */ 972 | public function setXmlDecoder($mixed) 973 | { 974 | if ($mixed === false) { 975 | $this->xmlDecoder = false; 976 | } elseif (is_callable($mixed)) { 977 | $this->xmlDecoder = $mixed; 978 | } 979 | } 980 | 981 | /** 982 | * Set Opt 983 | * 984 | * @access public 985 | * @param $option 986 | * @param $value 987 | * 988 | * @return boolean 989 | */ 990 | public function setOpt($option, $value) 991 | { 992 | $required_options = array( 993 | CURLOPT_RETURNTRANSFER => 'CURLOPT_RETURNTRANSFER', 994 | ); 995 | 996 | if (in_array($option, array_keys($required_options), true) && !($value === true)) { 997 | trigger_error($required_options[$option] . ' is a required option', E_USER_WARNING); 998 | } 999 | 1000 | $success = curl_setopt($this->curl, $option, $value); 1001 | if ($success) { 1002 | $this->options[$option] = $value; 1003 | } 1004 | return $success; 1005 | } 1006 | 1007 | /** 1008 | * Set Opts 1009 | * 1010 | * @access public 1011 | * @param $options 1012 | * 1013 | * @return boolean 1014 | * Returns true if all options were successfully set. If an option could not be successfully set, false is 1015 | * immediately returned, ignoring any future options in the options array. Similar to curl_setopt_array(). 1016 | */ 1017 | public function setOpts($options) 1018 | { 1019 | foreach ($options as $option => $value) { 1020 | if (!$this->setOpt($option, $value)) { 1021 | return false; 1022 | } 1023 | } 1024 | return true; 1025 | } 1026 | 1027 | /** 1028 | * Set Referer 1029 | * 1030 | * @access public 1031 | * @param $referer 1032 | */ 1033 | public function setReferer($referer) 1034 | { 1035 | $this->setReferrer($referer); 1036 | } 1037 | 1038 | /** 1039 | * Set Referrer 1040 | * 1041 | * @access public 1042 | * @param $referrer 1043 | */ 1044 | public function setReferrer($referrer) 1045 | { 1046 | $this->setOpt(CURLOPT_REFERER, $referrer); 1047 | } 1048 | 1049 | /** 1050 | * Set Retry 1051 | * 1052 | * Number of retries to attempt or decider callable. Maximum number of 1053 | * attempts is $maximum_number_of_retries + 1. 1054 | * 1055 | * @access public 1056 | * @param $mixed 1057 | */ 1058 | public function setRetry($mixed) 1059 | { 1060 | if (is_callable($mixed)) { 1061 | $this->retryDecider = $mixed; 1062 | } elseif (is_int($mixed)) { 1063 | $maximum_number_of_retries = $mixed; 1064 | $this->remainingRetries = $maximum_number_of_retries; 1065 | } 1066 | } 1067 | 1068 | /** 1069 | * Set Timeout 1070 | * 1071 | * @access public 1072 | * @param $seconds 1073 | */ 1074 | public function setTimeout($seconds) 1075 | { 1076 | $this->setOpt(CURLOPT_TIMEOUT, $seconds); 1077 | } 1078 | 1079 | /** 1080 | * Set Url 1081 | * 1082 | * @access public 1083 | * @param $url 1084 | * @param $mixed_data 1085 | */ 1086 | public function setUrl($url, $mixed_data = '') 1087 | { 1088 | $built_url = $this->buildUrl($url, $mixed_data); 1089 | 1090 | if ($this->url === null) { 1091 | $this->url = (string)new Url($built_url); 1092 | } else { 1093 | $this->url = (string)new Url($this->url, $built_url); 1094 | } 1095 | 1096 | $this->setOpt(CURLOPT_URL, $this->url); 1097 | } 1098 | 1099 | /** 1100 | * Set User Agent 1101 | * 1102 | * @access public 1103 | * @param $user_agent 1104 | */ 1105 | public function setUserAgent($user_agent) 1106 | { 1107 | $this->setOpt(CURLOPT_USERAGENT, $user_agent); 1108 | } 1109 | 1110 | /** 1111 | * Attempt Retry 1112 | * 1113 | * @access public 1114 | */ 1115 | public function attemptRetry() 1116 | { 1117 | $attempt_retry = false; 1118 | if ($this->error) { 1119 | if ($this->retryDecider === null) { 1120 | $attempt_retry = $this->remainingRetries >= 1; 1121 | } else { 1122 | $attempt_retry = call_user_func($this->retryDecider, $this); 1123 | } 1124 | if ($attempt_retry) { 1125 | $this->retries += 1; 1126 | if ($this->remainingRetries) { 1127 | $this->remainingRetries -= 1; 1128 | } 1129 | } 1130 | } 1131 | return $attempt_retry; 1132 | } 1133 | 1134 | /** 1135 | * Success 1136 | * 1137 | * @access public 1138 | * @param $callback 1139 | */ 1140 | public function success($callback) 1141 | { 1142 | $this->successCallback = $callback; 1143 | } 1144 | 1145 | /** 1146 | * Unset Header 1147 | * 1148 | * Remove extra header previously set using Curl::setHeader(). 1149 | * 1150 | * @access public 1151 | * @param $key 1152 | */ 1153 | public function unsetHeader($key) 1154 | { 1155 | unset($this->headers[$key]); 1156 | $headers = array(); 1157 | foreach ($this->headers as $key => $value) { 1158 | $headers[] = $key . ': ' . $value; 1159 | } 1160 | $this->setOpt(CURLOPT_HTTPHEADER, $headers); 1161 | } 1162 | 1163 | /** 1164 | * Remove Header 1165 | * 1166 | * Remove an internal header from the request. 1167 | * Using `curl -H "Host:" ...' is equivalent to $curl->removeHeader('Host');. 1168 | * 1169 | * @access public 1170 | * @param $key 1171 | */ 1172 | public function removeHeader($key) 1173 | { 1174 | $this->setHeader($key, ''); 1175 | } 1176 | 1177 | /** 1178 | * Verbose 1179 | * 1180 | * @access public 1181 | * @param bool $on 1182 | * @param resource $output 1183 | */ 1184 | public function verbose($on = true, $output = STDERR) 1185 | { 1186 | // Turn off CURLINFO_HEADER_OUT for verbose to work. This has the side 1187 | // effect of causing Curl::requestHeaders to be empty. 1188 | if ($on) { 1189 | $this->setOpt(CURLINFO_HEADER_OUT, false); 1190 | } 1191 | $this->setOpt(CURLOPT_VERBOSE, $on); 1192 | $this->setOpt(CURLOPT_STDERR, $output); 1193 | } 1194 | 1195 | /** 1196 | * Reset 1197 | * 1198 | * @access public 1199 | */ 1200 | public function reset() 1201 | { 1202 | if (function_exists('curl_reset') && is_resource($this->curl)) { 1203 | curl_reset($this->curl); 1204 | } else { 1205 | $this->curl = curl_init(); 1206 | } 1207 | 1208 | $this->initialize(); 1209 | } 1210 | 1211 | /** 1212 | * Destruct 1213 | * 1214 | * @access public 1215 | */ 1216 | public function __destruct() 1217 | { 1218 | $this->close(); 1219 | } 1220 | 1221 | public function __get($name) 1222 | { 1223 | $return = null; 1224 | if (in_array($name, self::$deferredProperties) && is_callable(array($this, $getter = '__get_' . $name))) { 1225 | $return = $this->$name = $this->$getter(); 1226 | } 1227 | return $return; 1228 | } 1229 | 1230 | /** 1231 | * Get Effective Url 1232 | * 1233 | * @access private 1234 | */ 1235 | private function __get_effectiveUrl() 1236 | { 1237 | return $this->getInfo(CURLINFO_EFFECTIVE_URL); 1238 | } 1239 | 1240 | /** 1241 | * Get RFC 2616 1242 | * 1243 | * @access private 1244 | */ 1245 | private function __get_rfc2616() 1246 | { 1247 | return array_fill_keys(self::$RFC2616, true); 1248 | } 1249 | 1250 | /** 1251 | * Get RFC 6265 1252 | * 1253 | * @access private 1254 | */ 1255 | private function __get_rfc6265() 1256 | { 1257 | return array_fill_keys(self::$RFC6265, true); 1258 | } 1259 | 1260 | /** 1261 | * Get Total Time 1262 | * 1263 | * @access private 1264 | */ 1265 | private function __get_totalTime() 1266 | { 1267 | return $this->getInfo(CURLINFO_TOTAL_TIME); 1268 | } 1269 | 1270 | /** 1271 | * Build Cookies 1272 | * 1273 | * @access private 1274 | */ 1275 | private function buildCookies() 1276 | { 1277 | // Avoid using http_build_query() as unnecessary encoding is performed. 1278 | // http_build_query($this->cookies, '', '; '); 1279 | $this->setOpt(CURLOPT_COOKIE, implode('; ', array_map(function ($k, $v) { 1280 | return $k . '=' . $v; 1281 | }, array_keys($this->cookies), array_values($this->cookies)))); 1282 | } 1283 | 1284 | /** 1285 | * Build Url 1286 | * 1287 | * @access private 1288 | * @param $url 1289 | * @param $mixed_data 1290 | * 1291 | * @return string 1292 | */ 1293 | private function buildUrl($url, $mixed_data = '') 1294 | { 1295 | $query_string = ''; 1296 | if (!empty($mixed_data)) { 1297 | $query_mark = strpos($url, '?') > 0 ? '&' : '?'; 1298 | if (is_string($mixed_data)) { 1299 | $query_string .= $query_mark . $mixed_data; 1300 | } elseif (is_array($mixed_data)) { 1301 | $query_string .= $query_mark . http_build_query($mixed_data, '', '&'); 1302 | } 1303 | } 1304 | return $url . $query_string; 1305 | } 1306 | 1307 | /** 1308 | * Download Complete 1309 | * 1310 | * @access private 1311 | * @param $fh 1312 | */ 1313 | private function downloadComplete($fh) 1314 | { 1315 | if (!$this->error && $this->downloadCompleteCallback) { 1316 | rewind($fh); 1317 | $this->call($this->downloadCompleteCallback, $fh); 1318 | $this->downloadCompleteCallback = null; 1319 | } 1320 | 1321 | if (is_resource($fh)) { 1322 | fclose($fh); 1323 | } 1324 | 1325 | // Fix "PHP Notice: Use of undefined constant STDOUT" when reading the 1326 | // PHP script from stdin. Using null causes "Warning: curl_setopt(): 1327 | // supplied argument is not a valid File-Handle resource". 1328 | if (!defined('STDOUT')) { 1329 | define('STDOUT', fopen('php://stdout', 'w')); 1330 | } 1331 | 1332 | // Reset CURLOPT_FILE with STDOUT to avoid: "curl_exec(): CURLOPT_FILE 1333 | // resource has gone away, resetting to default". 1334 | $this->setOpt(CURLOPT_FILE, STDOUT); 1335 | 1336 | // Reset CURLOPT_RETURNTRANSFER to tell cURL to return subsequent 1337 | // responses as the return value of curl_exec(). Without this, 1338 | // curl_exec() will revert to returning boolean values. 1339 | $this->setOpt(CURLOPT_RETURNTRANSFER, true); 1340 | } 1341 | 1342 | /** 1343 | * Parse Headers 1344 | * 1345 | * @access private 1346 | * @param $raw_headers 1347 | * 1348 | * @return array 1349 | */ 1350 | private function parseHeaders($raw_headers) 1351 | { 1352 | $raw_headers = preg_split('/\r\n/', $raw_headers, null, PREG_SPLIT_NO_EMPTY); 1353 | $http_headers = new CaseInsensitiveArray(); 1354 | 1355 | $raw_headers_count = count($raw_headers); 1356 | for ($i = 1; $i < $raw_headers_count; $i++) { 1357 | if (strpos($raw_headers[$i], ':') !== false) { 1358 | list($key, $value) = explode(':', $raw_headers[$i], 2); 1359 | $key = trim($key); 1360 | $value = trim($value); 1361 | // Use isset() as array_key_exists() and ArrayAccess are not compatible. 1362 | if (isset($http_headers[$key])) { 1363 | $http_headers[$key] .= ',' . $value; 1364 | } else { 1365 | $http_headers[$key] = $value; 1366 | } 1367 | } 1368 | } 1369 | 1370 | return array(isset($raw_headers['0']) ? $raw_headers['0'] : '', $http_headers); 1371 | } 1372 | 1373 | /** 1374 | * Parse Request Headers 1375 | * 1376 | * @access private 1377 | * @param $raw_headers 1378 | * 1379 | * @return \Curl\CaseInsensitiveArray 1380 | */ 1381 | private function parseRequestHeaders($raw_headers) 1382 | { 1383 | $request_headers = new CaseInsensitiveArray(); 1384 | list($first_line, $headers) = $this->parseHeaders($raw_headers); 1385 | $request_headers['Request-Line'] = $first_line; 1386 | foreach ($headers as $key => $value) { 1387 | $request_headers[$key] = $value; 1388 | } 1389 | return $request_headers; 1390 | } 1391 | 1392 | /** 1393 | * Parse Response 1394 | * 1395 | * @access private 1396 | * @param $response_headers 1397 | * @param $raw_response 1398 | * 1399 | * @return mixed 1400 | * If the response content-type is json: 1401 | * Returns the json decoder's return value: A stdClass object when the default json decoder is used. 1402 | * If the response content-type is xml: 1403 | * Returns the xml decoder's return value: A SimpleXMLElement object when the default xml decoder is used. 1404 | * If the response content-type is something else: 1405 | * Returns the original raw response unless a default decoder has been set. 1406 | * If the response content-type cannot be determined: 1407 | * Returns the original raw response. 1408 | */ 1409 | private function parseResponse($response_headers, $raw_response) 1410 | { 1411 | $response = $raw_response; 1412 | if (isset($response_headers['Content-Type'])) { 1413 | if (preg_match($this->jsonPattern, $response_headers['Content-Type'])) { 1414 | if ($this->jsonDecoder) { 1415 | $args = $this->jsonDecoderArgs; 1416 | array_unshift($args, $response); 1417 | $response = call_user_func_array($this->jsonDecoder, $args); 1418 | } 1419 | } elseif (preg_match($this->xmlPattern, $response_headers['Content-Type'])) { 1420 | if ($this->xmlDecoder) { 1421 | $response = call_user_func($this->xmlDecoder, $response); 1422 | } 1423 | } else { 1424 | if ($this->defaultDecoder) { 1425 | $response = call_user_func($this->defaultDecoder, $response); 1426 | } 1427 | } 1428 | } 1429 | 1430 | return $response; 1431 | } 1432 | 1433 | /** 1434 | * Parse Response Headers 1435 | * 1436 | * @access private 1437 | * @param $raw_response_headers 1438 | * 1439 | * @return \Curl\CaseInsensitiveArray 1440 | */ 1441 | private function parseResponseHeaders($raw_response_headers) 1442 | { 1443 | $response_header_array = explode("\r\n\r\n", $raw_response_headers); 1444 | $response_header = ''; 1445 | for ($i = count($response_header_array) - 1; $i >= 0; $i--) { 1446 | if (stripos($response_header_array[$i], 'HTTP/') === 0) { 1447 | $response_header = $response_header_array[$i]; 1448 | break; 1449 | } 1450 | } 1451 | 1452 | $response_headers = new CaseInsensitiveArray(); 1453 | list($first_line, $headers) = $this->parseHeaders($response_header); 1454 | $response_headers['Status-Line'] = $first_line; 1455 | foreach ($headers as $key => $value) { 1456 | $response_headers[$key] = $value; 1457 | } 1458 | return $response_headers; 1459 | } 1460 | 1461 | /** 1462 | * Set Encoded Cookie 1463 | * 1464 | * @access private 1465 | * @param $key 1466 | * @param $value 1467 | */ 1468 | private function setEncodedCookie($key, $value) 1469 | { 1470 | $name_chars = array(); 1471 | foreach (str_split($key) as $name_char) { 1472 | if (isset($this->rfc2616[$name_char])) { 1473 | $name_chars[] = $name_char; 1474 | } else { 1475 | $name_chars[] = rawurlencode($name_char); 1476 | } 1477 | } 1478 | 1479 | $value_chars = array(); 1480 | foreach (str_split($value) as $value_char) { 1481 | if (isset($this->rfc6265[$value_char])) { 1482 | $value_chars[] = $value_char; 1483 | } else { 1484 | $value_chars[] = rawurlencode($value_char); 1485 | } 1486 | } 1487 | 1488 | $this->cookies[implode('', $name_chars)] = implode('', $value_chars); 1489 | } 1490 | 1491 | /** 1492 | * Initialize 1493 | * 1494 | * @access private 1495 | * @param $base_url 1496 | */ 1497 | private function initialize($base_url = null) 1498 | { 1499 | $this->id = uniqid('', true); 1500 | $this->setDefaultUserAgent(); 1501 | $this->setDefaultTimeout(); 1502 | $this->setOpt(CURLINFO_HEADER_OUT, true); 1503 | 1504 | // Create a placeholder to temporarily store the header callback data. 1505 | $header_callback_data = new \stdClass(); 1506 | $header_callback_data->rawResponseHeaders = ''; 1507 | $header_callback_data->responseCookies = array(); 1508 | $this->headerCallbackData = $header_callback_data; 1509 | $this->setOpt(CURLOPT_HEADERFUNCTION, createHeaderCallback($header_callback_data)); 1510 | 1511 | $this->setOpt(CURLOPT_RETURNTRANSFER, true); 1512 | $this->headers = new CaseInsensitiveArray(); 1513 | $this->setUrl($base_url); 1514 | } 1515 | } 1516 | 1517 | /** 1518 | * Create Header Callback 1519 | * 1520 | * Gather headers and parse cookies as response headers are received. Keep this function separate from the class so that 1521 | * unset($curl) automatically calls __destruct() as expected. Otherwise, manually calling $curl->close() will be 1522 | * necessary to prevent a memory leak. 1523 | * 1524 | * @param $header_callback_data 1525 | * 1526 | * @return callable 1527 | */ 1528 | function createHeaderCallback($header_callback_data) { 1529 | return function ($ch, $header) use ($header_callback_data) { 1530 | if (preg_match('/^Set-Cookie:\s*([^=]+)=([^;]+)/mi', $header, $cookie) === 1) { 1531 | $header_callback_data->responseCookies[$cookie[1]] = trim($cookie[2], " \n\r\t\0\x0B"); 1532 | } 1533 | $header_callback_data->rawResponseHeaders .= $header; 1534 | return strlen($header); 1535 | }; 1536 | } 1537 | -------------------------------------------------------------------------------- /vendor/php-curl-class/php-curl-class/src/Curl/Decoder.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 | $this->queueHandle($curl); 62 | $curl->setUrl($url, $query_parameters); 63 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'DELETE'); 64 | $curl->setOpt(CURLOPT_POSTFIELDS, $curl->buildPostData($data)); 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 | $this->queueHandle($curl); 81 | $curl->setUrl($url); 82 | 83 | // Use tmpfile() or php://temp to avoid "Too many open files" error. 84 | if (is_callable($mixed_filename)) { 85 | $callback = $mixed_filename; 86 | $curl->downloadCompleteCallback = $callback; 87 | $curl->fileHandle = tmpfile(); 88 | } else { 89 | $filename = $mixed_filename; 90 | $curl->downloadCompleteCallback = function ($instance, $fh) use ($filename) { 91 | file_put_contents($filename, stream_get_contents($fh)); 92 | }; 93 | $curl->fileHandle = fopen('php://temp', 'wb'); 94 | } 95 | 96 | $curl->setOpt(CURLOPT_FILE, $curl->fileHandle); 97 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'GET'); 98 | $curl->setOpt(CURLOPT_HTTPGET, true); 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 | $this->queueHandle($curl); 119 | $curl->setUrl($url, $data); 120 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'GET'); 121 | $curl->setOpt(CURLOPT_HTTPGET, true); 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 | $this->queueHandle($curl); 142 | $curl->setUrl($url, $data); 143 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'HEAD'); 144 | $curl->setOpt(CURLOPT_NOBODY, true); 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 | $this->queueHandle($curl); 165 | $curl->setUrl($url, $data); 166 | $curl->removeHeader('Content-Length'); 167 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'OPTIONS'); 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 | 187 | $curl = new Curl(); 188 | 189 | if (is_array($data) && empty($data)) { 190 | $curl->removeHeader('Content-Length'); 191 | } 192 | 193 | $this->queueHandle($curl); 194 | $curl->setUrl($url); 195 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'PATCH'); 196 | $curl->setOpt(CURLOPT_POSTFIELDS, $curl->buildPostData($data)); 197 | return $curl; 198 | } 199 | 200 | /** 201 | * Add Post 202 | * 203 | * @access public 204 | * @param $url 205 | * @param $data 206 | * @param $follow_303_with_post 207 | * If true, will cause 303 redirections to be followed using GET requests (default: false). 208 | * Note: Redirections are only followed if the CURLOPT_FOLLOWLOCATION option is set to true. 209 | * 210 | * @return object 211 | */ 212 | public function addPost($url, $data = '', $follow_303_with_post = false) 213 | { 214 | if (is_array($url)) { 215 | $follow_303_with_post = (bool)$data; 216 | $data = $url; 217 | $url = $this->baseUrl; 218 | } 219 | 220 | $curl = new Curl(); 221 | $this->queueHandle($curl); 222 | 223 | if (is_array($data) && empty($data)) { 224 | $curl->removeHeader('Content-Length'); 225 | } 226 | 227 | $curl->setUrl($url); 228 | 229 | /* 230 | * For post-redirect-get requests, the CURLOPT_CUSTOMREQUEST option must not 231 | * be set, otherwise cURL will perform POST requests for redirections. 232 | */ 233 | if (!$follow_303_with_post) { 234 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'POST'); 235 | } 236 | 237 | $curl->setOpt(CURLOPT_POST, true); 238 | $curl->setOpt(CURLOPT_POSTFIELDS, $curl->buildPostData($data)); 239 | return $curl; 240 | } 241 | 242 | /** 243 | * Add Put 244 | * 245 | * @access public 246 | * @param $url 247 | * @param $data 248 | * 249 | * @return object 250 | */ 251 | public function addPut($url, $data = array()) 252 | { 253 | if (is_array($url)) { 254 | $data = $url; 255 | $url = $this->baseUrl; 256 | } 257 | $curl = new Curl(); 258 | $this->queueHandle($curl); 259 | $curl->setUrl($url); 260 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'PUT'); 261 | $put_data = $curl->buildPostData($data); 262 | if (is_string($put_data)) { 263 | $curl->setHeader('Content-Length', strlen($put_data)); 264 | } 265 | $curl->setOpt(CURLOPT_POSTFIELDS, $put_data); 266 | return $curl; 267 | } 268 | 269 | /** 270 | * Add Search 271 | * 272 | * @access public 273 | * @param $url 274 | * @param $data 275 | * 276 | * @return object 277 | */ 278 | public function addSearch($url, $data = array()) 279 | { 280 | if (is_array($url)) { 281 | $data = $url; 282 | $url = $this->baseUrl; 283 | } 284 | $curl = new Curl(); 285 | $this->queueHandle($curl); 286 | $curl->setUrl($url); 287 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'SEARCH'); 288 | $put_data = $curl->buildPostData($data); 289 | if (is_string($put_data)) { 290 | $curl->setHeader('Content-Length', strlen($put_data)); 291 | } 292 | $curl->setOpt(CURLOPT_POSTFIELDS, $put_data); 293 | return $curl; 294 | } 295 | 296 | /** 297 | * Add Curl 298 | * 299 | * Add a Curl instance to the handle queue. 300 | * 301 | * @access public 302 | * @param $curl 303 | * 304 | * @return object 305 | */ 306 | public function addCurl(Curl $curl) 307 | { 308 | $this->queueHandle($curl); 309 | return $curl; 310 | } 311 | 312 | /** 313 | * Before Send 314 | * 315 | * @access public 316 | * @param $callback 317 | */ 318 | public function beforeSend($callback) 319 | { 320 | $this->beforeSendCallback = $callback; 321 | } 322 | 323 | /** 324 | * Close 325 | * 326 | * @access public 327 | */ 328 | public function close() 329 | { 330 | foreach ($this->curls as $curl) { 331 | $curl->close(); 332 | } 333 | 334 | if (is_resource($this->multiCurl)) { 335 | curl_multi_close($this->multiCurl); 336 | } 337 | } 338 | 339 | /** 340 | * Complete 341 | * 342 | * @access public 343 | * @param $callback 344 | */ 345 | public function complete($callback) 346 | { 347 | $this->completeCallback = $callback; 348 | } 349 | 350 | /** 351 | * Error 352 | * 353 | * @access public 354 | * @param $callback 355 | */ 356 | public function error($callback) 357 | { 358 | $this->errorCallback = $callback; 359 | } 360 | 361 | /** 362 | * Get Opt 363 | * 364 | * @access public 365 | * @param $option 366 | * 367 | * @return mixed 368 | */ 369 | public function getOpt($option) 370 | { 371 | return isset($this->options[$option]) ? $this->options[$option] : null; 372 | } 373 | 374 | /** 375 | * Set Basic Authentication 376 | * 377 | * @access public 378 | * @param $username 379 | * @param $password 380 | */ 381 | public function setBasicAuthentication($username, $password = '') 382 | { 383 | $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_BASIC); 384 | $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); 385 | } 386 | 387 | /** 388 | * Set Concurrency 389 | * 390 | * @access public 391 | * @param $concurrency 392 | */ 393 | public function setConcurrency($concurrency) 394 | { 395 | $this->concurrency = $concurrency; 396 | } 397 | 398 | /** 399 | * Set Digest Authentication 400 | * 401 | * @access public 402 | * @param $username 403 | * @param $password 404 | */ 405 | public function setDigestAuthentication($username, $password = '') 406 | { 407 | $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); 408 | $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); 409 | } 410 | 411 | /** 412 | * Set Cookie 413 | * 414 | * @access public 415 | * @param $key 416 | * @param $value 417 | */ 418 | public function setCookie($key, $value) 419 | { 420 | $this->cookies[$key] = $value; 421 | } 422 | 423 | /** 424 | * Set Cookies 425 | * 426 | * @access public 427 | * @param $cookies 428 | */ 429 | public function setCookies($cookies) 430 | { 431 | foreach ($cookies as $key => $value) { 432 | $this->cookies[$key] = $value; 433 | } 434 | } 435 | 436 | /** 437 | * Set Port 438 | * 439 | * @access public 440 | * @param $port 441 | */ 442 | public function setPort($port) 443 | { 444 | $this->setOpt(CURLOPT_PORT, intval($port)); 445 | } 446 | 447 | /** 448 | * Set Connect Timeout 449 | * 450 | * @access public 451 | * @param $seconds 452 | */ 453 | public function setConnectTimeout($seconds) 454 | { 455 | $this->setOpt(CURLOPT_CONNECTTIMEOUT, $seconds); 456 | } 457 | 458 | /** 459 | * Set Cookie String 460 | * 461 | * @access public 462 | * @param $string 463 | */ 464 | public function setCookieString($string) 465 | { 466 | $this->setOpt(CURLOPT_COOKIE, $string); 467 | } 468 | 469 | /** 470 | * Set Cookie File 471 | * 472 | * @access public 473 | * @param $cookie_file 474 | */ 475 | public function setCookieFile($cookie_file) 476 | { 477 | $this->setOpt(CURLOPT_COOKIEFILE, $cookie_file); 478 | } 479 | 480 | /** 481 | * Set Cookie Jar 482 | * 483 | * @access public 484 | * @param $cookie_jar 485 | */ 486 | public function setCookieJar($cookie_jar) 487 | { 488 | $this->setOpt(CURLOPT_COOKIEJAR, $cookie_jar); 489 | } 490 | 491 | /** 492 | * Set Header 493 | * 494 | * Add extra header to include in the request. 495 | * 496 | * @access public 497 | * @param $key 498 | * @param $value 499 | */ 500 | public function setHeader($key, $value) 501 | { 502 | $this->headers[$key] = $value; 503 | $this->updateHeaders(); 504 | } 505 | 506 | /** 507 | * Set Headers 508 | * 509 | * Add extra headers to include in the request. 510 | * 511 | * @access public 512 | * @param $headers 513 | */ 514 | public function setHeaders($headers) 515 | { 516 | foreach ($headers as $key => $value) { 517 | $this->headers[$key] = $value; 518 | } 519 | $this->updateHeaders(); 520 | } 521 | 522 | /** 523 | * Set JSON Decoder 524 | * 525 | * @access public 526 | * @param $mixed boolean|callable 527 | */ 528 | public function setJsonDecoder($mixed) 529 | { 530 | if ($mixed === false) { 531 | $this->jsonDecoder = false; 532 | } elseif (is_callable($mixed)) { 533 | $this->jsonDecoder = $mixed; 534 | } 535 | } 536 | 537 | /** 538 | * Set XML Decoder 539 | * 540 | * @access public 541 | * @param $mixed boolean|callable 542 | */ 543 | public function setXmlDecoder($mixed) 544 | { 545 | if ($mixed === false) { 546 | $this->xmlDecoder = false; 547 | } elseif (is_callable($mixed)) { 548 | $this->xmlDecoder = $mixed; 549 | } 550 | } 551 | 552 | /** 553 | * Set Opt 554 | * 555 | * @access public 556 | * @param $option 557 | * @param $value 558 | */ 559 | public function setOpt($option, $value) 560 | { 561 | $this->options[$option] = $value; 562 | } 563 | 564 | /** 565 | * Set Opts 566 | * 567 | * @access public 568 | * @param $options 569 | */ 570 | public function setOpts($options) 571 | { 572 | foreach ($options as $option => $value) { 573 | $this->setOpt($option, $value); 574 | } 575 | } 576 | 577 | /** 578 | * Set Referer 579 | * 580 | * @access public 581 | * @param $referer 582 | */ 583 | public function setReferer($referer) 584 | { 585 | $this->setReferrer($referer); 586 | } 587 | 588 | /** 589 | * Set Referrer 590 | * 591 | * @access public 592 | * @param $referrer 593 | */ 594 | public function setReferrer($referrer) 595 | { 596 | $this->setOpt(CURLOPT_REFERER, $referrer); 597 | } 598 | 599 | /** 600 | * Set Retry 601 | * 602 | * Number of retries to attempt or decider callable. Maximum number of 603 | * attempts is $maximum_number_of_retries + 1. 604 | * 605 | * @access public 606 | * @param $mixed 607 | */ 608 | public function setRetry($mixed) 609 | { 610 | $this->retry = $mixed; 611 | } 612 | 613 | /** 614 | * Set Timeout 615 | * 616 | * @access public 617 | * @param $seconds 618 | */ 619 | public function setTimeout($seconds) 620 | { 621 | $this->setOpt(CURLOPT_TIMEOUT, $seconds); 622 | } 623 | 624 | /** 625 | * Set Url 626 | * 627 | * @access public 628 | * @param $url 629 | */ 630 | public function setUrl($url) 631 | { 632 | $this->baseUrl = $url; 633 | } 634 | 635 | /** 636 | * Set User Agent 637 | * 638 | * @access public 639 | * @param $user_agent 640 | */ 641 | public function setUserAgent($user_agent) 642 | { 643 | $this->setOpt(CURLOPT_USERAGENT, $user_agent); 644 | } 645 | 646 | /** 647 | * Start 648 | * 649 | * @access public 650 | */ 651 | public function start() 652 | { 653 | if ($this->isStarted) { 654 | return; 655 | } 656 | 657 | $this->isStarted = true; 658 | 659 | $concurrency = $this->concurrency; 660 | if ($concurrency > count($this->curls)) { 661 | $concurrency = count($this->curls); 662 | } 663 | 664 | for ($i = 0; $i < $concurrency; $i++) { 665 | $this->initHandle(array_shift($this->curls)); 666 | } 667 | 668 | do { 669 | // Wait for activity on any curl_multi connection when curl_multi_select (libcurl) fails to correctly block. 670 | // https://bugs.php.net/bug.php?id=63411 671 | if (curl_multi_select($this->multiCurl) === -1) { 672 | usleep(100000); 673 | } 674 | 675 | curl_multi_exec($this->multiCurl, $active); 676 | 677 | while (!($info_array = curl_multi_info_read($this->multiCurl)) === false) { 678 | if ($info_array['msg'] === CURLMSG_DONE) { 679 | foreach ($this->activeCurls as $key => $curl) { 680 | if ($curl->curl === $info_array['handle']) { 681 | // Set the error code for multi handles using the "result" key in the array returned by 682 | // curl_multi_info_read(). Using curl_errno() on a multi handle will incorrectly return 0 683 | // for errors. 684 | $curl->curlErrorCode = $info_array['result']; 685 | $curl->exec($curl->curl); 686 | 687 | if ($curl->attemptRetry()) { 688 | // Remove completed handle before adding again in order to retry request. 689 | curl_multi_remove_handle($this->multiCurl, $curl->curl); 690 | 691 | $curlm_error_code = curl_multi_add_handle($this->multiCurl, $curl->curl); 692 | if (!($curlm_error_code === CURLM_OK)) { 693 | throw new \ErrorException( 694 | 'cURL multi add handle error: ' . curl_multi_strerror($curlm_error_code) 695 | ); 696 | } 697 | } else { 698 | $curl->execDone(); 699 | 700 | // Remove completed instance from active curls. 701 | unset($this->activeCurls[$key]); 702 | 703 | // Start new requests before removing the handle of the completed one. 704 | while (count($this->curls) >= 1 && count($this->activeCurls) < $this->concurrency) { 705 | $this->initHandle(array_shift($this->curls)); 706 | } 707 | curl_multi_remove_handle($this->multiCurl, $curl->curl); 708 | 709 | // Clean up completed instance. 710 | $curl->close(); 711 | } 712 | 713 | break; 714 | } 715 | } 716 | } 717 | } 718 | 719 | if (!$active) { 720 | $active = count($this->activeCurls); 721 | } 722 | } while ($active > 0); 723 | 724 | $this->isStarted = false; 725 | } 726 | 727 | /** 728 | * Success 729 | * 730 | * @access public 731 | * @param $callback 732 | */ 733 | public function success($callback) 734 | { 735 | $this->successCallback = $callback; 736 | } 737 | 738 | /** 739 | * Unset Header 740 | * 741 | * Remove extra header previously set using Curl::setHeader(). 742 | * 743 | * @access public 744 | * @param $key 745 | */ 746 | public function unsetHeader($key) 747 | { 748 | unset($this->headers[$key]); 749 | } 750 | 751 | /** 752 | * Remove Header 753 | * 754 | * Remove an internal header from the request. 755 | * Using `curl -H "Host:" ...' is equivalent to $curl->removeHeader('Host');. 756 | * 757 | * @access public 758 | * @param $key 759 | */ 760 | public function removeHeader($key) 761 | { 762 | $this->setHeader($key, ''); 763 | } 764 | 765 | /** 766 | * Verbose 767 | * 768 | * @access public 769 | * @param bool $on 770 | * @param resource $output 771 | */ 772 | public function verbose($on = true, $output = STDERR) 773 | { 774 | // Turn off CURLINFO_HEADER_OUT for verbose to work. This has the side 775 | // effect of causing Curl::requestHeaders to be empty. 776 | if ($on) { 777 | $this->setOpt(CURLINFO_HEADER_OUT, false); 778 | } 779 | $this->setOpt(CURLOPT_VERBOSE, $on); 780 | $this->setOpt(CURLOPT_STDERR, $output); 781 | } 782 | 783 | /** 784 | * Destruct 785 | * 786 | * @access public 787 | */ 788 | public function __destruct() 789 | { 790 | $this->close(); 791 | } 792 | 793 | /** 794 | * Update Headers 795 | * 796 | * @access private 797 | */ 798 | private function updateHeaders() 799 | { 800 | foreach ($this->curls as $curl) { 801 | $curl->setHeaders($this->headers); 802 | } 803 | } 804 | 805 | /** 806 | * Queue Handle 807 | * 808 | * @access private 809 | * @param $curl 810 | */ 811 | private function queueHandle($curl) 812 | { 813 | // Use sequential ids to allow for ordered post processing. 814 | $curl->id = $this->nextCurlId++; 815 | $curl->isChildOfMultiCurl = true; 816 | $this->curls[$curl->id] = $curl; 817 | 818 | $curl->setHeaders($this->headers); 819 | } 820 | 821 | /** 822 | * Init Handle 823 | * 824 | * @access private 825 | * @param $curl 826 | * @throws \ErrorException 827 | */ 828 | private function initHandle($curl) 829 | { 830 | // Set callbacks if not already individually set. 831 | if ($curl->beforeSendCallback === null) { 832 | $curl->beforeSend($this->beforeSendCallback); 833 | } 834 | if ($curl->successCallback === null) { 835 | $curl->success($this->successCallback); 836 | } 837 | if ($curl->errorCallback === null) { 838 | $curl->error($this->errorCallback); 839 | } 840 | if ($curl->completeCallback === null) { 841 | $curl->complete($this->completeCallback); 842 | } 843 | 844 | // Set decoders if not already individually set. 845 | if ($curl->jsonDecoder === null) { 846 | $curl->setJsonDecoder($this->jsonDecoder); 847 | } 848 | if ($curl->xmlDecoder === null) { 849 | $curl->setXmlDecoder($this->xmlDecoder); 850 | } 851 | 852 | $curl->setOpts($this->options); 853 | $curl->setRetry($this->retry); 854 | $curl->setCookies($this->cookies); 855 | 856 | $curlm_error_code = curl_multi_add_handle($this->multiCurl, $curl->curl); 857 | if (!($curlm_error_code === CURLM_OK)) { 858 | throw new \ErrorException('cURL multi add handle error: ' . curl_multi_strerror($curlm_error_code)); 859 | } 860 | 861 | $this->activeCurls[$curl->id] = $curl; 862 | $curl->call($curl->beforeSendCallback); 863 | } 864 | } 865 | -------------------------------------------------------------------------------- /vendor/php-curl-class/php-curl-class/src/Curl/StrUtil.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 | if (!isset($b['path'])) { 96 | $b['path'] = '/'; 97 | } 98 | if (is_null($this->relativeUrl)) { 99 | return $this->unparseUrl($b); 100 | } 101 | $r = $this->parseUrl($this->relativeUrl); 102 | $r['authorized'] = isset($r['scheme']) || isset($r['host']) || isset($r['port']) 103 | || isset($r['user']) || isset($r['pass']); 104 | $target = array(); 105 | if (isset($r['scheme'])) { 106 | $target['scheme'] = $r['scheme']; 107 | $target['host'] = isset($r['host']) ? $r['host'] : null; 108 | $target['port'] = isset($r['port']) ? $r['port'] : null; 109 | $target['user'] = isset($r['user']) ? $r['user'] : null; 110 | $target['pass'] = isset($r['pass']) ? $r['pass'] : null; 111 | $target['path'] = isset($r['path']) ? self::removeDotSegments($r['path']) : null; 112 | $target['query'] = isset($r['query']) ? $r['query'] : null; 113 | } else { 114 | $target['scheme'] = isset($b['scheme']) ? $b['scheme'] : null; 115 | if ($r['authorized']) { 116 | $target['host'] = isset($r['host']) ? $r['host'] : null; 117 | $target['port'] = isset($r['port']) ? $r['port'] : null; 118 | $target['user'] = isset($r['user']) ? $r['user'] : null; 119 | $target['pass'] = isset($r['pass']) ? $r['pass'] : null; 120 | $target['path'] = isset($r['path']) ? self::removeDotSegments($r['path']) : null; 121 | $target['query'] = isset($r['query']) ? $r['query'] : null; 122 | } else { 123 | $target['host'] = isset($b['host']) ? $b['host'] : null; 124 | $target['port'] = isset($b['port']) ? $b['port'] : null; 125 | $target['user'] = isset($b['user']) ? $b['user'] : null; 126 | $target['pass'] = isset($b['pass']) ? $b['pass'] : null; 127 | if (!isset($r['path']) || $r['path'] === '') { 128 | $target['path'] = $b['path']; 129 | $target['query'] = isset($r['query']) ? $r['query'] : (isset($b['query']) ? $b['query'] : null); 130 | } else { 131 | if (StrUtil::startsWith($r['path'], '/')) { 132 | $target['path'] = self::removeDotSegments($r['path']); 133 | } else { 134 | $base = \mb_strrchr($b['path'], '/', true); 135 | if ($base === false) { 136 | $base = ''; 137 | } 138 | $target['path'] = self::removeDotSegments($base . '/' . $r['path']); 139 | } 140 | $target['query'] = isset($r['query']) ? $r['query'] : null; 141 | } 142 | } 143 | } 144 | if ($this->relativeUrl === '') { 145 | $target['fragment'] = isset($b['fragment']) ? $b['fragment'] : null; 146 | } else { 147 | $target['fragment'] = isset($r['fragment']) ? $r['fragment'] : null; 148 | } 149 | $absolutized_url = $this->unparseUrl($target); 150 | return $absolutized_url; 151 | } 152 | 153 | /** 154 | * Parse url. 155 | * 156 | * Parse url into components of a URI as specified by RFC 3986. 157 | */ 158 | private function parseUrl($url) 159 | { 160 | // ALPHA = A-Z / a-z 161 | $alpha = 'A-Za-z'; 162 | 163 | // DIGIT = 0-9 164 | $digit = '0-9'; 165 | 166 | // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 167 | $unreserved = $alpha . $digit . preg_quote('-._~'); 168 | 169 | // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 170 | // / "*" / "+" / "," / ";" / "=" / "#" 171 | $sub_delims = preg_quote('!$&\'()*+,;=#'); 172 | 173 | // HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" 174 | $hexdig = $digit . 'A-F'; 175 | // "The uppercase hexadecimal digits 'A' through 'F' are equivalent to 176 | // the lowercase digits 'a' through 'f', respectively." 177 | $hexdig .= 'a-f'; 178 | 179 | $pattern = '/(?:[^' . $unreserved . $sub_delims . preg_quote(':@%/?', '/') . ']++|%(?![' . $hexdig . ']{2}))/'; 180 | $url = preg_replace_callback( 181 | $pattern, 182 | function ($matches) { 183 | return rawurlencode($matches[0]); 184 | }, 185 | $url 186 | ); 187 | return parse_url($url); 188 | } 189 | 190 | /** 191 | * Unparse url. 192 | * 193 | * Combine url components into a url. 194 | */ 195 | private function unparseUrl($parsed_url) { 196 | $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; 197 | $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; 198 | $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; 199 | $user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; 200 | $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; 201 | $pass = ($user || $pass) ? $pass . '@' : ''; 202 | $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; 203 | $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; 204 | $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; 205 | $unparsed_url = $scheme . $user . $pass . $host . $port . $path . $query . $fragment; 206 | return $unparsed_url; 207 | } 208 | } 209 | --------------------------------------------------------------------------------