From d6a9d4240bc2f47966de60287b67d7676b7a477a Mon Sep 17 00:00:00 2001 From: ShadowySpirits Date: Mon, 21 Jan 2019 01:10:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20Bug=20=E5=B9=B6=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=96=B0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Plugin.php | 1122 ++++++++------- README.md | 45 +- SDK/upyun.class.php | 483 ------- Upyun/src/Upyun/Api/Form.php | 44 + Upyun/src/Upyun/Api/Pretreat.php | 84 ++ Upyun/src/Upyun/Api/Rest.php | 108 ++ Upyun/src/Upyun/Api/SyncVideo.php | 42 + Upyun/src/Upyun/Config.php | 147 ++ Upyun/src/Upyun/Signature.php | 98 ++ Upyun/src/Upyun/Uploader.php | 120 ++ Upyun/src/Upyun/Upyun.php | 538 +++++++ Upyun/src/Upyun/Util.php | 90 ++ Upyun/vendor/autoload.php | 7 + Upyun/vendor/composer/ClassLoader.php | 445 ++++++ Upyun/vendor/composer/LICENSE | 21 + Upyun/vendor/composer/autoload_classmap.php | 95 ++ Upyun/vendor/composer/autoload_files.php | 12 + Upyun/vendor/composer/autoload_namespaces.php | 9 + Upyun/vendor/composer/autoload_psr4.php | 15 + Upyun/vendor/composer/autoload_real.php | 70 + Upyun/vendor/composer/autoload_static.php | 158 +++ Upyun/vendor/composer/installed.json | 241 ++++ Upyun/vendor/guzzlehttp/guzzle/CHANGELOG.md | 1264 +++++++++++++++++ Upyun/vendor/guzzlehttp/guzzle/LICENSE | 19 + Upyun/vendor/guzzlehttp/guzzle/README.md | 89 ++ Upyun/vendor/guzzlehttp/guzzle/UPGRADING.md | 1203 ++++++++++++++++ Upyun/vendor/guzzlehttp/guzzle/composer.json | 44 + Upyun/vendor/guzzlehttp/guzzle/src/Client.php | 414 ++++++ .../guzzlehttp/guzzle/src/ClientInterface.php | 84 ++ .../guzzle/src/Cookie/CookieJar.php | 314 ++++ .../guzzle/src/Cookie/CookieJarInterface.php | 84 ++ .../guzzle/src/Cookie/FileCookieJar.php | 90 ++ .../guzzle/src/Cookie/SessionCookieJar.php | 71 + .../guzzle/src/Cookie/SetCookie.php | 404 ++++++ .../src/Exception/BadResponseException.php | 27 + .../guzzle/src/Exception/ClientException.php | 7 + .../guzzle/src/Exception/ConnectException.php | 37 + .../guzzle/src/Exception/GuzzleException.php | 4 + .../guzzle/src/Exception/RequestException.php | 217 +++ .../guzzle/src/Exception/SeekException.php | 27 + .../guzzle/src/Exception/ServerException.php | 7 + .../Exception/TooManyRedirectsException.php | 4 + .../src/Exception/TransferException.php | 4 + .../guzzle/src/Handler/CurlFactory.php | 559 ++++++++ .../src/Handler/CurlFactoryInterface.php | 27 + .../guzzle/src/Handler/CurlHandler.php | 45 + .../guzzle/src/Handler/CurlMultiHandler.php | 197 +++ .../guzzle/src/Handler/EasyHandle.php | 92 ++ .../guzzle/src/Handler/MockHandler.php | 189 +++ .../guzzlehttp/guzzle/src/Handler/Proxy.php | 55 + .../guzzle/src/Handler/StreamHandler.php | 533 +++++++ .../guzzlehttp/guzzle/src/HandlerStack.php | 273 ++++ .../guzzle/src/MessageFormatter.php | 182 +++ .../guzzlehttp/guzzle/src/Middleware.php | 254 ++++ Upyun/vendor/guzzlehttp/guzzle/src/Pool.php | 123 ++ .../guzzle/src/PrepareBodyMiddleware.php | 106 ++ .../guzzle/src/RedirectMiddleware.php | 237 ++++ .../guzzlehttp/guzzle/src/RequestOptions.php | 255 ++++ .../guzzlehttp/guzzle/src/RetryMiddleware.php | 112 ++ .../guzzlehttp/guzzle/src/TransferStats.php | 126 ++ .../guzzlehttp/guzzle/src/UriTemplate.php | 241 ++++ .../guzzlehttp/guzzle/src/functions.php | 331 +++++ .../guzzle/src/functions_include.php | 6 + Upyun/vendor/guzzlehttp/promises/CHANGELOG.md | 65 + Upyun/vendor/guzzlehttp/promises/LICENSE | 19 + Upyun/vendor/guzzlehttp/promises/Makefile | 13 + Upyun/vendor/guzzlehttp/promises/README.md | 504 +++++++ .../vendor/guzzlehttp/promises/composer.json | 34 + .../promises/src/AggregateException.php | 16 + .../promises/src/CancellationException.php | 9 + .../guzzlehttp/promises/src/Coroutine.php | 151 ++ .../guzzlehttp/promises/src/EachPromise.php | 229 +++ .../promises/src/FulfilledPromise.php | 82 ++ .../guzzlehttp/promises/src/Promise.php | 280 ++++ .../promises/src/PromiseInterface.php | 93 ++ .../promises/src/PromisorInterface.php | 15 + .../promises/src/RejectedPromise.php | 87 ++ .../promises/src/RejectionException.php | 47 + .../guzzlehttp/promises/src/TaskQueue.php | 66 + .../promises/src/TaskQueueInterface.php | 25 + .../guzzlehttp/promises/src/functions.php | 457 ++++++ .../promises/src/functions_include.php | 6 + Upyun/vendor/guzzlehttp/psr7/CHANGELOG.md | 110 ++ Upyun/vendor/guzzlehttp/psr7/LICENSE | 19 + Upyun/vendor/guzzlehttp/psr7/README.md | 739 ++++++++++ Upyun/vendor/guzzlehttp/psr7/composer.json | 39 + .../guzzlehttp/psr7/src/AppendStream.php | 233 +++ .../guzzlehttp/psr7/src/BufferStream.php | 137 ++ .../guzzlehttp/psr7/src/CachingStream.php | 138 ++ .../guzzlehttp/psr7/src/DroppingStream.php | 42 + Upyun/vendor/guzzlehttp/psr7/src/FnStream.php | 149 ++ .../guzzlehttp/psr7/src/InflateStream.php | 52 + .../guzzlehttp/psr7/src/LazyOpenStream.php | 39 + .../guzzlehttp/psr7/src/LimitStream.php | 155 ++ .../guzzlehttp/psr7/src/MessageTrait.php | 183 +++ .../guzzlehttp/psr7/src/MultipartStream.php | 153 ++ .../guzzlehttp/psr7/src/NoSeekStream.php | 22 + .../vendor/guzzlehttp/psr7/src/PumpStream.php | 165 +++ Upyun/vendor/guzzlehttp/psr7/src/Request.php | 142 ++ Upyun/vendor/guzzlehttp/psr7/src/Response.php | 132 ++ .../guzzlehttp/psr7/src/ServerRequest.php | 358 +++++ Upyun/vendor/guzzlehttp/psr7/src/Stream.php | 257 ++++ .../psr7/src/StreamDecoratorTrait.php | 149 ++ .../guzzlehttp/psr7/src/StreamWrapper.php | 121 ++ .../guzzlehttp/psr7/src/UploadedFile.php | 316 +++++ Upyun/vendor/guzzlehttp/psr7/src/Uri.php | 702 +++++++++ .../guzzlehttp/psr7/src/UriNormalizer.php | 216 +++ .../guzzlehttp/psr7/src/UriResolver.php | 219 +++ .../vendor/guzzlehttp/psr7/src/functions.php | 828 +++++++++++ .../guzzlehttp/psr7/src/functions_include.php | 6 + Upyun/vendor/psr/http-message/CHANGELOG.md | 36 + Upyun/vendor/psr/http-message/LICENSE | 19 + Upyun/vendor/psr/http-message/README.md | 13 + Upyun/vendor/psr/http-message/composer.json | 26 + .../psr/http-message/src/MessageInterface.php | 187 +++ .../psr/http-message/src/RequestInterface.php | 129 ++ .../http-message/src/ResponseInterface.php | 68 + .../src/ServerRequestInterface.php | 261 ++++ .../psr/http-message/src/StreamInterface.php | 158 +++ .../src/UploadedFileInterface.php | 123 ++ .../psr/http-message/src/UriInterface.php | 323 +++++ "\346\210\252\345\233\276.png" | Bin 0 -> 63692 bytes 122 files changed, 20689 insertions(+), 1024 deletions(-) delete mode 100644 SDK/upyun.class.php create mode 100644 Upyun/src/Upyun/Api/Form.php create mode 100644 Upyun/src/Upyun/Api/Pretreat.php create mode 100644 Upyun/src/Upyun/Api/Rest.php create mode 100644 Upyun/src/Upyun/Api/SyncVideo.php create mode 100644 Upyun/src/Upyun/Config.php create mode 100644 Upyun/src/Upyun/Signature.php create mode 100644 Upyun/src/Upyun/Uploader.php create mode 100644 Upyun/src/Upyun/Upyun.php create mode 100644 Upyun/src/Upyun/Util.php create mode 100644 Upyun/vendor/autoload.php create mode 100644 Upyun/vendor/composer/ClassLoader.php create mode 100644 Upyun/vendor/composer/LICENSE create mode 100644 Upyun/vendor/composer/autoload_classmap.php create mode 100644 Upyun/vendor/composer/autoload_files.php create mode 100644 Upyun/vendor/composer/autoload_namespaces.php create mode 100644 Upyun/vendor/composer/autoload_psr4.php create mode 100644 Upyun/vendor/composer/autoload_real.php create mode 100644 Upyun/vendor/composer/autoload_static.php create mode 100644 Upyun/vendor/composer/installed.json create mode 100644 Upyun/vendor/guzzlehttp/guzzle/CHANGELOG.md create mode 100644 Upyun/vendor/guzzlehttp/guzzle/LICENSE create mode 100644 Upyun/vendor/guzzlehttp/guzzle/README.md create mode 100644 Upyun/vendor/guzzlehttp/guzzle/UPGRADING.md create mode 100644 Upyun/vendor/guzzlehttp/guzzle/composer.json create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Client.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/ClientInterface.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Exception/ClientException.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Exception/ConnectException.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Exception/TransferException.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/HandlerStack.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/MessageFormatter.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Middleware.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/Pool.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/RequestOptions.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/RetryMiddleware.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/TransferStats.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/UriTemplate.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/functions.php create mode 100644 Upyun/vendor/guzzlehttp/guzzle/src/functions_include.php create mode 100644 Upyun/vendor/guzzlehttp/promises/CHANGELOG.md create mode 100644 Upyun/vendor/guzzlehttp/promises/LICENSE create mode 100644 Upyun/vendor/guzzlehttp/promises/Makefile create mode 100644 Upyun/vendor/guzzlehttp/promises/README.md create mode 100644 Upyun/vendor/guzzlehttp/promises/composer.json create mode 100644 Upyun/vendor/guzzlehttp/promises/src/AggregateException.php create mode 100644 Upyun/vendor/guzzlehttp/promises/src/CancellationException.php create mode 100644 Upyun/vendor/guzzlehttp/promises/src/Coroutine.php create mode 100644 Upyun/vendor/guzzlehttp/promises/src/EachPromise.php create mode 100644 Upyun/vendor/guzzlehttp/promises/src/FulfilledPromise.php create mode 100644 Upyun/vendor/guzzlehttp/promises/src/Promise.php create mode 100644 Upyun/vendor/guzzlehttp/promises/src/PromiseInterface.php create mode 100644 Upyun/vendor/guzzlehttp/promises/src/PromisorInterface.php create mode 100644 Upyun/vendor/guzzlehttp/promises/src/RejectedPromise.php create mode 100644 Upyun/vendor/guzzlehttp/promises/src/RejectionException.php create mode 100644 Upyun/vendor/guzzlehttp/promises/src/TaskQueue.php create mode 100644 Upyun/vendor/guzzlehttp/promises/src/TaskQueueInterface.php create mode 100644 Upyun/vendor/guzzlehttp/promises/src/functions.php create mode 100644 Upyun/vendor/guzzlehttp/promises/src/functions_include.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/CHANGELOG.md create mode 100644 Upyun/vendor/guzzlehttp/psr7/LICENSE create mode 100644 Upyun/vendor/guzzlehttp/psr7/README.md create mode 100644 Upyun/vendor/guzzlehttp/psr7/composer.json create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/AppendStream.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/BufferStream.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/CachingStream.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/DroppingStream.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/FnStream.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/InflateStream.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/LazyOpenStream.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/LimitStream.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/MessageTrait.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/MultipartStream.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/NoSeekStream.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/PumpStream.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/Request.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/Response.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/ServerRequest.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/Stream.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/StreamWrapper.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/UploadedFile.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/Uri.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/UriNormalizer.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/UriResolver.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/functions.php create mode 100644 Upyun/vendor/guzzlehttp/psr7/src/functions_include.php create mode 100644 Upyun/vendor/psr/http-message/CHANGELOG.md create mode 100644 Upyun/vendor/psr/http-message/LICENSE create mode 100644 Upyun/vendor/psr/http-message/README.md create mode 100644 Upyun/vendor/psr/http-message/composer.json create mode 100644 Upyun/vendor/psr/http-message/src/MessageInterface.php create mode 100644 Upyun/vendor/psr/http-message/src/RequestInterface.php create mode 100644 Upyun/vendor/psr/http-message/src/ResponseInterface.php create mode 100644 Upyun/vendor/psr/http-message/src/ServerRequestInterface.php create mode 100644 Upyun/vendor/psr/http-message/src/StreamInterface.php create mode 100644 Upyun/vendor/psr/http-message/src/UploadedFileInterface.php create mode 100644 Upyun/vendor/psr/http-message/src/UriInterface.php create mode 100644 "\346\210\252\345\233\276.png" diff --git a/Plugin.php b/Plugin.php index 64aaf11..64b0999 100644 --- a/Plugin.php +++ b/Plugin.php @@ -1,23 +1,27 @@ Ryan - * + * 又拍云(UpYun)文件管理 Modified by SSpirits + * * @package UpyunFile * @author codesee - * @version 0.8.1 + * @version 0.9.0 * @link http://pengzhiyong.com * @dependence 1.0-* - * @date 2015-12-26 + * @date 2019-1-20 */ +use Upyun\Upyun; +use Upyun\Config; + class UpyunFile_Plugin implements Typecho_Plugin_Interface { - //上传文件目录 - const UPLOAD_DIR = '/usr/uploads' ; - + //上传文件目录 + const UPLOAD_DIR = '/typecho/uploads'; + const IMG_EXT = array('JPG', 'JPEG', 'PNG', 'BMP'); + /** * 激活插件方法,如果激活失败,直接抛出异常 - * + * * @access public * @return void * @throws Typecho_Plugin_Exception @@ -29,68 +33,93 @@ public static function activate() Typecho_Plugin::factory('Widget_Upload')->deleteHandle = array('UpyunFile_Plugin', 'deleteHandle'); Typecho_Plugin::factory('Widget_Upload')->attachmentHandle = array('UpyunFile_Plugin', 'attachmentHandle'); Typecho_Plugin::factory('Widget_Upload')->attachmentDataHandle = array('UpyunFile_Plugin', 'attachmentDataHandle'); - Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array('UpyunFile_Plugin','replace'); - return _t('插件已经激活,请正确设置插件'); + Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array('UpyunFile_Plugin', 'replace'); + Typecho_Plugin::factory('Widget_Archive')->beforeRender = array('UpyunFile_Plugin', 'Widget_Archive_beforeRender'); + return _t('插件已经激活,请正确设置插件'); } - + /** * 禁用插件方法,如果禁用失败,直接抛出异常 - * + * * @static * @access public * @return void * @throws Typecho_Plugin_Exception */ - public static function deactivate(){ - return _t('插件已被禁用'); - } - + public static function deactivate() + { + return _t('插件已被禁用'); + } + /** * 获取插件配置面板 - * + * * @access public * @param Typecho_Widget_Helper_Form $form 配置面板 * @return void */ public static function config(Typecho_Widget_Helper_Form $form) { - $convert = new Typecho_Widget_Helper_Form_Element_Radio('convert' , array('1'=>_t('开启'),'0'=>_t('关闭')),'0',_t('图片链接修改'),_t('把文章中图片的链接修改为又拍云 CDN 链接,启用前请把放在 typecho 上传目录中的图片同步到又拍云中')); - $form->addInput($convert); - $upyundomain = new Typecho_Widget_Helper_Form_Element_Text('upyundomain', NULL, 'http://', _t('绑定域名:'), _t('该绑定域名为绑定Upyun服务的域名,由Upyun提供,注意以http://开头,最后不要加/')); - $form->addInput($upyundomain->addRule('required',_t('您必须填写绑定域名,它是由Upyun提供'))); - - $upyunpathmode = new Typecho_Widget_Helper_Form_Element_Radio( + $convert = new Typecho_Widget_Helper_Form_Element_Radio('convert', array('1' => _t('开启'), '0' => _t('关闭')), '0', _t('图片链接修改'), _t('把文章中图片的链接修改为又拍云 CDN 链接,启用前请把放在 typecho 上传目录中的图片同步到又拍云中')); + $form->addInput($convert); + $upyundomain = new Typecho_Widget_Helper_Form_Element_Text('upyundomain', NULL, 'https://', _t('绑定域名:'), _t('该绑定域名为绑定Upyun服务的域名,由Upyun提供,注意以 http(s):// 开头,最后不要加 /')); + $form->addInput($upyundomain->addRule('required', _t('您必须填写绑定域名,它是由 Upyun 提供'))); + + $upyunpathmode = new Typecho_Widget_Helper_Form_Element_Radio( 'mode', - array('typecho' => _t('Typecho结构(' . self::getUploadDir() . '/年/月/文件名)'),'simple' => _t('精简结构(/年/月/文件名)')), + array('typecho' => _t('Typecho结构(' . self::getUploadDir() . '/年/月/文件名)'), 'simple' => _t('精简结构(/年/月/文件名)')), 'typecho', _t('目录结构模式'), - _t('默认为Typecho结构模式') + _t('默认为 Typecho 结构模式') ); - - $form->addInput($upyunpathmode); - + + $form->addInput($upyunpathmode); + $upyunhost = new Typecho_Widget_Helper_Form_Element_Text('upyunhost', NULL, NULL, _t('服务名称:')); - $upyunhost->input->setAttribute('class','mini'); - $form->addInput($upyunhost->addRule('required',_t('您必须填写服务名称,它是由Upyun提供'))); - + $upyunhost->input->setAttribute('class', 'mini'); + $form->addInput($upyunhost->addRule('required', _t('您必须填写服务名称,它是由Upyun提供'))); + $upyunuser = new Typecho_Widget_Helper_Form_Element_Text('upyunuser', NULL, NULL, _t('操作员:')); - $upyunuser->input->setAttribute('class','mini'); - $form->addInput($upyunuser->addRule('required',_t('您必须填写操作员,它是由Upyun提供'))); + $upyunuser->input->setAttribute('class', 'mini'); + $form->addInput($upyunuser->addRule('required', _t('您必须填写操作员,它是由 Upyun 提供'))); $upyunpwd = new Typecho_Widget_Helper_Form_Element_Password('upyunpwd', NULL, NULL, _t('密码:')); - $form->addInput($upyunpwd->addRule('required',_t('您必须填写密码,它是由Upyun提供')) - ->addRule(array('UpyunFile_Plugin', 'validate'), _t('验证不通过,请核对Upyun操作员和密码是否输入正确'))); + $form->addInput($upyunpwd->addRule('required', _t('您必须填写密码,它是由 Upyun 提供')) + ->addRule(array('UpyunFile_Plugin', 'validate'), _t('验证不通过,请核对 Upyun 操作员和密码是否输入正确'))); + + $convertPic = new Typecho_Widget_Helper_Form_Element_Radio('convertPic', array('1' => _t('开启'), '0' => _t('关闭')), '0', _t('又拍云图片处理'), _t('启用本功能需在又拍云控制台中创建缩略图版本,又拍云文档:https://help.upyun.com/knowledge-base/image/#thumb')); + $form->addInput($convertPic); + + $thumbId = new Typecho_Widget_Helper_Form_Element_Text('thumbId', NULL, NULL, _t('图片处理 - 缩略图版本名称:'), _t('启用又拍云图片处理必须正确填写缩略图版本名称')); + $thumbId->input->setAttribute('class', 'mini'); + $form->addInput($thumbId); + + $outputFormat = new Typecho_Widget_Helper_Form_Element_Text('outputFormat', NULL, NULL, _t('图片处理 - 转码输出格式:'), _t('如:jpg、png、webp 等,不填即为源文件拓展名')); + $outputFormat->input->setAttribute('class', 'mini'); + $form->addInput($outputFormat); + + $addToken = new Typecho_Widget_Helper_Form_Element_Radio('addToken', array('1' => _t('开启'), '0' => _t('关闭')), '0', _t('又拍云 Token 防盗链'), _t('启用本功能需在又拍云控制台中启用 Token 防盗链')); + $form->addInput($addToken); + + $secret = new Typecho_Widget_Helper_Form_Element_Text('secret', NULL, NULL, _t('密钥'), _t('启用又拍云 Token 防盗链必须正确填写又拍云控制台中设置的密钥')); + $form->addInput($secret); + + $etime = new Typecho_Widget_Helper_Form_Element_Text('etime', NULL, NULL, _t('签名过期时间'), _t('单位为秒')); + $etime->input->setAttribute('class', 'mini'); + $form->addInput($etime); } - + /** * 个人用户的配置面板 - * + * * @access public * @param Typecho_Widget_Helper_Form $form * @return void */ - public static function personalConfig(Typecho_Widget_Helper_Form $form){} - + public static function personalConfig(Typecho_Widget_Helper_Form $form) + { + } + /** * 上传文件处理函数 * @@ -103,7 +132,7 @@ public static function uploadHandle($file) if (empty($file['name'])) { return false; } - + //获取扩展名 $ext = self::getSafeName($file['name']); @@ -112,40 +141,54 @@ public static function uploadHandle($file) } $options = Typecho_Widget::widget('Widget_Options'); + $date = new Typecho_Date($options->gmtTime); - + //构建路径 /year/month/ $path = '/' . $date->year . '/' . $date->month; $settings = $options->plugin('UpyunFile'); - if($settings->mode == 'typecho'){ - $path = self::getUploadDir() . $path; - } - + $thumbId = ''; + if ($settings->mode == 'typecho') { + $path = self::getUploadDir() . $path; + } + //获取文件名及文件路径 - $fileName = sprintf('%u', crc32(uniqid())) . '.' . $ext; - $path = $path . '/' . $fileName; - - $uploadfile = self::getUploadFile($file); - - if (!isset($uploadfile)) { - return false; - } - else{ - //上传文件 - $upyun = self::upyunInit(); - $fh = fopen($uploadfile , 'rb'); - $upyun->writeFile($path, $fh ,TRUE); - fclose($fh); - } - - if (!isset($file['size'])){ - $fileInfo = $upyun->getFileInfo($path); - $file['size'] = $fileInfo['x-upyun-file-size']; - } + if (!empty($settings->convertPic) && !empty($settings->outputFormat)) { + foreach (self::IMG_EXT as $item) { + if (strcasecmp($ext, $item) == 0) { + $ext = $settings->outputFormat; + $thumbId = $settings->thumbId; + break; + } + } + } + $fileName = sprintf('%u', crc32(uniqid())) . ".$ext"; + $path = $path . '/' . $fileName; + + $uploadfile = self::getUploadFile($file); + + if (!isset($uploadfile)) { + return false; + } else { + //上传文件 + $upyun = self::upyunInit(); + $fh = fopen($uploadfile, 'rb'); + if (empty($thumbId)) { + $upyun->write($path, $fh); + } else { + $upyun->write($path, $fh, array('x-gmkerl-thumb' => $settings->thumbId)); + } + fclose($fh); + } + + if (!isset($file['size'])) { + $fileInfo = $upyun->info($path); + $file['size'] = $fileInfo['x-upyun-file-size']; + } //返回相对存储路径 return array( - 'name' => $file['name'], + 'name' => empty($settings->outputFormat) ? $file['name'] : substr($file['name'], 0, strrpos($file['name'], '.') + 1) . $ext, 'path' => $path, 'size' => $file['size'], 'type' => $ext, @@ -166,7 +209,7 @@ public static function modifyHandle($content, $file) if (empty($file['name'])) { return false; } - + //获取扩展名 $ext = self::getSafeName($file['name']); @@ -176,24 +219,37 @@ public static function modifyHandle($content, $file) //获取文件路径 $path = $content['attachment']->path; - - $uploadfile = self::getUploadFile($file); - - if (!isset($uploadfile)) { - return false; - } - else{ - //修改文件 - $upyun = self::upyunInit(); - $fh = fopen($uploadfile , 'rb'); - $upyun->writeFile($path, $fh ,TRUE); - fclose($fh); - } - - if (!isset($file['size'])){ - $fileInfo = $upyun->getFileInfo($path); - $file['size'] = $fileInfo['x-upyun-file-size']; - } + + $uploadfile = self::getUploadFile($file); + + if (!isset($uploadfile)) { + return false; + } else { + //修改文件 + $settings = Typecho_Widget::widget('Widget_Options')->plugin('UpyunFile'); + $upyun = self::upyunInit(); + $thumbId = ''; + if (!empty($settings->convertPic) && !empty($settings->outputFormat)) { + foreach (self::IMG_EXT as $item) { + if (strcasecmp($ext, $item) == 0) { + $thumbId = $settings->thumbId; + break; + } + } + } + $fh = fopen($uploadfile, 'rb'); + if (empty($thumbId)) { + $upyun->write($path, $fh); + } else { + $upyun->write($path, $fh, array('x-gmkerl-thumb' => $settings->thumbId)); + } + fclose($fh); + } + + if (!isset($file['size'])) { + $fileInfo = $upyun->info($path); + $file['size'] = $fileInfo['x-upyun-file-size']; + } //返回相对存储路径 return array( @@ -215,7 +271,7 @@ public static function modifyHandle($content, $file) public static function deleteHandle(array $content) { $upyun = self::upyunInit(); - $path = $content['attachment']->path; + $path = $content['attachment']->path; return $upyun->delete($path); } @@ -243,36 +299,36 @@ public static function attachmentHandle(array $content) public static function attachmentDataHandle(array $content) { $upyun = self::upyunInit(); - return $upyun->getFileInfo($content['attachment']->path); + return $upyun->info($content['attachment']->path); } - /** + /** * 验证Upyun签名 - * + * * @access public - * + * * @return boolean */ - public static function validate() - { - $host = Typecho_Request::getInstance()->upyunhost; - $user = Typecho_Request::getInstance()->upyunuser; - $pwd = Typecho_Request::getInstance()->upyunpwd; - - $hostUsage = 0; - - try{ - require_once 'SDK/upyun.class.php'; - $upyun = new UpYun($host, $user, $pwd); - $hostUsage = (int)$upyun->getFolderUsage('/'); - } - catch(Exception $e){ - $hostUsage = -1; - } - - return $hostUsage >= 0; - } - + public static function validate() + { + $host = Typecho_Request::getInstance()->upyunhost; + $user = Typecho_Request::getInstance()->upyunuser; + $pwd = Typecho_Request::getInstance()->upyunpwd; + + $hostUsage = 0; + + try { + require_once 'Upyun/vendor/autoload.php'; + $serviceConfig = new Config($host, $user, $pwd); + $upyun = new Upyun($serviceConfig); + $hostUsage = (int)$upyun->usage(); + } catch (Exception $e) { + $hostUsage = -1; + } + + return $hostUsage >= 0; + } + /** * Upyun初始化 * @@ -282,396 +338,396 @@ public static function validate() public static function upyunInit() { $options = Typecho_Widget::widget('Widget_Options')->plugin('UpyunFile'); - require_once 'SDK/upyun.class.php'; - return new UpYun($options->upyunhost, $options->upyunuser, $options->upyunpwd); + require_once 'Upyun/vendor/autoload.php'; + $serviceConfig = new Config($options->upyunhost, $options->upyunuser, $options->upyunpwd); + return new Upyun($serviceConfig); } - - /** + + /** * 获取上传文件 * - * @param array $file 上传的文件 + * @param array $file 上传的文件 * @access private * @return string */ - private static function getUploadFile($file) - { - return isset($file['tmp_name']) ? $file['tmp_name'] : (isset($file['bytes']) ? $file['bytes'] : (isset($file['bits']) ? $file['bits'] : '')); - } - - /** - * 获取安全的文件名 - * - * @param string $name + private static function getUploadFile($file) + { + return isset($file['tmp_name']) ? $file['tmp_name'] : (isset($file['bytes']) ? $file['bytes'] : (isset($file['bits']) ? $file['bits'] : '')); + } + + /** + * 获取安全的文件名 + * + * @param string $name * @static * @access private * @return string */ - private static function getSafeName(&$name) - { - $name = str_replace(array('"', '<', '>'), '', $name); + private static function getSafeName(&$name) + { + $name = str_replace(array('"', '<', '>'), '', $name); $name = str_replace('\\', '/', $name); $name = false === strpos($name, '/') ? ('a' . $name) : str_replace('/', '/a', $name); $info = pathinfo($name); $name = substr($info['basename'], 1); - + return isset($info['extension']) ? strtolower($info['extension']) : ''; - } - - /** - *获取文件上传目录 - * @access private - * @return string - */ - private static function getUploadDir() - { - if(defined('__TYPECHO_UPLOAD_DIR__')) - { - return __TYPECHO_UPLOAD_DIR__; - } - else{ - return self::UPLOAD_DIR; - } - } - - /** - *获取文件Mime类型,处理掉异常 - * @access private - * @return string - */ - private static function mimeContentType($fileName){ - //TODO:避免该方法 - //避免Typecho mime-content-type引发的异常 - /*异常详情: - *Warning: mime_content_type(/path/filename.jpg) [function.mime-content-type]: - *failed to open stream: No such file or directory in /webroot/var/Typecho/Common.php on line 1058 - */ - @$mime = Typecho_Common::mimeContentType($fileName); - - if(!$mime){ - return self::getMime($fileName); - } - else{ - return $mime; - } - } - - /** - *获取文件Mime类型 - * @access private - * @return string - */ - private static function getMime($fileName){ - $mimeTypes = array( - 'ez' => 'application/andrew-inset', - 'csm' => 'application/cu-seeme', - 'cu' => 'application/cu-seeme', - 'tsp' => 'application/dsptype', - 'spl' => 'application/x-futuresplash', - 'hta' => 'application/hta', - 'cpt' => 'image/x-corelphotopaint', - 'hqx' => 'application/mac-binhex40', - 'nb' => 'application/mathematica', - 'mdb' => 'application/msaccess', - 'doc' => 'application/msword', - 'dot' => 'application/msword', - 'bin' => 'application/octet-stream', - 'oda' => 'application/oda', - 'ogg' => 'application/ogg', - 'prf' => 'application/pics-rules', - 'key' => 'application/pgp-keys', - 'pdf' => 'application/pdf', - 'pgp' => 'application/pgp-signature', - 'ps' => 'application/postscript', - 'ai' => 'application/postscript', - 'eps' => 'application/postscript', - 'rss' => 'application/rss+xml', - 'rtf' => 'text/rtf', - 'smi' => 'application/smil', - 'smil' => 'application/smil', - 'wp5' => 'application/wordperfect5.1', - 'xht' => 'application/xhtml+xml', - 'xhtml' => 'application/xhtml+xml', - 'zip' => 'application/zip', - 'cdy' => 'application/vnd.cinderella', - 'mif' => 'application/x-mif', - 'xls' => 'application/vnd.ms-excel', - 'xlb' => 'application/vnd.ms-excel', - 'cat' => 'application/vnd.ms-pki.seccat', - 'stl' => 'application/vnd.ms-pki.stl', - 'ppt' => 'application/vnd.ms-powerpoint', - 'pps' => 'application/vnd.ms-powerpoint', - 'pot' => 'application/vnd.ms-powerpoint', - 'sdc' => 'application/vnd.stardivision.calc', - 'sda' => 'application/vnd.stardivision.draw', - 'sdd' => 'application/vnd.stardivision.impress', - 'sdp' => 'application/vnd.stardivision.impress', - 'smf' => 'application/vnd.stardivision.math', - 'sdw' => 'application/vnd.stardivision.writer', - 'vor' => 'application/vnd.stardivision.writer', - 'sgl' => 'application/vnd.stardivision.writer-global', - 'sxc' => 'application/vnd.sun.xml.calc', - 'stc' => 'application/vnd.sun.xml.calc.template', - 'sxd' => 'application/vnd.sun.xml.draw', - 'std' => 'application/vnd.sun.xml.draw.template', - 'sxi' => 'application/vnd.sun.xml.impress', - 'sti' => 'application/vnd.sun.xml.impress.template', - 'sxm' => 'application/vnd.sun.xml.math', - 'sxw' => 'application/vnd.sun.xml.writer', - 'sxg' => 'application/vnd.sun.xml.writer.global', - 'stw' => 'application/vnd.sun.xml.writer.template', - 'sis' => 'application/vnd.symbian.install', - 'wbxml' => 'application/vnd.wap.wbxml', - 'wmlc' => 'application/vnd.wap.wmlc', - 'wmlsc' => 'application/vnd.wap.wmlscriptc', - 'wk' => 'application/x-123', - 'dmg' => 'application/x-apple-diskimage', - 'bcpio' => 'application/x-bcpio', - 'torrent' => 'application/x-bittorrent', - 'cdf' => 'application/x-cdf', - 'vcd' => 'application/x-cdlink', - 'pgn' => 'application/x-chess-pgn', - 'cpio' => 'application/x-cpio', - 'csh' => 'text/x-csh', - 'deb' => 'application/x-debian-package', - 'dcr' => 'application/x-director', - 'dir' => 'application/x-director', - 'dxr' => 'application/x-director', - 'wad' => 'application/x-doom', - 'dms' => 'application/x-dms', - 'dvi' => 'application/x-dvi', - 'pfa' => 'application/x-font', - 'pfb' => 'application/x-font', - 'gsf' => 'application/x-font', - 'pcf' => 'application/x-font', - 'pcf.Z' => 'application/x-font', - 'gnumeric' => 'application/x-gnumeric', - 'sgf' => 'application/x-go-sgf', - 'gcf' => 'application/x-graphing-calculator', - 'gtar' => 'application/x-gtar', - 'tgz' => 'application/x-gtar', - 'taz' => 'application/x-gtar', - 'gz' => 'application/x-gtar', - 'hdf' => 'application/x-hdf', - 'phtml' => 'application/x-httpd-php', - 'pht' => 'application/x-httpd-php', - 'php' => 'application/x-httpd-php', - 'phps' => 'application/x-httpd-php-source', - 'php3' => 'application/x-httpd-php3', - 'php3p' => 'application/x-httpd-php3-preprocessed', - 'php4' => 'application/x-httpd-php4', - 'ica' => 'application/x-ica', - 'ins' => 'application/x-internet-signup', - 'isp' => 'application/x-internet-signup', - 'iii' => 'application/x-iphone', - 'jar' => 'application/x-java-archive', - 'jnlp' => 'application/x-java-jnlp-file', - 'ser' => 'application/x-java-serialized-object', - 'class' => 'application/x-java-vm', - 'js' => 'application/x-javascript', - 'chrt' => 'application/x-kchart', - 'kil' => 'application/x-killustrator', - 'kpr' => 'application/x-kpresenter', - 'kpt' => 'application/x-kpresenter', - 'skp' => 'application/x-koan', - 'skd' => 'application/x-koan', - 'skt' => 'application/x-koan', - 'skm' => 'application/x-koan', - 'ksp' => 'application/x-kspread', - 'kwd' => 'application/x-kword', - 'kwt' => 'application/x-kword', - 'latex' => 'application/x-latex', - 'lha' => 'application/x-lha', - 'lzh' => 'application/x-lzh', - 'lzx' => 'application/x-lzx', - 'frm' => 'application/x-maker', - 'maker' => 'application/x-maker', - 'frame' => 'application/x-maker', - 'fm' => 'application/x-maker', - 'fb' => 'application/x-maker', - 'book' => 'application/x-maker', - 'fbdoc' => 'application/x-maker', - 'wmz' => 'application/x-ms-wmz', - 'wmd' => 'application/x-ms-wmd', - 'com' => 'application/x-msdos-program', - 'exe' => 'application/x-msdos-program', - 'bat' => 'application/x-msdos-program', - 'dll' => 'application/x-msdos-program', - 'msi' => 'application/x-msi', - 'nc' => 'application/x-netcdf', - 'pac' => 'application/x-ns-proxy-autoconfig', - 'nwc' => 'application/x-nwc', - 'o' => 'application/x-object', - 'oza' => 'application/x-oz-application', - 'pl' => 'application/x-perl', - 'pm' => 'application/x-perl', - 'p7r' => 'application/x-pkcs7-certreqresp', - 'crl' => 'application/x-pkcs7-crl', - 'qtl' => 'application/x-quicktimeplayer', - 'rpm' => 'audio/x-pn-realaudio-plugin', - 'shar' => 'application/x-shar', - 'swf' => 'application/x-shockwave-flash', - 'swfl' => 'application/x-shockwave-flash', - 'sh' => 'text/x-sh', - 'sit' => 'application/x-stuffit', - 'sv4cpio' => 'application/x-sv4cpio', - 'sv4crc' => 'application/x-sv4crc', - 'tar' => 'application/x-tar', - 'tcl' => 'text/x-tcl', - 'tex' => 'text/x-tex', - 'gf' => 'application/x-tex-gf', - 'pk' => 'application/x-tex-pk', - 'texinfo' => 'application/x-texinfo', - 'texi' => 'application/x-texinfo', - '~' => 'application/x-trash', - '%' => 'application/x-trash', - 'bak' => 'application/x-trash', - 'old' => 'application/x-trash', - 'sik' => 'application/x-trash', - 't' => 'application/x-troff', - 'tr' => 'application/x-troff', - 'roff' => 'application/x-troff', - 'man' => 'application/x-troff-man', - 'me' => 'application/x-troff-me', - 'ms' => 'application/x-troff-ms', - 'ustar' => 'application/x-ustar', - 'src' => 'application/x-wais-source', - 'wz' => 'application/x-wingz', - 'crt' => 'application/x-x509-ca-cert', - 'fig' => 'application/x-xfig', - 'au' => 'audio/basic', - 'snd' => 'audio/basic', - 'mid' => 'audio/midi', - 'midi' => 'audio/midi', - 'kar' => 'audio/midi', - 'mpga' => 'audio/mpeg', - 'mpega' => 'audio/mpeg', - 'mp2' => 'audio/mpeg', - 'mp3' => 'audio/mpeg', - 'm3u' => 'audio/x-mpegurl', - 'sid' => 'audio/prs.sid', - 'aif' => 'audio/x-aiff', - 'aiff' => 'audio/x-aiff', - 'aifc' => 'audio/x-aiff', - 'gsm' => 'audio/x-gsm', - 'wma' => 'audio/x-ms-wma', - 'wax' => 'audio/x-ms-wax', - 'ra' => 'audio/x-realaudio', - 'rm' => 'audio/x-pn-realaudio', - 'ram' => 'audio/x-pn-realaudio', - 'pls' => 'audio/x-scpls', - 'sd2' => 'audio/x-sd2', - 'wav' => 'audio/x-wav', - 'pdb' => 'chemical/x-pdb', - 'xyz' => 'chemical/x-xyz', - 'bmp' => 'image/x-ms-bmp', - 'gif' => 'image/gif', - 'ief' => 'image/ief', - 'jpeg' => 'image/jpeg', - 'jpg' => 'image/jpeg', - 'jpe' => 'image/jpeg', - 'pcx' => 'image/pcx', - 'png' => 'image/png', - 'svg' => 'image/svg+xml', - 'svgz' => 'image/svg+xml', - 'tiff' => 'image/tiff', - 'tif' => 'image/tiff', - 'wbmp' => 'image/vnd.wap.wbmp', - 'ras' => 'image/x-cmu-raster', - 'cdr' => 'image/x-coreldraw', - 'pat' => 'image/x-coreldrawpattern', - 'cdt' => 'image/x-coreldrawtemplate', - 'djvu' => 'image/x-djvu', - 'djv' => 'image/x-djvu', - 'ico' => 'image/x-icon', - 'art' => 'image/x-jg', - 'jng' => 'image/x-jng', - 'psd' => 'image/x-photoshop', - 'pnm' => 'image/x-portable-anymap', - 'pbm' => 'image/x-portable-bitmap', - 'pgm' => 'image/x-portable-graymap', - 'ppm' => 'image/x-portable-pixmap', - 'rgb' => 'image/x-rgb', - 'xbm' => 'image/x-xbitmap', - 'xpm' => 'image/x-xpixmap', - 'xwd' => 'image/x-xwindowdump', - 'igs' => 'model/iges', - 'iges' => 'model/iges', - 'msh' => 'model/mesh', - 'mesh' => 'model/mesh', - 'silo' => 'model/mesh', - 'wrl' => 'x-world/x-vrml', - 'vrml' => 'x-world/x-vrml', - 'csv' => 'text/comma-separated-values', - 'css' => 'text/css', - '323' => 'text/h323', - 'htm' => 'text/html', - 'html' => 'text/html', - 'uls' => 'text/iuls', - 'mml' => 'text/mathml', - 'asc' => 'text/plain', - 'txt' => 'text/plain', - 'text' => 'text/plain', - 'diff' => 'text/plain', - 'rtx' => 'text/richtext', - 'sct' => 'text/scriptlet', - 'wsc' => 'text/scriptlet', - 'tm' => 'text/texmacs', - 'ts' => 'text/texmacs', - 'tsv' => 'text/tab-separated-values', - 'jad' => 'text/vnd.sun.j2me.app-descriptor', - 'wml' => 'text/vnd.wap.wml', - 'wmls' => 'text/vnd.wap.wmlscript', - 'xml' => 'text/xml', - 'xsl' => 'text/xml', - 'h++' => 'text/x-c++hdr', - 'hpp' => 'text/x-c++hdr', - 'hxx' => 'text/x-c++hdr', - 'hh' => 'text/x-c++hdr', - 'c++' => 'text/x-c++src', - 'cpp' => 'text/x-c++src', - 'cxx' => 'text/x-c++src', - 'cc' => 'text/x-c++src', - 'h' => 'text/x-chdr', - 'c' => 'text/x-csrc', - 'java' => 'text/x-java', - 'moc' => 'text/x-moc', - 'p' => 'text/x-pascal', - 'pas' => 'text/x-pascal', - '***' => 'text/x-pcs-***', - 'shtml' => 'text/x-server-parsed-html', - 'etx' => 'text/x-setext', - 'tk' => 'text/x-tcl', - 'ltx' => 'text/x-tex', - 'sty' => 'text/x-tex', - 'cls' => 'text/x-tex', - 'vcs' => 'text/x-vcalendar', - 'vcf' => 'text/x-vcard', - 'dl' => 'video/dl', - 'fli' => 'video/fli', - 'gl' => 'video/gl', - 'mpeg' => 'video/mpeg', - 'mpg' => 'video/mpeg', - 'mpe' => 'video/mpeg', - 'qt' => 'video/quicktime', - 'mov' => 'video/quicktime', - 'mxu' => 'video/vnd.mpegurl', - 'dif' => 'video/x-dv', - 'dv' => 'video/x-dv', - 'lsf' => 'video/x-la-asf', - 'lsx' => 'video/x-la-asf', - 'mng' => 'video/x-mng', - 'asf' => 'video/x-ms-asf', - 'asx' => 'video/x-ms-asf', - 'wm' => 'video/x-ms-wm', - 'wmv' => 'video/x-ms-wmv', - 'wmx' => 'video/x-ms-wmx', - 'wvx' => 'video/x-ms-wvx', - 'avi' => 'video/x-msvideo', - 'movie' => 'video/x-sgi-movie', - 'ice' => 'x-conference/x-cooltalk', - 'vrm' => 'x-world/x-vrml', - 'rar' => 'application/x-rar-compressed', - 'cab' => 'application/vnd.ms-cab-compressed' + } + + /** + *获取文件上传目录 + * @access private + * @return string + */ + private static function getUploadDir() + { + if (defined('__TYPECHO_UPLOAD_DIR__')) { + return __TYPECHO_UPLOAD_DIR__; + } else { + return self::UPLOAD_DIR; + } + } + + /** + *获取文件Mime类型,处理掉异常 + * @access private + * @return string + */ + private static function mimeContentType($fileName) + { + //TODO:避免该方法 + //避免Typecho mime-content-type引发的异常 + /*异常详情: + *Warning: mime_content_type(/path/filename.jpg) [function.mime-content-type]: + *failed to open stream: No such file or directory in /webroot/var/Typecho/Common.php on line 1058 + */ + @$mime = Typecho_Common::mimeContentType($fileName); + + if (!$mime) { + return self::getMime($fileName); + } else { + return $mime; + } + } + + /** + *获取文件Mime类型 + * @access private + * @return string + */ + private static function getMime($fileName) + { + $mimeTypes = array( + 'ez' => 'application/andrew-inset', + 'csm' => 'application/cu-seeme', + 'cu' => 'application/cu-seeme', + 'tsp' => 'application/dsptype', + 'spl' => 'application/x-futuresplash', + 'hta' => 'application/hta', + 'cpt' => 'image/x-corelphotopaint', + 'hqx' => 'application/mac-binhex40', + 'nb' => 'application/mathematica', + 'mdb' => 'application/msaccess', + 'doc' => 'application/msword', + 'dot' => 'application/msword', + 'bin' => 'application/octet-stream', + 'oda' => 'application/oda', + 'ogg' => 'application/ogg', + 'prf' => 'application/pics-rules', + 'key' => 'application/pgp-keys', + 'pdf' => 'application/pdf', + 'pgp' => 'application/pgp-signature', + 'ps' => 'application/postscript', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'rss' => 'application/rss+xml', + 'rtf' => 'text/rtf', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'wp5' => 'application/wordperfect5.1', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'zip' => 'application/zip', + 'cdy' => 'application/vnd.cinderella', + 'mif' => 'application/x-mif', + 'xls' => 'application/vnd.ms-excel', + 'xlb' => 'application/vnd.ms-excel', + 'cat' => 'application/vnd.ms-pki.seccat', + 'stl' => 'application/vnd.ms-pki.stl', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pps' => 'application/vnd.ms-powerpoint', + 'pot' => 'application/vnd.ms-powerpoint', + 'sdc' => 'application/vnd.stardivision.calc', + 'sda' => 'application/vnd.stardivision.draw', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdp' => 'application/vnd.stardivision.impress', + 'smf' => 'application/vnd.stardivision.math', + 'sdw' => 'application/vnd.stardivision.writer', + 'vor' => 'application/vnd.stardivision.writer', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sxc' => 'application/vnd.sun.xml.calc', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'sxd' => 'application/vnd.sun.xml.draw', + 'std' => 'application/vnd.sun.xml.draw.template', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'sis' => 'application/vnd.symbian.install', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wk' => 'application/x-123', + 'dmg' => 'application/x-apple-diskimage', + 'bcpio' => 'application/x-bcpio', + 'torrent' => 'application/x-bittorrent', + 'cdf' => 'application/x-cdf', + 'vcd' => 'application/x-cdlink', + 'pgn' => 'application/x-chess-pgn', + 'cpio' => 'application/x-cpio', + 'csh' => 'text/x-csh', + 'deb' => 'application/x-debian-package', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'wad' => 'application/x-doom', + 'dms' => 'application/x-dms', + 'dvi' => 'application/x-dvi', + 'pfa' => 'application/x-font', + 'pfb' => 'application/x-font', + 'gsf' => 'application/x-font', + 'pcf' => 'application/x-font', + 'pcf.Z' => 'application/x-font', + 'gnumeric' => 'application/x-gnumeric', + 'sgf' => 'application/x-go-sgf', + 'gcf' => 'application/x-graphing-calculator', + 'gtar' => 'application/x-gtar', + 'tgz' => 'application/x-gtar', + 'taz' => 'application/x-gtar', + 'gz' => 'application/x-gtar', + 'hdf' => 'application/x-hdf', + 'phtml' => 'application/x-httpd-php', + 'pht' => 'application/x-httpd-php', + 'php' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'php3' => 'application/x-httpd-php3', + 'php3p' => 'application/x-httpd-php3-preprocessed', + 'php4' => 'application/x-httpd-php4', + 'ica' => 'application/x-ica', + 'ins' => 'application/x-internet-signup', + 'isp' => 'application/x-internet-signup', + 'iii' => 'application/x-iphone', + 'jar' => 'application/x-java-archive', + 'jnlp' => 'application/x-java-jnlp-file', + 'ser' => 'application/x-java-serialized-object', + 'class' => 'application/x-java-vm', + 'js' => 'application/x-javascript', + 'chrt' => 'application/x-kchart', + 'kil' => 'application/x-killustrator', + 'kpr' => 'application/x-kpresenter', + 'kpt' => 'application/x-kpresenter', + 'skp' => 'application/x-koan', + 'skd' => 'application/x-koan', + 'skt' => 'application/x-koan', + 'skm' => 'application/x-koan', + 'ksp' => 'application/x-kspread', + 'kwd' => 'application/x-kword', + 'kwt' => 'application/x-kword', + 'latex' => 'application/x-latex', + 'lha' => 'application/x-lha', + 'lzh' => 'application/x-lzh', + 'lzx' => 'application/x-lzx', + 'frm' => 'application/x-maker', + 'maker' => 'application/x-maker', + 'frame' => 'application/x-maker', + 'fm' => 'application/x-maker', + 'fb' => 'application/x-maker', + 'book' => 'application/x-maker', + 'fbdoc' => 'application/x-maker', + 'wmz' => 'application/x-ms-wmz', + 'wmd' => 'application/x-ms-wmd', + 'com' => 'application/x-msdos-program', + 'exe' => 'application/x-msdos-program', + 'bat' => 'application/x-msdos-program', + 'dll' => 'application/x-msdos-program', + 'msi' => 'application/x-msi', + 'nc' => 'application/x-netcdf', + 'pac' => 'application/x-ns-proxy-autoconfig', + 'nwc' => 'application/x-nwc', + 'o' => 'application/x-object', + 'oza' => 'application/x-oz-application', + 'pl' => 'application/x-perl', + 'pm' => 'application/x-perl', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'crl' => 'application/x-pkcs7-crl', + 'qtl' => 'application/x-quicktimeplayer', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'shar' => 'application/x-shar', + 'swf' => 'application/x-shockwave-flash', + 'swfl' => 'application/x-shockwave-flash', + 'sh' => 'text/x-sh', + 'sit' => 'application/x-stuffit', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'tar' => 'application/x-tar', + 'tcl' => 'text/x-tcl', + 'tex' => 'text/x-tex', + 'gf' => 'application/x-tex-gf', + 'pk' => 'application/x-tex-pk', + 'texinfo' => 'application/x-texinfo', + 'texi' => 'application/x-texinfo', + '~' => 'application/x-trash', + '%' => 'application/x-trash', + 'bak' => 'application/x-trash', + 'old' => 'application/x-trash', + 'sik' => 'application/x-trash', + 't' => 'application/x-troff', + 'tr' => 'application/x-troff', + 'roff' => 'application/x-troff', + 'man' => 'application/x-troff-man', + 'me' => 'application/x-troff-me', + 'ms' => 'application/x-troff-ms', + 'ustar' => 'application/x-ustar', + 'src' => 'application/x-wais-source', + 'wz' => 'application/x-wingz', + 'crt' => 'application/x-x509-ca-cert', + 'fig' => 'application/x-xfig', + 'au' => 'audio/basic', + 'snd' => 'audio/basic', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'kar' => 'audio/midi', + 'mpga' => 'audio/mpeg', + 'mpega' => 'audio/mpeg', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'm3u' => 'audio/x-mpegurl', + 'sid' => 'audio/prs.sid', + 'aif' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'gsm' => 'audio/x-gsm', + 'wma' => 'audio/x-ms-wma', + 'wax' => 'audio/x-ms-wax', + 'ra' => 'audio/x-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'pls' => 'audio/x-scpls', + 'sd2' => 'audio/x-sd2', + 'wav' => 'audio/x-wav', + 'pdb' => 'chemical/x-pdb', + 'xyz' => 'chemical/x-xyz', + 'bmp' => 'image/x-ms-bmp', + 'gif' => 'image/gif', + 'ief' => 'image/ief', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'pcx' => 'image/pcx', + 'png' => 'image/png', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'wbmp' => 'image/vnd.wap.wbmp', + 'ras' => 'image/x-cmu-raster', + 'cdr' => 'image/x-coreldraw', + 'pat' => 'image/x-coreldrawpattern', + 'cdt' => 'image/x-coreldrawtemplate', + 'djvu' => 'image/x-djvu', + 'djv' => 'image/x-djvu', + 'ico' => 'image/x-icon', + 'art' => 'image/x-jg', + 'jng' => 'image/x-jng', + 'psd' => 'image/x-photoshop', + 'pnm' => 'image/x-portable-anymap', + 'pbm' => 'image/x-portable-bitmap', + 'pgm' => 'image/x-portable-graymap', + 'ppm' => 'image/x-portable-pixmap', + 'rgb' => 'image/x-rgb', + 'xbm' => 'image/x-xbitmap', + 'xpm' => 'image/x-xpixmap', + 'xwd' => 'image/x-xwindowdump', + 'igs' => 'model/iges', + 'iges' => 'model/iges', + 'msh' => 'model/mesh', + 'mesh' => 'model/mesh', + 'silo' => 'model/mesh', + 'wrl' => 'x-world/x-vrml', + 'vrml' => 'x-world/x-vrml', + 'csv' => 'text/comma-separated-values', + 'css' => 'text/css', + '323' => 'text/h323', + 'htm' => 'text/html', + 'html' => 'text/html', + 'uls' => 'text/iuls', + 'mml' => 'text/mathml', + 'asc' => 'text/plain', + 'txt' => 'text/plain', + 'text' => 'text/plain', + 'diff' => 'text/plain', + 'rtx' => 'text/richtext', + 'sct' => 'text/scriptlet', + 'wsc' => 'text/scriptlet', + 'tm' => 'text/texmacs', + 'ts' => 'text/texmacs', + 'tsv' => 'text/tab-separated-values', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'wml' => 'text/vnd.wap.wml', + 'wmls' => 'text/vnd.wap.wmlscript', + 'xml' => 'text/xml', + 'xsl' => 'text/xml', + 'h++' => 'text/x-c++hdr', + 'hpp' => 'text/x-c++hdr', + 'hxx' => 'text/x-c++hdr', + 'hh' => 'text/x-c++hdr', + 'c++' => 'text/x-c++src', + 'cpp' => 'text/x-c++src', + 'cxx' => 'text/x-c++src', + 'cc' => 'text/x-c++src', + 'h' => 'text/x-chdr', + 'c' => 'text/x-csrc', + 'java' => 'text/x-java', + 'moc' => 'text/x-moc', + 'p' => 'text/x-pascal', + 'pas' => 'text/x-pascal', + '***' => 'text/x-pcs-***', + 'shtml' => 'text/x-server-parsed-html', + 'etx' => 'text/x-setext', + 'tk' => 'text/x-tcl', + 'ltx' => 'text/x-tex', + 'sty' => 'text/x-tex', + 'cls' => 'text/x-tex', + 'vcs' => 'text/x-vcalendar', + 'vcf' => 'text/x-vcard', + 'dl' => 'video/dl', + 'fli' => 'video/fli', + 'gl' => 'video/gl', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + 'mxu' => 'video/vnd.mpegurl', + 'dif' => 'video/x-dv', + 'dv' => 'video/x-dv', + 'lsf' => 'video/x-la-asf', + 'lsx' => 'video/x-la-asf', + 'mng' => 'video/x-mng', + 'asf' => 'video/x-ms-asf', + 'asx' => 'video/x-ms-asf', + 'wm' => 'video/x-ms-wm', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wvx' => 'video/x-ms-wvx', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie', + 'ice' => 'x-conference/x-cooltalk', + 'vrm' => 'x-world/x-vrml', + 'rar' => 'application/x-rar-compressed', + 'cab' => 'application/vnd.ms-cab-compressed' ); $part = explode('.', $fileName); @@ -685,35 +741,69 @@ private static function getMime($fileName){ } return 'application/octet-stream'; - } - /** - * 图片链接软修改 - * - * @access public - * @param $content - * @param $class - * @return $content - */ - public static function replace($text, $widget, $lastResult) { - if(Typecho_Widget::widget('Widget_Options')->Plugin('UpyunFile')->convert == 1) { - $text = empty($lastResult) ? $text : $lastResult; - if (($widget instanceof Widget_Archive)||($widget instanceof Widget_Abstract_Comments)) { - $options = Typecho_Widget::widget('Widget_Options'); - $settings = $options->plugin('UpyunFile'); - preg_match_all('/]*src=[\'"]?([^>\'"\s]*)[\'"]?[^>]*>/i',$text,$matches); - if($matches){ - foreach($matches[1] as $val){ - if(strpos($val,rtrim($options->siteUrl, '/')) !== false) { - if($settings->mode === 'typecho'){ - $text=str_replace(rtrim($options->siteUrl, '/') . '/usr/uploads', $settings->upyundomain . '/usr/uploads', $text); - } else { - $text=str_replace(rtrim($options->siteUrl, '/') . '/usr/uploads', $settings->upyundomain, $text); - } - } - } - } - } - } - return $text; - } + } + + /** + * 图片链接软修改 + * + * @access public + * @param $content + * @param $class + * @return $content + */ + public static function replace($text, $widget, $lastResult) + { + $text = empty($lastResult) ? $text : $lastResult; + $options = Typecho_Widget::widget('Widget_Options'); + $settings = $options->plugin('UpyunFile'); + if ($settings->convert == 1) { + if (($widget instanceof Widget_Archive) || ($widget instanceof Widget_Abstract_Comments)) { + preg_match_all('/]*src=[\'"]?([^>\'"\s]*)[\'"]?[^>]*>/i', $text, $matches); + if ($matches) { + foreach ($matches[1] as $val) { + if (strpos($val, rtrim($options->siteUrl, '/')) !== false) { + if ($settings->mode === 'typecho') { + $text = str_replace(rtrim($options->siteUrl, '/') . '/usr/uploads', $settings->upyundomain . self::getUploadDir(), $text); + } else { + $text = str_replace(rtrim($options->siteUrl, '/') . '/usr/uploads', $settings->upyundomain, $text); + } + } + } + } + } + } + return $text; + } + + public static function Widget_Archive_beforeRender() + { + ob_start('UpyunFile_Plugin::beforeRender'); + } + + public static function beforeRender($text) + { + $settings = Typecho_Widget::widget('Widget_Options')->plugin('UpyunFile'); + if ($settings->addToken == 1) { + preg_match_all('/https?:\/\/[-A-Za-z0-9+&@#\/\%?=~_|!:,.;]+[-A-Za-z0-9+&@#\/\%=~_|]/i', $text, $matches); + if ($matches) { + foreach (array_unique($matches[0]) as $val) { + if (strpos($val, $settings->upyundomain) !== false) { + if (isset($_COOKIE["upyun_token_etime"]) && $_COOKIE["upyun_token_etime"] - time() > 180) { + $etime = $_COOKIE["upyun_token_etime"]; + } else { + $etime = time() + $settings->etime; + setcookie("upyun_token_etime", $etime); + } + $sign = substr(md5($settings->secret . '&' . $etime . '&' . parse_url($val, PHP_URL_PATH)), 12, 8) . $etime; + if (parse_url($val, PHP_URL_QUERY) != null) { + $text = str_replace($val, $val . "&_upt=" . $sign, $text); + } else { + $text = str_replace($val, $val . "?_upt=" . $sign, $text); + } + } + } + } + } + return $text; + } } diff --git a/README.md b/README.md index fcf6b0c..c5f2f6f 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,37 @@ UpyunFile ========= -又拍云文件管理插件Typecho版 + 又拍云文件管理插件Typecho版 ####插件简介: - 基于Typecho 0.9(目前已支持Typecho1.0)原上传接口开发,继承原上传所有(上传、修改和删除)功能,有效分流网站流量,减轻系统负载。([更多...](http://pengzhiyong.com/archives/typecho-upyunfile-upgrade.html "UpyunFile详情介绍")) - + 本插件基于 [codesee/UpyunFile](https://github.com/codesee/UpyunFile) 二次开发,解决了原作的一些 bug,并加入了新功能。 + ####插件功能: - 1. 上传、修改和删除功能(基于原上传功能); - 2. CDN内容分发功能,承接又拍云强大的CDN内容分发; - 3. 绑定域名后,享受特有的文件URL路径,加快静态文件加载速度。 + 1. 上传、修改和删除功能 + 2. 静态资源接入又拍云 CDN + 3. 一键替换当前博客中所有图片的 URL 为 CDN 加速链接 + 4. 接入又拍云图片处理 + 5. 为博客静态资源加入 Token 防盗链 + + ![截图](./截图.png) ####使用方法: - 1. 下载此插件,上传至网站的/usr/plugins/目录下; - 2. 启用该插件,填写好Upyun空间信息,保存即可。 - -####注意事项: + 1. 在 [Release](https://github.com/ShadowySpirits/UpyunFile/releases) 中下载此插件的最新版,上传至网站的/usr/plugins/目录下; + 2. 启用该插件,正确填写相关信息,保存即可。 + 3. 详细使用以及其他技巧见此文:https://blog.sspirits.top/archives/new-version-of-UpyunFile-plugin-use-tutorial - 1. 本插件功能仅在启用并正确设置插件后才有效,在Typecho 0.9下测试完全正常,其它Typecho版本暂未测试; - 2. 未使用本插件之前所上传之文件,启用本插件后仍按照原URL路径 - 3. 本插件文件在Upyun空间生成目录结构有两种模式:Typecho原生态目录结构(形如/usr/uploads/年/月/文件名)和精简模式(/年/月/文件名),使用者可根据需要选择其中一种模式,文件URL路径为:绑定域名+目录结构。 - 4. 禁用该插件后,因Typecho仍然保存Upyun文件路径,为保证您的网站正常浏览,请不要变更Upyun空间名及绑定域名,或者搬移至其它空间,但仍然使用当前的文件目录结构。 - 5. 本插件为用户免费自由使用,作者力求插件功能完善,但对使用者涉及的文件内容不承担任何责任。 ####更新记录: -**2014-01-19:** 添加目录结构模式,使用者可根据需要选择喜好的模式; - -**2015-12-24:** 升级插件,以支持Typecho 1.0; - -**2016-12-12:** 升级Upyun SDK,以更好支持Upyun云存储。 +**2019-1-20:** 升级 SDK,修复 Bug,加入新功能   -####联系作者: +####鸣谢: + 原作者:[codesee](https://github.com/codesee) + + 图片链接替换:[benzBrake](https://github.com/benzBrake) + + 本插件为用户免费自由使用,作者力求插件功能完善,但对使用者涉及的文件内容不承担任何责任。 -1. 作者:codesee (大道至简) -2. 网址:; -3. 邮箱:zhiyongpeng(###)outlook.com; -4. QQ:1437 5907(添加好友请注明"UpyunFile"). diff --git a/SDK/upyun.class.php b/SDK/upyun.class.php deleted file mode 100644 index d4fc62f..0000000 --- a/SDK/upyun.class.php +++ /dev/null @@ -1,483 +0,0 @@ -code}]: {$this->message}\n"; - } -} - -class UpYunAuthorizationException extends UpYunException { - public function __construct($message, $code = 0, Exception $previous = null) { - parent::__construct($message, 401, $previous); - } -} - -class UpYunForbiddenException extends UpYunException { - public function __construct($message, $code = 0, Exception $previous = null) { - parent::__construct($message, 403, $previous); - } -} - -class UpYunNotFoundException extends UpYunException { - public function __construct($message, $code = 0, Exception $previous = null) { - parent::__construct($message, 404, $previous); - } -} - -class UpYunNotAcceptableException extends UpYunException { - public function __construct($message, $code = 0, Exception $previous = null) { - parent::__construct($message, 406, $previous); - } -} - -class UpYunServiceUnavailable extends UpYunException { - public function __construct($message, $code = 0, Exception $previous = null) { - parent::__construct($message, 503, $previous); - } -} - -class UpYun { - const VERSION = '2.0'; - - const ED_AUTO = 'v0.api.upyun.com'; - const ED_TELECOM = 'v1.api.upyun.com'; - const ED_CNC = 'v2.api.upyun.com'; - const ED_CTT = 'v3.api.upyun.com'; - - const CONTENT_TYPE = 'Content-Type'; - const CONTENT_MD5 = 'Content-MD5'; - const CONTENT_SECRET = 'Content-Secret'; - - // 缩略图 - const X_GMKERL_THUMBNAIL = 'x-gmkerl-thumbnail'; - const X_GMKERL_TYPE = 'x-gmkerl-type'; - const X_GMKERL_VALUE = 'x-gmkerl-value'; - const X_GMKERL_QUALITY = 'x-gmkerl-quality'; - const X_GMKERL_UNSHARP = 'x-gmkerl-unsharp'; - - private $_bucketname; - private $_username; - private $_password; - private $_timeout = 30; - - /** - * @deprecated - */ - private $_content_md5 = NULL; - - /** - * @deprecated - */ - private $_file_secret = NULL; - - /** - * @deprecated - */ - private $_file_infos= NULL; - - protected $endpoint; - - /** - * 初始化 UpYun 存储接口 - * @param $bucketname 空间名称 - * @param $username 操作员名称 - * @param $password 密码 - * - * @return object - */ - public function __construct($bucketname, $username, $password, $endpoint = NULL, $timeout = 30) { - $this->_bucketname = $bucketname; - $this->_username = $username; - $this->_password = md5($password); - $this->_timeout = $timeout; - - $this->endpoint = is_null($endpoint) ? self::ED_AUTO : $endpoint; - } - - /** - * 获取当前SDK版本号 - */ - public function version() { - return self::VERSION; - } - - /** - * 创建目录 - * @param $path 路径 - * @param $auto_mkdir 是否自动创建父级目录,最多10层次 - * - * @return void - */ - public function makeDir($path, $auto_mkdir = false) { - $headers = array('Folder' => 'true'); - if ($auto_mkdir) $headers['Mkdir'] = 'true'; - return $this->_do_request('PUT', $path, $headers); - } - - /** - * 删除目录和文件 - * @param string $path 路径 - * - * @return boolean - */ - public function delete($path) { - return $this->_do_request('DELETE', $path); - } - - - /** - * 上传文件 - * @param string $path 存储路径 - * @param mixed $file 需要上传的文件,可以是文件流或者文件内容 - * @param boolean $auto_mkdir 自动创建目录 - * @param array $opts 可选参数 - */ - public function writeFile($path, $file, $auto_mkdir = False, $opts = NULL) { - if (is_null($opts)) $opts = array(); - - if (!is_null($this->_content_md5)) $opts[self::CONTENT_MD5] = $this->_content_md5; - if (!is_null($this->_file_secret)) $opts[self::CONTENT_SECRET] = $this->_file_secret; - - if ($auto_mkdir === True) $opts['Mkdir'] = 'true'; - - return $this->_do_request('PUT', $path, $opts, $file); - } - - /** - * 下载文件 - * @param string $path 文件路径 - * @param mixed $file_handle - * - * @return mixed - */ - public function readFile($path, $file_handle = NULL) { - return $this->_do_request('GET', $path, NULL, NULL, $file_handle); - } - - /** - * 获取目录文件列表 - * - * @param string $path 查询路径 - * - * @return mixed - */ - public function getList($path = '/') { - $rsp = $this->_do_request('GET', $path); - - $list = array(); - if ($rsp) { - $rsp = explode("\n", $rsp); - foreach($rsp as $item) { - @list($name, $type, $size, $time) = explode("\t", trim($item)); - if (!empty($time)) { - $type = $type == 'N' ? 'file' : 'folder'; - } - - $item = array( - 'name' => $name, - 'type' => $type, - 'size' => intval($size), - 'time' => intval($time), - ); - array_push($list, $item); - } - } - - return $list; - } - - /** - * 获取空间使用情况 - * @param string $bucket - * @return mixed - * @throws UpYunAuthorizationException - * @throws UpYunException - * @throws UpYunForbiddenException - * @throws UpYunNotAcceptableException - * @throws UpYunNotFoundException - * @throws UpYunServiceUnavailable - */ - public function getFolderUsage($bucket = '/') { - return $this->_do_request('GET', "/{$this->_bucketname}/?usage"); - } - - /** - * 获取文件、目录信息 - * - * @param string $path 路径 - * - * @return mixed - */ - public function getFileInfo($path) { - $rsp = $this->_do_request('HEAD', $path); - - return $rsp; - } - - /** - * 连接签名方法 - * @param $method 请求方式 {GET, POST, PUT, DELETE} - * return 签名字符串 - */ - private function sign($method, $uri, $date, $length){ - $sign = "{$method}&{$uri}&{$date}&{$length}&{$this->_password}"; - return 'UpYun '.$this->_username.':'.md5($sign); - } - - /** - * HTTP REQUEST 封装 - * @param string $method HTTP REQUEST方法,包括PUT、POST、GET、OPTIONS、DELETE - * @param string $path 除Bucketname之外的请求路径,包括get参数 - * @param array $headers 请求需要的特殊HTTP HEADERS - * @param array $body 需要POST发送的数据 - * @param null $file_handle - * @return mixed - * @throws UpYunAuthorizationException - * @throws UpYunException - * @throws UpYunForbiddenException - * @throws UpYunNotAcceptableException - * @throws UpYunNotFoundException - * @throws UpYunServiceUnavailable - */ - protected function _do_request($method, $path, $headers = NULL, $body= NULL, $file_handle= NULL) {/*{{{*/ - $uri = "/{$this->_bucketname}{$path}"; - $ch = curl_init("http://{$this->endpoint}{$uri}"); - - $_headers = array('Expect:'); - if (!is_null($headers) && is_array($headers)){ - foreach($headers as $k => $v) { - array_push($_headers, "{$k}: {$v}"); - } - } - - $length = 0; - $date = gmdate('D, d M Y H:i:s \G\M\T'); - - if (!is_null($body)) { - if(is_resource($body)){ - fseek($body, 0, SEEK_END); - $length = ftell($body); - fseek($body, 0); - - array_push($_headers, "Content-Length: {$length}"); - curl_setopt($ch, CURLOPT_INFILE, $body); - curl_setopt($ch, CURLOPT_INFILESIZE, $length); - } - else { - $length = @strlen($body); - array_push($_headers, "Content-Length: {$length}"); - curl_setopt($ch, CURLOPT_POSTFIELDS, $body); - } - } - else { - array_push($_headers, "Content-Length: {$length}"); - } - - array_push($_headers, "Authorization: {$this->sign($method, $uri, $date, $length)}"); - array_push($_headers, "Date: {$date}"); - - curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers); - curl_setopt($ch, CURLOPT_TIMEOUT, $this->_timeout); - curl_setopt($ch, CURLOPT_HEADER, 1); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); - - if ($method == 'PUT' || $method == 'POST') { - curl_setopt($ch, CURLOPT_POST, 1); - } - else { - curl_setopt($ch, CURLOPT_POST, 0); - } - - if ($method == 'GET' && is_resource($file_handle)) { - curl_setopt($ch, CURLOPT_HEADER, 0); - curl_setopt($ch, CURLOPT_FILE, $file_handle); - } - - if ($method == 'HEAD') { - curl_setopt($ch, CURLOPT_NOBODY, true); - } - - $response = curl_exec($ch); - $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - - if ($http_code == 0) throw new UpYunException('Connection Failed', $http_code); - - curl_close($ch); - - $header_string = ''; - $body = ''; - - if ($method == 'GET' && is_resource($file_handle)) { - $header_string = ''; - $body = $response; - } - else { - list($header_string, $body) = explode("\r\n\r\n", $response, 2); - } - - //var_dump($http_code); - if ($http_code == 200) { - if ($method == 'GET' && is_null($file_handle)) { - return $body; - } - else { - $data = $this->_getHeadersData($header_string); - return count($data) > 0 ? $data : true; - } - } - else { - $message = $this->_getErrorMessage($header_string); - if (is_null($message) && $method == 'GET' && is_resource($file_handle)) { - $message = 'File Not Found'; - } - switch($http_code) { - case 401: - throw new UpYunAuthorizationException($message); - break; - case 403: - throw new UpYunForbiddenException($message); - break; - case 404: - throw new UpYunNotFoundException($message); - break; - case 406: - throw new UpYunNotAcceptableException($message); - break; - case 503: - throw new UpYunServiceUnavailable($message); - break; - default: - throw new UpYunException($message, $http_code); - } - } - }/*}}}*/ - - /** - * 处理HTTP HEADERS中返回的自定义数据 - * - * @param string $text header字符串 - * - * @return array - */ - private function _getHeadersData($text) { - $headers = explode("\r\n", $text); - $items = array(); - foreach($headers as $header) { - $header = trim($header); - if(strpos($header, 'x-upyun') !== False){ - list($k, $v) = explode(':', $header); - $items[trim($k)] = in_array(substr($k,8,5), array('width','heigh','frame')) ? intval($v) : trim($v); - } - } - return $items; - } - - /** - * 获取返回的错误信息 - * - * @param string $header_string - * - * @return mixed - */ - private function _getErrorMessage($header_string) { - list($status, $stash) = explode("\r\n", $header_string, 2); - list($v, $code, $message) = explode(" ", $status, 3); - return $message; - } - - /** - * 删除目录 - * @deprecated - * @param $path 路径 - * - * @return void - */ - public function rmDir($path) { - $this->_do_request('DELETE', $path); - } - - /** - * 删除文件 - * - * @deprecated - * @param string $path 要删除的文件路径 - * - * @return boolean - */ - public function deleteFile($path) { - $rsp = $this->_do_request('DELETE', $path); - } - - /** - * 获取目录文件列表 - * @deprecated - * - * @param string $path 要获取列表的目录 - * - * @return array - */ - public function readDir($path) { - return $this->getList($path); - } - - /** - * 获取空间使用情况 - * - * @deprecated 推荐直接使用 getFolderUsage('/')来获取 - * @return mixed - */ - public function getBucketUsage() { - return $this->getFolderUsage('/'); - } - - /** - * 切换 API 接口的域名 - * - * @deprecated - * @param $domain {默然 v0.api.upyun.com 自动识别, v1.api.upyun.com 电信, v2.api.upyun.com 联通, v3.api.upyun.com 移动} - * return null; - */ - public function setApiDomain($domain){/*{{{*/ - $this->endpoint = $domain; - }/*}}}*/ - - /** - * 设置待上传文件的 Content-MD5 值(如又拍云服务端收到的文件MD5值与用户设置的不一致,将回报 406 Not Acceptable 错误) - * - * @deprecated - * @param $str (文件 MD5 校验码) - * return null; - */ - public function setContentMD5($str){ - $this->_content_md5 = $str; - } - - /** - * 设置待上传文件的 访问密钥(注意:仅支持图片空!,设置密钥后,无法根据原文件URL直接访问,需带 URL 后面加上 (缩略图间隔标志符+密钥) 进行访问) - * 如缩略图间隔标志符为 ! ,密钥为 bac,上传文件路径为 /folder/test.jpg ,那么该图片的对外访问地址为: http://空间域名/folder/test.jpg!bac - * - * @deprecated - * @param $str (文件 MD5 校验码) - * return null; - */ - public function setFileSecret($str){ - $this->_file_secret = $str; - } - - /** - * @deprecated - * 获取上传文件后的信息(仅图片空间有返回数据) - * @param $key 信息字段名(x-upyun-width、x-upyun-height、x-upyun-frames、x-upyun-file-type) - * return value or NULL - */ - public function getWritedFileInfo($key){ - if(!isset($this->_file_infos))return NULL; - return $this->_file_infos[$key]; - } -} diff --git a/Upyun/src/Upyun/Api/Form.php b/Upyun/src/Upyun/Api/Form.php new file mode 100644 index 0000000..549c639 --- /dev/null +++ b/Upyun/src/Upyun/Api/Form.php @@ -0,0 +1,44 @@ +config->serviceName; + if (!isset($params['expiration'])) { + $params['expiration'] = time() + 30 * 60 * 60; // 30 分钟 + } + + $policy = Util::base64Json($params); + $method = 'POST'; + $signature = Signature::getBodySignature($this->config, $method, '/' . $params['service'], null, $policy); + $client = new Client([ + 'timeout' => $this->config->timeout, + ]); + + $response = $client->request($method, $this->endpoint, array( + 'multipart' => array( + array( + 'name' => 'policy', + 'contents' => $policy, + ), + array( + 'name' => 'authorization', + 'contents' => $signature, + ), + array( + 'name' => 'file', + 'contents' => $stream, + ) + ) + )); + return $response->getStatusCode() === 200; + } +} diff --git a/Upyun/src/Upyun/Api/Pretreat.php b/Upyun/src/Upyun/Api/Pretreat.php new file mode 100644 index 0000000..fd17a43 --- /dev/null +++ b/Upyun/src/Upyun/Api/Pretreat.php @@ -0,0 +1,84 @@ +processNotifyUrl) { + throw new \Exception("should config prosessNotifyUrl first."); + } + $this->config = $config; + } + + public function process($tasks, $optionalParams = array()) + { + $encodedTasks = Util::base64Json($tasks); + + $client = new Client([ + 'timeout' => $this->config->timeout, + ]); + + $params = array( + 'service' => $this->config->serviceName, + 'notify_url' => $this->config->processNotifyUrl, + 'tasks' => $encodedTasks, + ); + + $params = array_merge($params, $optionalParams); + + $path = '/pretreatment/'; + $method = 'POST'; + $signedHeaders = Signature::getHeaderSign($this->config, $method, $path); + + $url = $this->config->getPretreatEndPoint() . $path; + $response = $client->request($method, $url, [ + 'headers' => $signedHeaders, + 'form_params' => $params + ]); + + $body = $response->getBody()->getContents(); + return json_decode($body, true); + } + + + public function query($taskIds, $path) + { + $client = new Client([ + 'timeout' => $this->config->timeout, + ]); + + $params = array( + 'service' => $this->config->serviceName, + 'task_ids' => implode(',', $taskIds) + ); + $path = $path . '?' . http_build_query($params); + + $method = 'GET'; + $url = $this->config->getPretreatEndPoint() . $path; + $signedHeaders = Signature::getHeaderSign($this->config, $method, $path); + $response = $client->request($method, $url, [ + 'headers' => $signedHeaders + ]); + + if ($response->getStatusCode() === 200) { + $body = $response->getBody()->getContents(); + $result = json_decode($body, true); + if (is_array($result)) { + return $result['tasks']; + } + } + return false; + } +} diff --git a/Upyun/src/Upyun/Api/Rest.php b/Upyun/src/Upyun/Api/Rest.php new file mode 100644 index 0000000..f63d775 --- /dev/null +++ b/Upyun/src/Upyun/Api/Rest.php @@ -0,0 +1,108 @@ +config = $config; + $this->endpoint = $config->getProtocol() . Config::$restApiEndPoint . '/' . $config->serviceName; + } + + public function request($method, $storagePath) + { + $this->method = strtoupper($method); + $this->storagePath = '/' . ltrim($storagePath, '/'); + return $this; + } + + + /** + * @param string|resource $file + * + * @return $this + */ + public function withFile($file) + { + $stream = Psr7\stream_for($file); + $this->file = $stream; + + return $this; + } + + /** + * @return mixed|\Psr\Http\Message\ResponseInterface + */ + public function send() + { + $client = new Client([ + 'timeout' => $this->config->timeout, + ]); + + $url = $this->endpoint . $this->storagePath; + $body = null; + if ($this->file && $this->method === 'PUT') { + $body = $this->file; + } + + $request = new Psr7\Request( + $this->method, + Util::encodeURI($url), + $this->headers, + $body + ); + $authHeader = Signature::getHeaderSign($this->config, + $this->method, + $request->getUri()->getPath() + ); + foreach ($authHeader as $head => $value) { + $request = $request->withHeader($head, $value); + } + $response = $client->send($request, [ + 'debug' => $this->config->debug + ]); + + return $response; + } + + public function withHeader($header, $value) + { + $header = strtolower(trim($header)); + + $this->headers[$header] = $value; + return $this; + } + + public function withHeaders($headers) + { + if (is_array($headers)) { + foreach ($headers as $header => $value) { + $this->withHeader($header, $value); + } + } + return $this; + } +} diff --git a/Upyun/src/Upyun/Api/SyncVideo.php b/Upyun/src/Upyun/Api/SyncVideo.php new file mode 100644 index 0000000..39d1972 --- /dev/null +++ b/Upyun/src/Upyun/Api/SyncVideo.php @@ -0,0 +1,42 @@ +config = $config; + } + + public function process($params, $path) { + $client = new Client([ + 'timeout' => $this->config->timeout, + ]); + + $path = '/' . $this->config->serviceName . $path; + $method = 'POST'; + $signedHeaders = Signature::getHeaderSign($this->config, $method, $path); + + $url = $this->config->getSyncVideoEndPoint() . $path; + $response = $client->request($method, $url, [ + 'headers' => $signedHeaders, + 'json' => $params + ]); + + $body = $response->getBody()->getContents(); + return json_decode($body, true); + } +} \ No newline at end of file diff --git a/Upyun/src/Upyun/Config.php b/Upyun/src/Upyun/Config.php new file mode 100644 index 0000000..b32b809 --- /dev/null +++ b/Upyun/src/Upyun/Config.php @@ -0,0 +1,147 @@ +serviceName = $serviceName; + $this->bucketName = $serviceName; + $this->operatorName = $operatorName; + $this->setOperatorPassword($operatorPassword); + $this->useSsl = false; + self::$restApiEndPoint = self::ED_AUTO; + } + + public function setOperatorPassword($operatorPassword) + { + $this->operatorPassword = md5($operatorPassword); + } + + public function getFormApiKey() + { + if (! $this->formApiKey) { + throw new \Exception('form api key is empty.'); + } + + return $this->formApiKey; + } + + public function setFormApiKey($key) + { + $this->formApiKey = $key; + } + + public function getVersion() + { + return $this->version; + } + + public function getPretreatEndPoint() + { + return $this->getProtocol() . self::ED_VIDEO; + } + + public function getSyncVideoEndPoint() + { + return $this->getProtocol() . self::ED_SYNC_VIDEO; + } + + public function getProtocol() + { + return $this->useSsl ? 'https://' : 'http://'; + } +} diff --git a/Upyun/src/Upyun/Signature.php b/Upyun/src/Upyun/Signature.php new file mode 100644 index 0000000..0fb68fe --- /dev/null +++ b/Upyun/src/Upyun/Signature.php @@ -0,0 +1,98 @@ + $sign, + 'Date' => $gmtDate, + 'User-agent' => 'Php-Sdk/' . $serviceConfig->getVersion() + ); + return $headers; + } + + /** + * 获取请求缓存刷新接口需要的签名头 + * + * @param Config $serviceConfig + * @param $urlString + * + * @return array + */ + public static function getPurgeSignHeader(Config $serviceConfig, $urlString) + { + $gmtDate = gmdate('D, d M Y H:i:s \G\M\T'); + $sign = md5("$urlString&{$serviceConfig->serviceName}&$gmtDate&{$serviceConfig->operatorPassword}"); + return array( + 'Authorization' => "UpYun {$serviceConfig->serviceName}:{$serviceConfig->operatorName}:$sign", + 'Date' => $gmtDate, + 'User-agent' => 'Php-Sdk/' . $serviceConfig->getVersion() . ' (purge api)' + ); + } + + /** + * 获取表单 API 需要的签名,依据 body 签名规则计算 + * @param Config $serviceConfig + * @param $method 请求方法 + * @param $uri 请求路径 + * @param $date 请求时间 + * @param $policy + * @param $contentMd5 请求 body 的 md5 + * + * @return array + */ + public static function getBodySignature(Config $serviceConfig, $method, $uri, $date = null, $policy = null, $contentMd5 = null) + { + $data = array( + $method, + $uri + ); + if ($date) { + $data[] = $date; + } + + if ($policy) { + $data[] = $policy; + } + + if ($contentMd5) { + $data[] = $contentMd5; + } + $signature = base64_encode(hash_hmac('sha1', implode('&', $data), $serviceConfig->operatorPassword, true)); + return 'UPYUN ' . $serviceConfig->operatorName . ':' . $signature; + } +} diff --git a/Upyun/src/Upyun/Uploader.php b/Upyun/src/Upyun/Uploader.php new file mode 100644 index 0000000..862644e --- /dev/null +++ b/Upyun/src/Upyun/Uploader.php @@ -0,0 +1,120 @@ +config = $config; + } + + public function upload($path, $file, $params, $withAsyncProcess) + { + $stream = Psr7\stream_for($file); + $size = $stream->getSize(); + $useBlock = $this->needUseBlock($size); + + if ($withAsyncProcess) { + $req = new Form($this->config); + return $req->upload($path, $stream, $params); + } + + if (! $useBlock) { + $req = new Rest($this->config); + return $req->request('PUT', $path) + ->withHeaders($params) + ->withFile($stream) + ->send(); + } else { + return $this->pointUpload($path, $stream, $params); + } + } + + /** + * 断点续传 + * @param $path + * @param $stream + * @param $params + * + * @return mixed|\Psr\Http\Message\ResponseInterface + * @throws \Exception + */ + private function pointUpload($path, $stream, $params) + { + $req = new Rest($this->config); + $headers = array(); + if (is_array($params)) { + foreach ($params as $key => $val) { + $headers['X-Upyun-Meta-' . $key] = $val; + } + } + $res = $req->request('PUT', $path) + ->withHeaders(array_merge(array( + 'X-Upyun-Multi-Stage' => 'initiate', + 'X-Upyun-Multi-Type' => Psr7\mimetype_from_filename($path), + 'X-Upyun-Multi-Length' => $stream->getSize(), + ), $headers)) + ->send(); + if ($res->getStatusCode() !== 204) { + throw new \Exception('init request failed when poinit upload!'); + } + + $init = Util::getHeaderParams($res->getHeaders()); + $uuid = $init['x-upyun-multi-uuid']; + $blockSize = 1024 * 1024; + $partId = 0; + do { + $fileBlock = $stream->read($blockSize); + $res = $req->request('PUT', $path) + ->withHeaders(array( + 'X-Upyun-Multi-Stage' => 'upload', + 'X-Upyun-Multi-Uuid' => $uuid, + 'X-Upyun-Part-Id' => $partId + )) + ->withFile(Psr7\stream_for($fileBlock)) + ->send(); + + if ($res->getStatusCode() !== 204) { + throw new \Exception('upload request failed when poinit upload!'); + } + $data = Util::getHeaderParams($res->getHeaders()); + $partId = $data['x-upyun-next-part-id']; + } while ($partId != -1); + + $res = $req->request('PUT', $path) + ->withHeaders(array( + 'X-Upyun-Multi-Uuid' => $uuid, + 'X-Upyun-Multi-Stage' => 'complete' + )) + ->send(); + + if ($res->getStatusCode() != 204 && $res->getStatusCode() != 201) { + throw new \Exception('end request failed when poinit upload!'); + } + return $res; + } + + private function needUseBlock($fileSize) + { + if ($this->config->uploadType === 'BLOCK') { + return true; + } elseif ($this->config->uploadType === 'AUTO' && + $fileSize >= $this->config->sizeBoundary) { + return true; + } else { + return false; + } + } +} diff --git a/Upyun/src/Upyun/Upyun.php b/Upyun/src/Upyun/Upyun.php new file mode 100644 index 0000000..08cfa12 --- /dev/null +++ b/Upyun/src/Upyun/Upyun.php @@ -0,0 +1,538 @@ +setConfig($config); + } + + /** + * 配置服务信息 + * + * 当需要操作的新的服务时,使用该方法传入新的服务配置即可 + * + * @param Config $config 服务配置 + * + * @return $this + */ + public function setConfig(Config $config) + { + $this->config = $config; + return $this; + } + + /** + * 上传一个文件到又拍云存储 + * + * 上传的文件格式支持文件流或者字符串方式上传。除简单的文件上传外,针对多媒体资源(图片、音视频),还可以设置同步/异步预处理多媒体资源,例如:图片的裁剪缩放,音视频的转码截图等等众多又拍云强大的云处理功能 + * + * @param string $path 被上传的文件在又拍云存储服务中保存的路径 + * @param string|resource $content 被上传的文件内容(字符串),或者打开该文件获得的文件句柄(文件流)。当上传本地大文件时,推荐使用文件流的方式上传 + * @param array $params 上传文件时,附加的自定义参数。支持 Content-MD5 Content-Type Content-Secret 等,详见 [上传参数](http://docs.upyun.com/api/rest_api/#_2),例如: + * - 设置文件[保护秘钥](http://docs.upyun.com/api/rest_api/#Content-Secret) `write($path, $content, array('Content-Secret' => 'my-secret'))`; + * - 添加[文件元信息](http://docs.upyun.com/api/rest_api/#metadata) `write($path, $content, array('X-Upyun-Meta-Foo' => + * 'bar'))` + * - [图片同步预处理](http://docs.upyun.com/cloud/image/#_5) `write($path, $content, array('x-gmkerl-thumb' => '/format/png'))` + * @param bool $withAsyncProcess 默认为 `false`,当上传图片或者音视频资源时,可以设置该参数为 `true`,开启图片音视频的[异步处理功能](http://docs.upyun.com/api/form_api/#_6) ,例如: + *``` + * // 以下参数会将新上传的图片,再异步生成另一份 png 格式的图片,原图不受影响 + * write($path, $content, array( + * 'apps' => array( + * array( + * 'name' => 'thumb', //异步图片处理任务 + * 'x-gmkerl-thumb' => '/format/png', // 格式化图片为 png 格式 + * 'save_as': '/iamge/png/new.png', // 处理成功后的图片保存路径 + * 'notify_url': 'http://your.notify.url' // 异步任务完成后的回调地址 + * ) + * ) + * ), true); + *``` + * + * + * + * @return array|bool 若文件是图片则返回图片基本信息,如:`array('x-upyun-width' => 123, 'x-upyun-height' => 50, 'x-upyun-frames' + * => 1, 'x-upyun-file-type' => 'JPEG')`,否则返回空数组。当使用异步预处理功能时,返回结果为布尔值,成功为 `true`。 + * + * @throws \Exception 上传失败时,抛出异常 + */ + public function write($path, $content, $params = array(), $withAsyncProcess = false) + { + if (!$content) { + throw new \Exception('write content can not be empty.'); + } + + $upload = new Uploader($this->config); + $response = $upload->upload($path, $content, $params, $withAsyncProcess); + if ($withAsyncProcess) { + return $response; + } + return Util::getHeaderParams($response->getHeaders()); + } + + /** + * 读取云存储文件/目录内容 + * + * @param string $path 又拍云存储中的文件或者目录路径 + * @param resource $saveHandler 文件内容写入本地文件流。例如 `$saveHandler = fopen('/local/file', 'w') + * `。当设置该参数时,将以文件流的方式,直接将又拍云中的文件写入本地的文件流,或其他可以写入的流 + * @param array $params 可选参数,读取目录内容时,需要设置三个参数: `X-List-Iter` 分页开始位置(第一页不需要设置),`X-List-Limit` 获取的文件数量(默认 100,最大 + * 10000),`X-List-Order` 结果以时间正序或者倒序 + * + * @return mixed $return 当读取文件且没有设置 `$saveHandler` 参数时,返回一个字符串类型,表示文件内容;设置了 `$saveHandler` 参数时,返回布尔值 + * `true`。当读取目录时,返回一个数组,表示目录下的文件列表。目录下文件内容过多时,需要通过判断返回数组中的 `is_end` 属性,进行分页读取内容 + * + * @throws \Exception + */ + public function read($path, $saveHandler = null, $params = array()) + { + $req = new Rest($this->config); + $response = $req->request('GET', $path) + ->withHeaders($params) + ->send(); + + + $params = Util::getHeaderParams($response->getHeaders()); + + + if (! isset($params['x-upyun-list-iter'])) { + if (is_resource($saveHandler)) { + Psr7\copy_to_stream($response->getBody(), Psr7\stream_for($saveHandler)); + return true; + } else { + return $response->getBody()->getContents(); + } + } else { + $files = Util::parseDir($response->getBody()->getContents()); + return array('files' => $files, 'is_end' => $params['x-upyun-list-iter'] === 'g2gCZAAEbmV4dGQAA2VvZg', 'iter' => $params['x-upyun-list-iter']); + } + } + + /** + * 判断文件是否存在于又拍云存储 + * + * 注意: 对刚删除的文件, 立即调用该方法可能会返回 true, 因为服务端执行删除操作后可能会有很短暂的延迟. + * + * @param string $path 云存储的文件路径 + * + * @return bool 存在时返回 `true`,否则返回 `false` + * @throws \Exception + */ + public function has($path) + { + $req = new Rest($this->config); + try { + $req->request('HEAD', $path) + ->send(); + } catch (GuzzleHttp\Exception\BadResponseException $e) { + $statusCode = $e->getResponse()->getStatusCode(); + if ($statusCode === 404) { + return false; + } else { + throw $e; + } + } + + return true; + } + + /** + * 获取云存储文件/目录的基本信息 + * + * @param string $path 云存储的文件路径 + * @param array $otherHeaders 设置了后,方法将返回其他 http header 中的信息,默认为空 + * + * @return array 返回一个数组,默认包含以下 key + * - `x-upyun-file-type` 当 $path 是目录时,值为 *folder*,当 $path 是文件时,值为 *file*, + * - `x-upyun-file-size` 文件大小 + * - `x-upyun-file-date` 文件的创建时间 + */ + public function info($path, $otherHeaders = array()) + { + $req = new Rest($this->config); + $response = $req->request('HEAD', $path) + ->send(); + return Util::getHeaderParams($response->getHeaders(), $otherHeaders); + } + + /** + * 获取文件的文档类型 + * @param string $path 云存储文件路径 + * @return string 文档类型,e.g: `appcation/json`,获取失败返回空字符串 + */ + public function getMimetype($path) + { + $params = $this->info($path, array('content-type')); + if (isset($params['content-type'])) { + return explode(';', $params['content-type'])[0]; + } + return ''; + } + + /** + * 删除文件或者目录 + * + * @param string $path 文件或目录在又拍云存储的路径 + * @param bool $async 是否异步删除,默认为 false,表示同步删除。当需要批量删除大量文件时,必须选择异步删除 + * + * @return bool 删除成功返回 true,否则 false + * @throws \Exception 删除不存在的文件将会抛出异常 + */ + public function delete($path, $async = false) + { + $req = new Rest($this->config); + $req->request('DELETE', $path); + if ($async) { + $req->withHeader('x-upyun-async', 'true'); + } + $res = $req->send(); + return $res->getStatusCode() === 200; + } + + /** + * 创建目录 + * + * @param string $path 需要在又拍云存储创建的目录路径 + * + * @return bool 创建成功返回 true,否则返回 false + * @throws \Exception + */ + public function createDir($path) + { + $path = rtrim($path, '/') . '/'; + $req = new Rest($this->config); + $res = $req->request('POST', $path) + ->withHeader('folder', 'true') + ->send(); + return $res->getStatusCode() === 200; + } + + /** + * 删除文件或者目录 + * + * @param string $path 需要被删除的云存储文件或目录路径 + * + * @return bool 成功返回 true,否则 false + * @throws \Exception + */ + public function deleteDir($path) + { + return $this->delete($path); + } + + /** + * 获取目录下存储使用量 + * + * @param string $path 云存储目录路径,默认为根目录,表示整个云存储服务使用的空间大小 + * @return string 存储使用量,单位字节 + * @throws \Exception + */ + public function usage($path = '/') + { + $path = rtrim($path, '/') . '/'; + $req = new Rest($this->config); + $response = $req->request('GET', $path . '?usage') + ->send(); + + return $response->getBody()->getContents(); + } + + /** + * 刷新缓存 + * + * @param array|string $urls 需要刷新的文件 url 列表 + * + * @return array 刷新失败的 url 列表,若全部刷新成功则为空数组 + */ + public function purge($urls) + { + $urlString = $urls; + if (is_array($urls)) { + $urlString = implode("\n", $urls); + } + + $client = new Client([ + 'timeout' => $this->config->timeout + ]); + $response = $client->request('POST', Config::ED_PURGE, [ + 'headers' => Signature::getPurgeSignHeader($this->config, $urlString), + 'form_params' => ['purge' => $urlString] + ]); + $result = json_decode($response->getBody()->getContents(), true); + return $result['invalid_domain_of_url']; + } + + /** + * 异步云处理 + * + * 该方法是基于[又拍云云处理](http://docs.upyun.com/cloud/) 服务实现,可以实现音视频的转码、切片、剪辑;文件的压缩解压缩;文件拉取功能 + * + * 注意: + * - 所有需要调用该方法处理的资源,必须已经上传到云存储服务 + * - 使用 `process` 之前,必须配置 `config->processNotifyUrl`,否则会提交任务失败 + * + * 例如视频转码: + * ``` + * process(array( + * array( + * 'type' => 'video', // video 表示视频任务, audio 表示音频任务 + * 'avopts' => '/s/240p(4:3)/as/1/r/30', // 处理参数,`s` 表示输出的分辨率,`r` 表示视频帧率,`as` 表示是否自动调整分辨率 + * 'save_as' => '/video/240/new.mp4', // 新视频在又拍云存储的保存路径 + * ), + * ... // 同时还可以添加其他任务 + * ), Upyun::$PROCESS_TYPE_MEDIA, $source) + * ``` + * + * @param array $tasks 需要处理的任务 + * @param string $type 异步云处理任务类型,可选值: + * - `Upyun::$PROCESS_TYPE_MEDIA` 异步音视频处理 + * - `Upyun::$PROCESS_TYPE_ZIP` 文件压缩 + * - `Upyun::$PROCESS_TYPE_UNZIP` 文件解压 + * - `Upyun::$PROCESS_TYPE_SYNC_FILE` 文件拉取 + * - `Upyun::$PROCESS_TYPE_STITCH` 图片拼接 + * @param string $source 可选参数,处理异步音视频任务时,需要传递该参数,表示需要处理的文件路径 + * + * @return array 任务 ID,提交了多少任务,便会返回多少任务 ID,与提交任务的顺序保持一致。可以通过任务 ID 查询处理进度。格式如下: + * ``` + * array( + * '35f0148d414a688a275bf915ba7cebb2', + * '98adbaa52b2f63d6d7f327a0ff223348', + * ) + * ``` + * @throws \Exception + */ + public function process($tasks, $type, $source = '') + { + $video = new Api\Pretreat($this->config); + + $options = array(); + switch($type) { + case self::$PROCESS_TYPE_MEDIA: + $options['accept'] = 'json'; + $options['source'] = $source; + break; + case self::$PROCESS_TYPE_ZIP: + $options['app_name'] = 'compress'; + break; + case self::$PROCESS_TYPE_UNZIP: + $options['app_name'] = 'depress'; + break; + case self::$PROCESS_TYPE_SYNC_FILE: + $options['app_name'] = 'spiderman'; + break; + case self::$PROCESS_TYPE_SYNC_FILE: + $options['app_name'] = 'uconvert'; + break; + case self::$PROCESS_TYPE_STITCH: + $options['app_name'] = 'jigsaw'; + break; + default: + throw new \Exception('upyun - not support process type.'); + + } + return $video->process($tasks, $options); + } + + /** + * 查询异步云处理任务进度 + * + * 根据 `process` 方法返回的任务 ID,通过该访问查询处理进度 + * + * @param array $taskIds 任务 ID + * + * @return bool|array 查询失败返回布尔值 `false`,否则返回每个任务的百分比进度信息,格式如下: + * ``` + * array( + * '35f0148d414a688a275bf915ba7cebb2' => 100, // 100 表示任务完成 + * 'c3103189fa906a5354d29bd807e8dc51' => 35, + * '98adbaa52b2f63d6d7f327a0ff223348' => null, // null 表示任务未开始,或异常 + * ) + * ``` + */ + public function queryProcessStatus($taskIds) + { + $video = new Api\Pretreat($this->config); + return $video->query($taskIds, '/status/'); + } + + /** + * 查询异步云处理任务结果 + * + * 根据 `process` 方法返回的任务 ID,通过该访问查询处理结果,会包含每个任务详细信息 + * @param array $taskIds 任务 ID + * + * @return bool|mixed 查询失败返回 `false`,否则返回每个任务的处理结果,格式如下: + * ``` + * array( + * '9d9c32b63a1034834e77672c6f51f661' => array( + * 'path' => array('/v2.mp4'), + * 'signature' => '4042c1f07f546d28', + * 'status_code' => 200, + * 'service_name' => 'your_storage_service', + * 'description' => 'OK', + * 'task_id' => '9d9c32b63a1034834e77672c6f51f661', + * 'timestamp' => 1472010684 + * ) + * ) + * ``` + */ + public function queryProcessResult($taskIds) + { + $video = new Api\Pretreat($this->config); + return $video->query($taskIds, '/result/'); + } + + /** + * 多个 m3u8 文件拼接 + * @param array $files 保存在又拍云云存储中的多个 m3u8 文件路径 + * @param string $saveAs 拼接生成的新 m3u8 文件保存路径 + * + * @return array 见 [m3u8 拼接 - 响应](http://docs.upyun.com/cloud/sync_video/#_3) + */ + public function m3u8Concat($files, $saveAs) + { + $p = new Api\SyncVideo($this->config); + return $p->process([ + 'm3u8s' => $files, + 'save_as' => $saveAs, + ], '/m3u8er/concat'); + } + + /** + * 单个 m3u8 文件剪辑 + * @param string $file 需要剪辑的又拍云云存储中的 m3u8 文件路径 + * @param string $saveAs 剪辑完成后新的 m3u8 文件保存路径 + * @param array $slice 需要被保留或删除的片段。 + * @param bool $$isInclude 默认为 `true` 表示 `$slice` 参数描述的片段被保留,否则表示 `$slice` 参数描述的片段被删除 + * @param bool $index 指定 `$slice` 参数的格式,默认为 `false` 表示使用时间范围描述片段,单位秒:`[<开始时间>, <结束时间>]`;`true` 表示使用 `m3u8` 文件的分片序号,从 0 开始,这种方式可以一次对多个片段操作 + * + * @return array 见 [m3u8 剪辑 - 响应](http://docs.upyun.com/cloud/sync_video/#_6) + */ + public function m3u8Clip($file, $saveAs, $slice = array(), $isInclude = true, $index = false) + { + $p = new Api\SyncVideo($this->config); + $params = [ + 'm3u8' => $file, + 'save_as' => $saveAs, + 'index' => $index, + ]; + if ($$isInclude) { + $params['include'] = $slice; + } else { + $params['exclude'] = $slice; + } + return $p->process($params, '/m3u8er/clip'); + } + + /** + * 获取单个 m3u8 文件描述信息 + * @param string $file 又拍云云存储的中的 m3u8 文件路径 + * + * @return array 见 [获取 m3u8 信息 - 响应](http://docs.upyun.com/cloud/sync_video/#_6) + */ + public function m3u8Meta($file) + { + $p = new Api\SyncVideo($this->config); + return $p->process([ + 'm3u8' => $file, + ], '/m3u8er/get_meta'); + } + + /** + * 视频截图,可以对 mp4、m3u8 等视频文件进行截图 + * @param string $file 需要截图的又拍云云存储中的视频文件路径 + * @param string $saveAs 截图保存路径 + * @param string $point 截图时间点,`HH:MM:SS` 格式 + * @param string $size 截图尺寸 `宽x高` 格式的字符串。默认和视频尺寸一致 + * @param string $format 截图保存的格式,默认根据 `$saveAs` 参数的后缀生成,可以指定 `jpg | png | webp` 三种格式 + * + * @return array 见 [视频截图 - 响应](http://docs.upyun.com/cloud/sync_video/#m3u8_2) + */ + public function snapshot($file, $saveAs, $point, $size = '', $format = '') + { + $p = new Api\SyncVideo($this->config); + $params = [ + 'source' => $file, + 'save_as' => $saveAs, + 'point' => $point, + ]; + if ($size) { + $params['size'] = $size; + } + if ($format) { + $params['format'] = $format; + } + return $p->process($params, '/snapshot'); + } + + /** + * 获取音视频文件元信息 + * @param string $file 又拍云云存储的中的音视频文件路径 + * + * @return array 见 [获取音视频文件信息 - 响应](http://docs.upyun.com/cloud/sync_video/#_16) + */ + public function avMeta($file) + { + $p = new Api\SyncVideo($this->config); + return $p->process([ + 'source' => $file, + ], '/avmeta/get_meta'); + } +} diff --git a/Upyun/src/Upyun/Util.php b/Upyun/src/Upyun/Util.php new file mode 100644 index 0000000..63aec84 --- /dev/null +++ b/Upyun/src/Upyun/Util.php @@ -0,0 +1,90 @@ + $value) { + $header = strtolower($header); + if (strpos($header, 'x-upyun-') !== false) { + $params[$header] = $value[0]; + } else if (in_array($header, $otherParams)) { + $params[$header] = $value[0]; + } + } + return $params; + } + + public static function parseDir($body) + { + $files = array(); + if (!$body) { + return array(); + } + + $lines = explode("\n", $body); + foreach ($lines as $line) { + $file = []; + list($file['name'], $file['type'], $file['size'], $file['time']) = explode("\t", $line, 4); + $files[] = $file; + } + + return $files; + } + + public static function base64Json($params) + { + return base64_encode(json_encode($params, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + } + + public static function stringifyHeaders($headers) + { + $return = array(); + foreach ($headers as $key => $value) { + $return[] = "$key: $value"; + } + return $return; + } + + public static function md5Hash($resource) + { + rewind($resource); + $ctx = hash_init('md5'); + hash_update_stream($ctx, $resource); + $md5 = hash_final($ctx); + return $md5; + } + + /** + * GuzzleHttp\Psr\Uri use `parse_url`,not good for utf-8, + * So should `encodeURI` first, before `new Psr7\Request` + * @see http://stackoverflow.com/questions/4929584/encodeuri-in-php + */ + public static function encodeURI($url) + { + $unescaped = array( + '%2D'=>'-','%5F'=>'_','%2E'=>'.','%21'=>'!', '%7E'=>'~', + '%2A'=>'*', '%27'=>"'", '%28'=>'(', '%29'=>')' + ); + $reserved = array( + '%3B'=>';','%2C'=>',','%2F'=>'/','%3F'=>'?','%3A'=>':', + '%40'=>'@','%26'=>'&','%3D'=>'=','%2B'=>'+','%24'=>'$' + ); + $score = array( + '%23'=>'#' + ); + return strtr(rawurlencode($url), array_merge($reserved, $unescaped, $score)); + } +} diff --git a/Upyun/vendor/autoload.php b/Upyun/vendor/autoload.php new file mode 100644 index 0000000..00c393e --- /dev/null +++ b/Upyun/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath.'\\'; + if (isset($this->prefixDirsPsr4[$search])) { + foreach ($this->prefixDirsPsr4[$search] as $dir) { + $length = $this->prefixLengthsPsr4[$first][$search]; + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/Upyun/vendor/composer/LICENSE b/Upyun/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/Upyun/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/Upyun/vendor/composer/autoload_classmap.php b/Upyun/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..766f2d7 --- /dev/null +++ b/Upyun/vendor/composer/autoload_classmap.php @@ -0,0 +1,95 @@ + $vendorDir . '/guzzlehttp/guzzle/src/Client.php', + 'GuzzleHttp\\ClientInterface' => $vendorDir . '/guzzlehttp/guzzle/src/ClientInterface.php', + 'GuzzleHttp\\Cookie\\CookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/CookieJar.php', + 'GuzzleHttp\\Cookie\\CookieJarInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php', + 'GuzzleHttp\\Cookie\\FileCookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php', + 'GuzzleHttp\\Cookie\\SessionCookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php', + 'GuzzleHttp\\Cookie\\SetCookie' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/SetCookie.php', + 'GuzzleHttp\\Exception\\BadResponseException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/BadResponseException.php', + 'GuzzleHttp\\Exception\\ClientException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ClientException.php', + 'GuzzleHttp\\Exception\\ConnectException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ConnectException.php', + 'GuzzleHttp\\Exception\\GuzzleException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/GuzzleException.php', + 'GuzzleHttp\\Exception\\RequestException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/RequestException.php', + 'GuzzleHttp\\Exception\\SeekException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/SeekException.php', + 'GuzzleHttp\\Exception\\ServerException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ServerException.php', + 'GuzzleHttp\\Exception\\TooManyRedirectsException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php', + 'GuzzleHttp\\Exception\\TransferException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/TransferException.php', + 'GuzzleHttp\\HandlerStack' => $vendorDir . '/guzzlehttp/guzzle/src/HandlerStack.php', + 'GuzzleHttp\\Handler\\CurlFactory' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlFactory.php', + 'GuzzleHttp\\Handler\\CurlFactoryInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php', + 'GuzzleHttp\\Handler\\CurlHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlHandler.php', + 'GuzzleHttp\\Handler\\CurlMultiHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php', + 'GuzzleHttp\\Handler\\EasyHandle' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/EasyHandle.php', + 'GuzzleHttp\\Handler\\MockHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/MockHandler.php', + 'GuzzleHttp\\Handler\\Proxy' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/Proxy.php', + 'GuzzleHttp\\Handler\\StreamHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/StreamHandler.php', + 'GuzzleHttp\\MessageFormatter' => $vendorDir . '/guzzlehttp/guzzle/src/MessageFormatter.php', + 'GuzzleHttp\\Middleware' => $vendorDir . '/guzzlehttp/guzzle/src/Middleware.php', + 'GuzzleHttp\\Pool' => $vendorDir . '/guzzlehttp/guzzle/src/Pool.php', + 'GuzzleHttp\\PrepareBodyMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php', + 'GuzzleHttp\\Promise\\AggregateException' => $vendorDir . '/guzzlehttp/promises/src/AggregateException.php', + 'GuzzleHttp\\Promise\\CancellationException' => $vendorDir . '/guzzlehttp/promises/src/CancellationException.php', + 'GuzzleHttp\\Promise\\Coroutine' => $vendorDir . '/guzzlehttp/promises/src/Coroutine.php', + 'GuzzleHttp\\Promise\\EachPromise' => $vendorDir . '/guzzlehttp/promises/src/EachPromise.php', + 'GuzzleHttp\\Promise\\FulfilledPromise' => $vendorDir . '/guzzlehttp/promises/src/FulfilledPromise.php', + 'GuzzleHttp\\Promise\\Promise' => $vendorDir . '/guzzlehttp/promises/src/Promise.php', + 'GuzzleHttp\\Promise\\PromiseInterface' => $vendorDir . '/guzzlehttp/promises/src/PromiseInterface.php', + 'GuzzleHttp\\Promise\\PromisorInterface' => $vendorDir . '/guzzlehttp/promises/src/PromisorInterface.php', + 'GuzzleHttp\\Promise\\RejectedPromise' => $vendorDir . '/guzzlehttp/promises/src/RejectedPromise.php', + 'GuzzleHttp\\Promise\\RejectionException' => $vendorDir . '/guzzlehttp/promises/src/RejectionException.php', + 'GuzzleHttp\\Promise\\TaskQueue' => $vendorDir . '/guzzlehttp/promises/src/TaskQueue.php', + 'GuzzleHttp\\Promise\\TaskQueueInterface' => $vendorDir . '/guzzlehttp/promises/src/TaskQueueInterface.php', + 'GuzzleHttp\\Psr7\\AppendStream' => $vendorDir . '/guzzlehttp/psr7/src/AppendStream.php', + 'GuzzleHttp\\Psr7\\BufferStream' => $vendorDir . '/guzzlehttp/psr7/src/BufferStream.php', + 'GuzzleHttp\\Psr7\\CachingStream' => $vendorDir . '/guzzlehttp/psr7/src/CachingStream.php', + 'GuzzleHttp\\Psr7\\DroppingStream' => $vendorDir . '/guzzlehttp/psr7/src/DroppingStream.php', + 'GuzzleHttp\\Psr7\\FnStream' => $vendorDir . '/guzzlehttp/psr7/src/FnStream.php', + 'GuzzleHttp\\Psr7\\InflateStream' => $vendorDir . '/guzzlehttp/psr7/src/InflateStream.php', + 'GuzzleHttp\\Psr7\\LazyOpenStream' => $vendorDir . '/guzzlehttp/psr7/src/LazyOpenStream.php', + 'GuzzleHttp\\Psr7\\LimitStream' => $vendorDir . '/guzzlehttp/psr7/src/LimitStream.php', + 'GuzzleHttp\\Psr7\\MessageTrait' => $vendorDir . '/guzzlehttp/psr7/src/MessageTrait.php', + 'GuzzleHttp\\Psr7\\MultipartStream' => $vendorDir . '/guzzlehttp/psr7/src/MultipartStream.php', + 'GuzzleHttp\\Psr7\\NoSeekStream' => $vendorDir . '/guzzlehttp/psr7/src/NoSeekStream.php', + 'GuzzleHttp\\Psr7\\PumpStream' => $vendorDir . '/guzzlehttp/psr7/src/PumpStream.php', + 'GuzzleHttp\\Psr7\\Request' => $vendorDir . '/guzzlehttp/psr7/src/Request.php', + 'GuzzleHttp\\Psr7\\Response' => $vendorDir . '/guzzlehttp/psr7/src/Response.php', + 'GuzzleHttp\\Psr7\\ServerRequest' => $vendorDir . '/guzzlehttp/psr7/src/ServerRequest.php', + 'GuzzleHttp\\Psr7\\Stream' => $vendorDir . '/guzzlehttp/psr7/src/Stream.php', + 'GuzzleHttp\\Psr7\\StreamDecoratorTrait' => $vendorDir . '/guzzlehttp/psr7/src/StreamDecoratorTrait.php', + 'GuzzleHttp\\Psr7\\StreamWrapper' => $vendorDir . '/guzzlehttp/psr7/src/StreamWrapper.php', + 'GuzzleHttp\\Psr7\\UploadedFile' => $vendorDir . '/guzzlehttp/psr7/src/UploadedFile.php', + 'GuzzleHttp\\Psr7\\Uri' => $vendorDir . '/guzzlehttp/psr7/src/Uri.php', + 'GuzzleHttp\\Psr7\\UriNormalizer' => $vendorDir . '/guzzlehttp/psr7/src/UriNormalizer.php', + 'GuzzleHttp\\Psr7\\UriResolver' => $vendorDir . '/guzzlehttp/psr7/src/UriResolver.php', + 'GuzzleHttp\\RedirectMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/RedirectMiddleware.php', + 'GuzzleHttp\\RequestOptions' => $vendorDir . '/guzzlehttp/guzzle/src/RequestOptions.php', + 'GuzzleHttp\\RetryMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/RetryMiddleware.php', + 'GuzzleHttp\\TransferStats' => $vendorDir . '/guzzlehttp/guzzle/src/TransferStats.php', + 'GuzzleHttp\\UriTemplate' => $vendorDir . '/guzzlehttp/guzzle/src/UriTemplate.php', + 'Psr\\Http\\Message\\MessageInterface' => $vendorDir . '/psr/http-message/src/MessageInterface.php', + 'Psr\\Http\\Message\\RequestInterface' => $vendorDir . '/psr/http-message/src/RequestInterface.php', + 'Psr\\Http\\Message\\ResponseInterface' => $vendorDir . '/psr/http-message/src/ResponseInterface.php', + 'Psr\\Http\\Message\\ServerRequestInterface' => $vendorDir . '/psr/http-message/src/ServerRequestInterface.php', + 'Psr\\Http\\Message\\StreamInterface' => $vendorDir . '/psr/http-message/src/StreamInterface.php', + 'Psr\\Http\\Message\\UploadedFileInterface' => $vendorDir . '/psr/http-message/src/UploadedFileInterface.php', + 'Psr\\Http\\Message\\UriInterface' => $vendorDir . '/psr/http-message/src/UriInterface.php', + 'Upyun\\Api\\Form' => $baseDir . '/src/Upyun/Api/Form.php', + 'Upyun\\Api\\Pretreat' => $baseDir . '/src/Upyun/Api/Pretreat.php', + 'Upyun\\Api\\Rest' => $baseDir . '/src/Upyun/Api/Rest.php', + 'Upyun\\Api\\SyncVideo' => $baseDir . '/src/Upyun/Api/SyncVideo.php', + 'Upyun\\Config' => $baseDir . '/src/Upyun/Config.php', + 'Upyun\\Signature' => $baseDir . '/src/Upyun/Signature.php', + 'Upyun\\Tests\\SignatureTest' => $baseDir . '/tests/SignatureTest.php', + 'Upyun\\Tests\\UpyunTest' => $baseDir . '/tests/UpyunTest.php', + 'Upyun\\Uploader' => $baseDir . '/src/Upyun/Uploader.php', + 'Upyun\\Upyun' => $baseDir . '/src/Upyun/Upyun.php', + 'Upyun\\Util' => $baseDir . '/src/Upyun/Util.php', +); diff --git a/Upyun/vendor/composer/autoload_files.php b/Upyun/vendor/composer/autoload_files.php new file mode 100644 index 0000000..dd37d85 --- /dev/null +++ b/Upyun/vendor/composer/autoload_files.php @@ -0,0 +1,12 @@ + $vendorDir . '/guzzlehttp/promises/src/functions_include.php', + 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', +); diff --git a/Upyun/vendor/composer/autoload_namespaces.php b/Upyun/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/Upyun/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($baseDir . '/tests'), + 'Upyun\\' => array($baseDir . '/src/Upyun'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'), + 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), + 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), + 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), +); diff --git a/Upyun/vendor/composer/autoload_real.php b/Upyun/vendor/composer/autoload_real.php new file mode 100644 index 0000000..ac5e9d6 --- /dev/null +++ b/Upyun/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInita84e991fabf2a525d44852e19efdb0b7::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInita84e991fabf2a525d44852e19efdb0b7::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequirea84e991fabf2a525d44852e19efdb0b7($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequirea84e991fabf2a525d44852e19efdb0b7($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/Upyun/vendor/composer/autoload_static.php b/Upyun/vendor/composer/autoload_static.php new file mode 100644 index 0000000..262a742 --- /dev/null +++ b/Upyun/vendor/composer/autoload_static.php @@ -0,0 +1,158 @@ + __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', + 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'U' => + array ( + 'Upyun\\Tests\\' => 12, + 'Upyun\\' => 6, + ), + 'P' => + array ( + 'Psr\\Http\\Message\\' => 17, + ), + 'G' => + array ( + 'GuzzleHttp\\Psr7\\' => 16, + 'GuzzleHttp\\Promise\\' => 19, + 'GuzzleHttp\\' => 11, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Upyun\\Tests\\' => + array ( + 0 => __DIR__ . '/../..' . '/tests', + ), + 'Upyun\\' => + array ( + 0 => __DIR__ . '/../..' . '/src/Upyun', + ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-message/src', + ), + 'GuzzleHttp\\Psr7\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', + ), + 'GuzzleHttp\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src', + ), + 'GuzzleHttp\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', + ), + ); + + public static $classMap = array ( + 'GuzzleHttp\\Client' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Client.php', + 'GuzzleHttp\\ClientInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/ClientInterface.php', + 'GuzzleHttp\\Cookie\\CookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/CookieJar.php', + 'GuzzleHttp\\Cookie\\CookieJarInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php', + 'GuzzleHttp\\Cookie\\FileCookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php', + 'GuzzleHttp\\Cookie\\SessionCookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php', + 'GuzzleHttp\\Cookie\\SetCookie' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/SetCookie.php', + 'GuzzleHttp\\Exception\\BadResponseException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/BadResponseException.php', + 'GuzzleHttp\\Exception\\ClientException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ClientException.php', + 'GuzzleHttp\\Exception\\ConnectException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ConnectException.php', + 'GuzzleHttp\\Exception\\GuzzleException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/GuzzleException.php', + 'GuzzleHttp\\Exception\\RequestException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/RequestException.php', + 'GuzzleHttp\\Exception\\SeekException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/SeekException.php', + 'GuzzleHttp\\Exception\\ServerException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ServerException.php', + 'GuzzleHttp\\Exception\\TooManyRedirectsException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php', + 'GuzzleHttp\\Exception\\TransferException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/TransferException.php', + 'GuzzleHttp\\HandlerStack' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/HandlerStack.php', + 'GuzzleHttp\\Handler\\CurlFactory' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlFactory.php', + 'GuzzleHttp\\Handler\\CurlFactoryInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php', + 'GuzzleHttp\\Handler\\CurlHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlHandler.php', + 'GuzzleHttp\\Handler\\CurlMultiHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php', + 'GuzzleHttp\\Handler\\EasyHandle' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/EasyHandle.php', + 'GuzzleHttp\\Handler\\MockHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/MockHandler.php', + 'GuzzleHttp\\Handler\\Proxy' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/Proxy.php', + 'GuzzleHttp\\Handler\\StreamHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/StreamHandler.php', + 'GuzzleHttp\\MessageFormatter' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/MessageFormatter.php', + 'GuzzleHttp\\Middleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Middleware.php', + 'GuzzleHttp\\Pool' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Pool.php', + 'GuzzleHttp\\PrepareBodyMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php', + 'GuzzleHttp\\Promise\\AggregateException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/AggregateException.php', + 'GuzzleHttp\\Promise\\CancellationException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/CancellationException.php', + 'GuzzleHttp\\Promise\\Coroutine' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Coroutine.php', + 'GuzzleHttp\\Promise\\EachPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/EachPromise.php', + 'GuzzleHttp\\Promise\\FulfilledPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/FulfilledPromise.php', + 'GuzzleHttp\\Promise\\Promise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Promise.php', + 'GuzzleHttp\\Promise\\PromiseInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/PromiseInterface.php', + 'GuzzleHttp\\Promise\\PromisorInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/PromisorInterface.php', + 'GuzzleHttp\\Promise\\RejectedPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/RejectedPromise.php', + 'GuzzleHttp\\Promise\\RejectionException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/RejectionException.php', + 'GuzzleHttp\\Promise\\TaskQueue' => __DIR__ . '/..' . '/guzzlehttp/promises/src/TaskQueue.php', + 'GuzzleHttp\\Promise\\TaskQueueInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/TaskQueueInterface.php', + 'GuzzleHttp\\Psr7\\AppendStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/AppendStream.php', + 'GuzzleHttp\\Psr7\\BufferStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/BufferStream.php', + 'GuzzleHttp\\Psr7\\CachingStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/CachingStream.php', + 'GuzzleHttp\\Psr7\\DroppingStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/DroppingStream.php', + 'GuzzleHttp\\Psr7\\FnStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/FnStream.php', + 'GuzzleHttp\\Psr7\\InflateStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/InflateStream.php', + 'GuzzleHttp\\Psr7\\LazyOpenStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/LazyOpenStream.php', + 'GuzzleHttp\\Psr7\\LimitStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/LimitStream.php', + 'GuzzleHttp\\Psr7\\MessageTrait' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MessageTrait.php', + 'GuzzleHttp\\Psr7\\MultipartStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MultipartStream.php', + 'GuzzleHttp\\Psr7\\NoSeekStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/NoSeekStream.php', + 'GuzzleHttp\\Psr7\\PumpStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/PumpStream.php', + 'GuzzleHttp\\Psr7\\Request' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Request.php', + 'GuzzleHttp\\Psr7\\Response' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Response.php', + 'GuzzleHttp\\Psr7\\ServerRequest' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/ServerRequest.php', + 'GuzzleHttp\\Psr7\\Stream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Stream.php', + 'GuzzleHttp\\Psr7\\StreamDecoratorTrait' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/StreamDecoratorTrait.php', + 'GuzzleHttp\\Psr7\\StreamWrapper' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/StreamWrapper.php', + 'GuzzleHttp\\Psr7\\UploadedFile' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UploadedFile.php', + 'GuzzleHttp\\Psr7\\Uri' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Uri.php', + 'GuzzleHttp\\Psr7\\UriNormalizer' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriNormalizer.php', + 'GuzzleHttp\\Psr7\\UriResolver' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriResolver.php', + 'GuzzleHttp\\RedirectMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RedirectMiddleware.php', + 'GuzzleHttp\\RequestOptions' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RequestOptions.php', + 'GuzzleHttp\\RetryMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RetryMiddleware.php', + 'GuzzleHttp\\TransferStats' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/TransferStats.php', + 'GuzzleHttp\\UriTemplate' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/UriTemplate.php', + 'Psr\\Http\\Message\\MessageInterface' => __DIR__ . '/..' . '/psr/http-message/src/MessageInterface.php', + 'Psr\\Http\\Message\\RequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/RequestInterface.php', + 'Psr\\Http\\Message\\ResponseInterface' => __DIR__ . '/..' . '/psr/http-message/src/ResponseInterface.php', + 'Psr\\Http\\Message\\ServerRequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/ServerRequestInterface.php', + 'Psr\\Http\\Message\\StreamInterface' => __DIR__ . '/..' . '/psr/http-message/src/StreamInterface.php', + 'Psr\\Http\\Message\\UploadedFileInterface' => __DIR__ . '/..' . '/psr/http-message/src/UploadedFileInterface.php', + 'Psr\\Http\\Message\\UriInterface' => __DIR__ . '/..' . '/psr/http-message/src/UriInterface.php', + 'Upyun\\Api\\Form' => __DIR__ . '/../..' . '/src/Upyun/Api/Form.php', + 'Upyun\\Api\\Pretreat' => __DIR__ . '/../..' . '/src/Upyun/Api/Pretreat.php', + 'Upyun\\Api\\Rest' => __DIR__ . '/../..' . '/src/Upyun/Api/Rest.php', + 'Upyun\\Api\\SyncVideo' => __DIR__ . '/../..' . '/src/Upyun/Api/SyncVideo.php', + 'Upyun\\Config' => __DIR__ . '/../..' . '/src/Upyun/Config.php', + 'Upyun\\Signature' => __DIR__ . '/../..' . '/src/Upyun/Signature.php', + 'Upyun\\Tests\\SignatureTest' => __DIR__ . '/../..' . '/tests/SignatureTest.php', + 'Upyun\\Tests\\UpyunTest' => __DIR__ . '/../..' . '/tests/UpyunTest.php', + 'Upyun\\Uploader' => __DIR__ . '/../..' . '/src/Upyun/Uploader.php', + 'Upyun\\Upyun' => __DIR__ . '/../..' . '/src/Upyun/Upyun.php', + 'Upyun\\Util' => __DIR__ . '/../..' . '/src/Upyun/Util.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInita84e991fabf2a525d44852e19efdb0b7::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInita84e991fabf2a525d44852e19efdb0b7::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInita84e991fabf2a525d44852e19efdb0b7::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/Upyun/vendor/composer/installed.json b/Upyun/vendor/composer/installed.json new file mode 100644 index 0000000..3e9fb26 --- /dev/null +++ b/Upyun/vendor/composer/installed.json @@ -0,0 +1,241 @@ +[ + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "version_normalized": "1.3.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://files.phpcomposer.com/files/guzzle/promises/a59da6cf61d80060647ff4d3eb2c03a2bc694646.zip", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "time": "2016-12-20T10:07:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ] + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://files.phpcomposer.com/files/php-fig/http-message/f6561bf28d520154e4b0ec72be95418abe6d9363.zip", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06T14:39:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ] + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "version_normalized": "1.4.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://files.phpcomposer.com/files/guzzle/psr7/f5b8a8512e2b58b0071a7280e39f14f72e05d87c.zip", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "time": "2017-03-20T17:10:46+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ] + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.3.0", + "version_normalized": "6.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699" + }, + "dist": { + "type": "zip", + "url": "https://files.phpcomposer.com/files/guzzle/guzzle/f4db5a78a5ea468d4831de7f0bf9d9415e348699.zip", + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0 || ^5.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "time": "2017-06-22T18:50:49+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ] + } +] diff --git a/Upyun/vendor/guzzlehttp/guzzle/CHANGELOG.md b/Upyun/vendor/guzzlehttp/guzzle/CHANGELOG.md new file mode 100644 index 0000000..b265cbc --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/CHANGELOG.md @@ -0,0 +1,1264 @@ +# CHANGELOG + +## 6.3.0 - 2017-06-22 + +* Feature: force IP resolution (ipv4 or ipv6) [#1608](https://github.com/guzzle/guzzle/pull/1608), [#1659](https://github.com/guzzle/guzzle/pull/1659) +* Improvement: Don't include summary in exception message when body is empty [#1621](https://github.com/guzzle/guzzle/pull/1621) +* Improvement: Handle `on_headers` option in MockHandler [#1580](https://github.com/guzzle/guzzle/pull/1580) +* Improvement: Added SUSE Linux CA path [#1609](https://github.com/guzzle/guzzle/issues/1609) +* Improvement: Use class reference for getting the name of the class instead of using hardcoded strings [#1641](https://github.com/guzzle/guzzle/pull/1641) +* Feature: Added `read_timeout` option [#1611](https://github.com/guzzle/guzzle/pull/1611) +* Bug fix: PHP 7.x fixes [#1685](https://github.com/guzzle/guzzle/pull/1685), [#1686](https://github.com/guzzle/guzzle/pull/1686), [#1811](https://github.com/guzzle/guzzle/pull/1811) +* Deprecation: BadResponseException instantiation without a response [#1642](https://github.com/guzzle/guzzle/pull/1642) +* Feature: Added NTLM auth [#1569](https://github.com/guzzle/guzzle/pull/1569) +* Feature: Track redirect HTTP status codes [#1711](https://github.com/guzzle/guzzle/pull/1711) +* Improvement: Check handler type during construction [#1745](https://github.com/guzzle/guzzle/pull/1745) +* Improvement: Always include the Content-Length if there's a body [#1721](https://github.com/guzzle/guzzle/pull/1721) +* Feature: Added convenience method to access a cookie by name [#1318](https://github.com/guzzle/guzzle/pull/1318) +* Bug fix: Fill `CURLOPT_CAPATH` and `CURLOPT_CAINFO` properly [#1684](https://github.com/guzzle/guzzle/pull/1684) +* Improvement: Use `\GuzzleHttp\Promise\rejection_for` function instead of object init [#1827](https://github.com/guzzle/guzzle/pull/1827) + + ++ Minor code cleanups, documentation fixes and clarifications. + +## 6.2.3 - 2017-02-28 + +* Fix deprecations with guzzle/psr7 version 1.4 + +## 6.2.2 - 2016-10-08 + +* Allow to pass nullable Response to delay callable +* Only add scheme when host is present +* Fix drain case where content-length is the literal string zero +* Obfuscate in-URL credentials in exceptions + +## 6.2.1 - 2016-07-18 + +* Address HTTP_PROXY security vulnerability, CVE-2016-5385: + https://httpoxy.org/ +* Fixing timeout bug with StreamHandler: + https://github.com/guzzle/guzzle/pull/1488 +* Only read up to `Content-Length` in PHP StreamHandler to avoid timeouts when + a server does not honor `Connection: close`. +* Ignore URI fragment when sending requests. + +## 6.2.0 - 2016-03-21 + +* Feature: added `GuzzleHttp\json_encode` and `GuzzleHttp\json_decode`. + https://github.com/guzzle/guzzle/pull/1389 +* Bug fix: Fix sleep calculation when waiting for delayed requests. + https://github.com/guzzle/guzzle/pull/1324 +* Feature: More flexible history containers. + https://github.com/guzzle/guzzle/pull/1373 +* Bug fix: defer sink stream opening in StreamHandler. + https://github.com/guzzle/guzzle/pull/1377 +* Bug fix: do not attempt to escape cookie values. + https://github.com/guzzle/guzzle/pull/1406 +* Feature: report original content encoding and length on decoded responses. + https://github.com/guzzle/guzzle/pull/1409 +* Bug fix: rewind seekable request bodies before dispatching to cURL. + https://github.com/guzzle/guzzle/pull/1422 +* Bug fix: provide an empty string to `http_build_query` for HHVM workaround. + https://github.com/guzzle/guzzle/pull/1367 + +## 6.1.1 - 2015-11-22 + +* Bug fix: Proxy::wrapSync() now correctly proxies to the appropriate handler + https://github.com/guzzle/guzzle/commit/911bcbc8b434adce64e223a6d1d14e9a8f63e4e4 +* Feature: HandlerStack is now more generic. + https://github.com/guzzle/guzzle/commit/f2102941331cda544745eedd97fc8fd46e1ee33e +* Bug fix: setting verify to false in the StreamHandler now disables peer + verification. https://github.com/guzzle/guzzle/issues/1256 +* Feature: Middleware now uses an exception factory, including more error + context. https://github.com/guzzle/guzzle/pull/1282 +* Feature: better support for disabled functions. + https://github.com/guzzle/guzzle/pull/1287 +* Bug fix: fixed regression where MockHandler was not using `sink`. + https://github.com/guzzle/guzzle/pull/1292 + +## 6.1.0 - 2015-09-08 + +* Feature: Added the `on_stats` request option to provide access to transfer + statistics for requests. https://github.com/guzzle/guzzle/pull/1202 +* Feature: Added the ability to persist session cookies in CookieJars. + https://github.com/guzzle/guzzle/pull/1195 +* Feature: Some compatibility updates for Google APP Engine + https://github.com/guzzle/guzzle/pull/1216 +* Feature: Added support for NO_PROXY to prevent the use of a proxy based on + a simple set of rules. https://github.com/guzzle/guzzle/pull/1197 +* Feature: Cookies can now contain square brackets. + https://github.com/guzzle/guzzle/pull/1237 +* Bug fix: Now correctly parsing `=` inside of quotes in Cookies. + https://github.com/guzzle/guzzle/pull/1232 +* Bug fix: Cusotm cURL options now correctly override curl options of the + same name. https://github.com/guzzle/guzzle/pull/1221 +* Bug fix: Content-Type header is now added when using an explicitly provided + multipart body. https://github.com/guzzle/guzzle/pull/1218 +* Bug fix: Now ignoring Set-Cookie headers that have no name. +* Bug fix: Reason phrase is no longer cast to an int in some cases in the + cURL handler. https://github.com/guzzle/guzzle/pull/1187 +* Bug fix: Remove the Authorization header when redirecting if the Host + header changes. https://github.com/guzzle/guzzle/pull/1207 +* Bug fix: Cookie path matching fixes + https://github.com/guzzle/guzzle/issues/1129 +* Bug fix: Fixing the cURL `body_as_string` setting + https://github.com/guzzle/guzzle/pull/1201 +* Bug fix: quotes are no longer stripped when parsing cookies. + https://github.com/guzzle/guzzle/issues/1172 +* Bug fix: `form_params` and `query` now always uses the `&` separator. + https://github.com/guzzle/guzzle/pull/1163 +* Bug fix: Adding a Content-Length to PHP stream wrapper requests if not set. + https://github.com/guzzle/guzzle/pull/1189 + +## 6.0.2 - 2015-07-04 + +* Fixed a memory leak in the curl handlers in which references to callbacks + were not being removed by `curl_reset`. +* Cookies are now extracted properly before redirects. +* Cookies now allow more character ranges. +* Decoded Content-Encoding responses are now modified to correctly reflect + their state if the encoding was automatically removed by a handler. This + means that the `Content-Encoding` header may be removed an the + `Content-Length` modified to reflect the message size after removing the + encoding. +* Added a more explicit error message when trying to use `form_params` and + `multipart` in the same request. +* Several fixes for HHVM support. +* Functions are now conditionally required using an additional level of + indirection to help with global Composer installations. + +## 6.0.1 - 2015-05-27 + +* Fixed a bug with serializing the `query` request option where the `&` + separator was missing. +* Added a better error message for when `body` is provided as an array. Please + use `form_params` or `multipart` instead. +* Various doc fixes. + +## 6.0.0 - 2015-05-26 + +* See the UPGRADING.md document for more information. +* Added `multipart` and `form_params` request options. +* Added `synchronous` request option. +* Added the `on_headers` request option. +* Fixed `expect` handling. +* No longer adding default middlewares in the client ctor. These need to be + present on the provided handler in order to work. +* Requests are no longer initiated when sending async requests with the + CurlMultiHandler. This prevents unexpected recursion from requests completing + while ticking the cURL loop. +* Removed the semantics of setting `default` to `true`. This is no longer + required now that the cURL loop is not ticked for async requests. +* Added request and response logging middleware. +* No longer allowing self signed certificates when using the StreamHandler. +* Ensuring that `sink` is valid if saving to a file. +* Request exceptions now include a "handler context" which provides handler + specific contextual information. +* Added `GuzzleHttp\RequestOptions` to allow request options to be applied + using constants. +* `$maxHandles` has been removed from CurlMultiHandler. +* `MultipartPostBody` is now part of the `guzzlehttp/psr7` package. + +## 5.3.0 - 2015-05-19 + +* Mock now supports `save_to` +* Marked `AbstractRequestEvent::getTransaction()` as public. +* Fixed a bug in which multiple headers using different casing would overwrite + previous headers in the associative array. +* Added `Utils::getDefaultHandler()` +* Marked `GuzzleHttp\Client::getDefaultUserAgent` as deprecated. +* URL scheme is now always lowercased. + +## 6.0.0-beta.1 + +* Requires PHP >= 5.5 +* Updated to use PSR-7 + * Requires immutable messages, which basically means an event based system + owned by a request instance is no longer possible. + * Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7). + * Removed the dependency on `guzzlehttp/streams`. These stream abstractions + are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7` + namespace. +* Added middleware and handler system + * Replaced the Guzzle event and subscriber system with a middleware system. + * No longer depends on RingPHP, but rather places the HTTP handlers directly + in Guzzle, operating on PSR-7 messages. + * Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which + means the `guzzlehttp/retry-subscriber` is now obsolete. + * Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`. +* Asynchronous responses + * No longer supports the `future` request option to send an async request. + Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`, + `getAsync`, etc.). + * Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid + recursion required by chaining and forwarding react promises. See + https://github.com/guzzle/promises + * Added `requestAsync` and `sendAsync` to send request asynchronously. + * Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests + asynchronously. +* Request options + * POST and form updates + * Added the `form_fields` and `form_files` request options. + * Removed the `GuzzleHttp\Post` namespace. + * The `body` request option no longer accepts an array for POST requests. + * The `exceptions` request option has been deprecated in favor of the + `http_errors` request options. + * The `save_to` request option has been deprecated in favor of `sink` request + option. +* Clients no longer accept an array of URI template string and variables for + URI variables. You will need to expand URI templates before passing them + into a client constructor or request method. +* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are + now magic methods that will send synchronous requests. +* Replaced `Utils.php` with plain functions in `functions.php`. +* Removed `GuzzleHttp\Collection`. +* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as + an array. +* Removed `GuzzleHttp\Query`. Query string handling is now handled using an + associative array passed into the `query` request option. The query string + is serialized using PHP's `http_build_query`. If you need more control, you + can pass the query string in as a string. +* `GuzzleHttp\QueryParser` has been replaced with the + `GuzzleHttp\Psr7\parse_query`. + +## 5.2.0 - 2015-01-27 + +* Added `AppliesHeadersInterface` to make applying headers to a request based + on the body more generic and not specific to `PostBodyInterface`. +* Reduced the number of stack frames needed to send requests. +* Nested futures are now resolved in the client rather than the RequestFsm +* Finishing state transitions is now handled in the RequestFsm rather than the + RingBridge. +* Added a guard in the Pool class to not use recursion for request retries. + +## 5.1.0 - 2014-12-19 + +* Pool class no longer uses recursion when a request is intercepted. +* The size of a Pool can now be dynamically adjusted using a callback. + See https://github.com/guzzle/guzzle/pull/943. +* Setting a request option to `null` when creating a request with a client will + ensure that the option is not set. This allows you to overwrite default + request options on a per-request basis. + See https://github.com/guzzle/guzzle/pull/937. +* Added the ability to limit which protocols are allowed for redirects by + specifying a `protocols` array in the `allow_redirects` request option. +* Nested futures due to retries are now resolved when waiting for synchronous + responses. See https://github.com/guzzle/guzzle/pull/947. +* `"0"` is now an allowed URI path. See + https://github.com/guzzle/guzzle/pull/935. +* `Query` no longer typehints on the `$query` argument in the constructor, + allowing for strings and arrays. +* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle + specific exceptions if necessary. + +## 5.0.3 - 2014-11-03 + +This change updates query strings so that they are treated as un-encoded values +by default where the value represents an un-encoded value to send over the +wire. A Query object then encodes the value before sending over the wire. This +means that even value query string values (e.g., ":") are url encoded. This +makes the Query class match PHP's http_build_query function. However, if you +want to send requests over the wire using valid query string characters that do +not need to be encoded, then you can provide a string to Url::setQuery() and +pass true as the second argument to specify that the query string is a raw +string that should not be parsed or encoded (unless a call to getQuery() is +subsequently made, forcing the query-string to be converted into a Query +object). + +## 5.0.2 - 2014-10-30 + +* Added a trailing `\r\n` to multipart/form-data payloads. See + https://github.com/guzzle/guzzle/pull/871 +* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs. +* Status codes are now returned as integers. See + https://github.com/guzzle/guzzle/issues/881 +* No longer overwriting an existing `application/x-www-form-urlencoded` header + when sending POST requests, allowing for customized headers. See + https://github.com/guzzle/guzzle/issues/877 +* Improved path URL serialization. + + * No longer double percent-encoding characters in the path or query string if + they are already encoded. + * Now properly encoding the supplied path to a URL object, instead of only + encoding ' ' and '?'. + * Note: This has been changed in 5.0.3 to now encode query string values by + default unless the `rawString` argument is provided when setting the query + string on a URL: Now allowing many more characters to be present in the + query string without being percent encoded. See http://tools.ietf.org/html/rfc3986#appendix-A + +## 5.0.1 - 2014-10-16 + +Bugfix release. + +* Fixed an issue where connection errors still returned response object in + error and end events event though the response is unusable. This has been + corrected so that a response is not returned in the `getResponse` method of + these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867 +* Fixed an issue where transfer statistics were not being populated in the + RingBridge. https://github.com/guzzle/guzzle/issues/866 + +## 5.0.0 - 2014-10-12 + +Adding support for non-blocking responses and some minor API cleanup. + +### New Features + +* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`. +* Added a public API for creating a default HTTP adapter. +* Updated the redirect plugin to be non-blocking so that redirects are sent + concurrently. Other plugins like this can now be updated to be non-blocking. +* Added a "progress" event so that you can get upload and download progress + events. +* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers + requests concurrently using a capped pool size as efficiently as possible. +* Added `hasListeners()` to EmitterInterface. +* Removed `GuzzleHttp\ClientInterface::sendAll` and marked + `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the + recommended way). + +### Breaking changes + +The breaking changes in this release are relatively minor. The biggest thing to +look out for is that request and response objects no longer implement fluent +interfaces. + +* Removed the fluent interfaces (i.e., `return $this`) from requests, + responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`, + `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and + `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of + why I did this: http://ocramius.github.io/blog/fluent-interfaces-are-evil/. + This also makes the Guzzle message interfaces compatible with the current + PSR-7 message proposal. +* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except + for the HTTP request functions from function.php, these functions are now + implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode` + moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to + `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to + `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be + `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php + caused problems for many users: they aren't PSR-4 compliant, require an + explicit include, and needed an if-guard to ensure that the functions are not + declared multiple times. +* Rewrote adapter layer. + * Removing all classes from `GuzzleHttp\Adapter`, these are now + implemented as callables that are stored in `GuzzleHttp\Ring\Client`. + * Removed the concept of "parallel adapters". Sending requests serially or + concurrently is now handled using a single adapter. + * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The + Transaction object now exposes the request, response, and client as public + properties. The getters and setters have been removed. +* Removed the "headers" event. This event was only useful for changing the + body a response once the headers of the response were known. You can implement + a similar behavior in a number of ways. One example might be to use a + FnStream that has access to the transaction being sent. For example, when the + first byte is written, you could check if the response headers match your + expectations, and if so, change the actual stream body that is being + written to. +* Removed the `asArray` parameter from + `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header + value as an array, then use the newly added `getHeaderAsArray()` method of + `MessageInterface`. This change makes the Guzzle interfaces compatible with + the PSR-7 interfaces. +* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add + custom request options using double-dispatch (this was an implementation + detail). Instead, you should now provide an associative array to the + constructor which is a mapping of the request option name mapping to a + function that applies the option value to a request. +* Removed the concept of "throwImmediately" from exceptions and error events. + This control mechanism was used to stop a transfer of concurrent requests + from completing. This can now be handled by throwing the exception or by + cancelling a pool of requests or each outstanding future request individually. +* Updated to "GuzzleHttp\Streams" 3.0. + * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a + `maxLen` parameter. This update makes the Guzzle streams project + compatible with the current PSR-7 proposal. + * `GuzzleHttp\Stream\Stream::__construct`, + `GuzzleHttp\Stream\Stream::factory`, and + `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second + argument. They now accept an associative array of options, including the + "size" key and "metadata" key which can be used to provide custom metadata. + +## 4.2.2 - 2014-09-08 + +* Fixed a memory leak in the CurlAdapter when reusing cURL handles. +* No longer using `request_fulluri` in stream adapter proxies. +* Relative redirects are now based on the last response, not the first response. + +## 4.2.1 - 2014-08-19 + +* Ensuring that the StreamAdapter does not always add a Content-Type header +* Adding automated github releases with a phar and zip + +## 4.2.0 - 2014-08-17 + +* Now merging in default options using a case-insensitive comparison. + Closes https://github.com/guzzle/guzzle/issues/767 +* Added the ability to automatically decode `Content-Encoding` response bodies + using the `decode_content` request option. This is set to `true` by default + to decode the response body if it comes over the wire with a + `Content-Encoding`. Set this value to `false` to disable decoding the + response content, and pass a string to provide a request `Accept-Encoding` + header and turn on automatic response decoding. This feature now allows you + to pass an `Accept-Encoding` header in the headers of a request but still + disable automatic response decoding. + Closes https://github.com/guzzle/guzzle/issues/764 +* Added the ability to throw an exception immediately when transferring + requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760 +* Updating guzzlehttp/streams dependency to ~2.1 +* No longer utilizing the now deprecated namespaced methods from the stream + package. + +## 4.1.8 - 2014-08-14 + +* Fixed an issue in the CurlFactory that caused setting the `stream=false` + request option to throw an exception. + See: https://github.com/guzzle/guzzle/issues/769 +* TransactionIterator now calls rewind on the inner iterator. + See: https://github.com/guzzle/guzzle/pull/765 +* You can now set the `Content-Type` header to `multipart/form-data` + when creating POST requests to force multipart bodies. + See https://github.com/guzzle/guzzle/issues/768 + +## 4.1.7 - 2014-08-07 + +* Fixed an error in the HistoryPlugin that caused the same request and response + to be logged multiple times when an HTTP protocol error occurs. +* Ensuring that cURL does not add a default Content-Type when no Content-Type + has been supplied by the user. This prevents the adapter layer from modifying + the request that is sent over the wire after any listeners may have already + put the request in a desired state (e.g., signed the request). +* Throwing an exception when you attempt to send requests that have the + "stream" set to true in parallel using the MultiAdapter. +* Only calling curl_multi_select when there are active cURL handles. This was + previously changed and caused performance problems on some systems due to PHP + always selecting until the maximum select timeout. +* Fixed a bug where multipart/form-data POST fields were not correctly + aggregated (e.g., values with "&"). + +## 4.1.6 - 2014-08-03 + +* Added helper methods to make it easier to represent messages as strings, + including getting the start line and getting headers as a string. + +## 4.1.5 - 2014-08-02 + +* Automatically retrying cURL "Connection died, retrying a fresh connect" + errors when possible. +* cURL implementation cleanup +* Allowing multiple event subscriber listeners to be registered per event by + passing an array of arrays of listener configuration. + +## 4.1.4 - 2014-07-22 + +* Fixed a bug that caused multi-part POST requests with more than one field to + serialize incorrectly. +* Paths can now be set to "0" +* `ResponseInterface::xml` now accepts a `libxml_options` option and added a + missing default argument that was required when parsing XML response bodies. +* A `save_to` stream is now created lazily, which means that files are not + created on disk unless a request succeeds. + +## 4.1.3 - 2014-07-15 + +* Various fixes to multipart/form-data POST uploads +* Wrapping function.php in an if-statement to ensure Guzzle can be used + globally and in a Composer install +* Fixed an issue with generating and merging in events to an event array +* POST headers are only applied before sending a request to allow you to change + the query aggregator used before uploading +* Added much more robust query string parsing +* Fixed various parsing and normalization issues with URLs +* Fixing an issue where multi-valued headers were not being utilized correctly + in the StreamAdapter + +## 4.1.2 - 2014-06-18 + +* Added support for sending payloads with GET requests + +## 4.1.1 - 2014-06-08 + +* Fixed an issue related to using custom message factory options in subclasses +* Fixed an issue with nested form fields in a multi-part POST +* Fixed an issue with using the `json` request option for POST requests +* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar` + +## 4.1.0 - 2014-05-27 + +* Added a `json` request option to easily serialize JSON payloads. +* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON. +* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`. +* Added the ability to provide an emitter to a client in the client constructor. +* Added the ability to persist a cookie session using $_SESSION. +* Added a trait that can be used to add event listeners to an iterator. +* Removed request method constants from RequestInterface. +* Fixed warning when invalid request start-lines are received. +* Updated MessageFactory to work with custom request option methods. +* Updated cacert bundle to latest build. + +4.0.2 (2014-04-16) +------------------ + +* Proxy requests using the StreamAdapter now properly use request_fulluri (#632) +* Added the ability to set scalars as POST fields (#628) + +## 4.0.1 - 2014-04-04 + +* The HTTP status code of a response is now set as the exception code of + RequestException objects. +* 303 redirects will now correctly switch from POST to GET requests. +* The default parallel adapter of a client now correctly uses the MultiAdapter. +* HasDataTrait now initializes the internal data array as an empty array so + that the toArray() method always returns an array. + +## 4.0.0 - 2014-03-29 + +* For more information on the 4.0 transition, see: + http://mtdowling.com/blog/2014/03/15/guzzle-4-rc/ +* For information on changes and upgrading, see: + https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 +* Added `GuzzleHttp\batch()` as a convenience function for sending requests in + parallel without needing to write asynchronous code. +* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`. + You can now pass a callable or an array of associative arrays where each + associative array contains the "fn", "priority", and "once" keys. + +## 4.0.0.rc-2 - 2014-03-25 + +* Removed `getConfig()` and `setConfig()` from clients to avoid confusion + around whether things like base_url, message_factory, etc. should be able to + be retrieved or modified. +* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface +* functions.php functions were renamed using snake_case to match PHP idioms +* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and + `GUZZLE_CURL_SELECT_TIMEOUT` environment variables +* Added the ability to specify custom `sendAll()` event priorities +* Added the ability to specify custom stream context options to the stream + adapter. +* Added a functions.php function for `get_path()` and `set_path()` +* CurlAdapter and MultiAdapter now use a callable to generate curl resources +* MockAdapter now properly reads a body and emits a `headers` event +* Updated Url class to check if a scheme and host are set before adding ":" + and "//". This allows empty Url (e.g., "") to be serialized as "". +* Parsing invalid XML no longer emits warnings +* Curl classes now properly throw AdapterExceptions +* Various performance optimizations +* Streams are created with the faster `Stream\create()` function +* Marked deprecation_proxy() as internal +* Test server is now a collection of static methods on a class + +## 4.0.0-rc.1 - 2014-03-15 + +* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 + +## 3.8.1 - 2014-01-28 + +* Bug: Always using GET requests when redirecting from a 303 response +* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in + `Guzzle\Http\ClientInterface::setSslVerification()` +* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL +* Bug: The body of a request can now be set to `"0"` +* Sending PHP stream requests no longer forces `HTTP/1.0` +* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of + each sub-exception +* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than + clobbering everything). +* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators) +* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`. + For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`. +* Now properly escaping the regular expression delimiter when matching Cookie domains. +* Network access is now disabled when loading XML documents + +## 3.8.0 - 2013-12-05 + +* Added the ability to define a POST name for a file +* JSON response parsing now properly walks additionalProperties +* cURL error code 18 is now retried automatically in the BackoffPlugin +* Fixed a cURL error when URLs contain fragments +* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were + CurlExceptions +* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e) +* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS` +* Fixed a bug that was encountered when parsing empty header parameters +* UriTemplate now has a `setRegex()` method to match the docs +* The `debug` request parameter now checks if it is truthy rather than if it exists +* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin +* Added the ability to combine URLs using strict RFC 3986 compliance +* Command objects can now return the validation errors encountered by the command +* Various fixes to cache revalidation (#437 and 29797e5) +* Various fixes to the AsyncPlugin +* Cleaned up build scripts + +## 3.7.4 - 2013-10-02 + +* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430) +* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp + (see https://github.com/aws/aws-sdk-php/issues/147) +* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots +* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420) +* Updated the bundled cacert.pem (#419) +* OauthPlugin now supports adding authentication to headers or query string (#425) + +## 3.7.3 - 2013-09-08 + +* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and + `CommandTransferException`. +* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description +* Schemas are only injected into response models when explicitly configured. +* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of + an EntityBody. +* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator. +* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`. +* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody() +* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin +* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests +* Bug fix: Properly parsing headers that contain commas contained in quotes +* Bug fix: mimetype guessing based on a filename is now case-insensitive + +## 3.7.2 - 2013-08-02 + +* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander + See https://github.com/guzzle/guzzle/issues/371 +* Bug fix: Cookie domains are now matched correctly according to RFC 6265 + See https://github.com/guzzle/guzzle/issues/377 +* Bug fix: GET parameters are now used when calculating an OAuth signature +* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted +* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched +* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input. + See https://github.com/guzzle/guzzle/issues/379 +* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See + https://github.com/guzzle/guzzle/pull/380 +* cURL multi cleanup and optimizations + +## 3.7.1 - 2013-07-05 + +* Bug fix: Setting default options on a client now works +* Bug fix: Setting options on HEAD requests now works. See #352 +* Bug fix: Moving stream factory before send event to before building the stream. See #353 +* Bug fix: Cookies no longer match on IP addresses per RFC 6265 +* Bug fix: Correctly parsing header parameters that are in `<>` and quotes +* Added `cert` and `ssl_key` as request options +* `Host` header can now diverge from the host part of a URL if the header is set manually +* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter +* OAuth parameters are only added via the plugin if they aren't already set +* Exceptions are now thrown when a URL cannot be parsed +* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails +* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin + +## 3.7.0 - 2013-06-10 + +* See UPGRADING.md for more information on how to upgrade. +* Requests now support the ability to specify an array of $options when creating a request to more easily modify a + request. You can pass a 'request.options' configuration setting to a client to apply default request options to + every request created by a client (e.g. default query string variables, headers, curl options, etc.). +* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`. + See `Guzzle\Http\StaticClient::mount`. +* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests + created by a command (e.g. custom headers, query string variables, timeout settings, etc.). +* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the + headers of a response +* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key + (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`) +* ServiceBuilders now support storing and retrieving arbitrary data +* CachePlugin can now purge all resources for a given URI +* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource +* CachePlugin now uses the Vary header to determine if a resource is a cache hit +* `Guzzle\Http\Message\Response` now implements `\Serializable` +* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters +* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable +* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()` +* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size +* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message +* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older + Symfony users can still use the old version of Monolog. +* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`. + Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`. +* Several performance improvements to `Guzzle\Common\Collection` +* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +* Added `Guzzle\Stream\StreamInterface::isRepeatable` +* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`. +* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`. +* Removed `Guzzle\Http\ClientInterface::expandTemplate()` +* Removed `Guzzle\Http\ClientInterface::setRequestFactory()` +* Removed `Guzzle\Http\ClientInterface::getCurlMulti()` +* Removed `Guzzle\Http\Message\RequestInterface::canCache` +* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect` +* Removed `Guzzle\Http\Message\RequestInterface::isRedirect` +* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. +* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting + `Guzzle\Common\Version::$emitWarnings` to true. +* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use + `$request->getResponseBody()->isRepeatable()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. + These will work through Guzzle 4.0 +* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params]. +* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`. +* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. +* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +* Marked `Guzzle\Common\Collection::inject()` as deprecated. +* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');` +* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +* Always setting X-cache headers on cached responses +* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +* Added `CacheStorageInterface::purge($url)` +* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +## 3.6.0 - 2013-05-29 + +* ServiceDescription now implements ToArrayInterface +* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters +* Guzzle can now correctly parse incomplete URLs +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess +* Added the ability to cast Model objects to a string to view debug information. + +## 3.5.0 - 2013-05-13 + +* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times +* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove + itself from the EventDispatcher) +* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values +* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too +* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a + non-existent key +* Bug: All __call() method arguments are now required (helps with mocking frameworks) +* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference + to help with refcount based garbage collection of resources created by sending a request +* Deprecating ZF1 cache and log adapters. These will be removed in the next major version. +* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the + HistoryPlugin for a history. +* Added a `responseBody` alias for the `response_body` location +* Refactored internals to no longer rely on Response::getRequest() +* HistoryPlugin can now be cast to a string +* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests + and responses that are sent over the wire +* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects + +## 3.4.3 - 2013-04-30 + +* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response +* Added a check to re-extract the temp cacert bundle from the phar before sending each request + +## 3.4.2 - 2013-04-29 + +* Bug fix: Stream objects now work correctly with "a" and "a+" modes +* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present +* Bug fix: AsyncPlugin no longer forces HEAD requests +* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter +* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails +* Setting a response on a request will write to the custom request body from the response body if one is specified +* LogPlugin now writes to php://output when STDERR is undefined +* Added the ability to set multiple POST files for the same key in a single call +* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default +* Added the ability to queue CurlExceptions to the MockPlugin +* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send) +* Configuration loading now allows remote files + +## 3.4.1 - 2013-04-16 + +* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti + handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost. +* Exceptions are now properly grouped when sending requests in parallel +* Redirects are now properly aggregated when a multi transaction fails +* Redirects now set the response on the original object even in the event of a failure +* Bug fix: Model names are now properly set even when using $refs +* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax +* Added support for oauth_callback in OAuth signatures +* Added support for oauth_verifier in OAuth signatures +* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection + +## 3.4.0 - 2013-04-11 + +* Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289 +* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289 +* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263 +* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264. +* Bug fix: Added `number` type to service descriptions. +* Bug fix: empty parameters are removed from an OAuth signature +* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header +* Bug fix: Fixed "array to string" error when validating a union of types in a service description +* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream +* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin. +* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs. +* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections. +* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if + the Content-Type can be determined based on the entity body or the path of the request. +* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder. +* Added support for a PSR-3 LogAdapter. +* Added a `command.after_prepare` event +* Added `oauth_callback` parameter to the OauthPlugin +* Added the ability to create a custom stream class when using a stream factory +* Added a CachingEntityBody decorator +* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized. +* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar. +* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies +* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This + means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use + POST fields or files (the latter is only used when emulating a form POST in the browser). +* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest + +## 3.3.1 - 2013-03-10 + +* Added the ability to create PHP streaming responses from HTTP requests +* Bug fix: Running any filters when parsing response headers with service descriptions +* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing +* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across + response location visitors. +* Bug fix: Removed the possibility of creating configuration files with circular dependencies +* RequestFactory::create() now uses the key of a POST file when setting the POST file name +* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set + +## 3.3.0 - 2013-03-03 + +* A large number of performance optimizations have been made +* Bug fix: Added 'wb' as a valid write mode for streams +* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned +* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()` +* BC: Removed `Guzzle\Http\Utils` class +* BC: Setting a service description on a client will no longer modify the client's command factories. +* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using + the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' +* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to + lowercase +* Operation parameter objects are now lazy loaded internally +* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses +* Added support for instantiating responseType=class responseClass classes. Classes must implement + `Guzzle\Service\Command\ResponseClassInterface` +* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These + additional properties also support locations and can be used to parse JSON responses where the outermost part of the + JSON is an array +* Added support for nested renaming of JSON models (rename sentAs to name) +* CachePlugin + * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error + * Debug headers can now added to cached response in the CachePlugin + +## 3.2.0 - 2013-02-14 + +* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients. +* URLs with no path no longer contain a "/" by default +* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url. +* BadResponseException no longer includes the full request and response message +* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface +* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface +* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription +* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list +* xmlEncoding can now be customized for the XML declaration of a XML service description operation +* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value + aggregation and no longer uses callbacks +* The URL encoding implementation of Guzzle\Http\QueryString can now be customized +* Bug fix: Filters were not always invoked for array service description parameters +* Bug fix: Redirects now use a target response body rather than a temporary response body +* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded +* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives + +## 3.1.2 - 2013-01-27 + +* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the + response body. For example, the XmlVisitor now parses the XML response into an array in the before() method. +* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent +* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444) +* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse() +* Setting default headers on a client after setting the user-agent will not erase the user-agent setting + +## 3.1.1 - 2013-01-20 + +* Adding wildcard support to Guzzle\Common\Collection::getPath() +* Adding alias support to ServiceBuilder configs +* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface + +## 3.1.0 - 2013-01-12 + +* BC: CurlException now extends from RequestException rather than BadResponseException +* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse() +* Added getData to ServiceDescriptionInterface +* Added context array to RequestInterface::setState() +* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http +* Bug: Adding required content-type when JSON request visitor adds JSON to a command +* Bug: Fixing the serialization of a service description with custom data +* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing + an array of successful and failed responses +* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection +* Added Guzzle\Http\IoEmittingEntityBody +* Moved command filtration from validators to location visitors +* Added `extends` attributes to service description parameters +* Added getModels to ServiceDescriptionInterface + +## 3.0.7 - 2012-12-19 + +* Fixing phar detection when forcing a cacert to system if null or true +* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()` +* Cleaning up `Guzzle\Common\Collection::inject` method +* Adding a response_body location to service descriptions + +## 3.0.6 - 2012-12-09 + +* CurlMulti performance improvements +* Adding setErrorResponses() to Operation +* composer.json tweaks + +## 3.0.5 - 2012-11-18 + +* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin +* Bug: Response body can now be a string containing "0" +* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert +* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs +* Added support for XML attributes in service description responses +* DefaultRequestSerializer now supports array URI parameter values for URI template expansion +* Added better mimetype guessing to requests and post files + +## 3.0.4 - 2012-11-11 + +* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value +* Bug: Cookies can now be added that have a name, domain, or value set to "0" +* Bug: Using the system cacert bundle when using the Phar +* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures +* Enhanced cookie jar de-duplication +* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added +* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies +* Added the ability to create any sort of hash for a stream rather than just an MD5 hash + +## 3.0.3 - 2012-11-04 + +* Implementing redirects in PHP rather than cURL +* Added PECL URI template extension and using as default parser if available +* Bug: Fixed Content-Length parsing of Response factory +* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams. +* Adding ToArrayInterface throughout library +* Fixing OauthPlugin to create unique nonce values per request + +## 3.0.2 - 2012-10-25 + +* Magic methods are enabled by default on clients +* Magic methods return the result of a command +* Service clients no longer require a base_url option in the factory +* Bug: Fixed an issue with URI templates where null template variables were being expanded + +## 3.0.1 - 2012-10-22 + +* Models can now be used like regular collection objects by calling filter, map, etc. +* Models no longer require a Parameter structure or initial data in the constructor +* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator` + +## 3.0.0 - 2012-10-15 + +* Rewrote service description format to be based on Swagger + * Now based on JSON schema + * Added nested input structures and nested response models + * Support for JSON and XML input and output models + * Renamed `commands` to `operations` + * Removed dot class notation + * Removed custom types +* Broke the project into smaller top-level namespaces to be more component friendly +* Removed support for XML configs and descriptions. Use arrays or JSON files. +* Removed the Validation component and Inspector +* Moved all cookie code to Guzzle\Plugin\Cookie +* Magic methods on a Guzzle\Service\Client now return the command un-executed. +* Calling getResult() or getResponse() on a command will lazily execute the command if needed. +* Now shipping with cURL's CA certs and using it by default +* Added previousResponse() method to response objects +* No longer sending Accept and Accept-Encoding headers on every request +* Only sending an Expect header by default when a payload is greater than 1MB +* Added/moved client options: + * curl.blacklist to curl.option.blacklist + * Added ssl.certificate_authority +* Added a Guzzle\Iterator component +* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin +* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin) +* Added a more robust caching plugin +* Added setBody to response objects +* Updating LogPlugin to use a more flexible MessageFormatter +* Added a completely revamped build process +* Cleaning up Collection class and removing default values from the get method +* Fixed ZF2 cache adapters + +## 2.8.8 - 2012-10-15 + +* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did + +## 2.8.7 - 2012-09-30 + +* Bug: Fixed config file aliases for JSON includes +* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests +* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload +* Bug: Hardening request and response parsing to account for missing parts +* Bug: Fixed PEAR packaging +* Bug: Fixed Request::getInfo +* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail +* Adding the ability for the namespace Iterator factory to look in multiple directories +* Added more getters/setters/removers from service descriptions +* Added the ability to remove POST fields from OAuth signatures +* OAuth plugin now supports 2-legged OAuth + +## 2.8.6 - 2012-09-05 + +* Added the ability to modify and build service descriptions +* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command +* Added a `json` parameter location +* Now allowing dot notation for classes in the CacheAdapterFactory +* Using the union of two arrays rather than an array_merge when extending service builder services and service params +* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references + in service builder config files. +* Services defined in two different config files that include one another will by default replace the previously + defined service, but you can now create services that extend themselves and merge their settings over the previous +* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like + '_default' with a default JSON configuration file. + +## 2.8.5 - 2012-08-29 + +* Bug: Suppressed empty arrays from URI templates +* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching +* Added support for HTTP responses that do not contain a reason phrase in the start-line +* AbstractCommand commands are now invokable +* Added a way to get the data used when signing an Oauth request before a request is sent + +## 2.8.4 - 2012-08-15 + +* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin +* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable. +* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream +* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream +* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5()) +* Added additional response status codes +* Removed SSL information from the default User-Agent header +* DELETE requests can now send an entity body +* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries +* Added the ability of the MockPlugin to consume mocked request bodies +* LogPlugin now exposes request and response objects in the extras array + +## 2.8.3 - 2012-07-30 + +* Bug: Fixed a case where empty POST requests were sent as GET requests +* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body +* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new +* Added multiple inheritance to service description commands +* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()` +* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything +* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles + +## 2.8.2 - 2012-07-24 + +* Bug: Query string values set to 0 are no longer dropped from the query string +* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()` +* Bug: `+` is now treated as an encoded space when parsing query strings +* QueryString and Collection performance improvements +* Allowing dot notation for class paths in filters attribute of a service descriptions + +## 2.8.1 - 2012-07-16 + +* Loosening Event Dispatcher dependency +* POST redirects can now be customized using CURLOPT_POSTREDIR + +## 2.8.0 - 2012-07-15 + +* BC: Guzzle\Http\Query + * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl) + * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding() + * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool) + * Changed the aggregation functions of QueryString to be static methods + * Can now use fromString() with querystrings that have a leading ? +* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters +* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body +* Cookies are no longer URL decoded by default +* Bug: URI template variables set to null are no longer expanded + +## 2.7.2 - 2012-07-02 + +* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser. +* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty() +* CachePlugin now allows for a custom request parameter function to check if a request can be cached +* Bug fix: CachePlugin now only caches GET and HEAD requests by default +* Bug fix: Using header glue when transferring headers over the wire +* Allowing deeply nested arrays for composite variables in URI templates +* Batch divisors can now return iterators or arrays + +## 2.7.1 - 2012-06-26 + +* Minor patch to update version number in UA string +* Updating build process + +## 2.7.0 - 2012-06-25 + +* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes. +* BC: Removed magic setX methods from commands +* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method +* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable. +* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity) +* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace +* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin +* Added the ability to set POST fields and files in a service description +* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method +* Adding a command.before_prepare event to clients +* Added BatchClosureTransfer and BatchClosureDivisor +* BatchTransferException now includes references to the batch divisor and transfer strategies +* Fixed some tests so that they pass more reliably +* Added Guzzle\Common\Log\ArrayLogAdapter + +## 2.6.6 - 2012-06-10 + +* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin +* BC: Removing Guzzle\Service\Command\CommandSet +* Adding generic batching system (replaces the batch queue plugin and command set) +* Updating ZF cache and log adapters and now using ZF's composer repository +* Bug: Setting the name of each ApiParam when creating through an ApiCommand +* Adding result_type, result_doc, deprecated, and doc_url to service descriptions +* Bug: Changed the default cookie header casing back to 'Cookie' + +## 2.6.5 - 2012-06-03 + +* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource() +* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from +* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data +* BC: Renaming methods in the CookieJarInterface +* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations +* Making the default glue for HTTP headers ';' instead of ',' +* Adding a removeValue to Guzzle\Http\Message\Header +* Adding getCookies() to request interface. +* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber() + +## 2.6.4 - 2012-05-30 + +* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class. +* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand +* Bug: Fixing magic method command calls on clients +* Bug: Email constraint only validates strings +* Bug: Aggregate POST fields when POST files are present in curl handle +* Bug: Fixing default User-Agent header +* Bug: Only appending or prepending parameters in commands if they are specified +* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes +* Allowing the use of dot notation for class namespaces when using instance_of constraint +* Added any_match validation constraint +* Added an AsyncPlugin +* Passing request object to the calculateWait method of the ExponentialBackoffPlugin +* Allowing the result of a command object to be changed +* Parsing location and type sub values when instantiating a service description rather than over and over at runtime + +## 2.6.3 - 2012-05-23 + +* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options. +* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields. +* You can now use an array of data when creating PUT request bodies in the request factory. +* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable. +* [Http] Adding support for Content-Type in multipart POST uploads per upload +* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1]) +* Adding more POST data operations for easier manipulation of POST data. +* You can now set empty POST fields. +* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files. +* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate. +* CS updates + +## 2.6.2 - 2012-05-19 + +* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method. + +## 2.6.1 - 2012-05-19 + +* [BC] Removing 'path' support in service descriptions. Use 'uri'. +* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache. +* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it. +* [BC] Removing Guzzle\Common\XmlElement. +* All commands, both dynamic and concrete, have ApiCommand objects. +* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits. +* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored. +* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible. + +## 2.6.0 - 2012-05-15 + +* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder +* [BC] Executing a Command returns the result of the command rather than the command +* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed. +* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args. +* [BC] Moving ResourceIterator* to Guzzle\Service\Resource +* [BC] Completely refactored ResourceIterators to iterate over a cloned command object +* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate +* [BC] Guzzle\Guzzle is now deprecated +* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject +* Adding Guzzle\Version class to give version information about Guzzle +* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate() +* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data +* ServiceDescription and ServiceBuilder are now cacheable using similar configs +* Changing the format of XML and JSON service builder configs. Backwards compatible. +* Cleaned up Cookie parsing +* Trimming the default Guzzle User-Agent header +* Adding a setOnComplete() method to Commands that is called when a command completes +* Keeping track of requests that were mocked in the MockPlugin +* Fixed a caching bug in the CacheAdapterFactory +* Inspector objects can be injected into a Command object +* Refactoring a lot of code and tests to be case insensitive when dealing with headers +* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL +* Adding the ability to set global option overrides to service builder configs +* Adding the ability to include other service builder config files from within XML and JSON files +* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method. + +## 2.5.0 - 2012-05-08 + +* Major performance improvements +* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated. +* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component. +* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}" +* Added the ability to passed parameters to all requests created by a client +* Added callback functionality to the ExponentialBackoffPlugin +* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies. +* Rewinding request stream bodies when retrying requests +* Exception is thrown when JSON response body cannot be decoded +* Added configurable magic method calls to clients and commands. This is off by default. +* Fixed a defect that added a hash to every parsed URL part +* Fixed duplicate none generation for OauthPlugin. +* Emitting an event each time a client is generated by a ServiceBuilder +* Using an ApiParams object instead of a Collection for parameters of an ApiCommand +* cache.* request parameters should be renamed to params.cache.* +* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle. +* Added the ability to disable type validation of service descriptions +* ServiceDescriptions and ServiceBuilders are now Serializable diff --git a/Upyun/vendor/guzzlehttp/guzzle/LICENSE b/Upyun/vendor/guzzlehttp/guzzle/LICENSE new file mode 100644 index 0000000..ea7f07c --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2016 Michael Dowling, https://github.com/mtdowling + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Upyun/vendor/guzzlehttp/guzzle/README.md b/Upyun/vendor/guzzlehttp/guzzle/README.md new file mode 100644 index 0000000..2f614d6 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/README.md @@ -0,0 +1,89 @@ +Guzzle, PHP HTTP client +======================= + +[![Build Status](https://travis-ci.org/guzzle/guzzle.svg?branch=master)](https://travis-ci.org/guzzle/guzzle) + +Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and +trivial to integrate with web services. + +- Simple interface for building query strings, POST requests, streaming large + uploads, streaming large downloads, using HTTP cookies, uploading JSON data, + etc... +- Can send both synchronous and asynchronous requests using the same interface. +- Uses PSR-7 interfaces for requests, responses, and streams. This allows you + to utilize other PSR-7 compatible libraries with Guzzle. +- Abstracts away the underlying HTTP transport, allowing you to write + environment and transport agnostic code; i.e., no hard dependency on cURL, + PHP streams, sockets, or non-blocking event loops. +- Middleware system allows you to augment and compose client behavior. + +```php +$client = new \GuzzleHttp\Client(); +$res = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle'); +echo $res->getStatusCode(); +// 200 +echo $res->getHeaderLine('content-type'); +// 'application/json; charset=utf8' +echo $res->getBody(); +// '{"id": 1420053, "name": "guzzle", ...}' + +// Send an asynchronous request. +$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org'); +$promise = $client->sendAsync($request)->then(function ($response) { + echo 'I completed! ' . $response->getBody(); +}); +$promise->wait(); +``` + +## Help and docs + +- [Documentation](http://guzzlephp.org/) +- [Stack Overflow](http://stackoverflow.com/questions/tagged/guzzle) +- [Gitter](https://gitter.im/guzzle/guzzle) + + +## Installing Guzzle + +The recommended way to install Guzzle is through +[Composer](http://getcomposer.org). + +```bash +# Install Composer +curl -sS https://getcomposer.org/installer | php +``` + +Next, run the Composer command to install the latest stable version of Guzzle: + +```bash +php composer.phar require guzzlehttp/guzzle +``` + +After installing, you need to require Composer's autoloader: + +```php +require 'vendor/autoload.php'; +``` + +You can then later update Guzzle using composer: + + ```bash +composer.phar update + ``` + + +## Version Guidance + +| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version | +|---------|------------|---------------------|--------------|---------------------|---------------------|-------|-------------| +| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >= 5.3.3 | +| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >= 5.4 | +| 5.x | Maintained | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >= 5.4 | +| 6.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >= 5.5 | + +[guzzle-3-repo]: https://github.com/guzzle/guzzle3 +[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x +[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3 +[guzzle-6-repo]: https://github.com/guzzle/guzzle +[guzzle-3-docs]: http://guzzle3.readthedocs.org/en/latest/ +[guzzle-5-docs]: http://guzzle.readthedocs.org/en/5.3/ +[guzzle-6-docs]: http://guzzle.readthedocs.org/en/latest/ diff --git a/Upyun/vendor/guzzlehttp/guzzle/UPGRADING.md b/Upyun/vendor/guzzlehttp/guzzle/UPGRADING.md new file mode 100644 index 0000000..91d1dcc --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/UPGRADING.md @@ -0,0 +1,1203 @@ +Guzzle Upgrade Guide +==================== + +5.0 to 6.0 +---------- + +Guzzle now uses [PSR-7](http://www.php-fig.org/psr/psr-7/) for HTTP messages. +Due to the fact that these messages are immutable, this prompted a refactoring +of Guzzle to use a middleware based system rather than an event system. Any +HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be +updated to work with the new immutable PSR-7 request and response objects. Any +event listeners or subscribers need to be updated to become middleware +functions that wrap handlers (or are injected into a +`GuzzleHttp\HandlerStack`). + +- Removed `GuzzleHttp\BatchResults` +- Removed `GuzzleHttp\Collection` +- Removed `GuzzleHttp\HasDataTrait` +- Removed `GuzzleHttp\ToArrayInterface` +- The `guzzlehttp/streams` dependency has been removed. Stream functionality + is now present in the `GuzzleHttp\Psr7` namespace provided by the + `guzzlehttp/psr7` package. +- Guzzle no longer uses ReactPHP promises and now uses the + `guzzlehttp/promises` library. We use a custom promise library for three + significant reasons: + 1. React promises (at the time of writing this) are recursive. Promise + chaining and promise resolution will eventually blow the stack. Guzzle + promises are not recursive as they use a sort of trampolining technique. + Note: there has been movement in the React project to modify promises to + no longer utilize recursion. + 2. Guzzle needs to have the ability to synchronously block on a promise to + wait for a result. Guzzle promises allows this functionality (and does + not require the use of recursion). + 3. Because we need to be able to wait on a result, doing so using React + promises requires wrapping react promises with RingPHP futures. This + overhead is no longer needed, reducing stack sizes, reducing complexity, + and improving performance. +- `GuzzleHttp\Mimetypes` has been moved to a function in + `GuzzleHttp\Psr7\mimetype_from_extension` and + `GuzzleHttp\Psr7\mimetype_from_filename`. +- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query + strings must now be passed into request objects as strings, or provided to + the `query` request option when creating requests with clients. The `query` + option uses PHP's `http_build_query` to convert an array to a string. If you + need a different serialization technique, you will need to pass the query + string in as a string. There are a couple helper functions that will make + working with query strings easier: `GuzzleHttp\Psr7\parse_query` and + `GuzzleHttp\Psr7\build_query`. +- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware + system based on PSR-7, using RingPHP and it's middleware system as well adds + more complexity than the benefits it provides. All HTTP handlers that were + present in RingPHP have been modified to work directly with PSR-7 messages + and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces + complexity in Guzzle, removes a dependency, and improves performance. RingPHP + will be maintained for Guzzle 5 support, but will no longer be a part of + Guzzle 6. +- As Guzzle now uses a middleware based systems the event system and RingPHP + integration has been removed. Note: while the event system has been removed, + it is possible to add your own type of event system that is powered by the + middleware system. + - Removed the `Event` namespace. + - Removed the `Subscriber` namespace. + - Removed `Transaction` class + - Removed `RequestFsm` + - Removed `RingBridge` + - `GuzzleHttp\Subscriber\Cookie` is now provided by + `GuzzleHttp\Middleware::cookies` + - `GuzzleHttp\Subscriber\HttpError` is now provided by + `GuzzleHttp\Middleware::httpError` + - `GuzzleHttp\Subscriber\History` is now provided by + `GuzzleHttp\Middleware::history` + - `GuzzleHttp\Subscriber\Mock` is now provided by + `GuzzleHttp\Handler\MockHandler` + - `GuzzleHttp\Subscriber\Prepare` is now provided by + `GuzzleHttp\PrepareBodyMiddleware` + - `GuzzleHttp\Subscriber\Redirect` is now provided by + `GuzzleHttp\RedirectMiddleware` +- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in + `GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone. +- Static functions in `GuzzleHttp\Utils` have been moved to namespaced + functions under the `GuzzleHttp` namespace. This requires either a Composer + based autoloader or you to include functions.php. +- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to + `GuzzleHttp\ClientInterface::getConfig`. +- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed. +- The `json` and `xml` methods of response objects has been removed. With the + migration to strictly adhering to PSR-7 as the interface for Guzzle messages, + adding methods to message interfaces would actually require Guzzle messages + to extend from PSR-7 messages rather then work with them directly. + +## Migrating to middleware + +The change to PSR-7 unfortunately required significant refactoring to Guzzle +due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event +system from plugins. The event system relied on mutability of HTTP messages and +side effects in order to work. With immutable messages, you have to change your +workflow to become more about either returning a value (e.g., functional +middlewares) or setting a value on an object. Guzzle v6 has chosen the +functional middleware approach. + +Instead of using the event system to listen for things like the `before` event, +you now create a stack based middleware function that intercepts a request on +the way in and the promise of the response on the way out. This is a much +simpler and more predictable approach than the event system and works nicely +with PSR-7 middleware. Due to the use of promises, the middleware system is +also asynchronous. + +v5: + +```php +use GuzzleHttp\Event\BeforeEvent; +$client = new GuzzleHttp\Client(); +// Get the emitter and listen to the before event. +$client->getEmitter()->on('before', function (BeforeEvent $e) { + // Guzzle v5 events relied on mutation + $e->getRequest()->setHeader('X-Foo', 'Bar'); +}); +``` + +v6: + +In v6, you can modify the request before it is sent using the `mapRequest` +middleware. The idiomatic way in v6 to modify the request/response lifecycle is +to setup a handler middleware stack up front and inject the handler into a +client. + +```php +use GuzzleHttp\Middleware; +// Create a handler stack that has all of the default middlewares attached +$handler = GuzzleHttp\HandlerStack::create(); +// Push the handler onto the handler stack +$handler->push(Middleware::mapRequest(function (RequestInterface $request) { + // Notice that we have to return a request object + return $request->withHeader('X-Foo', 'Bar'); +})); +// Inject the handler into the client +$client = new GuzzleHttp\Client(['handler' => $handler]); +``` + +## POST Requests + +This version added the [`form_params`](http://guzzle.readthedocs.org/en/latest/request-options.html#form_params) +and `multipart` request options. `form_params` is an associative array of +strings or array of strings and is used to serialize an +`application/x-www-form-urlencoded` POST request. The +[`multipart`](http://guzzle.readthedocs.org/en/latest/request-options.html#multipart) +option is now used to send a multipart/form-data POST request. + +`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add +POST files to a multipart/form-data request. + +The `body` option no longer accepts an array to send POST requests. Please use +`multipart` or `form_params` instead. + +The `base_url` option has been renamed to `base_uri`. + +4.x to 5.0 +---------- + +## Rewritten Adapter Layer + +Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send +HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor +is still supported, but it has now been renamed to `handler`. Instead of +passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP +`callable` that follows the RingPHP specification. + +## Removed Fluent Interfaces + +[Fluent interfaces were removed](http://ocramius.github.io/blog/fluent-interfaces-are-evil) +from the following classes: + +- `GuzzleHttp\Collection` +- `GuzzleHttp\Url` +- `GuzzleHttp\Query` +- `GuzzleHttp\Post\PostBody` +- `GuzzleHttp\Cookie\SetCookie` + +## Removed functions.php + +Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following +functions can be used as replacements. + +- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode` +- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath` +- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path` +- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however, + deprecated in favor of using `GuzzleHttp\Pool::batch()`. + +The "procedural" global client has been removed with no replacement (e.g., +`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client` +object as a replacement. + +## `throwImmediately` has been removed + +The concept of "throwImmediately" has been removed from exceptions and error +events. This control mechanism was used to stop a transfer of concurrent +requests from completing. This can now be handled by throwing the exception or +by cancelling a pool of requests or each outstanding future request +individually. + +## headers event has been removed + +Removed the "headers" event. This event was only useful for changing the +body a response once the headers of the response were known. You can implement +a similar behavior in a number of ways. One example might be to use a +FnStream that has access to the transaction being sent. For example, when the +first byte is written, you could check if the response headers match your +expectations, and if so, change the actual stream body that is being +written to. + +## Updates to HTTP Messages + +Removed the `asArray` parameter from +`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header +value as an array, then use the newly added `getHeaderAsArray()` method of +`MessageInterface`. This change makes the Guzzle interfaces compatible with +the PSR-7 interfaces. + +3.x to 4.0 +---------- + +## Overarching changes: + +- Now requires PHP 5.4 or greater. +- No longer requires cURL to send requests. +- Guzzle no longer wraps every exception it throws. Only exceptions that are + recoverable are now wrapped by Guzzle. +- Various namespaces have been removed or renamed. +- No longer requiring the Symfony EventDispatcher. A custom event dispatcher + based on the Symfony EventDispatcher is + now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant + speed and functionality improvements). + +Changes per Guzzle 3.x namespace are described below. + +## Batch + +The `Guzzle\Batch` namespace has been removed. This is best left to +third-parties to implement on top of Guzzle's core HTTP library. + +## Cache + +The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement +has been implemented yet, but hoping to utilize a PSR cache interface). + +## Common + +- Removed all of the wrapped exceptions. It's better to use the standard PHP + library for unrecoverable exceptions. +- `FromConfigInterface` has been removed. +- `Guzzle\Common\Version` has been removed. The VERSION constant can be found + at `GuzzleHttp\ClientInterface::VERSION`. + +### Collection + +- `getAll` has been removed. Use `toArray` to convert a collection to an array. +- `inject` has been removed. +- `keySearch` has been removed. +- `getPath` no longer supports wildcard expressions. Use something better like + JMESPath for this. +- `setPath` now supports appending to an existing array via the `[]` notation. + +### Events + +Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses +`GuzzleHttp\Event\Emitter`. + +- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by + `GuzzleHttp\Event\EmitterInterface`. +- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by + `GuzzleHttp\Event\Emitter`. +- `Symfony\Component\EventDispatcher\Event` is replaced by + `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in + `GuzzleHttp\Event\EventInterface`. +- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and + `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the + event emitter of a request, client, etc. now uses the `getEmitter` method + rather than the `getDispatcher` method. + +#### Emitter + +- Use the `once()` method to add a listener that automatically removes itself + the first time it is invoked. +- Use the `listeners()` method to retrieve a list of event listeners rather than + the `getListeners()` method. +- Use `emit()` instead of `dispatch()` to emit an event from an emitter. +- Use `attach()` instead of `addSubscriber()` and `detach()` instead of + `removeSubscriber()`. + +```php +$mock = new Mock(); +// 3.x +$request->getEventDispatcher()->addSubscriber($mock); +$request->getEventDispatcher()->removeSubscriber($mock); +// 4.x +$request->getEmitter()->attach($mock); +$request->getEmitter()->detach($mock); +``` + +Use the `on()` method to add a listener rather than the `addListener()` method. + +```php +// 3.x +$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } ); +// 4.x +$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } ); +``` + +## Http + +### General changes + +- The cacert.pem certificate has been moved to `src/cacert.pem`. +- Added the concept of adapters that are used to transfer requests over the + wire. +- Simplified the event system. +- Sending requests in parallel is still possible, but batching is no longer a + concept of the HTTP layer. Instead, you must use the `complete` and `error` + events to asynchronously manage parallel request transfers. +- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`. +- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`. +- QueryAggregators have been rewritten so that they are simply callable + functions. +- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in + `functions.php` for an easy to use static client instance. +- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from + `GuzzleHttp\Exception\TransferException`. + +### Client + +Calling methods like `get()`, `post()`, `head()`, etc. no longer create and +return a request, but rather creates a request, sends the request, and returns +the response. + +```php +// 3.0 +$request = $client->get('/'); +$response = $request->send(); + +// 4.0 +$response = $client->get('/'); + +// or, to mirror the previous behavior +$request = $client->createRequest('GET', '/'); +$response = $client->send($request); +``` + +`GuzzleHttp\ClientInterface` has changed. + +- The `send` method no longer accepts more than one request. Use `sendAll` to + send multiple requests in parallel. +- `setUserAgent()` has been removed. Use a default request option instead. You + could, for example, do something like: + `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`. +- `setSslVerification()` has been removed. Use default request options instead, + like `$client->setConfig('defaults/verify', true)`. + +`GuzzleHttp\Client` has changed. + +- The constructor now accepts only an associative array. You can include a + `base_url` string or array to use a URI template as the base URL of a client. + You can also specify a `defaults` key that is an associative array of default + request options. You can pass an `adapter` to use a custom adapter, + `batch_adapter` to use a custom adapter for sending requests in parallel, or + a `message_factory` to change the factory used to create HTTP requests and + responses. +- The client no longer emits a `client.create_request` event. +- Creating requests with a client no longer automatically utilize a URI + template. You must pass an array into a creational method (e.g., + `createRequest`, `get`, `put`, etc.) in order to expand a URI template. + +### Messages + +Messages no longer have references to their counterparts (i.e., a request no +longer has a reference to it's response, and a response no loger has a +reference to its request). This association is now managed through a +`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to +these transaction objects using request events that are emitted over the +lifecycle of a request. + +#### Requests with a body + +- `GuzzleHttp\Message\EntityEnclosingRequest` and + `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The + separation between requests that contain a body and requests that do not + contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface` + handles both use cases. +- Any method that previously accepts a `GuzzleHttp\Response` object now accept a + `GuzzleHttp\Message\ResponseInterface`. +- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to + `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create + both requests and responses and is implemented in + `GuzzleHttp\Message\MessageFactory`. +- POST field and file methods have been removed from the request object. You + must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface` + to control the format of a POST body. Requests that are created using a + standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use + a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if + the method is POST and no body is provided. + +```php +$request = $client->createRequest('POST', '/'); +$request->getBody()->setField('foo', 'bar'); +$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r'))); +``` + +#### Headers + +- `GuzzleHttp\Message\Header` has been removed. Header values are now simply + represented by an array of values or as a string. Header values are returned + as a string by default when retrieving a header value from a message. You can + pass an optional argument of `true` to retrieve a header value as an array + of strings instead of a single concatenated string. +- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to + `GuzzleHttp\Post`. This interface has been simplified and now allows the + addition of arbitrary headers. +- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most + of the custom headers are now handled separately in specific + subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has + been updated to properly handle headers that contain parameters (like the + `Link` header). + +#### Responses + +- `GuzzleHttp\Message\Response::getInfo()` and + `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event + system to retrieve this type of information. +- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed. +- `GuzzleHttp\Message\Response::getMessage()` has been removed. +- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific + methods have moved to the CacheSubscriber. +- Header specific helper functions like `getContentMd5()` have been removed. + Just use `getHeader('Content-MD5')` instead. +- `GuzzleHttp\Message\Response::setRequest()` and + `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event + system to work with request and response objects as a transaction. +- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the + Redirect subscriber instead. +- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have + been removed. Use `getStatusCode()` instead. + +#### Streaming responses + +Streaming requests can now be created by a client directly, returning a +`GuzzleHttp\Message\ResponseInterface` object that contains a body stream +referencing an open PHP HTTP stream. + +```php +// 3.0 +use Guzzle\Stream\PhpStreamRequestFactory; +$request = $client->get('/'); +$factory = new PhpStreamRequestFactory(); +$stream = $factory->fromRequest($request); +$data = $stream->read(1024); + +// 4.0 +$response = $client->get('/', ['stream' => true]); +// Read some data off of the stream in the response body +$data = $response->getBody()->read(1024); +``` + +#### Redirects + +The `configureRedirects()` method has been removed in favor of a +`allow_redirects` request option. + +```php +// Standard redirects with a default of a max of 5 redirects +$request = $client->createRequest('GET', '/', ['allow_redirects' => true]); + +// Strict redirects with a custom number of redirects +$request = $client->createRequest('GET', '/', [ + 'allow_redirects' => ['max' => 5, 'strict' => true] +]); +``` + +#### EntityBody + +EntityBody interfaces and classes have been removed or moved to +`GuzzleHttp\Stream`. All classes and interfaces that once required +`GuzzleHttp\EntityBodyInterface` now require +`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no +longer uses `GuzzleHttp\EntityBody::factory` but now uses +`GuzzleHttp\Stream\Stream::factory` or even better: +`GuzzleHttp\Stream\create()`. + +- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface` +- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream` +- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream` +- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream` +- `Guzzle\Http\IoEmittyinEntityBody` has been removed. + +#### Request lifecycle events + +Requests previously submitted a large number of requests. The number of events +emitted over the lifecycle of a request has been significantly reduced to make +it easier to understand how to extend the behavior of a request. All events +emitted during the lifecycle of a request now emit a custom +`GuzzleHttp\Event\EventInterface` object that contains context providing +methods and a way in which to modify the transaction at that specific point in +time (e.g., intercept the request and set a response on the transaction). + +- `request.before_send` has been renamed to `before` and now emits a + `GuzzleHttp\Event\BeforeEvent` +- `request.complete` has been renamed to `complete` and now emits a + `GuzzleHttp\Event\CompleteEvent`. +- `request.sent` has been removed. Use `complete`. +- `request.success` has been removed. Use `complete`. +- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`. +- `request.exception` has been removed. Use `error`. +- `request.receive.status_line` has been removed. +- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to + maintain a status update. +- `curl.callback.write` has been removed. Use a custom `StreamInterface` to + intercept writes. +- `curl.callback.read` has been removed. Use a custom `StreamInterface` to + intercept reads. + +`headers` is a new event that is emitted after the response headers of a +request have been received before the body of the response is downloaded. This +event emits a `GuzzleHttp\Event\HeadersEvent`. + +You can intercept a request and inject a response using the `intercept()` event +of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and +`GuzzleHttp\Event\ErrorEvent` event. + +See: http://docs.guzzlephp.org/en/latest/events.html + +## Inflection + +The `Guzzle\Inflection` namespace has been removed. This is not a core concern +of Guzzle. + +## Iterator + +The `Guzzle\Iterator` namespace has been removed. + +- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and + `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of + Guzzle itself. +- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent + class is shipped with PHP 5.4. +- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because + it's easier to just wrap an iterator in a generator that maps values. + +For a replacement of these iterators, see https://github.com/nikic/iter + +## Log + +The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The +`Guzzle\Log` namespace has been removed. Guzzle now relies on +`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been +moved to `GuzzleHttp\Subscriber\Log\Formatter`. + +## Parser + +The `Guzzle\Parser` namespace has been removed. This was previously used to +make it possible to plug in custom parsers for cookies, messages, URI +templates, and URLs; however, this level of complexity is not needed in Guzzle +so it has been removed. + +- Cookie: Cookie parsing logic has been moved to + `GuzzleHttp\Cookie\SetCookie::fromString`. +- Message: Message parsing logic for both requests and responses has been moved + to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only + used in debugging or deserializing messages, so it doesn't make sense for + Guzzle as a library to add this level of complexity to parsing messages. +- UriTemplate: URI template parsing has been moved to + `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL + URI template library if it is installed. +- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously + it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary, + then developers are free to subclass `GuzzleHttp\Url`. + +## Plugin + +The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`. +Several plugins are shipping with the core Guzzle library under this namespace. + +- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar + code has moved to `GuzzleHttp\Cookie`. +- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin. +- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is + received. +- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin. +- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before + sending. This subscriber is attached to all requests by default. +- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin. + +The following plugins have been removed (third-parties are free to re-implement +these if needed): + +- `GuzzleHttp\Plugin\Async` has been removed. +- `GuzzleHttp\Plugin\CurlAuth` has been removed. +- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This + functionality should instead be implemented with event listeners that occur + after normal response parsing occurs in the guzzle/command package. + +The following plugins are not part of the core Guzzle package, but are provided +in separate repositories: + +- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler + to build custom retry policies using simple functions rather than various + chained classes. See: https://github.com/guzzle/retry-subscriber +- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to + https://github.com/guzzle/cache-subscriber +- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to + https://github.com/guzzle/log-subscriber +- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to + https://github.com/guzzle/message-integrity-subscriber +- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to + `GuzzleHttp\Subscriber\MockSubscriber`. +- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to + https://github.com/guzzle/oauth-subscriber + +## Service + +The service description layer of Guzzle has moved into two separate packages: + +- http://github.com/guzzle/command Provides a high level abstraction over web + services by representing web service operations using commands. +- http://github.com/guzzle/guzzle-services Provides an implementation of + guzzle/command that provides request serialization and response parsing using + Guzzle service descriptions. + +## Stream + +Stream have moved to a separate package available at +https://github.com/guzzle/streams. + +`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take +on the responsibilities of `Guzzle\Http\EntityBody` and +`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number +of methods implemented by the `StreamInterface` has been drastically reduced to +allow developers to more easily extend and decorate stream behavior. + +## Removed methods from StreamInterface + +- `getStream` and `setStream` have been removed to better encapsulate streams. +- `getMetadata` and `setMetadata` have been removed in favor of + `GuzzleHttp\Stream\MetadataStreamInterface`. +- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been + removed. This data is accessible when + using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`. +- `rewind` has been removed. Use `seek(0)` for a similar behavior. + +## Renamed methods + +- `detachStream` has been renamed to `detach`. +- `feof` has been renamed to `eof`. +- `ftell` has been renamed to `tell`. +- `readLine` has moved from an instance method to a static class method of + `GuzzleHttp\Stream\Stream`. + +## Metadata streams + +`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams +that contain additional metadata accessible via `getMetadata()`. +`GuzzleHttp\Stream\StreamInterface::getMetadata` and +`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed. + +## StreamRequestFactory + +The entire concept of the StreamRequestFactory has been removed. The way this +was used in Guzzle 3 broke the actual interface of sending streaming requests +(instead of getting back a Response, you got a StreamInterface). Streaming +PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`. + +3.6 to 3.7 +---------- + +### Deprecations + +- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.: + +```php +\Guzzle\Common\Version::$emitWarnings = true; +``` + +The following APIs and options have been marked as deprecated: + +- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +- Marked `Guzzle\Common\Collection::inject()` as deprecated. +- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use + `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or + `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` + +3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational +request methods. When paired with a client's configuration settings, these options allow you to specify default settings +for various aspects of a request. Because these options make other previous configuration options redundant, several +configuration options and methods of a client and AbstractCommand have been deprecated. + +- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`. +- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`. +- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')` +- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0 + + $command = $client->getCommand('foo', array( + 'command.headers' => array('Test' => '123'), + 'command.response_body' => '/path/to/file' + )); + + // Should be changed to: + + $command = $client->getCommand('foo', array( + 'command.request_options' => array( + 'headers' => array('Test' => '123'), + 'save_as' => '/path/to/file' + ) + )); + +### Interface changes + +Additions and changes (you will need to update any implementations or subclasses you may have created): + +- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +- Added `Guzzle\Stream\StreamInterface::isRepeatable` +- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. + +The following methods were removed from interfaces. All of these methods are still available in the concrete classes +that implement them, but you should update your code to use alternative methods: + +- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or + `$client->setDefaultOption('headers/{header_name}', 'value')`. or + `$client->setDefaultOption('headers', array('header_name' => 'value'))`. +- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`. +- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail. +- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin. +- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin. +- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin. + +### Cache plugin breaking changes + +- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +- Always setting X-cache headers on cached responses +- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +- Added `CacheStorageInterface::purge($url)` +- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +3.5 to 3.6 +---------- + +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). + For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader(). + Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request. +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Moved getLinks() from Response to just be used on a Link header object. + +If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the +HeaderInterface (e.g. toArray(), getAll(), etc.). + +### Interface changes + +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() + +### Removed deprecated functions + +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). + +### Deprecations + +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. + +### Other changes + +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess + +3.3 to 3.4 +---------- + +Base URLs of a client now follow the rules of http://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs. + +3.2 to 3.3 +---------- + +### Response::getEtag() quote stripping removed + +`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header + +### Removed `Guzzle\Http\Utils` + +The `Guzzle\Http\Utils` class was removed. This class was only used for testing. + +### Stream wrapper and type + +`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase. + +### curl.emit_io became emit_io + +Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the +'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' + +3.1 to 3.2 +---------- + +### CurlMulti is no longer reused globally + +Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added +to a single client can pollute requests dispatched from other clients. + +If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the +ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is +created. + +```php +$multi = new Guzzle\Http\Curl\CurlMulti(); +$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json'); +$builder->addListener('service_builder.create_client', function ($event) use ($multi) { + $event['client']->setCurlMulti($multi); +} +}); +``` + +### No default path + +URLs no longer have a default path value of '/' if no path was specified. + +Before: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com/ +``` + +After: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com +``` + +### Less verbose BadResponseException + +The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and +response information. You can, however, get access to the request and response object by calling `getRequest()` or +`getResponse()` on the exception object. + +### Query parameter aggregation + +Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a +setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is +responsible for handling the aggregation of multi-valued query string variables into a flattened hash. + +2.8 to 3.x +---------- + +### Guzzle\Service\Inspector + +Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig` + +**Before** + +```php +use Guzzle\Service\Inspector; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Inspector::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +**After** + +```php +use Guzzle\Common\Collection; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Collection::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +### Convert XML Service Descriptions to JSON + +**Before** + +```xml + + + + + + Get a list of groups + + + Uses a search query to get a list of groups + + + + Create a group + + + + + Delete a group by ID + + + + + + + Update a group + + + + + + +``` + +**After** + +```json +{ + "name": "Zendesk REST API v2", + "apiVersion": "2012-12-31", + "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users", + "operations": { + "list_groups": { + "httpMethod":"GET", + "uri": "groups.json", + "summary": "Get a list of groups" + }, + "search_groups":{ + "httpMethod":"GET", + "uri": "search.json?query=\"{query} type:group\"", + "summary": "Uses a search query to get a list of groups", + "parameters":{ + "query":{ + "location": "uri", + "description":"Zendesk Search Query", + "type": "string", + "required": true + } + } + }, + "create_group": { + "httpMethod":"POST", + "uri": "groups.json", + "summary": "Create a group", + "parameters":{ + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + }, + "delete_group": { + "httpMethod":"DELETE", + "uri": "groups/{id}.json", + "summary": "Delete a group", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to delete by ID", + "type": "integer", + "required": true + } + } + }, + "get_group": { + "httpMethod":"GET", + "uri": "groups/{id}.json", + "summary": "Get a ticket", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to get by ID", + "type": "integer", + "required": true + } + } + }, + "update_group": { + "httpMethod":"PUT", + "uri": "groups/{id}.json", + "summary": "Update a group", + "parameters":{ + "id": { + "location": "uri", + "description":"Group to update by ID", + "type": "integer", + "required": true + }, + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + } +} +``` + +### Guzzle\Service\Description\ServiceDescription + +Commands are now called Operations + +**Before** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getCommands(); // @returns ApiCommandInterface[] +$sd->hasCommand($name); +$sd->getCommand($name); // @returns ApiCommandInterface|null +$sd->addCommand($command); // @param ApiCommandInterface $command +``` + +**After** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getOperations(); // @returns OperationInterface[] +$sd->hasOperation($name); +$sd->getOperation($name); // @returns OperationInterface|null +$sd->addOperation($operation); // @param OperationInterface $operation +``` + +### Guzzle\Common\Inflection\Inflector + +Namespace is now `Guzzle\Inflection\Inflector` + +### Guzzle\Http\Plugin + +Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below. + +### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log + +Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively. + +**Before** + +```php +use Guzzle\Common\Log\ClosureLogAdapter; +use Guzzle\Http\Plugin\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $verbosity is an integer indicating desired message verbosity level +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE); +``` + +**After** + +```php +use Guzzle\Log\ClosureLogAdapter; +use Guzzle\Log\MessageFormatter; +use Guzzle\Plugin\Log\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $format is a string indicating desired message format -- @see MessageFormatter +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT); +``` + +### Guzzle\Http\Plugin\CurlAuthPlugin + +Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`. + +### Guzzle\Http\Plugin\ExponentialBackoffPlugin + +Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes. + +**Before** + +```php +use Guzzle\Http\Plugin\ExponentialBackoffPlugin; + +$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge( + ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429) + )); + +$client->addSubscriber($backoffPlugin); +``` + +**After** + +```php +use Guzzle\Plugin\Backoff\BackoffPlugin; +use Guzzle\Plugin\Backoff\HttpBackoffStrategy; + +// Use convenient factory method instead -- see implementation for ideas of what +// you can do with chaining backoff strategies +$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge( + HttpBackoffStrategy::getDefaultFailureCodes(), array(429) + )); +$client->addSubscriber($backoffPlugin); +``` + +### Known Issues + +#### [BUG] Accept-Encoding header behavior changed unintentionally. + +(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e) + +In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to +properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen. +See issue #217 for a workaround, or use a version containing the fix. diff --git a/Upyun/vendor/guzzlehttp/guzzle/composer.json b/Upyun/vendor/guzzlehttp/guzzle/composer.json new file mode 100644 index 0000000..65687a5 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/composer.json @@ -0,0 +1,44 @@ +{ + "name": "guzzlehttp/guzzle", + "type": "library", + "description": "Guzzle is a PHP HTTP client library", + "keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"], + "homepage": "http://guzzlephp.org/", + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.5", + "guzzlehttp/psr7": "^1.4", + "guzzlehttp/promises": "^1.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0 || ^5.0", + "psr/log": "^1.0" + }, + "autoload": { + "files": ["src/functions_include.php"], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\": "tests/" + } + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/Client.php b/Upyun/vendor/guzzlehttp/guzzle/src/Client.php new file mode 100644 index 0000000..de4df8a --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/Client.php @@ -0,0 +1,414 @@ + 'http://www.foo.com/1.0/', + * 'timeout' => 0, + * 'allow_redirects' => false, + * 'proxy' => '192.168.16.1:10' + * ]); + * + * Client configuration settings include the following options: + * + * - handler: (callable) Function that transfers HTTP requests over the + * wire. The function is called with a Psr7\Http\Message\RequestInterface + * and array of transfer options, and must return a + * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a + * Psr7\Http\Message\ResponseInterface on success. "handler" is a + * constructor only option that cannot be overridden in per/request + * options. If no handler is provided, a default handler will be created + * that enables all of the request options below by attaching all of the + * default middleware to the handler. + * - base_uri: (string|UriInterface) Base URI of the client that is merged + * into relative URIs. Can be a string or instance of UriInterface. + * - **: any request option + * + * @param array $config Client configuration settings. + * + * @see \GuzzleHttp\RequestOptions for a list of available request options. + */ + public function __construct(array $config = []) + { + if (!isset($config['handler'])) { + $config['handler'] = HandlerStack::create(); + } elseif (!is_callable($config['handler'])) { + throw new \InvalidArgumentException('handler must be a callable'); + } + + // Convert the base_uri to a UriInterface + if (isset($config['base_uri'])) { + $config['base_uri'] = Psr7\uri_for($config['base_uri']); + } + + $this->configureDefaults($config); + } + + public function __call($method, $args) + { + if (count($args) < 1) { + throw new \InvalidArgumentException('Magic request methods require a URI and optional options array'); + } + + $uri = $args[0]; + $opts = isset($args[1]) ? $args[1] : []; + + return substr($method, -5) === 'Async' + ? $this->requestAsync(substr($method, 0, -5), $uri, $opts) + : $this->request($method, $uri, $opts); + } + + public function sendAsync(RequestInterface $request, array $options = []) + { + // Merge the base URI into the request URI if needed. + $options = $this->prepareDefaults($options); + + return $this->transfer( + $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')), + $options + ); + } + + public function send(RequestInterface $request, array $options = []) + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->sendAsync($request, $options)->wait(); + } + + public function requestAsync($method, $uri = '', array $options = []) + { + $options = $this->prepareDefaults($options); + // Remove request modifying parameter because it can be done up-front. + $headers = isset($options['headers']) ? $options['headers'] : []; + $body = isset($options['body']) ? $options['body'] : null; + $version = isset($options['version']) ? $options['version'] : '1.1'; + // Merge the URI into the base URI. + $uri = $this->buildUri($uri, $options); + if (is_array($body)) { + $this->invalidBody(); + } + $request = new Psr7\Request($method, $uri, $headers, $body, $version); + // Remove the option so that they are not doubly-applied. + unset($options['headers'], $options['body'], $options['version']); + + return $this->transfer($request, $options); + } + + public function request($method, $uri = '', array $options = []) + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->requestAsync($method, $uri, $options)->wait(); + } + + public function getConfig($option = null) + { + return $option === null + ? $this->config + : (isset($this->config[$option]) ? $this->config[$option] : null); + } + + private function buildUri($uri, array $config) + { + // for BC we accept null which would otherwise fail in uri_for + $uri = Psr7\uri_for($uri === null ? '' : $uri); + + if (isset($config['base_uri'])) { + $uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri); + } + + return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; + } + + /** + * Configures the default options for a client. + * + * @param array $config + */ + private function configureDefaults(array $config) + { + $defaults = [ + 'allow_redirects' => RedirectMiddleware::$defaultSettings, + 'http_errors' => true, + 'decode_content' => true, + 'verify' => true, + 'cookies' => false + ]; + + // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set. + + // We can only trust the HTTP_PROXY environment variable in a CLI + // process due to the fact that PHP has no reliable mechanism to + // get environment variables that start with "HTTP_". + if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) { + $defaults['proxy']['http'] = getenv('HTTP_PROXY'); + } + + if ($proxy = getenv('HTTPS_PROXY')) { + $defaults['proxy']['https'] = $proxy; + } + + if ($noProxy = getenv('NO_PROXY')) { + $cleanedNoProxy = str_replace(' ', '', $noProxy); + $defaults['proxy']['no'] = explode(',', $cleanedNoProxy); + } + + $this->config = $config + $defaults; + + if (!empty($config['cookies']) && $config['cookies'] === true) { + $this->config['cookies'] = new CookieJar(); + } + + // Add the default user-agent header. + if (!isset($this->config['headers'])) { + $this->config['headers'] = ['User-Agent' => default_user_agent()]; + } else { + // Add the User-Agent header if one was not already set. + foreach (array_keys($this->config['headers']) as $name) { + if (strtolower($name) === 'user-agent') { + return; + } + } + $this->config['headers']['User-Agent'] = default_user_agent(); + } + } + + /** + * Merges default options into the array. + * + * @param array $options Options to modify by reference + * + * @return array + */ + private function prepareDefaults($options) + { + $defaults = $this->config; + + if (!empty($defaults['headers'])) { + // Default headers are only added if they are not present. + $defaults['_conditional'] = $defaults['headers']; + unset($defaults['headers']); + } + + // Special handling for headers is required as they are added as + // conditional headers and as headers passed to a request ctor. + if (array_key_exists('headers', $options)) { + // Allows default headers to be unset. + if ($options['headers'] === null) { + $defaults['_conditional'] = null; + unset($options['headers']); + } elseif (!is_array($options['headers'])) { + throw new \InvalidArgumentException('headers must be an array'); + } + } + + // Shallow merge defaults underneath options. + $result = $options + $defaults; + + // Remove null values. + foreach ($result as $k => $v) { + if ($v === null) { + unset($result[$k]); + } + } + + return $result; + } + + /** + * Transfers the given request and applies request options. + * + * The URI of the request is not modified and the request options are used + * as-is without merging in default options. + * + * @param RequestInterface $request + * @param array $options + * + * @return Promise\PromiseInterface + */ + private function transfer(RequestInterface $request, array $options) + { + // save_to -> sink + if (isset($options['save_to'])) { + $options['sink'] = $options['save_to']; + unset($options['save_to']); + } + + // exceptions -> http_errors + if (isset($options['exceptions'])) { + $options['http_errors'] = $options['exceptions']; + unset($options['exceptions']); + } + + $request = $this->applyOptions($request, $options); + $handler = $options['handler']; + + try { + return Promise\promise_for($handler($request, $options)); + } catch (\Exception $e) { + return Promise\rejection_for($e); + } + } + + /** + * Applies the array of request options to a request. + * + * @param RequestInterface $request + * @param array $options + * + * @return RequestInterface + */ + private function applyOptions(RequestInterface $request, array &$options) + { + $modify = []; + + if (isset($options['form_params'])) { + if (isset($options['multipart'])) { + throw new \InvalidArgumentException('You cannot use ' + . 'form_params and multipart at the same time. Use the ' + . 'form_params option if you want to send application/' + . 'x-www-form-urlencoded requests, and the multipart ' + . 'option to send multipart/form-data requests.'); + } + $options['body'] = http_build_query($options['form_params'], '', '&'); + unset($options['form_params']); + $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + if (isset($options['multipart'])) { + $options['body'] = new Psr7\MultipartStream($options['multipart']); + unset($options['multipart']); + } + + if (isset($options['json'])) { + $options['body'] = \GuzzleHttp\json_encode($options['json']); + unset($options['json']); + $options['_conditional']['Content-Type'] = 'application/json'; + } + + if (!empty($options['decode_content']) + && $options['decode_content'] !== true + ) { + $modify['set_headers']['Accept-Encoding'] = $options['decode_content']; + } + + if (isset($options['headers'])) { + if (isset($modify['set_headers'])) { + $modify['set_headers'] = $options['headers'] + $modify['set_headers']; + } else { + $modify['set_headers'] = $options['headers']; + } + unset($options['headers']); + } + + if (isset($options['body'])) { + if (is_array($options['body'])) { + $this->invalidBody(); + } + $modify['body'] = Psr7\stream_for($options['body']); + unset($options['body']); + } + + if (!empty($options['auth']) && is_array($options['auth'])) { + $value = $options['auth']; + $type = isset($value[2]) ? strtolower($value[2]) : 'basic'; + switch ($type) { + case 'basic': + $modify['set_headers']['Authorization'] = 'Basic ' + . base64_encode("$value[0]:$value[1]"); + break; + case 'digest': + // @todo: Do not rely on curl + $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST; + $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + case 'ntlm': + $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_NTLM; + $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + } + } + + if (isset($options['query'])) { + $value = $options['query']; + if (is_array($value)) { + $value = http_build_query($value, null, '&', PHP_QUERY_RFC3986); + } + if (!is_string($value)) { + throw new \InvalidArgumentException('query must be a string or array'); + } + $modify['query'] = $value; + unset($options['query']); + } + + // Ensure that sink is not an invalid value. + if (isset($options['sink'])) { + // TODO: Add more sink validation? + if (is_bool($options['sink'])) { + throw new \InvalidArgumentException('sink must not be a boolean'); + } + } + + $request = Psr7\modify_request($request, $modify); + if ($request->getBody() instanceof Psr7\MultipartStream) { + // Use a multipart/form-data POST if a Content-Type is not set. + $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary=' + . $request->getBody()->getBoundary(); + } + + // Merge in conditional headers if they are not present. + if (isset($options['_conditional'])) { + // Build up the changes so it's in a single clone of the message. + $modify = []; + foreach ($options['_conditional'] as $k => $v) { + if (!$request->hasHeader($k)) { + $modify['set_headers'][$k] = $v; + } + } + $request = Psr7\modify_request($request, $modify); + // Don't pass this internal value along to middleware/handlers. + unset($options['_conditional']); + } + + return $request; + } + + private function invalidBody() + { + throw new \InvalidArgumentException('Passing in the "body" request ' + . 'option as an array to send a POST request has been deprecated. ' + . 'Please use the "form_params" request option to send a ' + . 'application/x-www-form-urlencoded request, or the "multipart" ' + . 'request option to send a multipart/form-data request.'); + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/ClientInterface.php b/Upyun/vendor/guzzlehttp/guzzle/src/ClientInterface.php new file mode 100644 index 0000000..5a67b66 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/ClientInterface.php @@ -0,0 +1,84 @@ +strictMode = $strictMode; + + foreach ($cookieArray as $cookie) { + if (!($cookie instanceof SetCookie)) { + $cookie = new SetCookie($cookie); + } + $this->setCookie($cookie); + } + } + + /** + * Create a new Cookie jar from an associative array and domain. + * + * @param array $cookies Cookies to create the jar from + * @param string $domain Domain to set the cookies to + * + * @return self + */ + public static function fromArray(array $cookies, $domain) + { + $cookieJar = new self(); + foreach ($cookies as $name => $value) { + $cookieJar->setCookie(new SetCookie([ + 'Domain' => $domain, + 'Name' => $name, + 'Value' => $value, + 'Discard' => true + ])); + } + + return $cookieJar; + } + + /** + * @deprecated + */ + public static function getCookieValue($value) + { + return $value; + } + + /** + * Evaluate if this cookie should be persisted to storage + * that survives between requests. + * + * @param SetCookie $cookie Being evaluated. + * @param bool $allowSessionCookies If we should persist session cookies + * @return bool + */ + public static function shouldPersist( + SetCookie $cookie, + $allowSessionCookies = false + ) { + if ($cookie->getExpires() || $allowSessionCookies) { + if (!$cookie->getDiscard()) { + return true; + } + } + + return false; + } + + /** + * Finds and returns the cookie based on the name + * + * @param string $name cookie name to search for + * @return SetCookie|null cookie that was found or null if not found + */ + public function getCookieByName($name) + { + // don't allow a null name + if($name === null) { + return null; + } + foreach($this->cookies as $cookie) { + if($cookie->getName() !== null && strcasecmp($cookie->getName(), $name) === 0) { + return $cookie; + } + } + } + + public function toArray() + { + return array_map(function (SetCookie $cookie) { + return $cookie->toArray(); + }, $this->getIterator()->getArrayCopy()); + } + + public function clear($domain = null, $path = null, $name = null) + { + if (!$domain) { + $this->cookies = []; + return; + } elseif (!$path) { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain) { + return !$cookie->matchesDomain($domain); + } + ); + } elseif (!$name) { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain) { + return !($cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } else { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain, $name) { + return !($cookie->getName() == $name && + $cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } + } + + public function clearSessionCookies() + { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) { + return !$cookie->getDiscard() && $cookie->getExpires(); + } + ); + } + + public function setCookie(SetCookie $cookie) + { + // If the name string is empty (but not 0), ignore the set-cookie + // string entirely. + $name = $cookie->getName(); + if (!$name && $name !== '0') { + return false; + } + + // Only allow cookies with set and valid domain, name, value + $result = $cookie->validate(); + if ($result !== true) { + if ($this->strictMode) { + throw new \RuntimeException('Invalid cookie: ' . $result); + } else { + $this->removeCookieIfEmpty($cookie); + return false; + } + } + + // Resolve conflicts with previously set cookies + foreach ($this->cookies as $i => $c) { + + // Two cookies are identical, when their path, and domain are + // identical. + if ($c->getPath() != $cookie->getPath() || + $c->getDomain() != $cookie->getDomain() || + $c->getName() != $cookie->getName() + ) { + continue; + } + + // The previously set cookie is a discard cookie and this one is + // not so allow the new cookie to be set + if (!$cookie->getDiscard() && $c->getDiscard()) { + unset($this->cookies[$i]); + continue; + } + + // If the new cookie's expiration is further into the future, then + // replace the old cookie + if ($cookie->getExpires() > $c->getExpires()) { + unset($this->cookies[$i]); + continue; + } + + // If the value has changed, we better change it + if ($cookie->getValue() !== $c->getValue()) { + unset($this->cookies[$i]); + continue; + } + + // The cookie exists, so no need to continue + return false; + } + + $this->cookies[] = $cookie; + + return true; + } + + public function count() + { + return count($this->cookies); + } + + public function getIterator() + { + return new \ArrayIterator(array_values($this->cookies)); + } + + public function extractCookies( + RequestInterface $request, + ResponseInterface $response + ) { + if ($cookieHeader = $response->getHeader('Set-Cookie')) { + foreach ($cookieHeader as $cookie) { + $sc = SetCookie::fromString($cookie); + if (!$sc->getDomain()) { + $sc->setDomain($request->getUri()->getHost()); + } + if (0 !== strpos($sc->getPath(), '/')) { + $sc->setPath($this->getCookiePathFromRequest($request)); + } + $this->setCookie($sc); + } + } + } + + /** + * Computes cookie path following RFC 6265 section 5.1.4 + * + * @link https://tools.ietf.org/html/rfc6265#section-5.1.4 + * + * @param RequestInterface $request + * @return string + */ + private function getCookiePathFromRequest(RequestInterface $request) + { + $uriPath = $request->getUri()->getPath(); + if ('' === $uriPath) { + return '/'; + } + if (0 !== strpos($uriPath, '/')) { + return '/'; + } + if ('/' === $uriPath) { + return '/'; + } + if (0 === $lastSlashPos = strrpos($uriPath, '/')) { + return '/'; + } + + return substr($uriPath, 0, $lastSlashPos); + } + + public function withCookieHeader(RequestInterface $request) + { + $values = []; + $uri = $request->getUri(); + $scheme = $uri->getScheme(); + $host = $uri->getHost(); + $path = $uri->getPath() ?: '/'; + + foreach ($this->cookies as $cookie) { + if ($cookie->matchesPath($path) && + $cookie->matchesDomain($host) && + !$cookie->isExpired() && + (!$cookie->getSecure() || $scheme === 'https') + ) { + $values[] = $cookie->getName() . '=' + . $cookie->getValue(); + } + } + + return $values + ? $request->withHeader('Cookie', implode('; ', $values)) + : $request; + } + + /** + * If a cookie already exists and the server asks to set it again with a + * null value, the cookie must be deleted. + * + * @param SetCookie $cookie + */ + private function removeCookieIfEmpty(SetCookie $cookie) + { + $cookieValue = $cookie->getValue(); + if ($cookieValue === null || $cookieValue === '') { + $this->clear( + $cookie->getDomain(), + $cookie->getPath(), + $cookie->getName() + ); + } + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/Upyun/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php new file mode 100644 index 0000000..2cf298a --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php @@ -0,0 +1,84 @@ +filename = $cookieFile; + $this->storeSessionCookies = $storeSessionCookies; + + if (file_exists($cookieFile)) { + $this->load($cookieFile); + } + } + + /** + * Saves the file when shutting down + */ + public function __destruct() + { + $this->save($this->filename); + } + + /** + * Saves the cookies to a file. + * + * @param string $filename File to save + * @throws \RuntimeException if the file cannot be found or created + */ + public function save($filename) + { + $json = []; + foreach ($this as $cookie) { + /** @var SetCookie $cookie */ + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $jsonStr = \GuzzleHttp\json_encode($json); + if (false === file_put_contents($filename, $jsonStr)) { + throw new \RuntimeException("Unable to save file {$filename}"); + } + } + + /** + * Load cookies from a JSON formatted file. + * + * Old cookies are kept unless overwritten by newly loaded ones. + * + * @param string $filename Cookie file to load. + * @throws \RuntimeException if the file cannot be loaded. + */ + public function load($filename) + { + $json = file_get_contents($filename); + if (false === $json) { + throw new \RuntimeException("Unable to load file {$filename}"); + } elseif ($json === '') { + return; + } + + $data = \GuzzleHttp\json_decode($json, true); + if (is_array($data)) { + foreach (json_decode($json, true) as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (strlen($data)) { + throw new \RuntimeException("Invalid cookie file: {$filename}"); + } + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/Upyun/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php new file mode 100644 index 0000000..e4bfafd --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php @@ -0,0 +1,71 @@ +sessionKey = $sessionKey; + $this->storeSessionCookies = $storeSessionCookies; + $this->load(); + } + + /** + * Saves cookies to session when shutting down + */ + public function __destruct() + { + $this->save(); + } + + /** + * Save cookies to the client session + */ + public function save() + { + $json = []; + foreach ($this as $cookie) { + /** @var SetCookie $cookie */ + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $_SESSION[$this->sessionKey] = json_encode($json); + } + + /** + * Load the contents of the client session into the data array + */ + protected function load() + { + if (!isset($_SESSION[$this->sessionKey])) { + return; + } + $data = json_decode($_SESSION[$this->sessionKey], true); + if (is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (strlen($data)) { + throw new \RuntimeException("Invalid cookie data"); + } + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/Upyun/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php new file mode 100644 index 0000000..c911e2a --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -0,0 +1,404 @@ + null, + 'Value' => null, + 'Domain' => null, + 'Path' => '/', + 'Max-Age' => null, + 'Expires' => null, + 'Secure' => false, + 'Discard' => false, + 'HttpOnly' => false + ]; + + /** @var array Cookie data */ + private $data; + + /** + * Create a new SetCookie object from a string + * + * @param string $cookie Set-Cookie header string + * + * @return self + */ + public static function fromString($cookie) + { + // Create the default return array + $data = self::$defaults; + // Explode the cookie string using a series of semicolons + $pieces = array_filter(array_map('trim', explode(';', $cookie))); + // The name of the cookie (first kvp) must include an equal sign. + if (empty($pieces) || !strpos($pieces[0], '=')) { + return new self($data); + } + + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + + $cookieParts = explode('=', $part, 2); + $key = trim($cookieParts[0]); + $value = isset($cookieParts[1]) + ? trim($cookieParts[1], " \n\r\t\0\x0B") + : true; + + // Only check for non-cookies when cookies have been found + if (empty($data['Name'])) { + $data['Name'] = $key; + $data['Value'] = $value; + } else { + foreach (array_keys(self::$defaults) as $search) { + if (!strcasecmp($search, $key)) { + $data[$search] = $value; + continue 2; + } + } + $data[$key] = $value; + } + } + + return new self($data); + } + + /** + * @param array $data Array of cookie data provided by a Cookie parser + */ + public function __construct(array $data = []) + { + $this->data = array_replace(self::$defaults, $data); + // Extract the Expires value and turn it into a UNIX timestamp if needed + if (!$this->getExpires() && $this->getMaxAge()) { + // Calculate the Expires date + $this->setExpires(time() + $this->getMaxAge()); + } elseif ($this->getExpires() && !is_numeric($this->getExpires())) { + $this->setExpires($this->getExpires()); + } + } + + public function __toString() + { + $str = $this->data['Name'] . '=' . $this->data['Value'] . '; '; + foreach ($this->data as $k => $v) { + if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) { + if ($k === 'Expires') { + $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; '; + } else { + $str .= ($v === true ? $k : "{$k}={$v}") . '; '; + } + } + } + + return rtrim($str, '; '); + } + + public function toArray() + { + return $this->data; + } + + /** + * Get the cookie name + * + * @return string + */ + public function getName() + { + return $this->data['Name']; + } + + /** + * Set the cookie name + * + * @param string $name Cookie name + */ + public function setName($name) + { + $this->data['Name'] = $name; + } + + /** + * Get the cookie value + * + * @return string + */ + public function getValue() + { + return $this->data['Value']; + } + + /** + * Set the cookie value + * + * @param string $value Cookie value + */ + public function setValue($value) + { + $this->data['Value'] = $value; + } + + /** + * Get the domain + * + * @return string|null + */ + public function getDomain() + { + return $this->data['Domain']; + } + + /** + * Set the domain of the cookie + * + * @param string $domain + */ + public function setDomain($domain) + { + $this->data['Domain'] = $domain; + } + + /** + * Get the path + * + * @return string + */ + public function getPath() + { + return $this->data['Path']; + } + + /** + * Set the path of the cookie + * + * @param string $path Path of the cookie + */ + public function setPath($path) + { + $this->data['Path'] = $path; + } + + /** + * Maximum lifetime of the cookie in seconds + * + * @return int|null + */ + public function getMaxAge() + { + return $this->data['Max-Age']; + } + + /** + * Set the max-age of the cookie + * + * @param int $maxAge Max age of the cookie in seconds + */ + public function setMaxAge($maxAge) + { + $this->data['Max-Age'] = $maxAge; + } + + /** + * The UNIX timestamp when the cookie Expires + * + * @return mixed + */ + public function getExpires() + { + return $this->data['Expires']; + } + + /** + * Set the unix timestamp for which the cookie will expire + * + * @param int $timestamp Unix timestamp + */ + public function setExpires($timestamp) + { + $this->data['Expires'] = is_numeric($timestamp) + ? (int) $timestamp + : strtotime($timestamp); + } + + /** + * Get whether or not this is a secure cookie + * + * @return null|bool + */ + public function getSecure() + { + return $this->data['Secure']; + } + + /** + * Set whether or not the cookie is secure + * + * @param bool $secure Set to true or false if secure + */ + public function setSecure($secure) + { + $this->data['Secure'] = $secure; + } + + /** + * Get whether or not this is a session cookie + * + * @return null|bool + */ + public function getDiscard() + { + return $this->data['Discard']; + } + + /** + * Set whether or not this is a session cookie + * + * @param bool $discard Set to true or false if this is a session cookie + */ + public function setDiscard($discard) + { + $this->data['Discard'] = $discard; + } + + /** + * Get whether or not this is an HTTP only cookie + * + * @return bool + */ + public function getHttpOnly() + { + return $this->data['HttpOnly']; + } + + /** + * Set whether or not this is an HTTP only cookie + * + * @param bool $httpOnly Set to true or false if this is HTTP only + */ + public function setHttpOnly($httpOnly) + { + $this->data['HttpOnly'] = $httpOnly; + } + + /** + * Check if the cookie matches a path value. + * + * A request-path path-matches a given cookie-path if at least one of + * the following conditions holds: + * + * - The cookie-path and the request-path are identical. + * - The cookie-path is a prefix of the request-path, and the last + * character of the cookie-path is %x2F ("/"). + * - The cookie-path is a prefix of the request-path, and the first + * character of the request-path that is not included in the cookie- + * path is a %x2F ("/") character. + * + * @param string $requestPath Path to check against + * + * @return bool + */ + public function matchesPath($requestPath) + { + $cookiePath = $this->getPath(); + + // Match on exact matches or when path is the default empty "/" + if ($cookiePath === '/' || $cookiePath == $requestPath) { + return true; + } + + // Ensure that the cookie-path is a prefix of the request path. + if (0 !== strpos($requestPath, $cookiePath)) { + return false; + } + + // Match if the last character of the cookie-path is "/" + if (substr($cookiePath, -1, 1) === '/') { + return true; + } + + // Match if the first character not included in cookie path is "/" + return substr($requestPath, strlen($cookiePath), 1) === '/'; + } + + /** + * Check if the cookie matches a domain value + * + * @param string $domain Domain to check against + * + * @return bool + */ + public function matchesDomain($domain) + { + // Remove the leading '.' as per spec in RFC 6265. + // http://tools.ietf.org/html/rfc6265#section-5.2.3 + $cookieDomain = ltrim($this->getDomain(), '.'); + + // Domain not set or exact match. + if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) { + return true; + } + + // Matching the subdomain according to RFC 6265. + // http://tools.ietf.org/html/rfc6265#section-5.1.3 + if (filter_var($domain, FILTER_VALIDATE_IP)) { + return false; + } + + return (bool) preg_match('/\.' . preg_quote($cookieDomain) . '$/', $domain); + } + + /** + * Check if the cookie is expired + * + * @return bool + */ + public function isExpired() + { + return $this->getExpires() && time() > $this->getExpires(); + } + + /** + * Check if the cookie is valid according to RFC 6265 + * + * @return bool|string Returns true if valid or an error message if invalid + */ + public function validate() + { + // Names must not be empty, but can be 0 + $name = $this->getName(); + if (empty($name) && !is_numeric($name)) { + return 'The cookie name must not be empty'; + } + + // Check if any of the invalid characters are present in the cookie name + if (preg_match( + '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/', + $name) + ) { + return 'Cookie name must not contain invalid characters: ASCII ' + . 'Control characters (0-31;127), space, tab and the ' + . 'following characters: ()<>@,;:\"/?={}'; + } + + // Value must not be empty, but can be 0 + $value = $this->getValue(); + if (empty($value) && !is_numeric($value)) { + return 'The cookie value must not be empty'; + } + + // Domains must not be empty, but can be 0 + // A "0" is not a valid internet domain, but may be used as server name + // in a private network. + $domain = $this->getDomain(); + if (empty($domain) && !is_numeric($domain)) { + return 'The cookie domain must not be empty'; + } + + return true; + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php b/Upyun/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php new file mode 100644 index 0000000..427d896 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php @@ -0,0 +1,27 @@ +getStatusCode() + : 0; + parent::__construct($message, $code, $previous); + $this->request = $request; + $this->response = $response; + $this->handlerContext = $handlerContext; + } + + /** + * Wrap non-RequestExceptions with a RequestException + * + * @param RequestInterface $request + * @param \Exception $e + * + * @return RequestException + */ + public static function wrapException(RequestInterface $request, \Exception $e) + { + return $e instanceof RequestException + ? $e + : new RequestException($e->getMessage(), $request, null, $e); + } + + /** + * Factory method to create a new exception with a normalized error message + * + * @param RequestInterface $request Request + * @param ResponseInterface $response Response received + * @param \Exception $previous Previous exception + * @param array $ctx Optional handler context. + * + * @return self + */ + public static function create( + RequestInterface $request, + ResponseInterface $response = null, + \Exception $previous = null, + array $ctx = [] + ) { + if (!$response) { + return new self( + 'Error completing request', + $request, + null, + $previous, + $ctx + ); + } + + $level = (int) floor($response->getStatusCode() / 100); + if ($level === 4) { + $label = 'Client error'; + $className = ClientException::class; + } elseif ($level === 5) { + $label = 'Server error'; + $className = ServerException::class; + } else { + $label = 'Unsuccessful request'; + $className = __CLASS__; + } + + $uri = $request->getUri(); + $uri = static::obfuscateUri($uri); + + // Client Error: `GET /` resulted in a `404 Not Found` response: + // ... (truncated) + $message = sprintf( + '%s: `%s %s` resulted in a `%s %s` response', + $label, + $request->getMethod(), + $uri, + $response->getStatusCode(), + $response->getReasonPhrase() + ); + + $summary = static::getResponseBodySummary($response); + + if ($summary !== null) { + $message .= ":\n{$summary}\n"; + } + + return new $className($message, $request, $response, $previous, $ctx); + } + + /** + * Get a short summary of the response + * + * Will return `null` if the response is not printable. + * + * @param ResponseInterface $response + * + * @return string|null + */ + public static function getResponseBodySummary(ResponseInterface $response) + { + $body = $response->getBody(); + + if (!$body->isSeekable()) { + return null; + } + + $size = $body->getSize(); + + if ($size === 0) { + return null; + } + + $summary = $body->read(120); + $body->rewind(); + + if ($size > 120) { + $summary .= ' (truncated...)'; + } + + // Matches any printable character, including unicode characters: + // letters, marks, numbers, punctuation, spacing, and separators. + if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) { + return null; + } + + return $summary; + } + + /** + * Obfuscates URI if there is an username and a password present + * + * @param UriInterface $uri + * + * @return UriInterface + */ + private static function obfuscateUri($uri) + { + $userInfo = $uri->getUserInfo(); + + if (false !== ($pos = strpos($userInfo, ':'))) { + return $uri->withUserInfo(substr($userInfo, 0, $pos), '***'); + } + + return $uri; + } + + /** + * Get the request that caused the exception + * + * @return RequestInterface + */ + public function getRequest() + { + return $this->request; + } + + /** + * Get the associated response + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->response; + } + + /** + * Check if a response was received + * + * @return bool + */ + public function hasResponse() + { + return $this->response !== null; + } + + /** + * Get contextual information about the error from the underlying handler. + * + * The contents of this array will vary depending on which handler you are + * using. It may also be just an empty array. Relying on this data will + * couple you to a specific handler, but can give more debug information + * when needed. + * + * @return array + */ + public function getHandlerContext() + { + return $this->handlerContext; + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php b/Upyun/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php new file mode 100644 index 0000000..a77c289 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php @@ -0,0 +1,27 @@ +stream = $stream; + $msg = $msg ?: 'Could not seek the stream to position ' . $pos; + parent::__construct($msg); + } + + /** + * @return StreamInterface + */ + public function getStream() + { + return $this->stream; + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php b/Upyun/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php new file mode 100644 index 0000000..7cdd340 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php @@ -0,0 +1,7 @@ +maxHandles = $maxHandles; + } + + public function create(RequestInterface $request, array $options) + { + if (isset($options['curl']['body_as_string'])) { + $options['_body_as_string'] = $options['curl']['body_as_string']; + unset($options['curl']['body_as_string']); + } + + $easy = new EasyHandle; + $easy->request = $request; + $easy->options = $options; + $conf = $this->getDefaultConf($easy); + $this->applyMethod($easy, $conf); + $this->applyHandlerOptions($easy, $conf); + $this->applyHeaders($easy, $conf); + unset($conf['_headers']); + + // Add handler options from the request configuration options + if (isset($options['curl'])) { + $conf = array_replace($conf, $options['curl']); + } + + $conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy); + $easy->handle = $this->handles + ? array_pop($this->handles) + : curl_init(); + curl_setopt_array($easy->handle, $conf); + + return $easy; + } + + public function release(EasyHandle $easy) + { + $resource = $easy->handle; + unset($easy->handle); + + if (count($this->handles) >= $this->maxHandles) { + curl_close($resource); + } else { + // Remove all callback functions as they can hold onto references + // and are not cleaned up by curl_reset. Using curl_setopt_array + // does not work for some reason, so removing each one + // individually. + curl_setopt($resource, CURLOPT_HEADERFUNCTION, null); + curl_setopt($resource, CURLOPT_READFUNCTION, null); + curl_setopt($resource, CURLOPT_WRITEFUNCTION, null); + curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null); + curl_reset($resource); + $this->handles[] = $resource; + } + } + + /** + * Completes a cURL transaction, either returning a response promise or a + * rejected promise. + * + * @param callable $handler + * @param EasyHandle $easy + * @param CurlFactoryInterface $factory Dictates how the handle is released + * + * @return \GuzzleHttp\Promise\PromiseInterface + */ + public static function finish( + callable $handler, + EasyHandle $easy, + CurlFactoryInterface $factory + ) { + if (isset($easy->options['on_stats'])) { + self::invokeStats($easy); + } + + if (!$easy->response || $easy->errno) { + return self::finishError($handler, $easy, $factory); + } + + // Return the response if it is present and there is no error. + $factory->release($easy); + + // Rewind the body of the response if possible. + $body = $easy->response->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + + return new FulfilledPromise($easy->response); + } + + private static function invokeStats(EasyHandle $easy) + { + $curlStats = curl_getinfo($easy->handle); + $stats = new TransferStats( + $easy->request, + $easy->response, + $curlStats['total_time'], + $easy->errno, + $curlStats + ); + call_user_func($easy->options['on_stats'], $stats); + } + + private static function finishError( + callable $handler, + EasyHandle $easy, + CurlFactoryInterface $factory + ) { + // Get error information and release the handle to the factory. + $ctx = [ + 'errno' => $easy->errno, + 'error' => curl_error($easy->handle), + ] + curl_getinfo($easy->handle); + $factory->release($easy); + + // Retry when nothing is present or when curl failed to rewind. + if (empty($easy->options['_err_message']) + && (!$easy->errno || $easy->errno == 65) + ) { + return self::retryFailedRewind($handler, $easy, $ctx); + } + + return self::createRejection($easy, $ctx); + } + + private static function createRejection(EasyHandle $easy, array $ctx) + { + static $connectionErrors = [ + CURLE_OPERATION_TIMEOUTED => true, + CURLE_COULDNT_RESOLVE_HOST => true, + CURLE_COULDNT_CONNECT => true, + CURLE_SSL_CONNECT_ERROR => true, + CURLE_GOT_NOTHING => true, + ]; + + // If an exception was encountered during the onHeaders event, then + // return a rejected promise that wraps that exception. + if ($easy->onHeadersException) { + return \GuzzleHttp\Promise\rejection_for( + new RequestException( + 'An error was encountered during the on_headers event', + $easy->request, + $easy->response, + $easy->onHeadersException, + $ctx + ) + ); + } + + $message = sprintf( + 'cURL error %s: %s (%s)', + $ctx['errno'], + $ctx['error'], + 'see http://curl.haxx.se/libcurl/c/libcurl-errors.html' + ); + + // Create a connection exception if it was a specific error code. + $error = isset($connectionErrors[$easy->errno]) + ? new ConnectException($message, $easy->request, null, $ctx) + : new RequestException($message, $easy->request, $easy->response, null, $ctx); + + return \GuzzleHttp\Promise\rejection_for($error); + } + + private function getDefaultConf(EasyHandle $easy) + { + $conf = [ + '_headers' => $easy->request->getHeaders(), + CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), + CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''), + CURLOPT_RETURNTRANSFER => false, + CURLOPT_HEADER => false, + CURLOPT_CONNECTTIMEOUT => 150, + ]; + + if (defined('CURLOPT_PROTOCOLS')) { + $conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + $version = $easy->request->getProtocolVersion(); + if ($version == 1.1) { + $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; + } elseif ($version == 2.0) { + $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0; + } else { + $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; + } + + return $conf; + } + + private function applyMethod(EasyHandle $easy, array &$conf) + { + $body = $easy->request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size > 0) { + $this->applyBody($easy->request, $easy->options, $conf); + return; + } + + $method = $easy->request->getMethod(); + if ($method === 'PUT' || $method === 'POST') { + // See http://tools.ietf.org/html/rfc7230#section-3.3.2 + if (!$easy->request->hasHeader('Content-Length')) { + $conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0'; + } + } elseif ($method === 'HEAD') { + $conf[CURLOPT_NOBODY] = true; + unset( + $conf[CURLOPT_WRITEFUNCTION], + $conf[CURLOPT_READFUNCTION], + $conf[CURLOPT_FILE], + $conf[CURLOPT_INFILE] + ); + } + } + + private function applyBody(RequestInterface $request, array $options, array &$conf) + { + $size = $request->hasHeader('Content-Length') + ? (int) $request->getHeaderLine('Content-Length') + : null; + + // Send the body as a string if the size is less than 1MB OR if the + // [curl][body_as_string] request value is set. + if (($size !== null && $size < 1000000) || + !empty($options['_body_as_string']) + ) { + $conf[CURLOPT_POSTFIELDS] = (string) $request->getBody(); + // Don't duplicate the Content-Length header + $this->removeHeader('Content-Length', $conf); + $this->removeHeader('Transfer-Encoding', $conf); + } else { + $conf[CURLOPT_UPLOAD] = true; + if ($size !== null) { + $conf[CURLOPT_INFILESIZE] = $size; + $this->removeHeader('Content-Length', $conf); + } + $body = $request->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { + return $body->read($length); + }; + } + + // If the Expect header is not present, prevent curl from adding it + if (!$request->hasHeader('Expect')) { + $conf[CURLOPT_HTTPHEADER][] = 'Expect:'; + } + + // cURL sometimes adds a content-type by default. Prevent this. + if (!$request->hasHeader('Content-Type')) { + $conf[CURLOPT_HTTPHEADER][] = 'Content-Type:'; + } + } + + private function applyHeaders(EasyHandle $easy, array &$conf) + { + foreach ($conf['_headers'] as $name => $values) { + foreach ($values as $value) { + $conf[CURLOPT_HTTPHEADER][] = "$name: $value"; + } + } + + // Remove the Accept header if one was not set + if (!$easy->request->hasHeader('Accept')) { + $conf[CURLOPT_HTTPHEADER][] = 'Accept:'; + } + } + + /** + * Remove a header from the options array. + * + * @param string $name Case-insensitive header to remove + * @param array $options Array of options to modify + */ + private function removeHeader($name, array &$options) + { + foreach (array_keys($options['_headers']) as $key) { + if (!strcasecmp($key, $name)) { + unset($options['_headers'][$key]); + return; + } + } + } + + private function applyHandlerOptions(EasyHandle $easy, array &$conf) + { + $options = $easy->options; + if (isset($options['verify'])) { + if ($options['verify'] === false) { + unset($conf[CURLOPT_CAINFO]); + $conf[CURLOPT_SSL_VERIFYHOST] = 0; + $conf[CURLOPT_SSL_VERIFYPEER] = false; + } else { + $conf[CURLOPT_SSL_VERIFYHOST] = 2; + $conf[CURLOPT_SSL_VERIFYPEER] = true; + if (is_string($options['verify'])) { + // Throw an error if the file/folder/link path is not valid or doesn't exist. + if (!file_exists($options['verify'])) { + throw new \InvalidArgumentException( + "SSL CA bundle not found: {$options['verify']}" + ); + } + // If it's a directory or a link to a directory use CURLOPT_CAPATH. + // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO. + if (is_dir($options['verify']) || + (is_link($options['verify']) && is_dir(readlink($options['verify'])))) { + $conf[CURLOPT_CAPATH] = $options['verify']; + } else { + $conf[CURLOPT_CAINFO] = $options['verify']; + } + } + } + } + + if (!empty($options['decode_content'])) { + $accept = $easy->request->getHeaderLine('Accept-Encoding'); + if ($accept) { + $conf[CURLOPT_ENCODING] = $accept; + } else { + $conf[CURLOPT_ENCODING] = ''; + // Don't let curl send the header over the wire + $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; + } + } + + if (isset($options['sink'])) { + $sink = $options['sink']; + if (!is_string($sink)) { + $sink = \GuzzleHttp\Psr7\stream_for($sink); + } elseif (!is_dir(dirname($sink))) { + // Ensure that the directory exists before failing in curl. + throw new \RuntimeException(sprintf( + 'Directory %s does not exist for sink value of %s', + dirname($sink), + $sink + )); + } else { + $sink = new LazyOpenStream($sink, 'w+'); + } + $easy->sink = $sink; + $conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) { + return $sink->write($write); + }; + } else { + // Use a default temp stream if no sink was set. + $conf[CURLOPT_FILE] = fopen('php://temp', 'w+'); + $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]); + } + $timeoutRequiresNoSignal = false; + if (isset($options['timeout'])) { + $timeoutRequiresNoSignal |= $options['timeout'] < 1; + $conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000; + } + + // CURL default value is CURL_IPRESOLVE_WHATEVER + if (isset($options['force_ip_resolve'])) { + if ('v4' === $options['force_ip_resolve']) { + $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4; + } else if ('v6' === $options['force_ip_resolve']) { + $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6; + } + } + + if (isset($options['connect_timeout'])) { + $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1; + $conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000; + } + + if ($timeoutRequiresNoSignal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') { + $conf[CURLOPT_NOSIGNAL] = true; + } + + if (isset($options['proxy'])) { + if (!is_array($options['proxy'])) { + $conf[CURLOPT_PROXY] = $options['proxy']; + } else { + $scheme = $easy->request->getUri()->getScheme(); + if (isset($options['proxy'][$scheme])) { + $host = $easy->request->getUri()->getHost(); + if (!isset($options['proxy']['no']) || + !\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no']) + ) { + $conf[CURLOPT_PROXY] = $options['proxy'][$scheme]; + } + } + } + } + + if (isset($options['cert'])) { + $cert = $options['cert']; + if (is_array($cert)) { + $conf[CURLOPT_SSLCERTPASSWD] = $cert[1]; + $cert = $cert[0]; + } + if (!file_exists($cert)) { + throw new \InvalidArgumentException( + "SSL certificate not found: {$cert}" + ); + } + $conf[CURLOPT_SSLCERT] = $cert; + } + + if (isset($options['ssl_key'])) { + $sslKey = $options['ssl_key']; + if (is_array($sslKey)) { + $conf[CURLOPT_SSLKEYPASSWD] = $sslKey[1]; + $sslKey = $sslKey[0]; + } + if (!file_exists($sslKey)) { + throw new \InvalidArgumentException( + "SSL private key not found: {$sslKey}" + ); + } + $conf[CURLOPT_SSLKEY] = $sslKey; + } + + if (isset($options['progress'])) { + $progress = $options['progress']; + if (!is_callable($progress)) { + throw new \InvalidArgumentException( + 'progress client option must be callable' + ); + } + $conf[CURLOPT_NOPROGRESS] = false; + $conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) { + $args = func_get_args(); + // PHP 5.5 pushed the handle onto the start of the args + if (is_resource($args[0])) { + array_shift($args); + } + call_user_func_array($progress, $args); + }; + } + + if (!empty($options['debug'])) { + $conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']); + $conf[CURLOPT_VERBOSE] = true; + } + } + + /** + * This function ensures that a response was set on a transaction. If one + * was not set, then the request is retried if possible. This error + * typically means you are sending a payload, curl encountered a + * "Connection died, retrying a fresh connect" error, tried to rewind the + * stream, and then encountered a "necessary data rewind wasn't possible" + * error, causing the request to be sent through curl_multi_info_read() + * without an error status. + */ + private static function retryFailedRewind( + callable $handler, + EasyHandle $easy, + array $ctx + ) { + try { + // Only rewind if the body has been read from. + $body = $easy->request->getBody(); + if ($body->tell() > 0) { + $body->rewind(); + } + } catch (\RuntimeException $e) { + $ctx['error'] = 'The connection unexpectedly failed without ' + . 'providing an error. The request would have been retried, ' + . 'but attempting to rewind the request body failed. ' + . 'Exception: ' . $e; + return self::createRejection($easy, $ctx); + } + + // Retry no more than 3 times before giving up. + if (!isset($easy->options['_curl_retries'])) { + $easy->options['_curl_retries'] = 1; + } elseif ($easy->options['_curl_retries'] == 2) { + $ctx['error'] = 'The cURL request was retried 3 times ' + . 'and did not succeed. The most likely reason for the failure ' + . 'is that cURL was unable to rewind the body of the request ' + . 'and subsequent retries resulted in the same error. Turn on ' + . 'the debug option to see what went wrong. See ' + . 'https://bugs.php.net/bug.php?id=47204 for more information.'; + return self::createRejection($easy, $ctx); + } else { + $easy->options['_curl_retries']++; + } + + return $handler($easy->request, $easy->options); + } + + private function createHeaderFn(EasyHandle $easy) + { + if (isset($easy->options['on_headers'])) { + $onHeaders = $easy->options['on_headers']; + + if (!is_callable($onHeaders)) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + } else { + $onHeaders = null; + } + + return function ($ch, $h) use ( + $onHeaders, + $easy, + &$startingResponse + ) { + $value = trim($h); + if ($value === '') { + $startingResponse = true; + $easy->createResponse(); + if ($onHeaders !== null) { + try { + $onHeaders($easy->response); + } catch (\Exception $e) { + // Associate the exception with the handle and trigger + // a curl header write error by returning 0. + $easy->onHeadersException = $e; + return -1; + } + } + } elseif ($startingResponse) { + $startingResponse = false; + $easy->headers = [$value]; + } else { + $easy->headers[] = $value; + } + return strlen($h); + }; + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php b/Upyun/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php new file mode 100644 index 0000000..b0fc236 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php @@ -0,0 +1,27 @@ +factory = isset($options['handle_factory']) + ? $options['handle_factory'] + : new CurlFactory(3); + } + + public function __invoke(RequestInterface $request, array $options) + { + if (isset($options['delay'])) { + usleep($options['delay'] * 1000); + } + + $easy = $this->factory->create($request, $options); + curl_exec($easy->handle); + $easy->errno = curl_errno($easy->handle); + + return CurlFactory::finish($this, $easy, $this->factory); + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/Upyun/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php new file mode 100644 index 0000000..945d06e --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php @@ -0,0 +1,197 @@ +factory = isset($options['handle_factory']) + ? $options['handle_factory'] : new CurlFactory(50); + $this->selectTimeout = isset($options['select_timeout']) + ? $options['select_timeout'] : 1; + } + + public function __get($name) + { + if ($name === '_mh') { + return $this->_mh = curl_multi_init(); + } + + throw new \BadMethodCallException(); + } + + public function __destruct() + { + if (isset($this->_mh)) { + curl_multi_close($this->_mh); + unset($this->_mh); + } + } + + public function __invoke(RequestInterface $request, array $options) + { + $easy = $this->factory->create($request, $options); + $id = (int) $easy->handle; + + $promise = new Promise( + [$this, 'execute'], + function () use ($id) { return $this->cancel($id); } + ); + + $this->addRequest(['easy' => $easy, 'deferred' => $promise]); + + return $promise; + } + + /** + * Ticks the curl event loop. + */ + public function tick() + { + // Add any delayed handles if needed. + if ($this->delays) { + $currentTime = microtime(true); + foreach ($this->delays as $id => $delay) { + if ($currentTime >= $delay) { + unset($this->delays[$id]); + curl_multi_add_handle( + $this->_mh, + $this->handles[$id]['easy']->handle + ); + } + } + } + + // Step through the task queue which may add additional requests. + P\queue()->run(); + + if ($this->active && + curl_multi_select($this->_mh, $this->selectTimeout) === -1 + ) { + // Perform a usleep if a select returns -1. + // See: https://bugs.php.net/bug.php?id=61141 + usleep(250); + } + + while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM); + + $this->processMessages(); + } + + /** + * Runs until all outstanding connections have completed. + */ + public function execute() + { + $queue = P\queue(); + + while ($this->handles || !$queue->isEmpty()) { + // If there are no transfers, then sleep for the next delay + if (!$this->active && $this->delays) { + usleep($this->timeToNext()); + } + $this->tick(); + } + } + + private function addRequest(array $entry) + { + $easy = $entry['easy']; + $id = (int) $easy->handle; + $this->handles[$id] = $entry; + if (empty($easy->options['delay'])) { + curl_multi_add_handle($this->_mh, $easy->handle); + } else { + $this->delays[$id] = microtime(true) + ($easy->options['delay'] / 1000); + } + } + + /** + * Cancels a handle from sending and removes references to it. + * + * @param int $id Handle ID to cancel and remove. + * + * @return bool True on success, false on failure. + */ + private function cancel($id) + { + // Cannot cancel if it has been processed. + if (!isset($this->handles[$id])) { + return false; + } + + $handle = $this->handles[$id]['easy']->handle; + unset($this->delays[$id], $this->handles[$id]); + curl_multi_remove_handle($this->_mh, $handle); + curl_close($handle); + + return true; + } + + private function processMessages() + { + while ($done = curl_multi_info_read($this->_mh)) { + $id = (int) $done['handle']; + curl_multi_remove_handle($this->_mh, $done['handle']); + + if (!isset($this->handles[$id])) { + // Probably was cancelled. + continue; + } + + $entry = $this->handles[$id]; + unset($this->handles[$id], $this->delays[$id]); + $entry['easy']->errno = $done['result']; + $entry['deferred']->resolve( + CurlFactory::finish( + $this, + $entry['easy'], + $this->factory + ) + ); + } + } + + private function timeToNext() + { + $currentTime = microtime(true); + $nextTime = PHP_INT_MAX; + foreach ($this->delays as $time) { + if ($time < $nextTime) { + $nextTime = $time; + } + } + + return max(0, $nextTime - $currentTime) * 1000000; + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php b/Upyun/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php new file mode 100644 index 0000000..7754e91 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php @@ -0,0 +1,92 @@ +headers)) { + throw new \RuntimeException('No headers have been received'); + } + + // HTTP-version SP status-code SP reason-phrase + $startLine = explode(' ', array_shift($this->headers), 3); + $headers = \GuzzleHttp\headers_from_lines($this->headers); + $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); + + if (!empty($this->options['decode_content']) + && isset($normalizedKeys['content-encoding']) + ) { + $headers['x-encoded-content-encoding'] + = $headers[$normalizedKeys['content-encoding']]; + unset($headers[$normalizedKeys['content-encoding']]); + if (isset($normalizedKeys['content-length'])) { + $headers['x-encoded-content-length'] + = $headers[$normalizedKeys['content-length']]; + + $bodyLength = (int) $this->sink->getSize(); + if ($bodyLength) { + $headers[$normalizedKeys['content-length']] = $bodyLength; + } else { + unset($headers[$normalizedKeys['content-length']]); + } + } + } + + // Attach a response to the easy handle with the parsed headers. + $this->response = new Response( + $startLine[1], + $headers, + $this->sink, + substr($startLine[0], 5), + isset($startLine[2]) ? (string) $startLine[2] : null + ); + } + + public function __get($name) + { + $msg = $name === 'handle' + ? 'The EasyHandle has been released' + : 'Invalid property: ' . $name; + throw new \BadMethodCallException($msg); + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php b/Upyun/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php new file mode 100644 index 0000000..d892061 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php @@ -0,0 +1,189 @@ +onFulfilled = $onFulfilled; + $this->onRejected = $onRejected; + + if ($queue) { + call_user_func_array([$this, 'append'], $queue); + } + } + + public function __invoke(RequestInterface $request, array $options) + { + if (!$this->queue) { + throw new \OutOfBoundsException('Mock queue is empty'); + } + + if (isset($options['delay'])) { + usleep($options['delay'] * 1000); + } + + $this->lastRequest = $request; + $this->lastOptions = $options; + $response = array_shift($this->queue); + + if (isset($options['on_headers'])) { + if (!is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + try { + $options['on_headers']($response); + } catch (\Exception $e) { + $msg = 'An error was encountered during the on_headers event'; + $response = new RequestException($msg, $request, $response, $e); + } + } + + if (is_callable($response)) { + $response = call_user_func($response, $request, $options); + } + + $response = $response instanceof \Exception + ? \GuzzleHttp\Promise\rejection_for($response) + : \GuzzleHttp\Promise\promise_for($response); + + return $response->then( + function ($value) use ($request, $options) { + $this->invokeStats($request, $options, $value); + if ($this->onFulfilled) { + call_user_func($this->onFulfilled, $value); + } + if (isset($options['sink'])) { + $contents = (string) $value->getBody(); + $sink = $options['sink']; + + if (is_resource($sink)) { + fwrite($sink, $contents); + } elseif (is_string($sink)) { + file_put_contents($sink, $contents); + } elseif ($sink instanceof \Psr\Http\Message\StreamInterface) { + $sink->write($contents); + } + } + + return $value; + }, + function ($reason) use ($request, $options) { + $this->invokeStats($request, $options, null, $reason); + if ($this->onRejected) { + call_user_func($this->onRejected, $reason); + } + return \GuzzleHttp\Promise\rejection_for($reason); + } + ); + } + + /** + * Adds one or more variadic requests, exceptions, callables, or promises + * to the queue. + */ + public function append() + { + foreach (func_get_args() as $value) { + if ($value instanceof ResponseInterface + || $value instanceof \Exception + || $value instanceof PromiseInterface + || is_callable($value) + ) { + $this->queue[] = $value; + } else { + throw new \InvalidArgumentException('Expected a response or ' + . 'exception. Found ' . \GuzzleHttp\describe_type($value)); + } + } + } + + /** + * Get the last received request. + * + * @return RequestInterface + */ + public function getLastRequest() + { + return $this->lastRequest; + } + + /** + * Get the last received request options. + * + * @return array + */ + public function getLastOptions() + { + return $this->lastOptions; + } + + /** + * Returns the number of remaining items in the queue. + * + * @return int + */ + public function count() + { + return count($this->queue); + } + + private function invokeStats( + RequestInterface $request, + array $options, + ResponseInterface $response = null, + $reason = null + ) { + if (isset($options['on_stats'])) { + $stats = new TransferStats($request, $response, 0, $reason); + call_user_func($options['on_stats'], $stats); + } + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php b/Upyun/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php new file mode 100644 index 0000000..f8b00be --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php @@ -0,0 +1,55 @@ +withoutHeader('Expect'); + + // Append a content-length header if body size is zero to match + // cURL's behavior. + if (0 === $request->getBody()->getSize()) { + $request = $request->withHeader('Content-Length', 0); + } + + return $this->createResponse( + $request, + $options, + $this->createStream($request, $options), + $startTime + ); + } catch (\InvalidArgumentException $e) { + throw $e; + } catch (\Exception $e) { + // Determine if the error was a networking error. + $message = $e->getMessage(); + // This list can probably get more comprehensive. + if (strpos($message, 'getaddrinfo') // DNS lookup failed + || strpos($message, 'Connection refused') + || strpos($message, "couldn't connect to host") // error on HHVM + ) { + $e = new ConnectException($e->getMessage(), $request, $e); + } + $e = RequestException::wrapException($request, $e); + $this->invokeStats($options, $request, $startTime, null, $e); + + return \GuzzleHttp\Promise\rejection_for($e); + } + } + + private function invokeStats( + array $options, + RequestInterface $request, + $startTime, + ResponseInterface $response = null, + $error = null + ) { + if (isset($options['on_stats'])) { + $stats = new TransferStats( + $request, + $response, + microtime(true) - $startTime, + $error, + [] + ); + call_user_func($options['on_stats'], $stats); + } + } + + private function createResponse( + RequestInterface $request, + array $options, + $stream, + $startTime + ) { + $hdrs = $this->lastHeaders; + $this->lastHeaders = []; + $parts = explode(' ', array_shift($hdrs), 3); + $ver = explode('/', $parts[0])[1]; + $status = $parts[1]; + $reason = isset($parts[2]) ? $parts[2] : null; + $headers = \GuzzleHttp\headers_from_lines($hdrs); + list ($stream, $headers) = $this->checkDecode($options, $headers, $stream); + $stream = Psr7\stream_for($stream); + $sink = $stream; + + if (strcasecmp('HEAD', $request->getMethod())) { + $sink = $this->createSink($stream, $options); + } + + $response = new Psr7\Response($status, $headers, $sink, $ver, $reason); + + if (isset($options['on_headers'])) { + try { + $options['on_headers']($response); + } catch (\Exception $e) { + $msg = 'An error was encountered during the on_headers event'; + $ex = new RequestException($msg, $request, $response, $e); + return \GuzzleHttp\Promise\rejection_for($ex); + } + } + + // Do not drain when the request is a HEAD request because they have + // no body. + if ($sink !== $stream) { + $this->drain( + $stream, + $sink, + $response->getHeaderLine('Content-Length') + ); + } + + $this->invokeStats($options, $request, $startTime, $response, null); + + return new FulfilledPromise($response); + } + + private function createSink(StreamInterface $stream, array $options) + { + if (!empty($options['stream'])) { + return $stream; + } + + $sink = isset($options['sink']) + ? $options['sink'] + : fopen('php://temp', 'r+'); + + return is_string($sink) + ? new Psr7\LazyOpenStream($sink, 'w+') + : Psr7\stream_for($sink); + } + + private function checkDecode(array $options, array $headers, $stream) + { + // Automatically decode responses when instructed. + if (!empty($options['decode_content'])) { + $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); + if (isset($normalizedKeys['content-encoding'])) { + $encoding = $headers[$normalizedKeys['content-encoding']]; + if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') { + $stream = new Psr7\InflateStream( + Psr7\stream_for($stream) + ); + $headers['x-encoded-content-encoding'] + = $headers[$normalizedKeys['content-encoding']]; + // Remove content-encoding header + unset($headers[$normalizedKeys['content-encoding']]); + // Fix content-length header + if (isset($normalizedKeys['content-length'])) { + $headers['x-encoded-content-length'] + = $headers[$normalizedKeys['content-length']]; + + $length = (int) $stream->getSize(); + if ($length === 0) { + unset($headers[$normalizedKeys['content-length']]); + } else { + $headers[$normalizedKeys['content-length']] = [$length]; + } + } + } + } + } + + return [$stream, $headers]; + } + + /** + * Drains the source stream into the "sink" client option. + * + * @param StreamInterface $source + * @param StreamInterface $sink + * @param string $contentLength Header specifying the amount of + * data to read. + * + * @return StreamInterface + * @throws \RuntimeException when the sink option is invalid. + */ + private function drain( + StreamInterface $source, + StreamInterface $sink, + $contentLength + ) { + // If a content-length header is provided, then stop reading once + // that number of bytes has been read. This can prevent infinitely + // reading from a stream when dealing with servers that do not honor + // Connection: Close headers. + Psr7\copy_to_stream( + $source, + $sink, + (strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1 + ); + + $sink->seek(0); + $source->close(); + + return $sink; + } + + /** + * Create a resource and check to ensure it was created successfully + * + * @param callable $callback Callable that returns stream resource + * + * @return resource + * @throws \RuntimeException on error + */ + private function createResource(callable $callback) + { + $errors = null; + set_error_handler(function ($_, $msg, $file, $line) use (&$errors) { + $errors[] = [ + 'message' => $msg, + 'file' => $file, + 'line' => $line + ]; + return true; + }); + + $resource = $callback(); + restore_error_handler(); + + if (!$resource) { + $message = 'Error creating resource: '; + foreach ($errors as $err) { + foreach ($err as $key => $value) { + $message .= "[$key] $value" . PHP_EOL; + } + } + throw new \RuntimeException(trim($message)); + } + + return $resource; + } + + private function createStream(RequestInterface $request, array $options) + { + static $methods; + if (!$methods) { + $methods = array_flip(get_class_methods(__CLASS__)); + } + + // HTTP/1.1 streams using the PHP stream wrapper require a + // Connection: close header + if ($request->getProtocolVersion() == '1.1' + && !$request->hasHeader('Connection') + ) { + $request = $request->withHeader('Connection', 'close'); + } + + // Ensure SSL is verified by default + if (!isset($options['verify'])) { + $options['verify'] = true; + } + + $params = []; + $context = $this->getDefaultContext($request, $options); + + if (isset($options['on_headers']) && !is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + + if (!empty($options)) { + foreach ($options as $key => $value) { + $method = "add_{$key}"; + if (isset($methods[$method])) { + $this->{$method}($request, $context, $value, $params); + } + } + } + + if (isset($options['stream_context'])) { + if (!is_array($options['stream_context'])) { + throw new \InvalidArgumentException('stream_context must be an array'); + } + $context = array_replace_recursive( + $context, + $options['stream_context'] + ); + } + + // Microsoft NTLM authentication only supported with curl handler + if (isset($options['auth']) + && is_array($options['auth']) + && isset($options['auth'][2]) + && 'ntlm' == $options['auth'][2] + ) { + + throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler'); + } + + $uri = $this->resolveHost($request, $options); + + $context = $this->createResource( + function () use ($context, $params) { + return stream_context_create($context, $params); + } + ); + + return $this->createResource( + function () use ($uri, &$http_response_header, $context, $options) { + $resource = fopen((string) $uri, 'r', null, $context); + $this->lastHeaders = $http_response_header; + + if (isset($options['read_timeout'])) { + $readTimeout = $options['read_timeout']; + $sec = (int) $readTimeout; + $usec = ($readTimeout - $sec) * 100000; + stream_set_timeout($resource, $sec, $usec); + } + + return $resource; + } + ); + } + + private function resolveHost(RequestInterface $request, array $options) + { + $uri = $request->getUri(); + + if (isset($options['force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) { + if ('v4' === $options['force_ip_resolve']) { + $records = dns_get_record($uri->getHost(), DNS_A); + if (!isset($records[0]['ip'])) { + throw new ConnectException(sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request); + } + $uri = $uri->withHost($records[0]['ip']); + } elseif ('v6' === $options['force_ip_resolve']) { + $records = dns_get_record($uri->getHost(), DNS_AAAA); + if (!isset($records[0]['ipv6'])) { + throw new ConnectException(sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request); + } + $uri = $uri->withHost('[' . $records[0]['ipv6'] . ']'); + } + } + + return $uri; + } + + private function getDefaultContext(RequestInterface $request) + { + $headers = ''; + foreach ($request->getHeaders() as $name => $value) { + foreach ($value as $val) { + $headers .= "$name: $val\r\n"; + } + } + + $context = [ + 'http' => [ + 'method' => $request->getMethod(), + 'header' => $headers, + 'protocol_version' => $request->getProtocolVersion(), + 'ignore_errors' => true, + 'follow_location' => 0, + ], + ]; + + $body = (string) $request->getBody(); + + if (!empty($body)) { + $context['http']['content'] = $body; + // Prevent the HTTP handler from adding a Content-Type header. + if (!$request->hasHeader('Content-Type')) { + $context['http']['header'] .= "Content-Type:\r\n"; + } + } + + $context['http']['header'] = rtrim($context['http']['header']); + + return $context; + } + + private function add_proxy(RequestInterface $request, &$options, $value, &$params) + { + if (!is_array($value)) { + $options['http']['proxy'] = $value; + } else { + $scheme = $request->getUri()->getScheme(); + if (isset($value[$scheme])) { + if (!isset($value['no']) + || !\GuzzleHttp\is_host_in_noproxy( + $request->getUri()->getHost(), + $value['no'] + ) + ) { + $options['http']['proxy'] = $value[$scheme]; + } + } + } + } + + private function add_timeout(RequestInterface $request, &$options, $value, &$params) + { + if ($value > 0) { + $options['http']['timeout'] = $value; + } + } + + private function add_verify(RequestInterface $request, &$options, $value, &$params) + { + if ($value === true) { + // PHP 5.6 or greater will find the system cert by default. When + // < 5.6, use the Guzzle bundled cacert. + if (PHP_VERSION_ID < 50600) { + $options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle(); + } + } elseif (is_string($value)) { + $options['ssl']['cafile'] = $value; + if (!file_exists($value)) { + throw new \RuntimeException("SSL CA bundle not found: $value"); + } + } elseif ($value === false) { + $options['ssl']['verify_peer'] = false; + $options['ssl']['verify_peer_name'] = false; + return; + } else { + throw new \InvalidArgumentException('Invalid verify request option'); + } + + $options['ssl']['verify_peer'] = true; + $options['ssl']['verify_peer_name'] = true; + $options['ssl']['allow_self_signed'] = false; + } + + private function add_cert(RequestInterface $request, &$options, $value, &$params) + { + if (is_array($value)) { + $options['ssl']['passphrase'] = $value[1]; + $value = $value[0]; + } + + if (!file_exists($value)) { + throw new \RuntimeException("SSL certificate not found: {$value}"); + } + + $options['ssl']['local_cert'] = $value; + } + + private function add_progress(RequestInterface $request, &$options, $value, &$params) + { + $this->addNotification( + $params, + function ($code, $a, $b, $c, $transferred, $total) use ($value) { + if ($code == STREAM_NOTIFY_PROGRESS) { + $value($total, $transferred, null, null); + } + } + ); + } + + private function add_debug(RequestInterface $request, &$options, $value, &$params) + { + if ($value === false) { + return; + } + + static $map = [ + STREAM_NOTIFY_CONNECT => 'CONNECT', + STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', + STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', + STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', + STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', + STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', + STREAM_NOTIFY_PROGRESS => 'PROGRESS', + STREAM_NOTIFY_FAILURE => 'FAILURE', + STREAM_NOTIFY_COMPLETED => 'COMPLETED', + STREAM_NOTIFY_RESOLVE => 'RESOLVE', + ]; + static $args = ['severity', 'message', 'message_code', + 'bytes_transferred', 'bytes_max']; + + $value = \GuzzleHttp\debug_resource($value); + $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment(''); + $this->addNotification( + $params, + function () use ($ident, $value, $map, $args) { + $passed = func_get_args(); + $code = array_shift($passed); + fprintf($value, '<%s> [%s] ', $ident, $map[$code]); + foreach (array_filter($passed) as $i => $v) { + fwrite($value, $args[$i] . ': "' . $v . '" '); + } + fwrite($value, "\n"); + } + ); + } + + private function addNotification(array &$params, callable $notify) + { + // Wrap the existing function if needed. + if (!isset($params['notification'])) { + $params['notification'] = $notify; + } else { + $params['notification'] = $this->callArray([ + $params['notification'], + $notify + ]); + } + } + + private function callArray(array $functions) + { + return function () use ($functions) { + $args = func_get_args(); + foreach ($functions as $fn) { + call_user_func_array($fn, $args); + } + }; + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/HandlerStack.php b/Upyun/vendor/guzzlehttp/guzzle/src/HandlerStack.php new file mode 100644 index 0000000..a72e38a --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/HandlerStack.php @@ -0,0 +1,273 @@ +push(Middleware::httpErrors(), 'http_errors'); + $stack->push(Middleware::redirect(), 'allow_redirects'); + $stack->push(Middleware::cookies(), 'cookies'); + $stack->push(Middleware::prepareBody(), 'prepare_body'); + + return $stack; + } + + /** + * @param callable $handler Underlying HTTP handler. + */ + public function __construct(callable $handler = null) + { + $this->handler = $handler; + } + + /** + * Invokes the handler stack as a composed handler + * + * @param RequestInterface $request + * @param array $options + */ + public function __invoke(RequestInterface $request, array $options) + { + $handler = $this->resolve(); + + return $handler($request, $options); + } + + /** + * Dumps a string representation of the stack. + * + * @return string + */ + public function __toString() + { + $depth = 0; + $stack = []; + if ($this->handler) { + $stack[] = "0) Handler: " . $this->debugCallable($this->handler); + } + + $result = ''; + foreach (array_reverse($this->stack) as $tuple) { + $depth++; + $str = "{$depth}) Name: '{$tuple[1]}', "; + $str .= "Function: " . $this->debugCallable($tuple[0]); + $result = "> {$str}\n{$result}"; + $stack[] = $str; + } + + foreach (array_keys($stack) as $k) { + $result .= "< {$stack[$k]}\n"; + } + + return $result; + } + + /** + * Set the HTTP handler that actually returns a promise. + * + * @param callable $handler Accepts a request and array of options and + * returns a Promise. + */ + public function setHandler(callable $handler) + { + $this->handler = $handler; + $this->cached = null; + } + + /** + * Returns true if the builder has a handler. + * + * @return bool + */ + public function hasHandler() + { + return (bool) $this->handler; + } + + /** + * Unshift a middleware to the bottom of the stack. + * + * @param callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function unshift(callable $middleware, $name = null) + { + array_unshift($this->stack, [$middleware, $name]); + $this->cached = null; + } + + /** + * Push a middleware to the top of the stack. + * + * @param callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function push(callable $middleware, $name = '') + { + $this->stack[] = [$middleware, $name]; + $this->cached = null; + } + + /** + * Add a middleware before another middleware by name. + * + * @param string $findName Middleware to find + * @param callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function before($findName, callable $middleware, $withName = '') + { + $this->splice($findName, $withName, $middleware, true); + } + + /** + * Add a middleware after another middleware by name. + * + * @param string $findName Middleware to find + * @param callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function after($findName, callable $middleware, $withName = '') + { + $this->splice($findName, $withName, $middleware, false); + } + + /** + * Remove a middleware by instance or name from the stack. + * + * @param callable|string $remove Middleware to remove by instance or name. + */ + public function remove($remove) + { + $this->cached = null; + $idx = is_callable($remove) ? 0 : 1; + $this->stack = array_values(array_filter( + $this->stack, + function ($tuple) use ($idx, $remove) { + return $tuple[$idx] !== $remove; + } + )); + } + + /** + * Compose the middleware and handler into a single callable function. + * + * @return callable + */ + public function resolve() + { + if (!$this->cached) { + if (!($prev = $this->handler)) { + throw new \LogicException('No handler has been specified'); + } + + foreach (array_reverse($this->stack) as $fn) { + $prev = $fn[0]($prev); + } + + $this->cached = $prev; + } + + return $this->cached; + } + + /** + * @param $name + * @return int + */ + private function findByName($name) + { + foreach ($this->stack as $k => $v) { + if ($v[1] === $name) { + return $k; + } + } + + throw new \InvalidArgumentException("Middleware not found: $name"); + } + + /** + * Splices a function into the middleware list at a specific position. + * + * @param $findName + * @param $withName + * @param callable $middleware + * @param $before + */ + private function splice($findName, $withName, callable $middleware, $before) + { + $this->cached = null; + $idx = $this->findByName($findName); + $tuple = [$middleware, $withName]; + + if ($before) { + if ($idx === 0) { + array_unshift($this->stack, $tuple); + } else { + $replacement = [$tuple, $this->stack[$idx]]; + array_splice($this->stack, $idx, 1, $replacement); + } + } elseif ($idx === count($this->stack) - 1) { + $this->stack[] = $tuple; + } else { + $replacement = [$this->stack[$idx], $tuple]; + array_splice($this->stack, $idx, 1, $replacement); + } + } + + /** + * Provides a debug string for a given callable. + * + * @param array|callable $fn Function to write as a string. + * + * @return string + */ + private function debugCallable($fn) + { + if (is_string($fn)) { + return "callable({$fn})"; + } + + if (is_array($fn)) { + return is_string($fn[0]) + ? "callable({$fn[0]}::{$fn[1]})" + : "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])"; + } + + return 'callable(' . spl_object_hash($fn) . ')'; + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/MessageFormatter.php b/Upyun/vendor/guzzlehttp/guzzle/src/MessageFormatter.php new file mode 100644 index 0000000..6b090a9 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/MessageFormatter.php @@ -0,0 +1,182 @@ +>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}"; + const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}'; + + /** @var string Template used to format log messages */ + private $template; + + /** + * @param string $template Log message template + */ + public function __construct($template = self::CLF) + { + $this->template = $template ?: self::CLF; + } + + /** + * Returns a formatted message string. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface $response Response that was received + * @param \Exception $error Exception that was received + * + * @return string + */ + public function format( + RequestInterface $request, + ResponseInterface $response = null, + \Exception $error = null + ) { + $cache = []; + + return preg_replace_callback( + '/{\s*([A-Za-z_\-\.0-9]+)\s*}/', + function (array $matches) use ($request, $response, $error, &$cache) { + + if (isset($cache[$matches[1]])) { + return $cache[$matches[1]]; + } + + $result = ''; + switch ($matches[1]) { + case 'request': + $result = Psr7\str($request); + break; + case 'response': + $result = $response ? Psr7\str($response) : ''; + break; + case 'req_headers': + $result = trim($request->getMethod() + . ' ' . $request->getRequestTarget()) + . ' HTTP/' . $request->getProtocolVersion() . "\r\n" + . $this->headers($request); + break; + case 'res_headers': + $result = $response ? + sprintf( + 'HTTP/%s %d %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ) . "\r\n" . $this->headers($response) + : 'NULL'; + break; + case 'req_body': + $result = $request->getBody(); + break; + case 'res_body': + $result = $response ? $response->getBody() : 'NULL'; + break; + case 'ts': + case 'date_iso_8601': + $result = gmdate('c'); + break; + case 'date_common_log': + $result = date('d/M/Y:H:i:s O'); + break; + case 'method': + $result = $request->getMethod(); + break; + case 'version': + $result = $request->getProtocolVersion(); + break; + case 'uri': + case 'url': + $result = $request->getUri(); + break; + case 'target': + $result = $request->getRequestTarget(); + break; + case 'req_version': + $result = $request->getProtocolVersion(); + break; + case 'res_version': + $result = $response + ? $response->getProtocolVersion() + : 'NULL'; + break; + case 'host': + $result = $request->getHeaderLine('Host'); + break; + case 'hostname': + $result = gethostname(); + break; + case 'code': + $result = $response ? $response->getStatusCode() : 'NULL'; + break; + case 'phrase': + $result = $response ? $response->getReasonPhrase() : 'NULL'; + break; + case 'error': + $result = $error ? $error->getMessage() : 'NULL'; + break; + default: + // handle prefixed dynamic headers + if (strpos($matches[1], 'req_header_') === 0) { + $result = $request->getHeaderLine(substr($matches[1], 11)); + } elseif (strpos($matches[1], 'res_header_') === 0) { + $result = $response + ? $response->getHeaderLine(substr($matches[1], 11)) + : 'NULL'; + } + } + + $cache[$matches[1]] = $result; + return $result; + }, + $this->template + ); + } + + private function headers(MessageInterface $message) + { + $result = ''; + foreach ($message->getHeaders() as $name => $values) { + $result .= $name . ': ' . implode(', ', $values) . "\r\n"; + } + + return trim($result); + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/Middleware.php b/Upyun/vendor/guzzlehttp/guzzle/src/Middleware.php new file mode 100644 index 0000000..9d79bd2 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/Middleware.php @@ -0,0 +1,254 @@ +withCookieHeader($request); + return $handler($request, $options) + ->then(function ($response) use ($cookieJar, $request) { + $cookieJar->extractCookies($request, $response); + return $response; + } + ); + }; + }; + } + + /** + * Middleware that throws exceptions for 4xx or 5xx responses when the + * "http_error" request option is set to true. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function httpErrors() + { + return function (callable $handler) { + return function ($request, array $options) use ($handler) { + if (empty($options['http_errors'])) { + return $handler($request, $options); + } + return $handler($request, $options)->then( + function (ResponseInterface $response) use ($request, $handler) { + $code = $response->getStatusCode(); + if ($code < 400) { + return $response; + } + throw RequestException::create($request, $response); + } + ); + }; + }; + } + + /** + * Middleware that pushes history data to an ArrayAccess container. + * + * @param array $container Container to hold the history (by reference). + * + * @return callable Returns a function that accepts the next handler. + * @throws \InvalidArgumentException if container is not an array or ArrayAccess. + */ + public static function history(&$container) + { + if (!is_array($container) && !$container instanceof \ArrayAccess) { + throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess'); + } + + return function (callable $handler) use (&$container) { + return function ($request, array $options) use ($handler, &$container) { + return $handler($request, $options)->then( + function ($value) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => $value, + 'error' => null, + 'options' => $options + ]; + return $value; + }, + function ($reason) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => null, + 'error' => $reason, + 'options' => $options + ]; + return \GuzzleHttp\Promise\rejection_for($reason); + } + ); + }; + }; + } + + /** + * Middleware that invokes a callback before and after sending a request. + * + * The provided listener cannot modify or alter the response. It simply + * "taps" into the chain to be notified before returning the promise. The + * before listener accepts a request and options array, and the after + * listener accepts a request, options array, and response promise. + * + * @param callable $before Function to invoke before forwarding the request. + * @param callable $after Function invoked after forwarding. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function tap(callable $before = null, callable $after = null) + { + return function (callable $handler) use ($before, $after) { + return function ($request, array $options) use ($handler, $before, $after) { + if ($before) { + $before($request, $options); + } + $response = $handler($request, $options); + if ($after) { + $after($request, $options, $response); + } + return $response; + }; + }; + } + + /** + * Middleware that handles request redirects. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function redirect() + { + return function (callable $handler) { + return new RedirectMiddleware($handler); + }; + } + + /** + * Middleware that retries requests based on the boolean result of + * invoking the provided "decider" function. + * + * If no delay function is provided, a simple implementation of exponential + * backoff will be utilized. + * + * @param callable $decider Function that accepts the number of retries, + * a request, [response], and [exception] and + * returns true if the request is to be retried. + * @param callable $delay Function that accepts the number of retries and + * returns the number of milliseconds to delay. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function retry(callable $decider, callable $delay = null) + { + return function (callable $handler) use ($decider, $delay) { + return new RetryMiddleware($decider, $handler, $delay); + }; + } + + /** + * Middleware that logs requests, responses, and errors using a message + * formatter. + * + * @param LoggerInterface $logger Logs messages. + * @param MessageFormatter $formatter Formatter used to create message strings. + * @param string $logLevel Level at which to log requests. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = LogLevel::INFO) + { + return function (callable $handler) use ($logger, $formatter, $logLevel) { + return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) { + return $handler($request, $options)->then( + function ($response) use ($logger, $request, $formatter, $logLevel) { + $message = $formatter->format($request, $response); + $logger->log($logLevel, $message); + return $response; + }, + function ($reason) use ($logger, $request, $formatter) { + $response = $reason instanceof RequestException + ? $reason->getResponse() + : null; + $message = $formatter->format($request, $response, $reason); + $logger->notice($message); + return \GuzzleHttp\Promise\rejection_for($reason); + } + ); + }; + }; + } + + /** + * This middleware adds a default content-type if possible, a default + * content-length or transfer-encoding header, and the expect header. + * + * @return callable + */ + public static function prepareBody() + { + return function (callable $handler) { + return new PrepareBodyMiddleware($handler); + }; + } + + /** + * Middleware that applies a map function to the request before passing to + * the next handler. + * + * @param callable $fn Function that accepts a RequestInterface and returns + * a RequestInterface. + * @return callable + */ + public static function mapRequest(callable $fn) + { + return function (callable $handler) use ($fn) { + return function ($request, array $options) use ($handler, $fn) { + return $handler($fn($request), $options); + }; + }; + } + + /** + * Middleware that applies a map function to the resolved promise's + * response. + * + * @param callable $fn Function that accepts a ResponseInterface and + * returns a ResponseInterface. + * @return callable + */ + public static function mapResponse(callable $fn) + { + return function (callable $handler) use ($fn) { + return function ($request, array $options) use ($handler, $fn) { + return $handler($request, $options)->then($fn); + }; + }; + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/Pool.php b/Upyun/vendor/guzzlehttp/guzzle/src/Pool.php new file mode 100644 index 0000000..8f1be33 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/Pool.php @@ -0,0 +1,123 @@ + $rfn) { + if ($rfn instanceof RequestInterface) { + yield $key => $client->sendAsync($rfn, $opts); + } elseif (is_callable($rfn)) { + yield $key => $rfn($opts); + } else { + throw new \InvalidArgumentException('Each value yielded by ' + . 'the iterator must be a Psr7\Http\Message\RequestInterface ' + . 'or a callable that returns a promise that fulfills ' + . 'with a Psr7\Message\Http\ResponseInterface object.'); + } + } + }; + + $this->each = new EachPromise($requests(), $config); + } + + public function promise() + { + return $this->each->promise(); + } + + /** + * Sends multiple requests concurrently and returns an array of responses + * and exceptions that uses the same ordering as the provided requests. + * + * IMPORTANT: This method keeps every request and response in memory, and + * as such, is NOT recommended when sending a large number or an + * indeterminate number of requests concurrently. + * + * @param ClientInterface $client Client used to send the requests + * @param array|\Iterator $requests Requests to send concurrently. + * @param array $options Passes through the options available in + * {@see GuzzleHttp\Pool::__construct} + * + * @return array Returns an array containing the response or an exception + * in the same order that the requests were sent. + * @throws \InvalidArgumentException if the event format is incorrect. + */ + public static function batch( + ClientInterface $client, + $requests, + array $options = [] + ) { + $res = []; + self::cmpCallback($options, 'fulfilled', $res); + self::cmpCallback($options, 'rejected', $res); + $pool = new static($client, $requests, $options); + $pool->promise()->wait(); + ksort($res); + + return $res; + } + + private static function cmpCallback(array &$options, $name, array &$results) + { + if (!isset($options[$name])) { + $options[$name] = function ($v, $k) use (&$results) { + $results[$k] = $v; + }; + } else { + $currentFn = $options[$name]; + $options[$name] = function ($v, $k) use (&$results, $currentFn) { + $currentFn($v, $k); + $results[$k] = $v; + }; + } + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php b/Upyun/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php new file mode 100644 index 0000000..2eb95f9 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php @@ -0,0 +1,106 @@ +nextHandler = $nextHandler; + } + + /** + * @param RequestInterface $request + * @param array $options + * + * @return PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + $fn = $this->nextHandler; + + // Don't do anything if the request has no body. + if ($request->getBody()->getSize() === 0) { + return $fn($request, $options); + } + + $modify = []; + + // Add a default content-type if possible. + if (!$request->hasHeader('Content-Type')) { + if ($uri = $request->getBody()->getMetadata('uri')) { + if ($type = Psr7\mimetype_from_filename($uri)) { + $modify['set_headers']['Content-Type'] = $type; + } + } + } + + // Add a default content-length or transfer-encoding header. + if (!$request->hasHeader('Content-Length') + && !$request->hasHeader('Transfer-Encoding') + ) { + $size = $request->getBody()->getSize(); + if ($size !== null) { + $modify['set_headers']['Content-Length'] = $size; + } else { + $modify['set_headers']['Transfer-Encoding'] = 'chunked'; + } + } + + // Add the expect header if needed. + $this->addExpectHeader($request, $options, $modify); + + return $fn(Psr7\modify_request($request, $modify), $options); + } + + private function addExpectHeader( + RequestInterface $request, + array $options, + array &$modify + ) { + // Determine if the Expect header should be used + if ($request->hasHeader('Expect')) { + return; + } + + $expect = isset($options['expect']) ? $options['expect'] : null; + + // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0 + if ($expect === false || $request->getProtocolVersion() < 1.1) { + return; + } + + // The expect header is unconditionally enabled + if ($expect === true) { + $modify['set_headers']['Expect'] = '100-Continue'; + return; + } + + // By default, send the expect header when the payload is > 1mb + if ($expect === null) { + $expect = 1048576; + } + + // Always add if the body cannot be rewound, the size cannot be + // determined, or the size is greater than the cutoff threshold + $body = $request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size >= (int) $expect || !$body->isSeekable()) { + $modify['set_headers']['Expect'] = '100-Continue'; + } + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php b/Upyun/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php new file mode 100644 index 0000000..131b771 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php @@ -0,0 +1,237 @@ + 5, + 'protocols' => ['http', 'https'], + 'strict' => false, + 'referer' => false, + 'track_redirects' => false, + ]; + + /** @var callable */ + private $nextHandler; + + /** + * @param callable $nextHandler Next handler to invoke. + */ + public function __construct(callable $nextHandler) + { + $this->nextHandler = $nextHandler; + } + + /** + * @param RequestInterface $request + * @param array $options + * + * @return PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + $fn = $this->nextHandler; + + if (empty($options['allow_redirects'])) { + return $fn($request, $options); + } + + if ($options['allow_redirects'] === true) { + $options['allow_redirects'] = self::$defaultSettings; + } elseif (!is_array($options['allow_redirects'])) { + throw new \InvalidArgumentException('allow_redirects must be true, false, or array'); + } else { + // Merge the default settings with the provided settings + $options['allow_redirects'] += self::$defaultSettings; + } + + if (empty($options['allow_redirects']['max'])) { + return $fn($request, $options); + } + + return $fn($request, $options) + ->then(function (ResponseInterface $response) use ($request, $options) { + return $this->checkRedirect($request, $options, $response); + }); + } + + /** + * @param RequestInterface $request + * @param array $options + * @param ResponseInterface|PromiseInterface $response + * + * @return ResponseInterface|PromiseInterface + */ + public function checkRedirect( + RequestInterface $request, + array $options, + ResponseInterface $response + ) { + if (substr($response->getStatusCode(), 0, 1) != '3' + || !$response->hasHeader('Location') + ) { + return $response; + } + + $this->guardMax($request, $options); + $nextRequest = $this->modifyRequest($request, $options, $response); + + if (isset($options['allow_redirects']['on_redirect'])) { + call_user_func( + $options['allow_redirects']['on_redirect'], + $request, + $response, + $nextRequest->getUri() + ); + } + + /** @var PromiseInterface|ResponseInterface $promise */ + $promise = $this($nextRequest, $options); + + // Add headers to be able to track history of redirects. + if (!empty($options['allow_redirects']['track_redirects'])) { + return $this->withTracking( + $promise, + (string) $nextRequest->getUri(), + $response->getStatusCode() + ); + } + + return $promise; + } + + private function withTracking(PromiseInterface $promise, $uri, $statusCode) + { + return $promise->then( + function (ResponseInterface $response) use ($uri, $statusCode) { + // Note that we are pushing to the front of the list as this + // would be an earlier response than what is currently present + // in the history header. + $historyHeader = $response->getHeader(self::HISTORY_HEADER); + $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER); + array_unshift($historyHeader, $uri); + array_unshift($statusHeader, $statusCode); + return $response->withHeader(self::HISTORY_HEADER, $historyHeader) + ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader); + } + ); + } + + private function guardMax(RequestInterface $request, array &$options) + { + $current = isset($options['__redirect_count']) + ? $options['__redirect_count'] + : 0; + $options['__redirect_count'] = $current + 1; + $max = $options['allow_redirects']['max']; + + if ($options['__redirect_count'] > $max) { + throw new TooManyRedirectsException( + "Will not follow more than {$max} redirects", + $request + ); + } + } + + /** + * @param RequestInterface $request + * @param array $options + * @param ResponseInterface $response + * + * @return RequestInterface + */ + public function modifyRequest( + RequestInterface $request, + array $options, + ResponseInterface $response + ) { + // Request modifications to apply. + $modify = []; + $protocols = $options['allow_redirects']['protocols']; + + // Use a GET request if this is an entity enclosing request and we are + // not forcing RFC compliance, but rather emulating what all browsers + // would do. + $statusCode = $response->getStatusCode(); + if ($statusCode == 303 || + ($statusCode <= 302 && $request->getBody() && !$options['allow_redirects']['strict']) + ) { + $modify['method'] = 'GET'; + $modify['body'] = ''; + } + + $modify['uri'] = $this->redirectUri($request, $response, $protocols); + Psr7\rewind_body($request); + + // Add the Referer header if it is told to do so and only + // add the header if we are not redirecting from https to http. + if ($options['allow_redirects']['referer'] + && $modify['uri']->getScheme() === $request->getUri()->getScheme() + ) { + $uri = $request->getUri()->withUserInfo('', ''); + $modify['set_headers']['Referer'] = (string) $uri; + } else { + $modify['remove_headers'][] = 'Referer'; + } + + // Remove Authorization header if host is different. + if ($request->getUri()->getHost() !== $modify['uri']->getHost()) { + $modify['remove_headers'][] = 'Authorization'; + } + + return Psr7\modify_request($request, $modify); + } + + /** + * Set the appropriate URL on the request based on the location header + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param array $protocols + * + * @return UriInterface + */ + private function redirectUri( + RequestInterface $request, + ResponseInterface $response, + array $protocols + ) { + $location = Psr7\UriResolver::resolve( + $request->getUri(), + new Psr7\Uri($response->getHeaderLine('Location')) + ); + + // Ensure that the redirect URI is allowed based on the protocols. + if (!in_array($location->getScheme(), $protocols)) { + throw new BadResponseException( + sprintf( + 'Redirect URI, %s, does not use one of the allowed redirect protocols: %s', + $location, + implode(', ', $protocols) + ), + $request, + $response + ); + } + + return $location; + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/RequestOptions.php b/Upyun/vendor/guzzlehttp/guzzle/src/RequestOptions.php new file mode 100644 index 0000000..c6aacfb --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/RequestOptions.php @@ -0,0 +1,255 @@ +decider = $decider; + $this->nextHandler = $nextHandler; + $this->delay = $delay ?: __CLASS__ . '::exponentialDelay'; + } + + /** + * Default exponential backoff delay function. + * + * @param $retries + * + * @return int + */ + public static function exponentialDelay($retries) + { + return (int) pow(2, $retries - 1); + } + + /** + * @param RequestInterface $request + * @param array $options + * + * @return PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + if (!isset($options['retries'])) { + $options['retries'] = 0; + } + + $fn = $this->nextHandler; + return $fn($request, $options) + ->then( + $this->onFulfilled($request, $options), + $this->onRejected($request, $options) + ); + } + + private function onFulfilled(RequestInterface $req, array $options) + { + return function ($value) use ($req, $options) { + if (!call_user_func( + $this->decider, + $options['retries'], + $req, + $value, + null + )) { + return $value; + } + return $this->doRetry($req, $options, $value); + }; + } + + private function onRejected(RequestInterface $req, array $options) + { + return function ($reason) use ($req, $options) { + if (!call_user_func( + $this->decider, + $options['retries'], + $req, + null, + $reason + )) { + return \GuzzleHttp\Promise\rejection_for($reason); + } + return $this->doRetry($req, $options); + }; + } + + private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null) + { + $options['delay'] = call_user_func($this->delay, ++$options['retries'], $response); + + return $this($request, $options); + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/TransferStats.php b/Upyun/vendor/guzzlehttp/guzzle/src/TransferStats.php new file mode 100644 index 0000000..15f717e --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/TransferStats.php @@ -0,0 +1,126 @@ +request = $request; + $this->response = $response; + $this->transferTime = $transferTime; + $this->handlerErrorData = $handlerErrorData; + $this->handlerStats = $handlerStats; + } + + /** + * @return RequestInterface + */ + public function getRequest() + { + return $this->request; + } + + /** + * Returns the response that was received (if any). + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->response; + } + + /** + * Returns true if a response was received. + * + * @return bool + */ + public function hasResponse() + { + return $this->response !== null; + } + + /** + * Gets handler specific error data. + * + * This might be an exception, a integer representing an error code, or + * anything else. Relying on this value assumes that you know what handler + * you are using. + * + * @return mixed + */ + public function getHandlerErrorData() + { + return $this->handlerErrorData; + } + + /** + * Get the effective URI the request was sent to. + * + * @return UriInterface + */ + public function getEffectiveUri() + { + return $this->request->getUri(); + } + + /** + * Get the estimated time the request was being transferred by the handler. + * + * @return float Time in seconds. + */ + public function getTransferTime() + { + return $this->transferTime; + } + + /** + * Gets an array of all of the handler specific transfer data. + * + * @return array + */ + public function getHandlerStats() + { + return $this->handlerStats; + } + + /** + * Get a specific handler statistic from the handler by name. + * + * @param string $stat Handler specific transfer stat to retrieve. + * + * @return mixed|null + */ + public function getHandlerStat($stat) + { + return isset($this->handlerStats[$stat]) + ? $this->handlerStats[$stat] + : null; + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/UriTemplate.php b/Upyun/vendor/guzzlehttp/guzzle/src/UriTemplate.php new file mode 100644 index 0000000..0b1623e --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/UriTemplate.php @@ -0,0 +1,241 @@ + ['prefix' => '', 'joiner' => ',', 'query' => false], + '+' => ['prefix' => '', 'joiner' => ',', 'query' => false], + '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false], + '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false], + '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false], + ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true], + '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true], + '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true] + ]; + + /** @var array Delimiters */ + private static $delims = [':', '/', '?', '#', '[', ']', '@', '!', '$', + '&', '\'', '(', ')', '*', '+', ',', ';', '=']; + + /** @var array Percent encoded delimiters */ + private static $delimsPct = ['%3A', '%2F', '%3F', '%23', '%5B', '%5D', + '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', + '%3B', '%3D']; + + public function expand($template, array $variables) + { + if (false === strpos($template, '{')) { + return $template; + } + + $this->template = $template; + $this->variables = $variables; + + return preg_replace_callback( + '/\{([^\}]+)\}/', + [$this, 'expandMatch'], + $this->template + ); + } + + /** + * Parse an expression into parts + * + * @param string $expression Expression to parse + * + * @return array Returns an associative array of parts + */ + private function parseExpression($expression) + { + $result = []; + + if (isset(self::$operatorHash[$expression[0]])) { + $result['operator'] = $expression[0]; + $expression = substr($expression, 1); + } else { + $result['operator'] = ''; + } + + foreach (explode(',', $expression) as $value) { + $value = trim($value); + $varspec = []; + if ($colonPos = strpos($value, ':')) { + $varspec['value'] = substr($value, 0, $colonPos); + $varspec['modifier'] = ':'; + $varspec['position'] = (int) substr($value, $colonPos + 1); + } elseif (substr($value, -1) === '*') { + $varspec['modifier'] = '*'; + $varspec['value'] = substr($value, 0, -1); + } else { + $varspec['value'] = (string) $value; + $varspec['modifier'] = ''; + } + $result['values'][] = $varspec; + } + + return $result; + } + + /** + * Process an expansion + * + * @param array $matches Matches met in the preg_replace_callback + * + * @return string Returns the replacement string + */ + private function expandMatch(array $matches) + { + static $rfc1738to3986 = ['+' => '%20', '%7e' => '~']; + + $replacements = []; + $parsed = self::parseExpression($matches[1]); + $prefix = self::$operatorHash[$parsed['operator']]['prefix']; + $joiner = self::$operatorHash[$parsed['operator']]['joiner']; + $useQuery = self::$operatorHash[$parsed['operator']]['query']; + + foreach ($parsed['values'] as $value) { + + if (!isset($this->variables[$value['value']])) { + continue; + } + + $variable = $this->variables[$value['value']]; + $actuallyUseQuery = $useQuery; + $expanded = ''; + + if (is_array($variable)) { + + $isAssoc = $this->isAssoc($variable); + $kvp = []; + foreach ($variable as $key => $var) { + + if ($isAssoc) { + $key = rawurlencode($key); + $isNestedArray = is_array($var); + } else { + $isNestedArray = false; + } + + if (!$isNestedArray) { + $var = rawurlencode($var); + if ($parsed['operator'] === '+' || + $parsed['operator'] === '#' + ) { + $var = $this->decodeReserved($var); + } + } + + if ($value['modifier'] === '*') { + if ($isAssoc) { + if ($isNestedArray) { + // Nested arrays must allow for deeply nested + // structures. + $var = strtr( + http_build_query([$key => $var]), + $rfc1738to3986 + ); + } else { + $var = $key . '=' . $var; + } + } elseif ($key > 0 && $actuallyUseQuery) { + $var = $value['value'] . '=' . $var; + } + } + + $kvp[$key] = $var; + } + + if (empty($variable)) { + $actuallyUseQuery = false; + } elseif ($value['modifier'] === '*') { + $expanded = implode($joiner, $kvp); + if ($isAssoc) { + // Don't prepend the value name when using the explode + // modifier with an associative array. + $actuallyUseQuery = false; + } + } else { + if ($isAssoc) { + // When an associative array is encountered and the + // explode modifier is not set, then the result must be + // a comma separated list of keys followed by their + // respective values. + foreach ($kvp as $k => &$v) { + $v = $k . ',' . $v; + } + } + $expanded = implode(',', $kvp); + } + + } else { + if ($value['modifier'] === ':') { + $variable = substr($variable, 0, $value['position']); + } + $expanded = rawurlencode($variable); + if ($parsed['operator'] === '+' || $parsed['operator'] === '#') { + $expanded = $this->decodeReserved($expanded); + } + } + + if ($actuallyUseQuery) { + if (!$expanded && $joiner !== '&') { + $expanded = $value['value']; + } else { + $expanded = $value['value'] . '=' . $expanded; + } + } + + $replacements[] = $expanded; + } + + $ret = implode($joiner, $replacements); + if ($ret && $prefix) { + return $prefix . $ret; + } + + return $ret; + } + + /** + * Determines if an array is associative. + * + * This makes the assumption that input arrays are sequences or hashes. + * This assumption is a tradeoff for accuracy in favor of speed, but it + * should work in almost every case where input is supplied for a URI + * template. + * + * @param array $array Array to check + * + * @return bool + */ + private function isAssoc(array $array) + { + return $array && array_keys($array)[0] !== 0; + } + + /** + * Removes percent encoding on reserved characters (used with + and # + * modifiers). + * + * @param string $string String to fix + * + * @return string + */ + private function decodeReserved($string) + { + return str_replace(self::$delimsPct, self::$delims, $string); + } +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/functions.php b/Upyun/vendor/guzzlehttp/guzzle/src/functions.php new file mode 100644 index 0000000..59e212e --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/functions.php @@ -0,0 +1,331 @@ +expand($template, $variables); +} + +/** + * Debug function used to describe the provided value type and class. + * + * @param mixed $input + * + * @return string Returns a string containing the type of the variable and + * if a class is provided, the class name. + */ +function describe_type($input) +{ + switch (gettype($input)) { + case 'object': + return 'object(' . get_class($input) . ')'; + case 'array': + return 'array(' . count($input) . ')'; + default: + ob_start(); + var_dump($input); + // normalize float vs double + return str_replace('double(', 'float(', rtrim(ob_get_clean())); + } +} + +/** + * Parses an array of header lines into an associative array of headers. + * + * @param array $lines Header lines array of strings in the following + * format: "Name: Value" + * @return array + */ +function headers_from_lines($lines) +{ + $headers = []; + + foreach ($lines as $line) { + $parts = explode(':', $line, 2); + $headers[trim($parts[0])][] = isset($parts[1]) + ? trim($parts[1]) + : null; + } + + return $headers; +} + +/** + * Returns a debug stream based on the provided variable. + * + * @param mixed $value Optional value + * + * @return resource + */ +function debug_resource($value = null) +{ + if (is_resource($value)) { + return $value; + } elseif (defined('STDOUT')) { + return STDOUT; + } + + return fopen('php://output', 'w'); +} + +/** + * Chooses and creates a default handler to use based on the environment. + * + * The returned handler is not wrapped by any default middlewares. + * + * @throws \RuntimeException if no viable Handler is available. + * @return callable Returns the best handler for the given system. + */ +function choose_handler() +{ + $handler = null; + if (function_exists('curl_multi_exec') && function_exists('curl_exec')) { + $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler()); + } elseif (function_exists('curl_exec')) { + $handler = new CurlHandler(); + } elseif (function_exists('curl_multi_exec')) { + $handler = new CurlMultiHandler(); + } + + if (ini_get('allow_url_fopen')) { + $handler = $handler + ? Proxy::wrapStreaming($handler, new StreamHandler()) + : new StreamHandler(); + } elseif (!$handler) { + throw new \RuntimeException('GuzzleHttp requires cURL, the ' + . 'allow_url_fopen ini setting, or a custom HTTP handler.'); + } + + return $handler; +} + +/** + * Get the default User-Agent string to use with Guzzle + * + * @return string + */ +function default_user_agent() +{ + static $defaultAgent = ''; + + if (!$defaultAgent) { + $defaultAgent = 'GuzzleHttp/' . Client::VERSION; + if (extension_loaded('curl') && function_exists('curl_version')) { + $defaultAgent .= ' curl/' . \curl_version()['version']; + } + $defaultAgent .= ' PHP/' . PHP_VERSION; + } + + return $defaultAgent; +} + +/** + * Returns the default cacert bundle for the current system. + * + * First, the openssl.cafile and curl.cainfo php.ini settings are checked. + * If those settings are not configured, then the common locations for + * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X + * and Windows are checked. If any of these file locations are found on + * disk, they will be utilized. + * + * Note: the result of this function is cached for subsequent calls. + * + * @return string + * @throws \RuntimeException if no bundle can be found. + */ +function default_ca_bundle() +{ + static $cached = null; + static $cafiles = [ + // Red Hat, CentOS, Fedora (provided by the ca-certificates package) + '/etc/pki/tls/certs/ca-bundle.crt', + // Ubuntu, Debian (provided by the ca-certificates package) + '/etc/ssl/certs/ca-certificates.crt', + // FreeBSD (provided by the ca_root_nss package) + '/usr/local/share/certs/ca-root-nss.crt', + // SLES 12 (provided by the ca-certificates package) + '/var/lib/ca-certificates/ca-bundle.pem', + // OS X provided by homebrew (using the default path) + '/usr/local/etc/openssl/cert.pem', + // Google app engine + '/etc/ca-certificates.crt', + // Windows? + 'C:\\windows\\system32\\curl-ca-bundle.crt', + 'C:\\windows\\curl-ca-bundle.crt', + ]; + + if ($cached) { + return $cached; + } + + if ($ca = ini_get('openssl.cafile')) { + return $cached = $ca; + } + + if ($ca = ini_get('curl.cainfo')) { + return $cached = $ca; + } + + foreach ($cafiles as $filename) { + if (file_exists($filename)) { + return $cached = $filename; + } + } + + throw new \RuntimeException(<<< EOT +No system CA bundle could be found in any of the the common system locations. +PHP versions earlier than 5.6 are not properly configured to use the system's +CA bundle by default. In order to verify peer certificates, you will need to +supply the path on disk to a certificate bundle to the 'verify' request +option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not +need a specific certificate bundle, then Mozilla provides a commonly used CA +bundle which can be downloaded here (provided by the maintainer of cURL): +https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once +you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP +ini setting to point to the path to the file, allowing you to omit the 'verify' +request option. See http://curl.haxx.se/docs/sslcerts.html for more +information. +EOT + ); +} + +/** + * Creates an associative array of lowercase header names to the actual + * header casing. + * + * @param array $headers + * + * @return array + */ +function normalize_header_keys(array $headers) +{ + $result = []; + foreach (array_keys($headers) as $key) { + $result[strtolower($key)] = $key; + } + + return $result; +} + +/** + * Returns true if the provided host matches any of the no proxy areas. + * + * This method will strip a port from the host if it is present. Each pattern + * can be matched with an exact match (e.g., "foo.com" == "foo.com") or a + * partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" == + * "baz.foo.com", but ".foo.com" != "foo.com"). + * + * Areas are matched in the following cases: + * 1. "*" (without quotes) always matches any hosts. + * 2. An exact match. + * 3. The area starts with "." and the area is the last part of the host. e.g. + * '.mit.edu' will match any host that ends with '.mit.edu'. + * + * @param string $host Host to check against the patterns. + * @param array $noProxyArray An array of host patterns. + * + * @return bool + */ +function is_host_in_noproxy($host, array $noProxyArray) +{ + if (strlen($host) === 0) { + throw new \InvalidArgumentException('Empty host provided'); + } + + // Strip port if present. + if (strpos($host, ':')) { + $host = explode($host, ':', 2)[0]; + } + + foreach ($noProxyArray as $area) { + // Always match on wildcards. + if ($area === '*') { + return true; + } elseif (empty($area)) { + // Don't match on empty values. + continue; + } elseif ($area === $host) { + // Exact matches. + return true; + } else { + // Special match if the area when prefixed with ".". Remove any + // existing leading "." and add a new leading ".". + $area = '.' . ltrim($area, '.'); + if (substr($host, -(strlen($area))) === $area) { + return true; + } + } + } + + return false; +} + +/** + * Wrapper for json_decode that throws when an error occurs. + * + * @param string $json JSON data to parse + * @param bool $assoc When true, returned objects will be converted + * into associative arrays. + * @param int $depth User specified recursion depth. + * @param int $options Bitmask of JSON decode options. + * + * @return mixed + * @throws \InvalidArgumentException if the JSON cannot be decoded. + * @link http://www.php.net/manual/en/function.json-decode.php + */ +function json_decode($json, $assoc = false, $depth = 512, $options = 0) +{ + $data = \json_decode($json, $assoc, $depth, $options); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new \InvalidArgumentException( + 'json_decode error: ' . json_last_error_msg()); + } + + return $data; +} + +/** + * Wrapper for JSON encoding that throws when an error occurs. + * + * @param mixed $value The value being encoded + * @param int $options JSON encode option bitmask + * @param int $depth Set the maximum depth. Must be greater than zero. + * + * @return string + * @throws \InvalidArgumentException if the JSON cannot be encoded. + * @link http://www.php.net/manual/en/function.json-encode.php + */ +function json_encode($value, $options = 0, $depth = 512) +{ + $json = \json_encode($value, $options, $depth); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new \InvalidArgumentException( + 'json_encode error: ' . json_last_error_msg()); + } + + return $json; +} diff --git a/Upyun/vendor/guzzlehttp/guzzle/src/functions_include.php b/Upyun/vendor/guzzlehttp/guzzle/src/functions_include.php new file mode 100644 index 0000000..a93393a --- /dev/null +++ b/Upyun/vendor/guzzlehttp/guzzle/src/functions_include.php @@ -0,0 +1,6 @@ + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Upyun/vendor/guzzlehttp/promises/Makefile b/Upyun/vendor/guzzlehttp/promises/Makefile new file mode 100644 index 0000000..8d5b3ef --- /dev/null +++ b/Upyun/vendor/guzzlehttp/promises/Makefile @@ -0,0 +1,13 @@ +all: clean test + +test: + vendor/bin/phpunit + +coverage: + vendor/bin/phpunit --coverage-html=artifacts/coverage + +view-coverage: + open artifacts/coverage/index.html + +clean: + rm -rf artifacts/* diff --git a/Upyun/vendor/guzzlehttp/promises/README.md b/Upyun/vendor/guzzlehttp/promises/README.md new file mode 100644 index 0000000..7b607e2 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/promises/README.md @@ -0,0 +1,504 @@ +# Guzzle Promises + +[Promises/A+](https://promisesaplus.com/) implementation that handles promise +chaining and resolution iteratively, allowing for "infinite" promise chaining +while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/) +for a general introduction to promises. + +- [Features](#features) +- [Quick start](#quick-start) +- [Synchronous wait](#synchronous-wait) +- [Cancellation](#cancellation) +- [API](#api) + - [Promise](#promise) + - [FulfilledPromise](#fulfilledpromise) + - [RejectedPromise](#rejectedpromise) +- [Promise interop](#promise-interop) +- [Implementation notes](#implementation-notes) + + +# Features + +- [Promises/A+](https://promisesaplus.com/) implementation. +- Promise resolution and chaining is handled iteratively, allowing for + "infinite" promise chaining. +- Promises have a synchronous `wait` method. +- Promises can be cancelled. +- Works with any object that has a `then` function. +- C# style async/await coroutine promises using + `GuzzleHttp\Promise\coroutine()`. + + +# Quick start + +A *promise* represents the eventual result of an asynchronous operation. The +primary way of interacting with a promise is through its `then` method, which +registers callbacks to receive either a promise's eventual value or the reason +why the promise cannot be fulfilled. + + +## Callbacks + +Callbacks are registered with the `then` method by providing an optional +`$onFulfilled` followed by an optional `$onRejected` function. + + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then( + // $onFulfilled + function ($value) { + echo 'The promise was fulfilled.'; + }, + // $onRejected + function ($reason) { + echo 'The promise was rejected.'; + } +); +``` + +*Resolving* a promise means that you either fulfill a promise with a *value* or +reject a promise with a *reason*. Resolving a promises triggers callbacks +registered with the promises's `then` method. These callbacks are triggered +only once and in the order in which they were added. + + +## Resolving a promise + +Promises are fulfilled using the `resolve($value)` method. Resolving a promise +with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger +all of the onFulfilled callbacks (resolving a promise with a rejected promise +will reject the promise and trigger the `$onRejected` callbacks). + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise + ->then(function ($value) { + // Return a value and don't break the chain + return "Hello, " . $value; + }) + // This then is executed after the first then and receives the value + // returned from the first then. + ->then(function ($value) { + echo $value; + }); + +// Resolving the promise triggers the $onFulfilled callbacks and outputs +// "Hello, reader". +$promise->resolve('reader.'); +``` + + +## Promise forwarding + +Promises can be chained one after the other. Each then in the chain is a new +promise. The return value of a promise is what's forwarded to the next +promise in the chain. Returning a promise in a `then` callback will cause the +subsequent promises in the chain to only be fulfilled when the returned promise +has been fulfilled. The next promise in the chain will be invoked with the +resolved value of the promise. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$nextPromise = new Promise(); + +$promise + ->then(function ($value) use ($nextPromise) { + echo $value; + return $nextPromise; + }) + ->then(function ($value) { + echo $value; + }); + +// Triggers the first callback and outputs "A" +$promise->resolve('A'); +// Triggers the second callback and outputs "B" +$nextPromise->resolve('B'); +``` + +## Promise rejection + +When a promise is rejected, the `$onRejected` callbacks are invoked with the +rejection reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + echo $reason; +}); + +$promise->reject('Error!'); +// Outputs "Error!" +``` + +## Rejection forwarding + +If an exception is thrown in an `$onRejected` callback, subsequent +`$onRejected` callbacks are invoked with the thrown exception as the reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + throw new \Exception($reason); +})->then(null, function ($reason) { + assert($reason->getMessage() === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +You can also forward a rejection down the promise chain by returning a +`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or +`$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + return new RejectedPromise($reason); +})->then(null, function ($reason) { + assert($reason === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +If an exception is not thrown in a `$onRejected` callback and the callback +does not return a rejected promise, downstream `$onFulfilled` callbacks are +invoked using the value returned from the `$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new Promise(); +$promise + ->then(null, function ($reason) { + return "It's ok"; + }) + ->then(function ($value) { + assert($value === "It's ok"); + }); + +$promise->reject('Error!'); +``` + +# Synchronous wait + +You can synchronously force promises to complete using a promise's `wait` +method. When creating a promise, you can provide a wait function that is used +to synchronously force a promise to complete. When a wait function is invoked +it is expected to deliver a value to the promise or reject the promise. If the +wait function does not deliver a value, then an exception is thrown. The wait +function provided to a promise constructor is invoked when the `wait` function +of the promise is called. + +```php +$promise = new Promise(function () use (&$promise) { + $promise->resolve('foo'); +}); + +// Calling wait will return the value of the promise. +echo $promise->wait(); // outputs "foo" +``` + +If an exception is encountered while invoking the wait function of a promise, +the promise is rejected with the exception and the exception is thrown. + +```php +$promise = new Promise(function () use (&$promise) { + throw new \Exception('foo'); +}); + +$promise->wait(); // throws the exception. +``` + +Calling `wait` on a promise that has been fulfilled will not trigger the wait +function. It will simply return the previously resolved value. + +```php +$promise = new Promise(function () { die('this is not called!'); }); +$promise->resolve('foo'); +echo $promise->wait(); // outputs "foo" +``` + +Calling `wait` on a promise that has been rejected will throw an exception. If +the rejection reason is an instance of `\Exception` the reason is thrown. +Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason +can be obtained by calling the `getReason` method of the exception. + +```php +$promise = new Promise(); +$promise->reject('foo'); +$promise->wait(); +``` + +> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo' + + +## Unwrapping a promise + +When synchronously waiting on a promise, you are joining the state of the +promise into the current state of execution (i.e., return the value of the +promise if it was fulfilled or throw an exception if it was rejected). This is +called "unwrapping" the promise. Waiting on a promise will by default unwrap +the promise state. + +You can force a promise to resolve and *not* unwrap the state of the promise +by passing `false` to the first argument of the `wait` function: + +```php +$promise = new Promise(); +$promise->reject('foo'); +// This will not throw an exception. It simply ensures the promise has +// been resolved. +$promise->wait(false); +``` + +When unwrapping a promise, the resolved value of the promise will be waited +upon until the unwrapped value is not a promise. This means that if you resolve +promise A with a promise B and unwrap promise A, the value returned by the +wait function will be the value delivered to promise B. + +**Note**: when you do not unwrap the promise, no value is returned. + + +# Cancellation + +You can cancel a promise that has not yet been fulfilled using the `cancel()` +method of a promise. When creating a promise you can provide an optional +cancel function that when invoked cancels the action of computing a resolution +of the promise. + + +# API + + +## Promise + +When creating a promise object, you can provide an optional `$waitFn` and +`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is +expected to resolve the promise. `$cancelFn` is a function with no arguments +that is expected to cancel the computation of a promise. It is invoked when the +`cancel()` method of a promise is called. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise( + function () use (&$promise) { + $promise->resolve('waited'); + }, + function () { + // do something that will cancel the promise computation (e.g., close + // a socket, cancel a database query, etc...) + } +); + +assert('waited' === $promise->wait()); +``` + +A promise has the following methods: + +- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface` + + Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler. + +- `otherwise(callable $onRejected) : PromiseInterface` + + Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled. + +- `wait($unwrap = true) : mixed` + + Synchronously waits on the promise to complete. + + `$unwrap` controls whether or not the value of the promise is returned for a + fulfilled promise or if an exception is thrown if the promise is rejected. + This is set to `true` by default. + +- `cancel()` + + Attempts to cancel the promise if possible. The promise being cancelled and + the parent most ancestor that has not yet been resolved will also be + cancelled. Any promises waiting on the cancelled promise to resolve will also + be cancelled. + +- `getState() : string` + + Returns the state of the promise. One of `pending`, `fulfilled`, or + `rejected`. + +- `resolve($value)` + + Fulfills the promise with the given `$value`. + +- `reject($reason)` + + Rejects the promise with the given `$reason`. + + +## FulfilledPromise + +A fulfilled promise can be created to represent a promise that has been +fulfilled. + +```php +use GuzzleHttp\Promise\FulfilledPromise; + +$promise = new FulfilledPromise('value'); + +// Fulfilled callbacks are immediately invoked. +$promise->then(function ($value) { + echo $value; +}); +``` + + +## RejectedPromise + +A rejected promise can be created to represent a promise that has been +rejected. + +```php +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new RejectedPromise('Error'); + +// Rejected callbacks are immediately invoked. +$promise->then(null, function ($reason) { + echo $reason; +}); +``` + + +# Promise interop + +This library works with foreign promises that have a `then` method. This means +you can use Guzzle promises with [React promises](https://github.com/reactphp/promise) +for example. When a foreign promise is returned inside of a then method +callback, promise resolution will occur recursively. + +```php +// Create a React promise +$deferred = new React\Promise\Deferred(); +$reactPromise = $deferred->promise(); + +// Create a Guzzle promise that is fulfilled with a React promise. +$guzzlePromise = new \GuzzleHttp\Promise\Promise(); +$guzzlePromise->then(function ($value) use ($reactPromise) { + // Do something something with the value... + // Return the React promise + return $reactPromise; +}); +``` + +Please note that wait and cancel chaining is no longer possible when forwarding +a foreign promise. You will need to wrap a third-party promise with a Guzzle +promise in order to utilize wait and cancel functions with foreign promises. + + +## Event Loop Integration + +In order to keep the stack size constant, Guzzle promises are resolved +asynchronously using a task queue. When waiting on promises synchronously, the +task queue will be automatically run to ensure that the blocking promise and +any forwarded promises are resolved. When using promises asynchronously in an +event loop, you will need to run the task queue on each tick of the loop. If +you do not run the task queue, then promises will not be resolved. + +You can run the task queue using the `run()` method of the global task queue +instance. + +```php +// Get the global task queue +$queue = \GuzzleHttp\Promise\queue(); +$queue->run(); +``` + +For example, you could use Guzzle promises with React using a periodic timer: + +```php +$loop = React\EventLoop\Factory::create(); +$loop->addPeriodicTimer(0, [$queue, 'run']); +``` + +*TODO*: Perhaps adding a `futureTick()` on each tick would be faster? + + +# Implementation notes + + +## Promise resolution and chaining is handled iteratively + +By shuffling pending handlers from one owner to another, promises are +resolved iteratively, allowing for "infinite" then chaining. + +```php +then(function ($v) { + // The stack size remains constant (a good thing) + echo xdebug_get_stack_depth() . ', '; + return $v + 1; + }); +} + +$parent->resolve(0); +var_dump($p->wait()); // int(1000) + +``` + +When a promise is fulfilled or rejected with a non-promise value, the promise +then takes ownership of the handlers of each child promise and delivers values +down the chain without using recursion. + +When a promise is resolved with another promise, the original promise transfers +all of its pending handlers to the new promise. When the new promise is +eventually resolved, all of the pending handlers are delivered the forwarded +value. + + +## A promise is the deferred. + +Some promise libraries implement promises using a deferred object to represent +a computation and a promise object to represent the delivery of the result of +the computation. This is a nice separation of computation and delivery because +consumers of the promise cannot modify the value that will be eventually +delivered. + +One side effect of being able to implement promise resolution and chaining +iteratively is that you need to be able for one promise to reach into the state +of another promise to shuffle around ownership of handlers. In order to achieve +this without making the handlers of a promise publicly mutable, a promise is +also the deferred value, allowing promises of the same parent class to reach +into and modify the private properties of promises of the same type. While this +does allow consumers of the value to modify the resolution or rejection of the +deferred, it is a small price to pay for keeping the stack size constant. + +```php +$promise = new Promise(); +$promise->then(function ($value) { echo $value; }); +// The promise is the deferred value, so you can deliver a value to it. +$promise->resolve('foo'); +// prints "foo" +``` diff --git a/Upyun/vendor/guzzlehttp/promises/composer.json b/Upyun/vendor/guzzlehttp/promises/composer.json new file mode 100644 index 0000000..ec41a61 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/promises/composer.json @@ -0,0 +1,34 @@ +{ + "name": "guzzlehttp/promises", + "description": "Guzzle promises library", + "keywords": ["promise"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "scripts": { + "test": "vendor/bin/phpunit", + "test-ci": "vendor/bin/phpunit --coverage-text" + }, + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + } +} diff --git a/Upyun/vendor/guzzlehttp/promises/src/AggregateException.php b/Upyun/vendor/guzzlehttp/promises/src/AggregateException.php new file mode 100644 index 0000000..6a5690c --- /dev/null +++ b/Upyun/vendor/guzzlehttp/promises/src/AggregateException.php @@ -0,0 +1,16 @@ +then(function ($v) { echo $v; }); + * + * @param callable $generatorFn Generator function to wrap into a promise. + * + * @return Promise + * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration + */ +final class Coroutine implements PromiseInterface +{ + /** + * @var PromiseInterface|null + */ + private $currentPromise; + + /** + * @var Generator + */ + private $generator; + + /** + * @var Promise + */ + private $result; + + public function __construct(callable $generatorFn) + { + $this->generator = $generatorFn(); + $this->result = new Promise(function () { + while (isset($this->currentPromise)) { + $this->currentPromise->wait(); + } + }); + $this->nextCoroutine($this->generator->current()); + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + return $this->result->then($onFulfilled, $onRejected); + } + + public function otherwise(callable $onRejected) + { + return $this->result->otherwise($onRejected); + } + + public function wait($unwrap = true) + { + return $this->result->wait($unwrap); + } + + public function getState() + { + return $this->result->getState(); + } + + public function resolve($value) + { + $this->result->resolve($value); + } + + public function reject($reason) + { + $this->result->reject($reason); + } + + public function cancel() + { + $this->currentPromise->cancel(); + $this->result->cancel(); + } + + private function nextCoroutine($yielded) + { + $this->currentPromise = promise_for($yielded) + ->then([$this, '_handleSuccess'], [$this, '_handleFailure']); + } + + /** + * @internal + */ + public function _handleSuccess($value) + { + unset($this->currentPromise); + try { + $next = $this->generator->send($value); + if ($this->generator->valid()) { + $this->nextCoroutine($next); + } else { + $this->result->resolve($value); + } + } catch (Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * @internal + */ + public function _handleFailure($reason) + { + unset($this->currentPromise); + try { + $nextYield = $this->generator->throw(exception_for($reason)); + // The throw was caught, so keep iterating on the coroutine + $this->nextCoroutine($nextYield); + } catch (Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } +} diff --git a/Upyun/vendor/guzzlehttp/promises/src/EachPromise.php b/Upyun/vendor/guzzlehttp/promises/src/EachPromise.php new file mode 100644 index 0000000..d0ddf60 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/promises/src/EachPromise.php @@ -0,0 +1,229 @@ +iterable = iter_for($iterable); + + if (isset($config['concurrency'])) { + $this->concurrency = $config['concurrency']; + } + + if (isset($config['fulfilled'])) { + $this->onFulfilled = $config['fulfilled']; + } + + if (isset($config['rejected'])) { + $this->onRejected = $config['rejected']; + } + } + + public function promise() + { + if ($this->aggregate) { + return $this->aggregate; + } + + try { + $this->createPromise(); + $this->iterable->rewind(); + $this->refillPending(); + } catch (\Throwable $e) { + $this->aggregate->reject($e); + } catch (\Exception $e) { + $this->aggregate->reject($e); + } + + return $this->aggregate; + } + + private function createPromise() + { + $this->mutex = false; + $this->aggregate = new Promise(function () { + reset($this->pending); + if (empty($this->pending) && !$this->iterable->valid()) { + $this->aggregate->resolve(null); + return; + } + + // Consume a potentially fluctuating list of promises while + // ensuring that indexes are maintained (precluding array_shift). + while ($promise = current($this->pending)) { + next($this->pending); + $promise->wait(); + if ($this->aggregate->getState() !== PromiseInterface::PENDING) { + return; + } + } + }); + + // Clear the references when the promise is resolved. + $clearFn = function () { + $this->iterable = $this->concurrency = $this->pending = null; + $this->onFulfilled = $this->onRejected = null; + }; + + $this->aggregate->then($clearFn, $clearFn); + } + + private function refillPending() + { + if (!$this->concurrency) { + // Add all pending promises. + while ($this->addPending() && $this->advanceIterator()); + return; + } + + // Add only up to N pending promises. + $concurrency = is_callable($this->concurrency) + ? call_user_func($this->concurrency, count($this->pending)) + : $this->concurrency; + $concurrency = max($concurrency - count($this->pending), 0); + // Concurrency may be set to 0 to disallow new promises. + if (!$concurrency) { + return; + } + // Add the first pending promise. + $this->addPending(); + // Note this is special handling for concurrency=1 so that we do + // not advance the iterator after adding the first promise. This + // helps work around issues with generators that might not have the + // next value to yield until promise callbacks are called. + while (--$concurrency + && $this->advanceIterator() + && $this->addPending()); + } + + private function addPending() + { + if (!$this->iterable || !$this->iterable->valid()) { + return false; + } + + $promise = promise_for($this->iterable->current()); + $idx = $this->iterable->key(); + + $this->pending[$idx] = $promise->then( + function ($value) use ($idx) { + if ($this->onFulfilled) { + call_user_func( + $this->onFulfilled, $value, $idx, $this->aggregate + ); + } + $this->step($idx); + }, + function ($reason) use ($idx) { + if ($this->onRejected) { + call_user_func( + $this->onRejected, $reason, $idx, $this->aggregate + ); + } + $this->step($idx); + } + ); + + return true; + } + + private function advanceIterator() + { + // Place a lock on the iterator so that we ensure to not recurse, + // preventing fatal generator errors. + if ($this->mutex) { + return false; + } + + $this->mutex = true; + + try { + $this->iterable->next(); + $this->mutex = false; + return true; + } catch (\Throwable $e) { + $this->aggregate->reject($e); + $this->mutex = false; + return false; + } catch (\Exception $e) { + $this->aggregate->reject($e); + $this->mutex = false; + return false; + } + } + + private function step($idx) + { + // If the promise was already resolved, then ignore this step. + if ($this->aggregate->getState() !== PromiseInterface::PENDING) { + return; + } + + unset($this->pending[$idx]); + + // Only refill pending promises if we are not locked, preventing the + // EachPromise to recursively invoke the provided iterator, which + // cause a fatal error: "Cannot resume an already running generator" + if ($this->advanceIterator() && !$this->checkIfFinished()) { + // Add more pending promises if possible. + $this->refillPending(); + } + } + + private function checkIfFinished() + { + if (!$this->pending && !$this->iterable->valid()) { + // Resolve the promise if there's nothing left to do. + $this->aggregate->resolve(null); + return true; + } + + return false; + } +} diff --git a/Upyun/vendor/guzzlehttp/promises/src/FulfilledPromise.php b/Upyun/vendor/guzzlehttp/promises/src/FulfilledPromise.php new file mode 100644 index 0000000..dbbeeb9 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/promises/src/FulfilledPromise.php @@ -0,0 +1,82 @@ +value = $value; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // Return itself if there is no onFulfilled function. + if (!$onFulfilled) { + return $this; + } + + $queue = queue(); + $p = new Promise([$queue, 'run']); + $value = $this->value; + $queue->add(static function () use ($p, $value, $onFulfilled) { + if ($p->getState() === self::PENDING) { + try { + $p->resolve($onFulfilled($value)); + } catch (\Throwable $e) { + $p->reject($e); + } catch (\Exception $e) { + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + return $unwrap ? $this->value : null; + } + + public function getState() + { + return self::FULFILLED; + } + + public function resolve($value) + { + if ($value !== $this->value) { + throw new \LogicException("Cannot resolve a fulfilled promise"); + } + } + + public function reject($reason) + { + throw new \LogicException("Cannot reject a fulfilled promise"); + } + + public function cancel() + { + // pass + } +} diff --git a/Upyun/vendor/guzzlehttp/promises/src/Promise.php b/Upyun/vendor/guzzlehttp/promises/src/Promise.php new file mode 100644 index 0000000..844ada0 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/promises/src/Promise.php @@ -0,0 +1,280 @@ +waitFn = $waitFn; + $this->cancelFn = $cancelFn; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + if ($this->state === self::PENDING) { + $p = new Promise(null, [$this, 'cancel']); + $this->handlers[] = [$p, $onFulfilled, $onRejected]; + $p->waitList = $this->waitList; + $p->waitList[] = $this; + return $p; + } + + // Return a fulfilled promise and immediately invoke any callbacks. + if ($this->state === self::FULFILLED) { + return $onFulfilled + ? promise_for($this->result)->then($onFulfilled) + : promise_for($this->result); + } + + // It's either cancelled or rejected, so return a rejected promise + // and immediately invoke any callbacks. + $rejection = rejection_for($this->result); + return $onRejected ? $rejection->then(null, $onRejected) : $rejection; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true) + { + $this->waitIfPending(); + + $inner = $this->result instanceof PromiseInterface + ? $this->result->wait($unwrap) + : $this->result; + + if ($unwrap) { + if ($this->result instanceof PromiseInterface + || $this->state === self::FULFILLED + ) { + return $inner; + } else { + // It's rejected so "unwrap" and throw an exception. + throw exception_for($inner); + } + } + } + + public function getState() + { + return $this->state; + } + + public function cancel() + { + if ($this->state !== self::PENDING) { + return; + } + + $this->waitFn = $this->waitList = null; + + if ($this->cancelFn) { + $fn = $this->cancelFn; + $this->cancelFn = null; + try { + $fn(); + } catch (\Throwable $e) { + $this->reject($e); + } catch (\Exception $e) { + $this->reject($e); + } + } + + // Reject the promise only if it wasn't rejected in a then callback. + if ($this->state === self::PENDING) { + $this->reject(new CancellationException('Promise has been cancelled')); + } + } + + public function resolve($value) + { + $this->settle(self::FULFILLED, $value); + } + + public function reject($reason) + { + $this->settle(self::REJECTED, $reason); + } + + private function settle($state, $value) + { + if ($this->state !== self::PENDING) { + // Ignore calls with the same resolution. + if ($state === $this->state && $value === $this->result) { + return; + } + throw $this->state === $state + ? new \LogicException("The promise is already {$state}.") + : new \LogicException("Cannot change a {$this->state} promise to {$state}"); + } + + if ($value === $this) { + throw new \LogicException('Cannot fulfill or reject a promise with itself'); + } + + // Clear out the state of the promise but stash the handlers. + $this->state = $state; + $this->result = $value; + $handlers = $this->handlers; + $this->handlers = null; + $this->waitList = $this->waitFn = null; + $this->cancelFn = null; + + if (!$handlers) { + return; + } + + // If the value was not a settled promise or a thenable, then resolve + // it in the task queue using the correct ID. + if (!method_exists($value, 'then')) { + $id = $state === self::FULFILLED ? 1 : 2; + // It's a success, so resolve the handlers in the queue. + queue()->add(static function () use ($id, $value, $handlers) { + foreach ($handlers as $handler) { + self::callHandler($id, $value, $handler); + } + }); + } elseif ($value instanceof Promise + && $value->getState() === self::PENDING + ) { + // We can just merge our handlers onto the next promise. + $value->handlers = array_merge($value->handlers, $handlers); + } else { + // Resolve the handlers when the forwarded promise is resolved. + $value->then( + static function ($value) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(1, $value, $handler); + } + }, + static function ($reason) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(2, $reason, $handler); + } + } + ); + } + } + + /** + * Call a stack of handlers using a specific callback index and value. + * + * @param int $index 1 (resolve) or 2 (reject). + * @param mixed $value Value to pass to the callback. + * @param array $handler Array of handler data (promise and callbacks). + * + * @return array Returns the next group to resolve. + */ + private static function callHandler($index, $value, array $handler) + { + /** @var PromiseInterface $promise */ + $promise = $handler[0]; + + // The promise may have been cancelled or resolved before placing + // this thunk in the queue. + if ($promise->getState() !== self::PENDING) { + return; + } + + try { + if (isset($handler[$index])) { + $promise->resolve($handler[$index]($value)); + } elseif ($index === 1) { + // Forward resolution values as-is. + $promise->resolve($value); + } else { + // Forward rejections down the chain. + $promise->reject($value); + } + } catch (\Throwable $reason) { + $promise->reject($reason); + } catch (\Exception $reason) { + $promise->reject($reason); + } + } + + private function waitIfPending() + { + if ($this->state !== self::PENDING) { + return; + } elseif ($this->waitFn) { + $this->invokeWaitFn(); + } elseif ($this->waitList) { + $this->invokeWaitList(); + } else { + // If there's not wait function, then reject the promise. + $this->reject('Cannot wait on a promise that has ' + . 'no internal wait function. You must provide a wait ' + . 'function when constructing the promise to be able to ' + . 'wait on a promise.'); + } + + queue()->run(); + + if ($this->state === self::PENDING) { + $this->reject('Invoking the wait callback did not resolve the promise'); + } + } + + private function invokeWaitFn() + { + try { + $wfn = $this->waitFn; + $this->waitFn = null; + $wfn(true); + } catch (\Exception $reason) { + if ($this->state === self::PENDING) { + // The promise has not been resolved yet, so reject the promise + // with the exception. + $this->reject($reason); + } else { + // The promise was already resolved, so there's a problem in + // the application. + throw $reason; + } + } + } + + private function invokeWaitList() + { + $waitList = $this->waitList; + $this->waitList = null; + + foreach ($waitList as $result) { + while (true) { + $result->waitIfPending(); + + if ($result->result instanceof Promise) { + $result = $result->result; + } else { + if ($result->result instanceof PromiseInterface) { + $result->result->wait(false); + } + break; + } + } + } + } +} diff --git a/Upyun/vendor/guzzlehttp/promises/src/PromiseInterface.php b/Upyun/vendor/guzzlehttp/promises/src/PromiseInterface.php new file mode 100644 index 0000000..8f5f4b9 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/promises/src/PromiseInterface.php @@ -0,0 +1,93 @@ +reason = $reason; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // If there's no onRejected callback then just return self. + if (!$onRejected) { + return $this; + } + + $queue = queue(); + $reason = $this->reason; + $p = new Promise([$queue, 'run']); + $queue->add(static function () use ($p, $reason, $onRejected) { + if ($p->getState() === self::PENDING) { + try { + // Return a resolved promise if onRejected does not throw. + $p->resolve($onRejected($reason)); + } catch (\Throwable $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } catch (\Exception $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + if ($unwrap) { + throw exception_for($this->reason); + } + } + + public function getState() + { + return self::REJECTED; + } + + public function resolve($value) + { + throw new \LogicException("Cannot resolve a rejected promise"); + } + + public function reject($reason) + { + if ($reason !== $this->reason) { + throw new \LogicException("Cannot reject a rejected promise"); + } + } + + public function cancel() + { + // pass + } +} diff --git a/Upyun/vendor/guzzlehttp/promises/src/RejectionException.php b/Upyun/vendor/guzzlehttp/promises/src/RejectionException.php new file mode 100644 index 0000000..07c1136 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/promises/src/RejectionException.php @@ -0,0 +1,47 @@ +reason = $reason; + + $message = 'The promise was rejected'; + + if ($description) { + $message .= ' with reason: ' . $description; + } elseif (is_string($reason) + || (is_object($reason) && method_exists($reason, '__toString')) + ) { + $message .= ' with reason: ' . $this->reason; + } elseif ($reason instanceof \JsonSerializable) { + $message .= ' with reason: ' + . json_encode($this->reason, JSON_PRETTY_PRINT); + } + + parent::__construct($message); + } + + /** + * Returns the rejection reason. + * + * @return mixed + */ + public function getReason() + { + return $this->reason; + } +} diff --git a/Upyun/vendor/guzzlehttp/promises/src/TaskQueue.php b/Upyun/vendor/guzzlehttp/promises/src/TaskQueue.php new file mode 100644 index 0000000..6e8a2a0 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/promises/src/TaskQueue.php @@ -0,0 +1,66 @@ +run(); + */ +class TaskQueue implements TaskQueueInterface +{ + private $enableShutdown = true; + private $queue = []; + + public function __construct($withShutdown = true) + { + if ($withShutdown) { + register_shutdown_function(function () { + if ($this->enableShutdown) { + // Only run the tasks if an E_ERROR didn't occur. + $err = error_get_last(); + if (!$err || ($err['type'] ^ E_ERROR)) { + $this->run(); + } + } + }); + } + } + + public function isEmpty() + { + return !$this->queue; + } + + public function add(callable $task) + { + $this->queue[] = $task; + } + + public function run() + { + /** @var callable $task */ + while ($task = array_shift($this->queue)) { + $task(); + } + } + + /** + * The task queue will be run and exhausted by default when the process + * exits IFF the exit is not the result of a PHP E_ERROR error. + * + * You can disable running the automatic shutdown of the queue by calling + * this function. If you disable the task queue shutdown process, then you + * MUST either run the task queue (as a result of running your event loop + * or manually using the run() method) or wait on each outstanding promise. + * + * Note: This shutdown will occur before any destructors are triggered. + */ + public function disableShutdown() + { + $this->enableShutdown = false; + } +} diff --git a/Upyun/vendor/guzzlehttp/promises/src/TaskQueueInterface.php b/Upyun/vendor/guzzlehttp/promises/src/TaskQueueInterface.php new file mode 100644 index 0000000..ac8306e --- /dev/null +++ b/Upyun/vendor/guzzlehttp/promises/src/TaskQueueInterface.php @@ -0,0 +1,25 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\queue()->run(); + * } + * + * + * @param TaskQueueInterface $assign Optionally specify a new queue instance. + * + * @return TaskQueueInterface + */ +function queue(TaskQueueInterface $assign = null) +{ + static $queue; + + if ($assign) { + $queue = $assign; + } elseif (!$queue) { + $queue = new TaskQueue(); + } + + return $queue; +} + +/** + * Adds a function to run in the task queue when it is next `run()` and returns + * a promise that is fulfilled or rejected with the result. + * + * @param callable $task Task function to run. + * + * @return PromiseInterface + */ +function task(callable $task) +{ + $queue = queue(); + $promise = new Promise([$queue, 'run']); + $queue->add(function () use ($task, $promise) { + try { + $promise->resolve($task()); + } catch (\Throwable $e) { + $promise->reject($e); + } catch (\Exception $e) { + $promise->reject($e); + } + }); + + return $promise; +} + +/** + * Creates a promise for a value if the value is not a promise. + * + * @param mixed $value Promise or value. + * + * @return PromiseInterface + */ +function promise_for($value) +{ + if ($value instanceof PromiseInterface) { + return $value; + } + + // Return a Guzzle promise that shadows the given promise. + if (method_exists($value, 'then')) { + $wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null; + $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null; + $promise = new Promise($wfn, $cfn); + $value->then([$promise, 'resolve'], [$promise, 'reject']); + return $promise; + } + + return new FulfilledPromise($value); +} + +/** + * Creates a rejected promise for a reason if the reason is not a promise. If + * the provided reason is a promise, then it is returned as-is. + * + * @param mixed $reason Promise or reason. + * + * @return PromiseInterface + */ +function rejection_for($reason) +{ + if ($reason instanceof PromiseInterface) { + return $reason; + } + + return new RejectedPromise($reason); +} + +/** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + * + * @return \Exception|\Throwable + */ +function exception_for($reason) +{ + return $reason instanceof \Exception || $reason instanceof \Throwable + ? $reason + : new RejectionException($reason); +} + +/** + * Returns an iterator for the given value. + * + * @param mixed $value + * + * @return \Iterator + */ +function iter_for($value) +{ + if ($value instanceof \Iterator) { + return $value; + } elseif (is_array($value)) { + return new \ArrayIterator($value); + } else { + return new \ArrayIterator([$value]); + } +} + +/** + * Synchronously waits on a promise to resolve and returns an inspection state + * array. + * + * Returns a state associative array containing a "state" key mapping to a + * valid promise state. If the state of the promise is "fulfilled", the array + * will contain a "value" key mapping to the fulfilled value of the promise. If + * the promise is rejected, the array will contain a "reason" key mapping to + * the rejection reason of the promise. + * + * @param PromiseInterface $promise Promise or value. + * + * @return array + */ +function inspect(PromiseInterface $promise) +{ + try { + return [ + 'state' => PromiseInterface::FULFILLED, + 'value' => $promise->wait() + ]; + } catch (RejectionException $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()]; + } catch (\Throwable $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } catch (\Exception $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } +} + +/** + * Waits on all of the provided promises, but does not unwrap rejected promises + * as thrown exception. + * + * Returns an array of inspection state arrays. + * + * @param PromiseInterface[] $promises Traversable of promises to wait upon. + * + * @return array + * @see GuzzleHttp\Promise\inspect for the inspection state array format. + */ +function inspect_all($promises) +{ + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = inspect($promise); + } + + return $results; +} + +/** + * Waits on all of the provided promises and returns the fulfilled values. + * + * Returns an array that contains the value of each promise (in the same order + * the promises were provided). An exception is thrown if any of the promises + * are rejected. + * + * @param mixed $promises Iterable of PromiseInterface objects to wait on. + * + * @return array + * @throws \Exception on error + * @throws \Throwable on error in PHP >=7 + */ +function unwrap($promises) +{ + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = $promise->wait(); + } + + return $results; +} + +/** + * Given an array of promises, return a promise that is fulfilled when all the + * items in the array are fulfilled. + * + * The promise's fulfillment value is an array with fulfillment values at + * respective positions to the original array. If any promise in the array + * rejects, the returned promise is rejected with the rejection reason. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ +function all($promises) +{ + $results = []; + return each( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = $value; + }, + function ($reason, $idx, Promise $aggregate) { + $aggregate->reject($reason); + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); +} + +/** + * Initiate a competitive race between multiple promises or values (values will + * become immediately fulfilled promises). + * + * When count amount of promises have been fulfilled, the returned promise is + * fulfilled with an array that contains the fulfillment values of the winners + * in order of resolution. + * + * This prommise is rejected with a {@see GuzzleHttp\Promise\AggregateException} + * if the number of fulfilled promises is less than the desired $count. + * + * @param int $count Total number of promises. + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ +function some($count, $promises) +{ + $results = []; + $rejections = []; + + return each( + $promises, + function ($value, $idx, PromiseInterface $p) use (&$results, $count) { + if ($p->getState() !== PromiseInterface::PENDING) { + return; + } + $results[$idx] = $value; + if (count($results) >= $count) { + $p->resolve(null); + } + }, + function ($reason) use (&$rejections) { + $rejections[] = $reason; + } + )->then( + function () use (&$results, &$rejections, $count) { + if (count($results) !== $count) { + throw new AggregateException( + 'Not enough promises to fulfill count', + $rejections + ); + } + ksort($results); + return array_values($results); + } + ); +} + +/** + * Like some(), with 1 as count. However, if the promise fulfills, the + * fulfillment value is not an array of 1 but the value directly. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ +function any($promises) +{ + return some(1, $promises)->then(function ($values) { return $values[0]; }); +} + +/** + * Returns a promise that is fulfilled when all of the provided promises have + * been fulfilled or rejected. + * + * The returned promise is fulfilled with an array of inspection state arrays. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + * @see GuzzleHttp\Promise\inspect for the inspection state array format. + */ +function settle($promises) +{ + $results = []; + + return each( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; + }, + function ($reason, $idx) use (&$results) { + $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason]; + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); +} + +/** + * Given an iterator that yields promises or values, returns a promise that is + * fulfilled with a null value when the iterator has been consumed or the + * aggregate promise has been fulfilled or rejected. + * + * $onFulfilled is a function that accepts the fulfilled value, iterator + * index, and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate promise if needed. + * + * $onRejected is a function that accepts the rejection reason, iterator + * index, and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate promise if needed. + * + * @param mixed $iterable Iterator or array to iterate over. + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + */ +function each( + $iterable, + callable $onFulfilled = null, + callable $onRejected = null +) { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected + ]))->promise(); +} + +/** + * Like each, but only allows a certain number of outstanding promises at any + * given time. + * + * $concurrency may be an integer or a function that accepts the number of + * pending promises and returns a numeric concurrency limit value to allow for + * dynamic a concurrency size. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + */ +function each_limit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null +) { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected, + 'concurrency' => $concurrency + ]))->promise(); +} + +/** + * Like each_limit, but ensures that no promise in the given $iterable argument + * is rejected. If any promise is rejected, then the aggregate promise is + * rejected with the encountered rejection. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * + * @return PromiseInterface + */ +function each_limit_all( + $iterable, + $concurrency, + callable $onFulfilled = null +) { + return each_limit( + $iterable, + $concurrency, + $onFulfilled, + function ($reason, $idx, PromiseInterface $aggregate) { + $aggregate->reject($reason); + } + ); +} + +/** + * Returns true if a promise is fulfilled. + * + * @param PromiseInterface $promise + * + * @return bool + */ +function is_fulfilled(PromiseInterface $promise) +{ + return $promise->getState() === PromiseInterface::FULFILLED; +} + +/** + * Returns true if a promise is rejected. + * + * @param PromiseInterface $promise + * + * @return bool + */ +function is_rejected(PromiseInterface $promise) +{ + return $promise->getState() === PromiseInterface::REJECTED; +} + +/** + * Returns true if a promise is fulfilled or rejected. + * + * @param PromiseInterface $promise + * + * @return bool + */ +function is_settled(PromiseInterface $promise) +{ + return $promise->getState() !== PromiseInterface::PENDING; +} + +/** + * @see Coroutine + * + * @param callable $generatorFn + * + * @return PromiseInterface + */ +function coroutine(callable $generatorFn) +{ + return new Coroutine($generatorFn); +} diff --git a/Upyun/vendor/guzzlehttp/promises/src/functions_include.php b/Upyun/vendor/guzzlehttp/promises/src/functions_include.php new file mode 100644 index 0000000..34cd171 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/promises/src/functions_include.php @@ -0,0 +1,6 @@ +withPath('foo')->withHost('example.com')` will throw an exception + because the path of a URI with an authority must start with a slash "/" or be empty + - `(new Uri())->withScheme('http')` will return `'http://localhost'` +* Fix compatibility of URIs with `file` scheme and empty host. +* Added common URI utility methods based on RFC 3986 (see documentation in the readme): + - `Uri::isDefaultPort` + - `Uri::isAbsolute` + - `Uri::isNetworkPathReference` + - `Uri::isAbsolutePathReference` + - `Uri::isRelativePathReference` + - `Uri::isSameDocumentReference` + - `Uri::composeComponents` + - `UriNormalizer::normalize` + - `UriNormalizer::isEquivalent` + - `UriResolver::relativize` +* Deprecated `Uri::resolve` in favor of `UriResolver::resolve` +* Deprecated `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments` + +## 1.3.1 - 2016-06-25 + +* Fix `Uri::__toString` for network path references, e.g. `//example.org`. +* Fix missing lowercase normalization for host. +* Fix handling of URI components in case they are `'0'` in a lot of places, + e.g. as a user info password. +* Fix `Uri::withAddedHeader` to correctly merge headers with different case. +* Fix trimming of header values in `Uri::withAddedHeader`. Header values may + be surrounded by whitespace which should be ignored according to RFC 7230 + Section 3.2.4. This does not apply to header names. +* Fix `Uri::withAddedHeader` with an array of header values. +* Fix `Uri::resolve` when base path has no slash and handling of fragment. +* Fix handling of encoding in `Uri::with(out)QueryValue` so one can pass the + key/value both in encoded as well as decoded form to those methods. This is + consistent with withPath, withQuery etc. +* Fix `ServerRequest::withoutAttribute` when attribute value is null. + +## 1.3.0 - 2016-04-13 + +* Added remaining interfaces needed for full PSR7 compatibility + (ServerRequestInterface, UploadedFileInterface, etc.). +* Added support for stream_for from scalars. +* Can now extend Uri. +* Fixed a bug in validating request methods by making it more permissive. + +## 1.2.3 - 2016-02-18 + +* Fixed support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote + streams, which can sometimes return fewer bytes than requested with `fread`. +* Fixed handling of gzipped responses with FNAME headers. + +## 1.2.2 - 2016-01-22 + +* Added support for URIs without any authority. +* Added support for HTTP 451 'Unavailable For Legal Reasons.' +* Added support for using '0' as a filename. +* Added support for including non-standard ports in Host headers. + +## 1.2.1 - 2015-11-02 + +* Now supporting negative offsets when seeking to SEEK_END. + +## 1.2.0 - 2015-08-15 + +* Body as `"0"` is now properly added to a response. +* Now allowing forward seeking in CachingStream. +* Now properly parsing HTTP requests that contain proxy targets in + `parse_request`. +* functions.php is now conditionally required. +* user-info is no longer dropped when resolving URIs. + +## 1.1.0 - 2015-06-24 + +* URIs can now be relative. +* `multipart/form-data` headers are now overridden case-insensitively. +* URI paths no longer encode the following characters because they are allowed + in URIs: "(", ")", "*", "!", "'" +* A port is no longer added to a URI when the scheme is missing and no port is + present. + +## 1.0.0 - 2015-05-19 + +Initial release. + +Currently unsupported: + +- `Psr\Http\Message\ServerRequestInterface` +- `Psr\Http\Message\UploadedFileInterface` diff --git a/Upyun/vendor/guzzlehttp/psr7/LICENSE b/Upyun/vendor/guzzlehttp/psr7/LICENSE new file mode 100644 index 0000000..581d95f --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Upyun/vendor/guzzlehttp/psr7/README.md b/Upyun/vendor/guzzlehttp/psr7/README.md new file mode 100644 index 0000000..1649935 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/README.md @@ -0,0 +1,739 @@ +# PSR-7 Message Implementation + +This repository contains a full [PSR-7](http://www.php-fig.org/psr/psr-7/) +message implementation, several stream decorators, and some helpful +functionality like query string parsing. + + +[![Build Status](https://travis-ci.org/guzzle/psr7.svg?branch=master)](https://travis-ci.org/guzzle/psr7) + + +# Stream implementation + +This package comes with a number of stream implementations and stream +decorators. + + +## AppendStream + +`GuzzleHttp\Psr7\AppendStream` + +Reads from multiple streams, one after the other. + +```php +use GuzzleHttp\Psr7; + +$a = Psr7\stream_for('abc, '); +$b = Psr7\stream_for('123.'); +$composed = new Psr7\AppendStream([$a, $b]); + +$composed->addStream(Psr7\stream_for(' Above all listen to me')); + +echo $composed; // abc, 123. Above all listen to me. +``` + + +## BufferStream + +`GuzzleHttp\Psr7\BufferStream` + +Provides a buffer stream that can be written to fill a buffer, and read +from to remove bytes from the buffer. + +This stream returns a "hwm" metadata value that tells upstream consumers +what the configured high water mark of the stream is, or the maximum +preferred size of the buffer. + +```php +use GuzzleHttp\Psr7; + +// When more than 1024 bytes are in the buffer, it will begin returning +// false to writes. This is an indication that writers should slow down. +$buffer = new Psr7\BufferStream(1024); +``` + + +## CachingStream + +The CachingStream is used to allow seeking over previously read bytes on +non-seekable streams. This can be useful when transferring a non-seekable +entity body fails due to needing to rewind the stream (for example, resulting +from a redirect). Data that is read from the remote stream will be buffered in +a PHP temp stream so that previously read bytes are cached first in memory, +then on disk. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\stream_for(fopen('http://www.google.com', 'r')); +$stream = new Psr7\CachingStream($original); + +$stream->read(1024); +echo $stream->tell(); +// 1024 + +$stream->seek(0); +echo $stream->tell(); +// 0 +``` + + +## DroppingStream + +`GuzzleHttp\Psr7\DroppingStream` + +Stream decorator that begins dropping data once the size of the underlying +stream becomes too full. + +```php +use GuzzleHttp\Psr7; + +// Create an empty stream +$stream = Psr7\stream_for(); + +// Start dropping data when the stream has more than 10 bytes +$dropping = new Psr7\DroppingStream($stream, 10); + +$dropping->write('01234567890123456789'); +echo $stream; // 0123456789 +``` + + +## FnStream + +`GuzzleHttp\Psr7\FnStream` + +Compose stream implementations based on a hash of functions. + +Allows for easy testing and extension of a provided stream without needing +to create a concrete class for a simple extension point. + +```php + +use GuzzleHttp\Psr7; + +$stream = Psr7\stream_for('hi'); +$fnStream = Psr7\FnStream::decorate($stream, [ + 'rewind' => function () use ($stream) { + echo 'About to rewind - '; + $stream->rewind(); + echo 'rewound!'; + } +]); + +$fnStream->rewind(); +// Outputs: About to rewind - rewound! +``` + + +## InflateStream + +`GuzzleHttp\Psr7\InflateStream` + +Uses PHP's zlib.inflate filter to inflate deflate or gzipped content. + +This stream decorator skips the first 10 bytes of the given stream to remove +the gzip header, converts the provided stream to a PHP stream resource, +then appends the zlib.inflate filter. The stream is then converted back +to a Guzzle stream resource to be used as a Guzzle stream. + + +## LazyOpenStream + +`GuzzleHttp\Psr7\LazyOpenStream` + +Lazily reads or writes to a file that is opened only after an IO operation +take place on the stream. + +```php +use GuzzleHttp\Psr7; + +$stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); +// The file has not yet been opened... + +echo $stream->read(10); +// The file is opened and read from only when needed. +``` + + +## LimitStream + +`GuzzleHttp\Psr7\LimitStream` + +LimitStream can be used to read a subset or slice of an existing stream object. +This can be useful for breaking a large file into smaller pieces to be sent in +chunks (e.g. Amazon S3's multipart upload API). + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\stream_for(fopen('/tmp/test.txt', 'r+')); +echo $original->getSize(); +// >>> 1048576 + +// Limit the size of the body to 1024 bytes and start reading from byte 2048 +$stream = new Psr7\LimitStream($original, 1024, 2048); +echo $stream->getSize(); +// >>> 1024 +echo $stream->tell(); +// >>> 0 +``` + + +## MultipartStream + +`GuzzleHttp\Psr7\MultipartStream` + +Stream that when read returns bytes for a streaming multipart or +multipart/form-data stream. + + +## NoSeekStream + +`GuzzleHttp\Psr7\NoSeekStream` + +NoSeekStream wraps a stream and does not allow seeking. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\stream_for('foo'); +$noSeek = new Psr7\NoSeekStream($original); + +echo $noSeek->read(3); +// foo +var_export($noSeek->isSeekable()); +// false +$noSeek->seek(0); +var_export($noSeek->read(3)); +// NULL +``` + + +## PumpStream + +`GuzzleHttp\Psr7\PumpStream` + +Provides a read only stream that pumps data from a PHP callable. + +When invoking the provided callable, the PumpStream will pass the amount of +data requested to read to the callable. The callable can choose to ignore +this value and return fewer or more bytes than requested. Any extra data +returned by the provided callable is buffered internally until drained using +the read() function of the PumpStream. The provided callable MUST return +false when there is no more data to read. + + +## Implementing stream decorators + +Creating a stream decorator is very easy thanks to the +`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that +implement `Psr\Http\Message\StreamInterface` by proxying to an underlying +stream. Just `use` the `StreamDecoratorTrait` and implement your custom +methods. + +For example, let's say we wanted to call a specific function each time the last +byte is read from a stream. This could be implemented by overriding the +`read()` method. + +```php +use Psr\Http\Message\StreamInterface; +use GuzzleHttp\Psr7\StreamDecoratorTrait; + +class EofCallbackStream implements StreamInterface +{ + use StreamDecoratorTrait; + + private $callback; + + public function __construct(StreamInterface $stream, callable $cb) + { + $this->stream = $stream; + $this->callback = $cb; + } + + public function read($length) + { + $result = $this->stream->read($length); + + // Invoke the callback when EOF is hit. + if ($this->eof()) { + call_user_func($this->callback); + } + + return $result; + } +} +``` + +This decorator could be added to any existing stream and used like so: + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\stream_for('foo'); + +$eofStream = new EofCallbackStream($original, function () { + echo 'EOF!'; +}); + +$eofStream->read(2); +$eofStream->read(1); +// echoes "EOF!" +$eofStream->seek(0); +$eofStream->read(3); +// echoes "EOF!" +``` + + +## PHP StreamWrapper + +You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a +PSR-7 stream as a PHP stream resource. + +Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP +stream from a PSR-7 stream. + +```php +use GuzzleHttp\Psr7\StreamWrapper; + +$stream = GuzzleHttp\Psr7\stream_for('hello!'); +$resource = StreamWrapper::getResource($stream); +echo fread($resource, 6); // outputs hello! +``` + + +# Function API + +There are various functions available under the `GuzzleHttp\Psr7` namespace. + + +## `function str` + +`function str(MessageInterface $message)` + +Returns the string representation of an HTTP message. + +```php +$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); +echo GuzzleHttp\Psr7\str($request); +``` + + +## `function uri_for` + +`function uri_for($uri)` + +This function accepts a string or `Psr\Http\Message\UriInterface` and returns a +UriInterface for the given value. If the value is already a `UriInterface`, it +is returned as-is. + +```php +$uri = GuzzleHttp\Psr7\uri_for('http://example.com'); +assert($uri === GuzzleHttp\Psr7\uri_for($uri)); +``` + + +## `function stream_for` + +`function stream_for($resource = '', array $options = [])` + +Create a new stream based on the input type. + +Options is an associative array that can contain the following keys: + +* - metadata: Array of custom metadata. +* - size: Size of the stream. + +This method accepts the following `$resource` types: + +- `Psr\Http\Message\StreamInterface`: Returns the value as-is. +- `string`: Creates a stream object that uses the given string as the contents. +- `resource`: Creates a stream object that wraps the given PHP stream resource. +- `Iterator`: If the provided value implements `Iterator`, then a read-only + stream object will be created that wraps the given iterable. Each time the + stream is read from, data from the iterator will fill a buffer and will be + continuously called until the buffer is equal to the requested read size. + Subsequent read calls will first read from the buffer and then call `next` + on the underlying iterator until it is exhausted. +- `object` with `__toString()`: If the object has the `__toString()` method, + the object will be cast to a string and then a stream will be returned that + uses the string value. +- `NULL`: When `null` is passed, an empty stream object is returned. +- `callable` When a callable is passed, a read-only stream object will be + created that invokes the given callable. The callable is invoked with the + number of suggested bytes to read. The callable can return any number of + bytes, but MUST return `false` when there is no more data to return. The + stream object that wraps the callable will invoke the callable until the + number of requested bytes are available. Any additional bytes will be + buffered and used in subsequent reads. + +```php +$stream = GuzzleHttp\Psr7\stream_for('foo'); +$stream = GuzzleHttp\Psr7\stream_for(fopen('/path/to/file', 'r')); + +$generator function ($bytes) { + for ($i = 0; $i < $bytes; $i++) { + yield ' '; + } +} + +$stream = GuzzleHttp\Psr7\stream_for($generator(100)); +``` + + +## `function parse_header` + +`function parse_header($header)` + +Parse an array of header values containing ";" separated data into an array of +associative arrays representing the header key value pair data of the header. +When a parameter does not contain a value, but just contains a key, this +function will inject a key with a '' string value. + + +## `function normalize_header` + +`function normalize_header($header)` + +Converts an array of header values that may contain comma separated headers +into an array of headers with no comma separated values. + + +## `function modify_request` + +`function modify_request(RequestInterface $request, array $changes)` + +Clone and modify a request with the given changes. This method is useful for +reducing the number of clones needed to mutate a message. + +The changes can be one of: + +- method: (string) Changes the HTTP method. +- set_headers: (array) Sets the given headers. +- remove_headers: (array) Remove the given headers. +- body: (mixed) Sets the given body. +- uri: (UriInterface) Set the URI. +- query: (string) Set the query string value of the URI. +- version: (string) Set the protocol version. + + +## `function rewind_body` + +`function rewind_body(MessageInterface $message)` + +Attempts to rewind a message body and throws an exception on failure. The body +of the message will only be rewound if a call to `tell()` returns a value other +than `0`. + + +## `function try_fopen` + +`function try_fopen($filename, $mode)` + +Safely opens a PHP stream resource using a filename. + +When fopen fails, PHP normally raises a warning. This function adds an error +handler that checks for errors and throws an exception instead. + + +## `function copy_to_string` + +`function copy_to_string(StreamInterface $stream, $maxLen = -1)` + +Copy the contents of a stream into a string until the given number of bytes +have been read. + + +## `function copy_to_stream` + +`function copy_to_stream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)` + +Copy the contents of a stream into another stream until the given number of +bytes have been read. + + +## `function hash` + +`function hash(StreamInterface $stream, $algo, $rawOutput = false)` + +Calculate a hash of a Stream. This method reads the entire stream to calculate +a rolling hash (based on PHP's hash_init functions). + + +## `function readline` + +`function readline(StreamInterface $stream, $maxLength = null)` + +Read a line from the stream up to the maximum allowed buffer length. + + +## `function parse_request` + +`function parse_request($message)` + +Parses a request message string into a request object. + + +## `function parse_response` + +`function parse_response($message)` + +Parses a response message string into a response object. + + +## `function parse_query` + +`function parse_query($str, $urlEncoding = true)` + +Parse a query string into an associative array. + +If multiple values are found for the same key, the value of that key value pair +will become an array. This function does not parse nested PHP style arrays into +an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed into +`['foo[a]' => '1', 'foo[b]' => '2']`). + + +## `function build_query` + +`function build_query(array $params, $encoding = PHP_QUERY_RFC3986)` + +Build a query string from an array of key value pairs. + +This function can use the return value of parse_query() to build a query string. +This function does not modify the provided keys when an array is encountered +(like http_build_query would). + + +## `function mimetype_from_filename` + +`function mimetype_from_filename($filename)` + +Determines the mimetype of a file by looking at its extension. + + +## `function mimetype_from_extension` + +`function mimetype_from_extension($extension)` + +Maps a file extensions to a mimetype. + + +# Additional URI Methods + +Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class, +this library also provides additional functionality when working with URIs as static methods. + +## URI Types + +An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference. +An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI, +the base URI. Relative references can be divided into several forms according to +[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2): + +- network-path references, e.g. `//example.com/path` +- absolute-path references, e.g. `/path` +- relative-path references, e.g. `subpath` + +The following methods can be used to identify the type of the URI. + +### `GuzzleHttp\Psr7\Uri::isAbsolute` + +`public static function isAbsolute(UriInterface $uri): bool` + +Whether the URI is absolute, i.e. it has a scheme. + +### `GuzzleHttp\Psr7\Uri::isNetworkPathReference` + +`public static function isNetworkPathReference(UriInterface $uri): bool` + +Whether the URI is a network-path reference. A relative reference that begins with two slash characters is +termed an network-path reference. + +### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference` + +`public static function isAbsolutePathReference(UriInterface $uri): bool` + +Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is +termed an absolute-path reference. + +### `GuzzleHttp\Psr7\Uri::isRelativePathReference` + +`public static function isRelativePathReference(UriInterface $uri): bool` + +Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is +termed a relative-path reference. + +### `GuzzleHttp\Psr7\Uri::isSameDocumentReference` + +`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool` + +Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its +fragment component, identical to the base URI. When no base URI is given, only an empty URI reference +(apart from its fragment) is considered a same-document reference. + +## URI Components + +Additional methods to work with URI components. + +### `GuzzleHttp\Psr7\Uri::isDefaultPort` + +`public static function isDefaultPort(UriInterface $uri): bool` + +Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null +or the standard port. This method can be used independently of the implementation. + +### `GuzzleHttp\Psr7\Uri::composeComponents` + +`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string` + +Composes a URI reference string from its various components according to +[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called +manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`. + +### `GuzzleHttp\Psr7\Uri::fromParts` + +`public static function fromParts(array $parts): UriInterface` + +Creates a URI from a hash of [`parse_url`](http://php.net/manual/en/function.parse-url.php) components. + + +### `GuzzleHttp\Psr7\Uri::withQueryValue` + +`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface` + +Creates a new URI with a specific query string value. Any existing query string values that exactly match the +provided key are removed and replaced with the given key value pair. A value of null will set the query string +key without a value, e.g. "key" instead of "key=value". + + +### `GuzzleHttp\Psr7\Uri::withoutQueryValue` + +`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface` + +Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the +provided key are removed. + +## Reference Resolution + +`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according +to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers +do when resolving a link in a website based on the current request URI. + +### `GuzzleHttp\Psr7\UriResolver::resolve` + +`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface` + +Converts the relative URI into a new URI that is resolved against the base URI. + +### `GuzzleHttp\Psr7\UriResolver::removeDotSegments` + +`public static function removeDotSegments(string $path): string` + +Removes dot segments from a path and returns the new path according to +[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4). + +### `GuzzleHttp\Psr7\UriResolver::relativize` + +`public static function relativize(UriInterface $base, UriInterface $target): UriInterface` + +Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve(): + +```php +(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) +``` + +One use-case is to use the current request URI as base URI and then generate relative links in your documents +to reduce the document size or offer self-contained downloadable document archives. + +```php +$base = new Uri('http://example.com/a/b/'); +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. +echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. +``` + +## Normalization and Comparison + +`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to +[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6). + +### `GuzzleHttp\Psr7\UriNormalizer::normalize` + +`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface` + +Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface. +This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask +of normalizations to apply. The following normalizations are available: + +- `UriNormalizer::PRESERVING_NORMALIZATIONS` + + Default normalizations which only include the ones that preserve semantics. + +- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING` + + All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized. + + Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b` + +- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS` + + Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of + ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should + not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved + characters by URI normalizers. + + Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/` + +- `UriNormalizer::CONVERT_EMPTY_PATH` + + Converts the empty path to "/" for http and https URIs. + + Example: `http://example.org` → `http://example.org/` + +- `UriNormalizer::REMOVE_DEFAULT_HOST` + + Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host + "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to + RFC 3986. + + Example: `file://localhost/myfile` → `file:///myfile` + +- `UriNormalizer::REMOVE_DEFAULT_PORT` + + Removes the default port of the given URI scheme from the URI. + + Example: `http://example.org:80/` → `http://example.org/` + +- `UriNormalizer::REMOVE_DOT_SEGMENTS` + + Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would + change the semantics of the URI reference. + + Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html` + +- `UriNormalizer::REMOVE_DUPLICATE_SLASHES` + + Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes + and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization + may change the semantics. Encoded slashes (%2F) are not removed. + + Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html` + +- `UriNormalizer::SORT_QUERY_PARAMETERS` + + Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be + significant (this is not defined by the standard). So this normalization is not safe and may change the semantics + of the URI. + + Example: `?lang=en&article=fred` → `?article=fred&lang=en` + +### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent` + +`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool` + +Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given +`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent. +This of course assumes they will be resolved against the same base URI. If this is not the case, determination of +equivalence or difference of relative references does not mean anything. diff --git a/Upyun/vendor/guzzlehttp/psr7/composer.json b/Upyun/vendor/guzzlehttp/psr7/composer.json new file mode 100644 index 0000000..b1c5a90 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/composer.json @@ -0,0 +1,39 @@ +{ + "name": "guzzlehttp/psr7", + "type": "library", + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": ["request", "response", "message", "stream", "http", "uri", "url"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/AppendStream.php b/Upyun/vendor/guzzlehttp/psr7/src/AppendStream.php new file mode 100644 index 0000000..23039fd --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/AppendStream.php @@ -0,0 +1,233 @@ +addStream($stream); + } + } + + public function __toString() + { + try { + $this->rewind(); + return $this->getContents(); + } catch (\Exception $e) { + return ''; + } + } + + /** + * Add a stream to the AppendStream + * + * @param StreamInterface $stream Stream to append. Must be readable. + * + * @throws \InvalidArgumentException if the stream is not readable + */ + public function addStream(StreamInterface $stream) + { + if (!$stream->isReadable()) { + throw new \InvalidArgumentException('Each stream must be readable'); + } + + // The stream is only seekable if all streams are seekable + if (!$stream->isSeekable()) { + $this->seekable = false; + } + + $this->streams[] = $stream; + } + + public function getContents() + { + return copy_to_string($this); + } + + /** + * Closes each attached stream. + * + * {@inheritdoc} + */ + public function close() + { + $this->pos = $this->current = 0; + + foreach ($this->streams as $stream) { + $stream->close(); + } + + $this->streams = []; + } + + /** + * Detaches each attached stream + * + * {@inheritdoc} + */ + public function detach() + { + $this->close(); + $this->detached = true; + } + + public function tell() + { + return $this->pos; + } + + /** + * Tries to calculate the size by adding the size of each stream. + * + * If any of the streams do not return a valid number, then the size of the + * append stream cannot be determined and null is returned. + * + * {@inheritdoc} + */ + public function getSize() + { + $size = 0; + + foreach ($this->streams as $stream) { + $s = $stream->getSize(); + if ($s === null) { + return null; + } + $size += $s; + } + + return $size; + } + + public function eof() + { + return !$this->streams || + ($this->current >= count($this->streams) - 1 && + $this->streams[$this->current]->eof()); + } + + public function rewind() + { + $this->seek(0); + } + + /** + * Attempts to seek to the given position. Only supports SEEK_SET. + * + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if (!$this->seekable) { + throw new \RuntimeException('This AppendStream is not seekable'); + } elseif ($whence !== SEEK_SET) { + throw new \RuntimeException('The AppendStream can only seek with SEEK_SET'); + } + + $this->pos = $this->current = 0; + + // Rewind each stream + foreach ($this->streams as $i => $stream) { + try { + $stream->rewind(); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to seek stream ' + . $i . ' of the AppendStream', 0, $e); + } + } + + // Seek to the actual position by reading from each stream + while ($this->pos < $offset && !$this->eof()) { + $result = $this->read(min(8096, $offset - $this->pos)); + if ($result === '') { + break; + } + } + } + + /** + * Reads from all of the appended streams until the length is met or EOF. + * + * {@inheritdoc} + */ + public function read($length) + { + $buffer = ''; + $total = count($this->streams) - 1; + $remaining = $length; + $progressToNext = false; + + while ($remaining > 0) { + + // Progress to the next stream if needed. + if ($progressToNext || $this->streams[$this->current]->eof()) { + $progressToNext = false; + if ($this->current === $total) { + break; + } + $this->current++; + } + + $result = $this->streams[$this->current]->read($remaining); + + // Using a loose comparison here to match on '', false, and null + if ($result == null) { + $progressToNext = true; + continue; + } + + $buffer .= $result; + $remaining = $length - strlen($buffer); + } + + $this->pos += strlen($buffer); + + return $buffer; + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return false; + } + + public function isSeekable() + { + return $this->seekable; + } + + public function write($string) + { + throw new \RuntimeException('Cannot write to an AppendStream'); + } + + public function getMetadata($key = null) + { + return $key ? null : []; + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/BufferStream.php b/Upyun/vendor/guzzlehttp/psr7/src/BufferStream.php new file mode 100644 index 0000000..af4d4c2 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/BufferStream.php @@ -0,0 +1,137 @@ +hwm = $hwm; + } + + public function __toString() + { + return $this->getContents(); + } + + public function getContents() + { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + public function close() + { + $this->buffer = ''; + } + + public function detach() + { + $this->close(); + } + + public function getSize() + { + return strlen($this->buffer); + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return true; + } + + public function isSeekable() + { + return false; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + throw new \RuntimeException('Cannot seek a BufferStream'); + } + + public function eof() + { + return strlen($this->buffer) === 0; + } + + public function tell() + { + throw new \RuntimeException('Cannot determine the position of a BufferStream'); + } + + /** + * Reads data from the buffer. + */ + public function read($length) + { + $currentLength = strlen($this->buffer); + + if ($length >= $currentLength) { + // No need to slice the buffer because we don't have enough data. + $result = $this->buffer; + $this->buffer = ''; + } else { + // Slice up the result to provide a subset of the buffer. + $result = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + } + + return $result; + } + + /** + * Writes data to the buffer. + */ + public function write($string) + { + $this->buffer .= $string; + + // TODO: What should happen here? + if (strlen($this->buffer) >= $this->hwm) { + return false; + } + + return strlen($string); + } + + public function getMetadata($key = null) + { + if ($key == 'hwm') { + return $this->hwm; + } + + return $key ? null : []; + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/CachingStream.php b/Upyun/vendor/guzzlehttp/psr7/src/CachingStream.php new file mode 100644 index 0000000..ed68f08 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/CachingStream.php @@ -0,0 +1,138 @@ +remoteStream = $stream; + $this->stream = $target ?: new Stream(fopen('php://temp', 'r+')); + } + + public function getSize() + { + return max($this->stream->getSize(), $this->remoteStream->getSize()); + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + if ($whence == SEEK_SET) { + $byte = $offset; + } elseif ($whence == SEEK_CUR) { + $byte = $offset + $this->tell(); + } elseif ($whence == SEEK_END) { + $size = $this->remoteStream->getSize(); + if ($size === null) { + $size = $this->cacheEntireStream(); + } + $byte = $size + $offset; + } else { + throw new \InvalidArgumentException('Invalid whence'); + } + + $diff = $byte - $this->stream->getSize(); + + if ($diff > 0) { + // Read the remoteStream until we have read in at least the amount + // of bytes requested, or we reach the end of the file. + while ($diff > 0 && !$this->remoteStream->eof()) { + $this->read($diff); + $diff = $byte - $this->stream->getSize(); + } + } else { + // We can just do a normal seek since we've already seen this byte. + $this->stream->seek($byte); + } + } + + public function read($length) + { + // Perform a regular read on any previously read data from the buffer + $data = $this->stream->read($length); + $remaining = $length - strlen($data); + + // More data was requested so read from the remote stream + if ($remaining) { + // If data was written to the buffer in a position that would have + // been filled from the remote stream, then we must skip bytes on + // the remote stream to emulate overwriting bytes from that + // position. This mimics the behavior of other PHP stream wrappers. + $remoteData = $this->remoteStream->read( + $remaining + $this->skipReadBytes + ); + + if ($this->skipReadBytes) { + $len = strlen($remoteData); + $remoteData = substr($remoteData, $this->skipReadBytes); + $this->skipReadBytes = max(0, $this->skipReadBytes - $len); + } + + $data .= $remoteData; + $this->stream->write($remoteData); + } + + return $data; + } + + public function write($string) + { + // When appending to the end of the currently read stream, you'll want + // to skip bytes from being read from the remote stream to emulate + // other stream wrappers. Basically replacing bytes of data of a fixed + // length. + $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); + if ($overflow > 0) { + $this->skipReadBytes += $overflow; + } + + return $this->stream->write($string); + } + + public function eof() + { + return $this->stream->eof() && $this->remoteStream->eof(); + } + + /** + * Close both the remote stream and buffer stream + */ + public function close() + { + $this->remoteStream->close() && $this->stream->close(); + } + + private function cacheEntireStream() + { + $target = new FnStream(['write' => 'strlen']); + copy_to_stream($this, $target); + + return $this->tell(); + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/DroppingStream.php b/Upyun/vendor/guzzlehttp/psr7/src/DroppingStream.php new file mode 100644 index 0000000..8935c80 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/DroppingStream.php @@ -0,0 +1,42 @@ +stream = $stream; + $this->maxLength = $maxLength; + } + + public function write($string) + { + $diff = $this->maxLength - $this->stream->getSize(); + + // Begin returning 0 when the underlying stream is too large. + if ($diff <= 0) { + return 0; + } + + // Write the stream or a subset of the stream if needed. + if (strlen($string) < $diff) { + return $this->stream->write($string); + } + + return $this->stream->write(substr($string, 0, $diff)); + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/FnStream.php b/Upyun/vendor/guzzlehttp/psr7/src/FnStream.php new file mode 100644 index 0000000..cc9b445 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/FnStream.php @@ -0,0 +1,149 @@ +methods = $methods; + + // Create the functions on the class + foreach ($methods as $name => $fn) { + $this->{'_fn_' . $name} = $fn; + } + } + + /** + * Lazily determine which methods are not implemented. + * @throws \BadMethodCallException + */ + public function __get($name) + { + throw new \BadMethodCallException(str_replace('_fn_', '', $name) + . '() is not implemented in the FnStream'); + } + + /** + * The close method is called on the underlying stream only if possible. + */ + public function __destruct() + { + if (isset($this->_fn_close)) { + call_user_func($this->_fn_close); + } + } + + /** + * Adds custom functionality to an underlying stream by intercepting + * specific method calls. + * + * @param StreamInterface $stream Stream to decorate + * @param array $methods Hash of method name to a closure + * + * @return FnStream + */ + public static function decorate(StreamInterface $stream, array $methods) + { + // If any of the required methods were not provided, then simply + // proxy to the decorated stream. + foreach (array_diff(self::$slots, array_keys($methods)) as $diff) { + $methods[$diff] = [$stream, $diff]; + } + + return new self($methods); + } + + public function __toString() + { + return call_user_func($this->_fn___toString); + } + + public function close() + { + return call_user_func($this->_fn_close); + } + + public function detach() + { + return call_user_func($this->_fn_detach); + } + + public function getSize() + { + return call_user_func($this->_fn_getSize); + } + + public function tell() + { + return call_user_func($this->_fn_tell); + } + + public function eof() + { + return call_user_func($this->_fn_eof); + } + + public function isSeekable() + { + return call_user_func($this->_fn_isSeekable); + } + + public function rewind() + { + call_user_func($this->_fn_rewind); + } + + public function seek($offset, $whence = SEEK_SET) + { + call_user_func($this->_fn_seek, $offset, $whence); + } + + public function isWritable() + { + return call_user_func($this->_fn_isWritable); + } + + public function write($string) + { + return call_user_func($this->_fn_write, $string); + } + + public function isReadable() + { + return call_user_func($this->_fn_isReadable); + } + + public function read($length) + { + return call_user_func($this->_fn_read, $length); + } + + public function getContents() + { + return call_user_func($this->_fn_getContents); + } + + public function getMetadata($key = null) + { + return call_user_func($this->_fn_getMetadata, $key); + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/InflateStream.php b/Upyun/vendor/guzzlehttp/psr7/src/InflateStream.php new file mode 100644 index 0000000..0051d3f --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/InflateStream.php @@ -0,0 +1,52 @@ +read(10); + $filenameHeaderLength = $this->getLengthOfPossibleFilenameHeader($stream, $header); + // Skip the header, that is 10 + length of filename + 1 (nil) bytes + $stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength); + $resource = StreamWrapper::getResource($stream); + stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ); + $this->stream = new Stream($resource); + } + + /** + * @param StreamInterface $stream + * @param $header + * @return int + */ + private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header) + { + $filename_header_length = 0; + + if (substr(bin2hex($header), 6, 2) === '08') { + // we have a filename, read until nil + $filename_header_length = 1; + while ($stream->read(1) !== chr(0)) { + $filename_header_length++; + } + } + + return $filename_header_length; + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/Upyun/vendor/guzzlehttp/psr7/src/LazyOpenStream.php new file mode 100644 index 0000000..02cec3a --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/LazyOpenStream.php @@ -0,0 +1,39 @@ +filename = $filename; + $this->mode = $mode; + } + + /** + * Creates the underlying stream lazily when required. + * + * @return StreamInterface + */ + protected function createStream() + { + return stream_for(try_fopen($this->filename, $this->mode)); + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/LimitStream.php b/Upyun/vendor/guzzlehttp/psr7/src/LimitStream.php new file mode 100644 index 0000000..3c13d4f --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/LimitStream.php @@ -0,0 +1,155 @@ +stream = $stream; + $this->setLimit($limit); + $this->setOffset($offset); + } + + public function eof() + { + // Always return true if the underlying stream is EOF + if ($this->stream->eof()) { + return true; + } + + // No limit and the underlying stream is not at EOF + if ($this->limit == -1) { + return false; + } + + return $this->stream->tell() >= $this->offset + $this->limit; + } + + /** + * Returns the size of the limited subset of data + * {@inheritdoc} + */ + public function getSize() + { + if (null === ($length = $this->stream->getSize())) { + return null; + } elseif ($this->limit == -1) { + return $length - $this->offset; + } else { + return min($this->limit, $length - $this->offset); + } + } + + /** + * Allow for a bounded seek on the read limited stream + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if ($whence !== SEEK_SET || $offset < 0) { + throw new \RuntimeException(sprintf( + 'Cannot seek to offset % with whence %s', + $offset, + $whence + )); + } + + $offset += $this->offset; + + if ($this->limit !== -1) { + if ($offset > $this->offset + $this->limit) { + $offset = $this->offset + $this->limit; + } + } + + $this->stream->seek($offset); + } + + /** + * Give a relative tell() + * {@inheritdoc} + */ + public function tell() + { + return $this->stream->tell() - $this->offset; + } + + /** + * Set the offset to start limiting from + * + * @param int $offset Offset to seek to and begin byte limiting from + * + * @throws \RuntimeException if the stream cannot be seeked. + */ + public function setOffset($offset) + { + $current = $this->stream->tell(); + + if ($current !== $offset) { + // If the stream cannot seek to the offset position, then read to it + if ($this->stream->isSeekable()) { + $this->stream->seek($offset); + } elseif ($current > $offset) { + throw new \RuntimeException("Could not seek to stream offset $offset"); + } else { + $this->stream->read($offset - $current); + } + } + + $this->offset = $offset; + } + + /** + * Set the limit of bytes that the decorator allows to be read from the + * stream. + * + * @param int $limit Number of bytes to allow to be read from the stream. + * Use -1 for no limit. + */ + public function setLimit($limit) + { + $this->limit = $limit; + } + + public function read($length) + { + if ($this->limit == -1) { + return $this->stream->read($length); + } + + // Check if the current position is less than the total allowed + // bytes + original offset + $remaining = ($this->offset + $this->limit) - $this->stream->tell(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte + // limit is not exceeded + return $this->stream->read(min($remaining, $length)); + } + + return ''; + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/MessageTrait.php b/Upyun/vendor/guzzlehttp/psr7/src/MessageTrait.php new file mode 100644 index 0000000..1e4da64 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/MessageTrait.php @@ -0,0 +1,183 @@ + array of values */ + private $headers = []; + + /** @var array Map of lowercase header name => original name at registration */ + private $headerNames = []; + + /** @var string */ + private $protocol = '1.1'; + + /** @var StreamInterface */ + private $stream; + + public function getProtocolVersion() + { + return $this->protocol; + } + + public function withProtocolVersion($version) + { + if ($this->protocol === $version) { + return $this; + } + + $new = clone $this; + $new->protocol = $version; + return $new; + } + + public function getHeaders() + { + return $this->headers; + } + + public function hasHeader($header) + { + return isset($this->headerNames[strtolower($header)]); + } + + public function getHeader($header) + { + $header = strtolower($header); + + if (!isset($this->headerNames[$header])) { + return []; + } + + $header = $this->headerNames[$header]; + + return $this->headers[$header]; + } + + public function getHeaderLine($header) + { + return implode(', ', $this->getHeader($header)); + } + + public function withHeader($header, $value) + { + if (!is_array($value)) { + $value = [$value]; + } + + $value = $this->trimHeaderValues($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + unset($new->headers[$new->headerNames[$normalized]]); + } + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + + return $new; + } + + public function withAddedHeader($header, $value) + { + if (!is_array($value)) { + $value = [$value]; + } + + $value = $this->trimHeaderValues($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $new->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + } + + return $new; + } + + public function withoutHeader($header) + { + $normalized = strtolower($header); + + if (!isset($this->headerNames[$normalized])) { + return $this; + } + + $header = $this->headerNames[$normalized]; + + $new = clone $this; + unset($new->headers[$header], $new->headerNames[$normalized]); + + return $new; + } + + public function getBody() + { + if (!$this->stream) { + $this->stream = stream_for(''); + } + + return $this->stream; + } + + public function withBody(StreamInterface $body) + { + if ($body === $this->stream) { + return $this; + } + + $new = clone $this; + $new->stream = $body; + return $new; + } + + private function setHeaders(array $headers) + { + $this->headerNames = $this->headers = []; + foreach ($headers as $header => $value) { + if (!is_array($value)) { + $value = [$value]; + } + + $value = $this->trimHeaderValues($value); + $normalized = strtolower($header); + if (isset($this->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $this->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $this->headerNames[$normalized] = $header; + $this->headers[$header] = $value; + } + } + } + + /** + * Trims whitespace from the header values. + * + * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field. + * + * header-field = field-name ":" OWS field-value OWS + * OWS = *( SP / HTAB ) + * + * @param string[] $values Header values + * + * @return string[] Trimmed header values + * + * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 + */ + private function trimHeaderValues(array $values) + { + return array_map(function ($value) { + return trim($value, " \t"); + }, $values); + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/MultipartStream.php b/Upyun/vendor/guzzlehttp/psr7/src/MultipartStream.php new file mode 100644 index 0000000..c0fd584 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/MultipartStream.php @@ -0,0 +1,153 @@ +boundary = $boundary ?: sha1(uniqid('', true)); + $this->stream = $this->createStream($elements); + } + + /** + * Get the boundary + * + * @return string + */ + public function getBoundary() + { + return $this->boundary; + } + + public function isWritable() + { + return false; + } + + /** + * Get the headers needed before transferring the content of a POST file + */ + private function getHeaders(array $headers) + { + $str = ''; + foreach ($headers as $key => $value) { + $str .= "{$key}: {$value}\r\n"; + } + + return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n"; + } + + /** + * Create the aggregate stream that will be used to upload the POST data + */ + protected function createStream(array $elements) + { + $stream = new AppendStream(); + + foreach ($elements as $element) { + $this->addElement($stream, $element); + } + + // Add the trailing boundary with CRLF + $stream->addStream(stream_for("--{$this->boundary}--\r\n")); + + return $stream; + } + + private function addElement(AppendStream $stream, array $element) + { + foreach (['contents', 'name'] as $key) { + if (!array_key_exists($key, $element)) { + throw new \InvalidArgumentException("A '{$key}' key is required"); + } + } + + $element['contents'] = stream_for($element['contents']); + + if (empty($element['filename'])) { + $uri = $element['contents']->getMetadata('uri'); + if (substr($uri, 0, 6) !== 'php://') { + $element['filename'] = $uri; + } + } + + list($body, $headers) = $this->createElement( + $element['name'], + $element['contents'], + isset($element['filename']) ? $element['filename'] : null, + isset($element['headers']) ? $element['headers'] : [] + ); + + $stream->addStream(stream_for($this->getHeaders($headers))); + $stream->addStream($body); + $stream->addStream(stream_for("\r\n")); + } + + /** + * @return array + */ + private function createElement($name, StreamInterface $stream, $filename, array $headers) + { + // Set a default content-disposition header if one was no provided + $disposition = $this->getHeader($headers, 'content-disposition'); + if (!$disposition) { + $headers['Content-Disposition'] = ($filename === '0' || $filename) + ? sprintf('form-data; name="%s"; filename="%s"', + $name, + basename($filename)) + : "form-data; name=\"{$name}\""; + } + + // Set a default content-length header if one was no provided + $length = $this->getHeader($headers, 'content-length'); + if (!$length) { + if ($length = $stream->getSize()) { + $headers['Content-Length'] = (string) $length; + } + } + + // Set a default Content-Type if one was not supplied + $type = $this->getHeader($headers, 'content-type'); + if (!$type && ($filename === '0' || $filename)) { + if ($type = mimetype_from_filename($filename)) { + $headers['Content-Type'] = $type; + } + } + + return [$stream, $headers]; + } + + private function getHeader(array $headers, $key) + { + $lowercaseHeader = strtolower($key); + foreach ($headers as $k => $v) { + if (strtolower($k) === $lowercaseHeader) { + return $v; + } + } + + return null; + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/Upyun/vendor/guzzlehttp/psr7/src/NoSeekStream.php new file mode 100644 index 0000000..2332218 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/NoSeekStream.php @@ -0,0 +1,22 @@ +source = $source; + $this->size = isset($options['size']) ? $options['size'] : null; + $this->metadata = isset($options['metadata']) ? $options['metadata'] : []; + $this->buffer = new BufferStream(); + } + + public function __toString() + { + try { + return copy_to_string($this); + } catch (\Exception $e) { + return ''; + } + } + + public function close() + { + $this->detach(); + } + + public function detach() + { + $this->tellPos = false; + $this->source = null; + } + + public function getSize() + { + return $this->size; + } + + public function tell() + { + return $this->tellPos; + } + + public function eof() + { + return !$this->source; + } + + public function isSeekable() + { + return false; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + throw new \RuntimeException('Cannot seek a PumpStream'); + } + + public function isWritable() + { + return false; + } + + public function write($string) + { + throw new \RuntimeException('Cannot write to a PumpStream'); + } + + public function isReadable() + { + return true; + } + + public function read($length) + { + $data = $this->buffer->read($length); + $readLen = strlen($data); + $this->tellPos += $readLen; + $remaining = $length - $readLen; + + if ($remaining) { + $this->pump($remaining); + $data .= $this->buffer->read($remaining); + $this->tellPos += strlen($data) - $readLen; + } + + return $data; + } + + public function getContents() + { + $result = ''; + while (!$this->eof()) { + $result .= $this->read(1000000); + } + + return $result; + } + + public function getMetadata($key = null) + { + if (!$key) { + return $this->metadata; + } + + return isset($this->metadata[$key]) ? $this->metadata[$key] : null; + } + + private function pump($length) + { + if ($this->source) { + do { + $data = call_user_func($this->source, $length); + if ($data === false || $data === null) { + $this->source = null; + return; + } + $this->buffer->write($data); + $length -= strlen($data); + } while ($length > 0); + } + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/Request.php b/Upyun/vendor/guzzlehttp/psr7/src/Request.php new file mode 100644 index 0000000..0828548 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/Request.php @@ -0,0 +1,142 @@ +method = strtoupper($method); + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + + if (!$this->hasHeader('Host')) { + $this->updateHostFromUri(); + } + + if ($body !== '' && $body !== null) { + $this->stream = stream_for($body); + } + } + + public function getRequestTarget() + { + if ($this->requestTarget !== null) { + return $this->requestTarget; + } + + $target = $this->uri->getPath(); + if ($target == '') { + $target = '/'; + } + if ($this->uri->getQuery() != '') { + $target .= '?' . $this->uri->getQuery(); + } + + return $target; + } + + public function withRequestTarget($requestTarget) + { + if (preg_match('#\s#', $requestTarget)) { + throw new InvalidArgumentException( + 'Invalid request target provided; cannot contain whitespace' + ); + } + + $new = clone $this; + $new->requestTarget = $requestTarget; + return $new; + } + + public function getMethod() + { + return $this->method; + } + + public function withMethod($method) + { + $new = clone $this; + $new->method = strtoupper($method); + return $new; + } + + public function getUri() + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false) + { + if ($uri === $this->uri) { + return $this; + } + + $new = clone $this; + $new->uri = $uri; + + if (!$preserveHost) { + $new->updateHostFromUri(); + } + + return $new; + } + + private function updateHostFromUri() + { + $host = $this->uri->getHost(); + + if ($host == '') { + return; + } + + if (($port = $this->uri->getPort()) !== null) { + $host .= ':' . $port; + } + + if (isset($this->headerNames['host'])) { + $header = $this->headerNames['host']; + } else { + $header = 'Host'; + $this->headerNames['host'] = 'Host'; + } + // Ensure Host is the first header. + // See: http://tools.ietf.org/html/rfc7230#section-5.4 + $this->headers = [$header => [$host]] + $this->headers; + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/Response.php b/Upyun/vendor/guzzlehttp/psr7/src/Response.php new file mode 100644 index 0000000..2830c6c --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/Response.php @@ -0,0 +1,132 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-status', + 208 => 'Already Reported', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Switch Proxy', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 511 => 'Network Authentication Required', + ]; + + /** @var string */ + private $reasonPhrase = ''; + + /** @var int */ + private $statusCode = 200; + + /** + * @param int $status Status code + * @param array $headers Response headers + * @param string|null|resource|StreamInterface $body Response body + * @param string $version Protocol version + * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) + */ + public function __construct( + $status = 200, + array $headers = [], + $body = null, + $version = '1.1', + $reason = null + ) { + $this->statusCode = (int) $status; + + if ($body !== '' && $body !== null) { + $this->stream = stream_for($body); + } + + $this->setHeaders($headers); + if ($reason == '' && isset(self::$phrases[$this->statusCode])) { + $this->reasonPhrase = self::$phrases[$this->statusCode]; + } else { + $this->reasonPhrase = (string) $reason; + } + + $this->protocol = $version; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getReasonPhrase() + { + return $this->reasonPhrase; + } + + public function withStatus($code, $reasonPhrase = '') + { + $new = clone $this; + $new->statusCode = (int) $code; + if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) { + $reasonPhrase = self::$phrases[$new->statusCode]; + } + $new->reasonPhrase = $reasonPhrase; + return $new; + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/ServerRequest.php b/Upyun/vendor/guzzlehttp/psr7/src/ServerRequest.php new file mode 100644 index 0000000..575aab8 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/ServerRequest.php @@ -0,0 +1,358 @@ +serverParams = $serverParams; + + parent::__construct($method, $uri, $headers, $body, $version); + } + + /** + * Return an UploadedFile instance array. + * + * @param array $files A array which respect $_FILES structure + * @throws InvalidArgumentException for unrecognized values + * @return array + */ + public static function normalizeFiles(array $files) + { + $normalized = []; + + foreach ($files as $key => $value) { + if ($value instanceof UploadedFileInterface) { + $normalized[$key] = $value; + } elseif (is_array($value) && isset($value['tmp_name'])) { + $normalized[$key] = self::createUploadedFileFromSpec($value); + } elseif (is_array($value)) { + $normalized[$key] = self::normalizeFiles($value); + continue; + } else { + throw new InvalidArgumentException('Invalid value in files specification'); + } + } + + return $normalized; + } + + /** + * Create and return an UploadedFile instance from a $_FILES specification. + * + * If the specification represents an array of values, this method will + * delegate to normalizeNestedFileSpec() and return that return value. + * + * @param array $value $_FILES struct + * @return array|UploadedFileInterface + */ + private static function createUploadedFileFromSpec(array $value) + { + if (is_array($value['tmp_name'])) { + return self::normalizeNestedFileSpec($value); + } + + return new UploadedFile( + $value['tmp_name'], + (int) $value['size'], + (int) $value['error'], + $value['name'], + $value['type'] + ); + } + + /** + * Normalize an array of file specifications. + * + * Loops through all nested files and returns a normalized array of + * UploadedFileInterface instances. + * + * @param array $files + * @return UploadedFileInterface[] + */ + private static function normalizeNestedFileSpec(array $files = []) + { + $normalizedFiles = []; + + foreach (array_keys($files['tmp_name']) as $key) { + $spec = [ + 'tmp_name' => $files['tmp_name'][$key], + 'size' => $files['size'][$key], + 'error' => $files['error'][$key], + 'name' => $files['name'][$key], + 'type' => $files['type'][$key], + ]; + $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); + } + + return $normalizedFiles; + } + + /** + * Return a ServerRequest populated with superglobals: + * $_GET + * $_POST + * $_COOKIE + * $_FILES + * $_SERVER + * + * @return ServerRequestInterface + */ + public static function fromGlobals() + { + $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; + $headers = function_exists('getallheaders') ? getallheaders() : []; + $uri = self::getUriFromGlobals(); + $body = new LazyOpenStream('php://input', 'r+'); + $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1'; + + $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); + + return $serverRequest + ->withCookieParams($_COOKIE) + ->withQueryParams($_GET) + ->withParsedBody($_POST) + ->withUploadedFiles(self::normalizeFiles($_FILES)); + } + + /** + * Get a Uri populated with values from $_SERVER. + * + * @return UriInterface + */ + public static function getUriFromGlobals() { + $uri = new Uri(''); + + $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'); + + $hasPort = false; + if (isset($_SERVER['HTTP_HOST'])) { + $hostHeaderParts = explode(':', $_SERVER['HTTP_HOST']); + $uri = $uri->withHost($hostHeaderParts[0]); + if (isset($hostHeaderParts[1])) { + $hasPort = true; + $uri = $uri->withPort($hostHeaderParts[1]); + } + } elseif (isset($_SERVER['SERVER_NAME'])) { + $uri = $uri->withHost($_SERVER['SERVER_NAME']); + } elseif (isset($_SERVER['SERVER_ADDR'])) { + $uri = $uri->withHost($_SERVER['SERVER_ADDR']); + } + + if (!$hasPort && isset($_SERVER['SERVER_PORT'])) { + $uri = $uri->withPort($_SERVER['SERVER_PORT']); + } + + $hasQuery = false; + if (isset($_SERVER['REQUEST_URI'])) { + $requestUriParts = explode('?', $_SERVER['REQUEST_URI']); + $uri = $uri->withPath($requestUriParts[0]); + if (isset($requestUriParts[1])) { + $hasQuery = true; + $uri = $uri->withQuery($requestUriParts[1]); + } + } + + if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) { + $uri = $uri->withQuery($_SERVER['QUERY_STRING']); + } + + return $uri; + } + + + /** + * {@inheritdoc} + */ + public function getServerParams() + { + return $this->serverParams; + } + + /** + * {@inheritdoc} + */ + public function getUploadedFiles() + { + return $this->uploadedFiles; + } + + /** + * {@inheritdoc} + */ + public function withUploadedFiles(array $uploadedFiles) + { + $new = clone $this; + $new->uploadedFiles = $uploadedFiles; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getCookieParams() + { + return $this->cookieParams; + } + + /** + * {@inheritdoc} + */ + public function withCookieParams(array $cookies) + { + $new = clone $this; + $new->cookieParams = $cookies; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getQueryParams() + { + return $this->queryParams; + } + + /** + * {@inheritdoc} + */ + public function withQueryParams(array $query) + { + $new = clone $this; + $new->queryParams = $query; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getParsedBody() + { + return $this->parsedBody; + } + + /** + * {@inheritdoc} + */ + public function withParsedBody($data) + { + $new = clone $this; + $new->parsedBody = $data; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + public function getAttribute($attribute, $default = null) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $default; + } + + return $this->attributes[$attribute]; + } + + /** + * {@inheritdoc} + */ + public function withAttribute($attribute, $value) + { + $new = clone $this; + $new->attributes[$attribute] = $value; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function withoutAttribute($attribute) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $this; + } + + $new = clone $this; + unset($new->attributes[$attribute]); + + return $new; + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/Stream.php b/Upyun/vendor/guzzlehttp/psr7/src/Stream.php new file mode 100644 index 0000000..e336628 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/Stream.php @@ -0,0 +1,257 @@ + [ + 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, + 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, + 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a+' => true + ], + 'write' => [ + 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, + 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, + 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true + ] + ]; + + /** + * This constructor accepts an associative array of options. + * + * - size: (int) If a read stream would otherwise have an indeterminate + * size, but the size is known due to foreknowledge, then you can + * provide that size, in bytes. + * - metadata: (array) Any additional metadata to return when the metadata + * of the stream is accessed. + * + * @param resource $stream Stream resource to wrap. + * @param array $options Associative array of options. + * + * @throws \InvalidArgumentException if the stream is not a stream resource + */ + public function __construct($stream, $options = []) + { + if (!is_resource($stream)) { + throw new \InvalidArgumentException('Stream must be a resource'); + } + + if (isset($options['size'])) { + $this->size = $options['size']; + } + + $this->customMetadata = isset($options['metadata']) + ? $options['metadata'] + : []; + + $this->stream = $stream; + $meta = stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable']; + $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]); + $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]); + $this->uri = $this->getMetadata('uri'); + } + + public function __get($name) + { + if ($name == 'stream') { + throw new \RuntimeException('The stream is detached'); + } + + throw new \BadMethodCallException('No value for ' . $name); + } + + /** + * Closes the stream when the destructed + */ + public function __destruct() + { + $this->close(); + } + + public function __toString() + { + try { + $this->seek(0); + return (string) stream_get_contents($this->stream); + } catch (\Exception $e) { + return ''; + } + } + + public function getContents() + { + $contents = stream_get_contents($this->stream); + + if ($contents === false) { + throw new \RuntimeException('Unable to read stream contents'); + } + + return $contents; + } + + public function close() + { + if (isset($this->stream)) { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + } + + public function detach() + { + if (!isset($this->stream)) { + return null; + } + + $result = $this->stream; + unset($this->stream); + $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + public function getSize() + { + if ($this->size !== null) { + return $this->size; + } + + if (!isset($this->stream)) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($this->uri) { + clearstatcache(true, $this->uri); + } + + $stats = fstat($this->stream); + if (isset($stats['size'])) { + $this->size = $stats['size']; + return $this->size; + } + + return null; + } + + public function isReadable() + { + return $this->readable; + } + + public function isWritable() + { + return $this->writable; + } + + public function isSeekable() + { + return $this->seekable; + } + + public function eof() + { + return !$this->stream || feof($this->stream); + } + + public function tell() + { + $result = ftell($this->stream); + + if ($result === false) { + throw new \RuntimeException('Unable to determine stream position'); + } + + return $result; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + if (!$this->seekable) { + throw new \RuntimeException('Stream is not seekable'); + } elseif (fseek($this->stream, $offset, $whence) === -1) { + throw new \RuntimeException('Unable to seek to stream position ' + . $offset . ' with whence ' . var_export($whence, true)); + } + } + + public function read($length) + { + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + + if (0 === $length) { + return ''; + } + + $string = fread($this->stream, $length); + if (false === $string) { + throw new \RuntimeException('Unable to read from stream'); + } + + return $string; + } + + public function write($string) + { + if (!$this->writable) { + throw new \RuntimeException('Cannot write to a non-writable stream'); + } + + // We can't know the size after writing anything + $this->size = null; + $result = fwrite($this->stream, $string); + + if ($result === false) { + throw new \RuntimeException('Unable to write to stream'); + } + + return $result; + } + + public function getMetadata($key = null) + { + if (!isset($this->stream)) { + return $key ? null : []; + } elseif (!$key) { + return $this->customMetadata + stream_get_meta_data($this->stream); + } elseif (isset($this->customMetadata[$key])) { + return $this->customMetadata[$key]; + } + + $meta = stream_get_meta_data($this->stream); + + return isset($meta[$key]) ? $meta[$key] : null; + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/Upyun/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php new file mode 100644 index 0000000..daec6f5 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php @@ -0,0 +1,149 @@ +stream = $stream; + } + + /** + * Magic method used to create a new stream if streams are not added in + * the constructor of a decorator (e.g., LazyOpenStream). + * + * @param string $name Name of the property (allows "stream" only). + * + * @return StreamInterface + */ + public function __get($name) + { + if ($name == 'stream') { + $this->stream = $this->createStream(); + return $this->stream; + } + + throw new \UnexpectedValueException("$name not found on class"); + } + + public function __toString() + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Exception $e) { + // Really, PHP? https://bugs.php.net/bug.php?id=53648 + trigger_error('StreamDecorator::__toString exception: ' + . (string) $e, E_USER_ERROR); + return ''; + } + } + + public function getContents() + { + return copy_to_string($this); + } + + /** + * Allow decorators to implement custom methods + * + * @param string $method Missing method name + * @param array $args Method arguments + * + * @return mixed + */ + public function __call($method, array $args) + { + $result = call_user_func_array([$this->stream, $method], $args); + + // Always return the wrapped object if the result is a return $this + return $result === $this->stream ? $this : $result; + } + + public function close() + { + $this->stream->close(); + } + + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } + + public function detach() + { + return $this->stream->detach(); + } + + public function getSize() + { + return $this->stream->getSize(); + } + + public function eof() + { + return $this->stream->eof(); + } + + public function tell() + { + return $this->stream->tell(); + } + + public function isReadable() + { + return $this->stream->isReadable(); + } + + public function isWritable() + { + return $this->stream->isWritable(); + } + + public function isSeekable() + { + return $this->stream->isSeekable(); + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + $this->stream->seek($offset, $whence); + } + + public function read($length) + { + return $this->stream->read($length); + } + + public function write($string) + { + return $this->stream->write($string); + } + + /** + * Implement in subclasses to dynamically create streams when requested. + * + * @return StreamInterface + * @throws \BadMethodCallException + */ + protected function createStream() + { + throw new \BadMethodCallException('Not implemented'); + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/Upyun/vendor/guzzlehttp/psr7/src/StreamWrapper.php new file mode 100644 index 0000000..cf7b223 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/StreamWrapper.php @@ -0,0 +1,121 @@ +isReadable()) { + $mode = $stream->isWritable() ? 'r+' : 'r'; + } elseif ($stream->isWritable()) { + $mode = 'w'; + } else { + throw new \InvalidArgumentException('The stream must be readable, ' + . 'writable, or both.'); + } + + return fopen('guzzle://stream', $mode, null, stream_context_create([ + 'guzzle' => ['stream' => $stream] + ])); + } + + /** + * Registers the stream wrapper if needed + */ + public static function register() + { + if (!in_array('guzzle', stream_get_wrappers())) { + stream_wrapper_register('guzzle', __CLASS__); + } + } + + public function stream_open($path, $mode, $options, &$opened_path) + { + $options = stream_context_get_options($this->context); + + if (!isset($options['guzzle']['stream'])) { + return false; + } + + $this->mode = $mode; + $this->stream = $options['guzzle']['stream']; + + return true; + } + + public function stream_read($count) + { + return $this->stream->read($count); + } + + public function stream_write($data) + { + return (int) $this->stream->write($data); + } + + public function stream_tell() + { + return $this->stream->tell(); + } + + public function stream_eof() + { + return $this->stream->eof(); + } + + public function stream_seek($offset, $whence) + { + $this->stream->seek($offset, $whence); + + return true; + } + + public function stream_stat() + { + static $modeMap = [ + 'r' => 33060, + 'r+' => 33206, + 'w' => 33188 + ]; + + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => $modeMap[$this->mode], + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => $this->stream->getSize() ?: 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0 + ]; + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/UploadedFile.php b/Upyun/vendor/guzzlehttp/psr7/src/UploadedFile.php new file mode 100644 index 0000000..e62bd5c --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/UploadedFile.php @@ -0,0 +1,316 @@ +setError($errorStatus); + $this->setSize($size); + $this->setClientFilename($clientFilename); + $this->setClientMediaType($clientMediaType); + + if ($this->isOk()) { + $this->setStreamOrFile($streamOrFile); + } + } + + /** + * Depending on the value set file or stream variable + * + * @param mixed $streamOrFile + * @throws InvalidArgumentException + */ + private function setStreamOrFile($streamOrFile) + { + if (is_string($streamOrFile)) { + $this->file = $streamOrFile; + } elseif (is_resource($streamOrFile)) { + $this->stream = new Stream($streamOrFile); + } elseif ($streamOrFile instanceof StreamInterface) { + $this->stream = $streamOrFile; + } else { + throw new InvalidArgumentException( + 'Invalid stream or file provided for UploadedFile' + ); + } + } + + /** + * @param int $error + * @throws InvalidArgumentException + */ + private function setError($error) + { + if (false === is_int($error)) { + throw new InvalidArgumentException( + 'Upload file error status must be an integer' + ); + } + + if (false === in_array($error, UploadedFile::$errors)) { + throw new InvalidArgumentException( + 'Invalid error status for UploadedFile' + ); + } + + $this->error = $error; + } + + /** + * @param int $size + * @throws InvalidArgumentException + */ + private function setSize($size) + { + if (false === is_int($size)) { + throw new InvalidArgumentException( + 'Upload file size must be an integer' + ); + } + + $this->size = $size; + } + + /** + * @param mixed $param + * @return boolean + */ + private function isStringOrNull($param) + { + return in_array(gettype($param), ['string', 'NULL']); + } + + /** + * @param mixed $param + * @return boolean + */ + private function isStringNotEmpty($param) + { + return is_string($param) && false === empty($param); + } + + /** + * @param string|null $clientFilename + * @throws InvalidArgumentException + */ + private function setClientFilename($clientFilename) + { + if (false === $this->isStringOrNull($clientFilename)) { + throw new InvalidArgumentException( + 'Upload file client filename must be a string or null' + ); + } + + $this->clientFilename = $clientFilename; + } + + /** + * @param string|null $clientMediaType + * @throws InvalidArgumentException + */ + private function setClientMediaType($clientMediaType) + { + if (false === $this->isStringOrNull($clientMediaType)) { + throw new InvalidArgumentException( + 'Upload file client media type must be a string or null' + ); + } + + $this->clientMediaType = $clientMediaType; + } + + /** + * Return true if there is no upload error + * + * @return boolean + */ + private function isOk() + { + return $this->error === UPLOAD_ERR_OK; + } + + /** + * @return boolean + */ + public function isMoved() + { + return $this->moved; + } + + /** + * @throws RuntimeException if is moved or not ok + */ + private function validateActive() + { + if (false === $this->isOk()) { + throw new RuntimeException('Cannot retrieve stream due to upload error'); + } + + if ($this->isMoved()) { + throw new RuntimeException('Cannot retrieve stream after it has already been moved'); + } + } + + /** + * {@inheritdoc} + * @throws RuntimeException if the upload was not successful. + */ + public function getStream() + { + $this->validateActive(); + + if ($this->stream instanceof StreamInterface) { + return $this->stream; + } + + return new LazyOpenStream($this->file, 'r+'); + } + + /** + * {@inheritdoc} + * + * @see http://php.net/is_uploaded_file + * @see http://php.net/move_uploaded_file + * @param string $targetPath Path to which to move the uploaded file. + * @throws RuntimeException if the upload was not successful. + * @throws InvalidArgumentException if the $path specified is invalid. + * @throws RuntimeException on any error during the move operation, or on + * the second or subsequent call to the method. + */ + public function moveTo($targetPath) + { + $this->validateActive(); + + if (false === $this->isStringNotEmpty($targetPath)) { + throw new InvalidArgumentException( + 'Invalid path provided for move operation; must be a non-empty string' + ); + } + + if ($this->file) { + $this->moved = php_sapi_name() == 'cli' + ? rename($this->file, $targetPath) + : move_uploaded_file($this->file, $targetPath); + } else { + copy_to_stream( + $this->getStream(), + new LazyOpenStream($targetPath, 'w') + ); + + $this->moved = true; + } + + if (false === $this->moved) { + throw new RuntimeException( + sprintf('Uploaded file could not be moved to %s', $targetPath) + ); + } + } + + /** + * {@inheritdoc} + * + * @return int|null The file size in bytes or null if unknown. + */ + public function getSize() + { + return $this->size; + } + + /** + * {@inheritdoc} + * + * @see http://php.net/manual/en/features.file-upload.errors.php + * @return int One of PHP's UPLOAD_ERR_XXX constants. + */ + public function getError() + { + return $this->error; + } + + /** + * {@inheritdoc} + * + * @return string|null The filename sent by the client or null if none + * was provided. + */ + public function getClientFilename() + { + return $this->clientFilename; + } + + /** + * {@inheritdoc} + */ + public function getClientMediaType() + { + return $this->clientMediaType; + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/Uri.php b/Upyun/vendor/guzzlehttp/psr7/src/Uri.php new file mode 100644 index 0000000..f46c1db --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/Uri.php @@ -0,0 +1,702 @@ + 80, + 'https' => 443, + 'ftp' => 21, + 'gopher' => 70, + 'nntp' => 119, + 'news' => 119, + 'telnet' => 23, + 'tn3270' => 23, + 'imap' => 143, + 'pop' => 110, + 'ldap' => 389, + ]; + + private static $charUnreserved = 'a-zA-Z0-9_\-\.~'; + private static $charSubDelims = '!\$&\'\(\)\*\+,;='; + private static $replaceQuery = ['=' => '%3D', '&' => '%26']; + + /** @var string Uri scheme. */ + private $scheme = ''; + + /** @var string Uri user info. */ + private $userInfo = ''; + + /** @var string Uri host. */ + private $host = ''; + + /** @var int|null Uri port. */ + private $port; + + /** @var string Uri path. */ + private $path = ''; + + /** @var string Uri query string. */ + private $query = ''; + + /** @var string Uri fragment. */ + private $fragment = ''; + + /** + * @param string $uri URI to parse + */ + public function __construct($uri = '') + { + // weak type check to also accept null until we can add scalar type hints + if ($uri != '') { + $parts = parse_url($uri); + if ($parts === false) { + throw new \InvalidArgumentException("Unable to parse URI: $uri"); + } + $this->applyParts($parts); + } + } + + public function __toString() + { + return self::composeComponents( + $this->scheme, + $this->getAuthority(), + $this->path, + $this->query, + $this->fragment + ); + } + + /** + * Composes a URI reference string from its various components. + * + * Usually this method does not need to be called manually but instead is used indirectly via + * `Psr\Http\Message\UriInterface::__toString`. + * + * PSR-7 UriInterface treats an empty component the same as a missing component as + * getQuery(), getFragment() etc. always return a string. This explains the slight + * difference to RFC 3986 Section 5.3. + * + * Another adjustment is that the authority separator is added even when the authority is missing/empty + * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with + * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But + * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to + * that format). + * + * @param string $scheme + * @param string $authority + * @param string $path + * @param string $query + * @param string $fragment + * + * @return string + * + * @link https://tools.ietf.org/html/rfc3986#section-5.3 + */ + public static function composeComponents($scheme, $authority, $path, $query, $fragment) + { + $uri = ''; + + // weak type checks to also accept null until we can add scalar type hints + if ($scheme != '') { + $uri .= $scheme . ':'; + } + + if ($authority != ''|| $scheme === 'file') { + $uri .= '//' . $authority; + } + + $uri .= $path; + + if ($query != '') { + $uri .= '?' . $query; + } + + if ($fragment != '') { + $uri .= '#' . $fragment; + } + + return $uri; + } + + /** + * Whether the URI has the default port of the current scheme. + * + * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used + * independently of the implementation. + * + * @param UriInterface $uri + * + * @return bool + */ + public static function isDefaultPort(UriInterface $uri) + { + return $uri->getPort() === null + || (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]); + } + + /** + * Whether the URI is absolute, i.e. it has a scheme. + * + * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true + * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative + * to another URI, the base URI. Relative references can be divided into several forms: + * - network-path references, e.g. '//example.com/path' + * - absolute-path references, e.g. '/path' + * - relative-path references, e.g. 'subpath' + * + * @param UriInterface $uri + * + * @return bool + * @see Uri::isNetworkPathReference + * @see Uri::isAbsolutePathReference + * @see Uri::isRelativePathReference + * @link https://tools.ietf.org/html/rfc3986#section-4 + */ + public static function isAbsolute(UriInterface $uri) + { + return $uri->getScheme() !== ''; + } + + /** + * Whether the URI is a network-path reference. + * + * A relative reference that begins with two slash characters is termed an network-path reference. + * + * @param UriInterface $uri + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isNetworkPathReference(UriInterface $uri) + { + return $uri->getScheme() === '' && $uri->getAuthority() !== ''; + } + + /** + * Whether the URI is a absolute-path reference. + * + * A relative reference that begins with a single slash character is termed an absolute-path reference. + * + * @param UriInterface $uri + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isAbsolutePathReference(UriInterface $uri) + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && isset($uri->getPath()[0]) + && $uri->getPath()[0] === '/'; + } + + /** + * Whether the URI is a relative-path reference. + * + * A relative reference that does not begin with a slash character is termed a relative-path reference. + * + * @param UriInterface $uri + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isRelativePathReference(UriInterface $uri) + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/'); + } + + /** + * Whether the URI is a same-document reference. + * + * A same-document reference refers to a URI that is, aside from its fragment + * component, identical to the base URI. When no base URI is given, only an empty + * URI reference (apart from its fragment) is considered a same-document reference. + * + * @param UriInterface $uri The URI to check + * @param UriInterface|null $base An optional base URI to compare against + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.4 + */ + public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null) + { + if ($base !== null) { + $uri = UriResolver::resolve($base, $uri); + + return ($uri->getScheme() === $base->getScheme()) + && ($uri->getAuthority() === $base->getAuthority()) + && ($uri->getPath() === $base->getPath()) + && ($uri->getQuery() === $base->getQuery()); + } + + return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === ''; + } + + /** + * Removes dot segments from a path and returns the new path. + * + * @param string $path + * + * @return string + * + * @deprecated since version 1.4. Use UriResolver::removeDotSegments instead. + * @see UriResolver::removeDotSegments + */ + public static function removeDotSegments($path) + { + return UriResolver::removeDotSegments($path); + } + + /** + * Converts the relative URI into a new URI that is resolved against the base URI. + * + * @param UriInterface $base Base URI + * @param string|UriInterface $rel Relative URI + * + * @return UriInterface + * + * @deprecated since version 1.4. Use UriResolver::resolve instead. + * @see UriResolver::resolve + */ + public static function resolve(UriInterface $base, $rel) + { + if (!($rel instanceof UriInterface)) { + $rel = new self($rel); + } + + return UriResolver::resolve($base, $rel); + } + + /** + * Creates a new URI with a specific query string value removed. + * + * Any existing query string values that exactly match the provided key are + * removed. + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Query string key to remove. + * + * @return UriInterface + */ + public static function withoutQueryValue(UriInterface $uri, $key) + { + $current = $uri->getQuery(); + if ($current === '') { + return $uri; + } + + $decodedKey = rawurldecode($key); + $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) { + return rawurldecode(explode('=', $part)[0]) !== $decodedKey; + }); + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a new URI with a specific query string value. + * + * Any existing query string values that exactly match the provided key are + * removed and replaced with the given key value pair. + * + * A value of null will set the query string key without a value, e.g. "key" + * instead of "key=value". + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Key to set. + * @param string|null $value Value to set + * + * @return UriInterface + */ + public static function withQueryValue(UriInterface $uri, $key, $value) + { + $current = $uri->getQuery(); + + if ($current === '') { + $result = []; + } else { + $decodedKey = rawurldecode($key); + $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) { + return rawurldecode(explode('=', $part)[0]) !== $decodedKey; + }); + } + + // Query string separators ("=", "&") within the key or value need to be encoded + // (while preventing double-encoding) before setting the query string. All other + // chars that need percent-encoding will be encoded by withQuery(). + $key = strtr($key, self::$replaceQuery); + + if ($value !== null) { + $result[] = $key . '=' . strtr($value, self::$replaceQuery); + } else { + $result[] = $key; + } + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a URI from a hash of `parse_url` components. + * + * @param array $parts + * + * @return UriInterface + * @link http://php.net/manual/en/function.parse-url.php + * + * @throws \InvalidArgumentException If the components do not form a valid URI. + */ + public static function fromParts(array $parts) + { + $uri = new self(); + $uri->applyParts($parts); + $uri->validateState(); + + return $uri; + } + + public function getScheme() + { + return $this->scheme; + } + + public function getAuthority() + { + $authority = $this->host; + if ($this->userInfo !== '') { + $authority = $this->userInfo . '@' . $authority; + } + + if ($this->port !== null) { + $authority .= ':' . $this->port; + } + + return $authority; + } + + public function getUserInfo() + { + return $this->userInfo; + } + + public function getHost() + { + return $this->host; + } + + public function getPort() + { + return $this->port; + } + + public function getPath() + { + return $this->path; + } + + public function getQuery() + { + return $this->query; + } + + public function getFragment() + { + return $this->fragment; + } + + public function withScheme($scheme) + { + $scheme = $this->filterScheme($scheme); + + if ($this->scheme === $scheme) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withUserInfo($user, $password = null) + { + $info = $user; + if ($password != '') { + $info .= ':' . $password; + } + + if ($this->userInfo === $info) { + return $this; + } + + $new = clone $this; + $new->userInfo = $info; + $new->validateState(); + + return $new; + } + + public function withHost($host) + { + $host = $this->filterHost($host); + + if ($this->host === $host) { + return $this; + } + + $new = clone $this; + $new->host = $host; + $new->validateState(); + + return $new; + } + + public function withPort($port) + { + $port = $this->filterPort($port); + + if ($this->port === $port) { + return $this; + } + + $new = clone $this; + $new->port = $port; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withPath($path) + { + $path = $this->filterPath($path); + + if ($this->path === $path) { + return $this; + } + + $new = clone $this; + $new->path = $path; + $new->validateState(); + + return $new; + } + + public function withQuery($query) + { + $query = $this->filterQueryAndFragment($query); + + if ($this->query === $query) { + return $this; + } + + $new = clone $this; + $new->query = $query; + + return $new; + } + + public function withFragment($fragment) + { + $fragment = $this->filterQueryAndFragment($fragment); + + if ($this->fragment === $fragment) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + + return $new; + } + + /** + * Apply parse_url parts to a URI. + * + * @param array $parts Array of parse_url parts to apply. + */ + private function applyParts(array $parts) + { + $this->scheme = isset($parts['scheme']) + ? $this->filterScheme($parts['scheme']) + : ''; + $this->userInfo = isset($parts['user']) ? $parts['user'] : ''; + $this->host = isset($parts['host']) + ? $this->filterHost($parts['host']) + : ''; + $this->port = isset($parts['port']) + ? $this->filterPort($parts['port']) + : null; + $this->path = isset($parts['path']) + ? $this->filterPath($parts['path']) + : ''; + $this->query = isset($parts['query']) + ? $this->filterQueryAndFragment($parts['query']) + : ''; + $this->fragment = isset($parts['fragment']) + ? $this->filterQueryAndFragment($parts['fragment']) + : ''; + if (isset($parts['pass'])) { + $this->userInfo .= ':' . $parts['pass']; + } + + $this->removeDefaultPort(); + } + + /** + * @param string $scheme + * + * @return string + * + * @throws \InvalidArgumentException If the scheme is invalid. + */ + private function filterScheme($scheme) + { + if (!is_string($scheme)) { + throw new \InvalidArgumentException('Scheme must be a string'); + } + + return strtolower($scheme); + } + + /** + * @param string $host + * + * @return string + * + * @throws \InvalidArgumentException If the host is invalid. + */ + private function filterHost($host) + { + if (!is_string($host)) { + throw new \InvalidArgumentException('Host must be a string'); + } + + return strtolower($host); + } + + /** + * @param int|null $port + * + * @return int|null + * + * @throws \InvalidArgumentException If the port is invalid. + */ + private function filterPort($port) + { + if ($port === null) { + return null; + } + + $port = (int) $port; + if (1 > $port || 0xffff < $port) { + throw new \InvalidArgumentException( + sprintf('Invalid port: %d. Must be between 1 and 65535', $port) + ); + } + + return $port; + } + + private function removeDefaultPort() + { + if ($this->port !== null && self::isDefaultPort($this)) { + $this->port = null; + } + } + + /** + * Filters the path of a URI + * + * @param string $path + * + * @return string + * + * @throws \InvalidArgumentException If the path is invalid. + */ + private function filterPath($path) + { + if (!is_string($path)) { + throw new \InvalidArgumentException('Path must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $path + ); + } + + /** + * Filters the query string or fragment of a URI. + * + * @param string $str + * + * @return string + * + * @throws \InvalidArgumentException If the query or fragment is invalid. + */ + private function filterQueryAndFragment($str) + { + if (!is_string($str)) { + throw new \InvalidArgumentException('Query and fragment must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $str + ); + } + + private function rawurlencodeMatchZero(array $match) + { + return rawurlencode($match[0]); + } + + private function validateState() + { + if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) { + $this->host = self::HTTP_DEFAULT_HOST; + } + + if ($this->getAuthority() === '') { + if (0 === strpos($this->path, '//')) { + throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"'); + } + if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) { + throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon'); + } + } elseif (isset($this->path[0]) && $this->path[0] !== '/') { + @trigger_error( + 'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' . + 'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.', + E_USER_DEPRECATED + ); + $this->path = '/'. $this->path; + //throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty'); + } + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/Upyun/vendor/guzzlehttp/psr7/src/UriNormalizer.php new file mode 100644 index 0000000..384c29e --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/UriNormalizer.php @@ -0,0 +1,216 @@ +getPath() === '' && + ($uri->getScheme() === 'http' || $uri->getScheme() === 'https') + ) { + $uri = $uri->withPath('/'); + } + + if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') { + $uri = $uri->withHost(''); + } + + if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) { + $uri = $uri->withPort(null); + } + + if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) { + $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath())); + } + + if ($flags & self::REMOVE_DUPLICATE_SLASHES) { + $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath())); + } + + if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') { + $queryKeyValues = explode('&', $uri->getQuery()); + sort($queryKeyValues); + $uri = $uri->withQuery(implode('&', $queryKeyValues)); + } + + return $uri; + } + + /** + * Whether two URIs can be considered equivalent. + * + * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also + * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be + * resolved against the same base URI. If this is not the case, determination of equivalence or difference of + * relative references does not mean anything. + * + * @param UriInterface $uri1 An URI to compare + * @param UriInterface $uri2 An URI to compare + * @param int $normalizations A bitmask of normalizations to apply, see constants + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-6.1 + */ + public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS) + { + return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations); + } + + private static function capitalizePercentEncoding(UriInterface $uri) + { + $regex = '/(?:%[A-Fa-f0-9]{2})++/'; + + $callback = function (array $match) { + return strtoupper($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private static function decodeUnreservedCharacters(UriInterface $uri) + { + $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i'; + + $callback = function (array $match) { + return rawurldecode($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/UriResolver.php b/Upyun/vendor/guzzlehttp/psr7/src/UriResolver.php new file mode 100644 index 0000000..c1cb8a2 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/UriResolver.php @@ -0,0 +1,219 @@ +getScheme() != '') { + return $rel->withPath(self::removeDotSegments($rel->getPath())); + } + + if ($rel->getAuthority() != '') { + $targetAuthority = $rel->getAuthority(); + $targetPath = self::removeDotSegments($rel->getPath()); + $targetQuery = $rel->getQuery(); + } else { + $targetAuthority = $base->getAuthority(); + if ($rel->getPath() === '') { + $targetPath = $base->getPath(); + $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery(); + } else { + if ($rel->getPath()[0] === '/') { + $targetPath = $rel->getPath(); + } else { + if ($targetAuthority != '' && $base->getPath() === '') { + $targetPath = '/' . $rel->getPath(); + } else { + $lastSlashPos = strrpos($base->getPath(), '/'); + if ($lastSlashPos === false) { + $targetPath = $rel->getPath(); + } else { + $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath(); + } + } + } + $targetPath = self::removeDotSegments($targetPath); + $targetQuery = $rel->getQuery(); + } + } + + return new Uri(Uri::composeComponents( + $base->getScheme(), + $targetAuthority, + $targetPath, + $targetQuery, + $rel->getFragment() + )); + } + + /** + * Returns the target URI as a relative reference from the base URI. + * + * This method is the counterpart to resolve(): + * + * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) + * + * One use-case is to use the current request URI as base URI and then generate relative links in your documents + * to reduce the document size or offer self-contained downloadable document archives. + * + * $base = new Uri('http://example.com/a/b/'); + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. + * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. + * + * This method also accepts a target that is already relative and will try to relativize it further. Only a + * relative-path reference will be returned as-is. + * + * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well + * + * @param UriInterface $base Base URI + * @param UriInterface $target Target URI + * + * @return UriInterface The relative URI reference + */ + public static function relativize(UriInterface $base, UriInterface $target) + { + if ($target->getScheme() !== '' && + ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '') + ) { + return $target; + } + + if (Uri::isRelativePathReference($target)) { + // As the target is already highly relative we return it as-is. It would be possible to resolve + // the target with `$target = self::resolve($base, $target);` and then try make it more relative + // by removing a duplicate query. But let's not do that automatically. + return $target; + } + + if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) { + return $target->withScheme(''); + } + + // We must remove the path before removing the authority because if the path starts with two slashes, the URI + // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also + // invalid. + $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost(''); + + if ($base->getPath() !== $target->getPath()) { + return $emptyPathUri->withPath(self::getRelativePath($base, $target)); + } + + if ($base->getQuery() === $target->getQuery()) { + // Only the target fragment is left. And it must be returned even if base and target fragment are the same. + return $emptyPathUri->withQuery(''); + } + + // If the base URI has a query but the target has none, we cannot return an empty path reference as it would + // inherit the base query component when resolving. + if ($target->getQuery() === '') { + $segments = explode('/', $target->getPath()); + $lastSegment = end($segments); + + return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); + } + + return $emptyPathUri; + } + + private static function getRelativePath(UriInterface $base, UriInterface $target) + { + $sourceSegments = explode('/', $base->getPath()); + $targetSegments = explode('/', $target->getPath()); + array_pop($sourceSegments); + $targetLastSegment = array_pop($targetSegments); + foreach ($sourceSegments as $i => $segment) { + if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) { + unset($sourceSegments[$i], $targetSegments[$i]); + } else { + break; + } + } + $targetSegments[] = $targetLastSegment; + $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments); + + // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name. + if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) { + $relativePath = "./$relativePath"; + } elseif ('/' === $relativePath[0]) { + if ($base->getAuthority() != '' && $base->getPath() === '') { + // In this case an extra slash is added by resolve() automatically. So we must not add one here. + $relativePath = ".$relativePath"; + } else { + $relativePath = "./$relativePath"; + } + } + + return $relativePath; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/functions.php b/Upyun/vendor/guzzlehttp/psr7/src/functions.php new file mode 100644 index 0000000..e40348d --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/functions.php @@ -0,0 +1,828 @@ +getMethod() . ' ' + . $message->getRequestTarget()) + . ' HTTP/' . $message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: " . $message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' + . $message->getStatusCode() . ' ' + . $message->getReasonPhrase(); + } else { + throw new \InvalidArgumentException('Unknown message type'); + } + + foreach ($message->getHeaders() as $name => $values) { + $msg .= "\r\n{$name}: " . implode(', ', $values); + } + + return "{$msg}\r\n\r\n" . $message->getBody(); +} + +/** + * Returns a UriInterface for the given value. + * + * This function accepts a string or {@see Psr\Http\Message\UriInterface} and + * returns a UriInterface for the given value. If the value is already a + * `UriInterface`, it is returned as-is. + * + * @param string|UriInterface $uri + * + * @return UriInterface + * @throws \InvalidArgumentException + */ +function uri_for($uri) +{ + if ($uri instanceof UriInterface) { + return $uri; + } elseif (is_string($uri)) { + return new Uri($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); +} + +/** + * Create a new stream based on the input type. + * + * Options is an associative array that can contain the following keys: + * - metadata: Array of custom metadata. + * - size: Size of the stream. + * + * @param resource|string|null|int|float|bool|StreamInterface|callable $resource Entity body data + * @param array $options Additional options + * + * @return Stream + * @throws \InvalidArgumentException if the $resource arg is not valid. + */ +function stream_for($resource = '', array $options = []) +{ + if (is_scalar($resource)) { + $stream = fopen('php://temp', 'r+'); + if ($resource !== '') { + fwrite($stream, $resource); + fseek($stream, 0); + } + return new Stream($stream, $options); + } + + switch (gettype($resource)) { + case 'resource': + return new Stream($resource, $options); + case 'object': + if ($resource instanceof StreamInterface) { + return $resource; + } elseif ($resource instanceof \Iterator) { + return new PumpStream(function () use ($resource) { + if (!$resource->valid()) { + return false; + } + $result = $resource->current(); + $resource->next(); + return $result; + }, $options); + } elseif (method_exists($resource, '__toString')) { + return stream_for((string) $resource, $options); + } + break; + case 'NULL': + return new Stream(fopen('php://temp', 'r+'), $options); + } + + if (is_callable($resource)) { + return new PumpStream($resource, $options); + } + + throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource)); +} + +/** + * Parse an array of header values containing ";" separated data into an + * array of associative arrays representing the header key value pair + * data of the header. When a parameter does not contain a value, but just + * contains a key, this function will inject a key with a '' string value. + * + * @param string|array $header Header to parse into components. + * + * @return array Returns the parsed header values. + */ +function parse_header($header) +{ + static $trimmed = "\"' \n\t\r"; + $params = $matches = []; + + foreach (normalize_header($header) as $val) { + $part = []; + foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) { + if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) { + $m = $matches[0]; + if (isset($m[1])) { + $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); + } else { + $part[] = trim($m[0], $trimmed); + } + } + } + if ($part) { + $params[] = $part; + } + } + + return $params; +} + +/** + * Converts an array of header values that may contain comma separated + * headers into an array of headers with no comma separated values. + * + * @param string|array $header Header to normalize. + * + * @return array Returns the normalized header field values. + */ +function normalize_header($header) +{ + if (!is_array($header)) { + return array_map('trim', explode(',', $header)); + } + + $result = []; + foreach ($header as $value) { + foreach ((array) $value as $v) { + if (strpos($v, ',') === false) { + $result[] = $v; + continue; + } + foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) { + $result[] = trim($vv); + } + } + } + + return $result; +} + +/** + * Clone and modify a request with the given changes. + * + * The changes can be one of: + * - method: (string) Changes the HTTP method. + * - set_headers: (array) Sets the given headers. + * - remove_headers: (array) Remove the given headers. + * - body: (mixed) Sets the given body. + * - uri: (UriInterface) Set the URI. + * - query: (string) Set the query string value of the URI. + * - version: (string) Set the protocol version. + * + * @param RequestInterface $request Request to clone and modify. + * @param array $changes Changes to apply. + * + * @return RequestInterface + */ +function modify_request(RequestInterface $request, array $changes) +{ + if (!$changes) { + return $request; + } + + $headers = $request->getHeaders(); + + if (!isset($changes['uri'])) { + $uri = $request->getUri(); + } else { + // Remove the host header if one is on the URI + if ($host = $changes['uri']->getHost()) { + $changes['set_headers']['Host'] = $host; + + if ($port = $changes['uri']->getPort()) { + $standardPorts = ['http' => 80, 'https' => 443]; + $scheme = $changes['uri']->getScheme(); + if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { + $changes['set_headers']['Host'] .= ':'.$port; + } + } + } + $uri = $changes['uri']; + } + + if (!empty($changes['remove_headers'])) { + $headers = _caseless_remove($changes['remove_headers'], $headers); + } + + if (!empty($changes['set_headers'])) { + $headers = _caseless_remove(array_keys($changes['set_headers']), $headers); + $headers = $changes['set_headers'] + $headers; + } + + if (isset($changes['query'])) { + $uri = $uri->withQuery($changes['query']); + } + + if ($request instanceof ServerRequestInterface) { + return new ServerRequest( + isset($changes['method']) ? $changes['method'] : $request->getMethod(), + $uri, + $headers, + isset($changes['body']) ? $changes['body'] : $request->getBody(), + isset($changes['version']) + ? $changes['version'] + : $request->getProtocolVersion(), + $request->getServerParams() + ); + } + + return new Request( + isset($changes['method']) ? $changes['method'] : $request->getMethod(), + $uri, + $headers, + isset($changes['body']) ? $changes['body'] : $request->getBody(), + isset($changes['version']) + ? $changes['version'] + : $request->getProtocolVersion() + ); +} + +/** + * Attempts to rewind a message body and throws an exception on failure. + * + * The body of the message will only be rewound if a call to `tell()` returns a + * value other than `0`. + * + * @param MessageInterface $message Message to rewind + * + * @throws \RuntimeException + */ +function rewind_body(MessageInterface $message) +{ + $body = $message->getBody(); + + if ($body->tell()) { + $body->rewind(); + } +} + +/** + * Safely opens a PHP stream resource using a filename. + * + * When fopen fails, PHP normally raises a warning. This function adds an + * error handler that checks for errors and throws an exception instead. + * + * @param string $filename File to open + * @param string $mode Mode used to open the file + * + * @return resource + * @throws \RuntimeException if the file cannot be opened + */ +function try_fopen($filename, $mode) +{ + $ex = null; + set_error_handler(function () use ($filename, $mode, &$ex) { + $ex = new \RuntimeException(sprintf( + 'Unable to open %s using mode %s: %s', + $filename, + $mode, + func_get_args()[1] + )); + }); + + $handle = fopen($filename, $mode); + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $handle; +} + +/** + * Copy the contents of a stream into a string until the given number of + * bytes have been read. + * + * @param StreamInterface $stream Stream to read + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * @return string + * @throws \RuntimeException on error. + */ +function copy_to_string(StreamInterface $stream, $maxLen = -1) +{ + $buffer = ''; + + if ($maxLen === -1) { + while (!$stream->eof()) { + $buf = $stream->read(1048576); + // Using a loose equality here to match on '' and false. + if ($buf == null) { + break; + } + $buffer .= $buf; + } + return $buffer; + } + + $len = 0; + while (!$stream->eof() && $len < $maxLen) { + $buf = $stream->read($maxLen - $len); + // Using a loose equality here to match on '' and false. + if ($buf == null) { + break; + } + $buffer .= $buf; + $len = strlen($buffer); + } + + return $buffer; +} + +/** + * Copy the contents of a stream into another stream until the given number + * of bytes have been read. + * + * @param StreamInterface $source Stream to read from + * @param StreamInterface $dest Stream to write to + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ +function copy_to_stream( + StreamInterface $source, + StreamInterface $dest, + $maxLen = -1 +) { + $bufferSize = 8192; + + if ($maxLen === -1) { + while (!$source->eof()) { + if (!$dest->write($source->read($bufferSize))) { + break; + } + } + } else { + $remaining = $maxLen; + while ($remaining > 0 && !$source->eof()) { + $buf = $source->read(min($bufferSize, $remaining)); + $len = strlen($buf); + if (!$len) { + break; + } + $remaining -= $len; + $dest->write($buf); + } + } +} + +/** + * Calculate a hash of a Stream + * + * @param StreamInterface $stream Stream to calculate the hash for + * @param string $algo Hash algorithm (e.g. md5, crc32, etc) + * @param bool $rawOutput Whether or not to use raw output + * + * @return string Returns the hash of the stream + * @throws \RuntimeException on error. + */ +function hash( + StreamInterface $stream, + $algo, + $rawOutput = false +) { + $pos = $stream->tell(); + + if ($pos > 0) { + $stream->rewind(); + } + + $ctx = hash_init($algo); + while (!$stream->eof()) { + hash_update($ctx, $stream->read(1048576)); + } + + $out = hash_final($ctx, (bool) $rawOutput); + $stream->seek($pos); + + return $out; +} + +/** + * Read a line from the stream up to the maximum allowed buffer length + * + * @param StreamInterface $stream Stream to read from + * @param int $maxLength Maximum buffer length + * + * @return string|bool + */ +function readline(StreamInterface $stream, $maxLength = null) +{ + $buffer = ''; + $size = 0; + + while (!$stream->eof()) { + // Using a loose equality here to match on '' and false. + if (null == ($byte = $stream->read(1))) { + return $buffer; + } + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte === "\n" || ++$size === $maxLength - 1) { + break; + } + } + + return $buffer; +} + +/** + * Parses a request message string into a request object. + * + * @param string $message Request message string. + * + * @return Request + */ +function parse_request($message) +{ + $data = _parse_message($message); + $matches = []; + if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { + throw new \InvalidArgumentException('Invalid request string'); + } + $parts = explode(' ', $data['start-line'], 3); + $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; + + $request = new Request( + $parts[0], + $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1], + $data['headers'], + $data['body'], + $version + ); + + return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); +} + +/** + * Parses a response message string into a response object. + * + * @param string $message Response message string. + * + * @return Response + */ +function parse_response($message) +{ + $data = _parse_message($message); + // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space + // between status-code and reason-phrase is required. But browsers accept + // responses without space and reason as well. + if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { + throw new \InvalidArgumentException('Invalid response string'); + } + $parts = explode(' ', $data['start-line'], 3); + + return new Response( + $parts[1], + $data['headers'], + $data['body'], + explode('/', $parts[0])[1], + isset($parts[2]) ? $parts[2] : null + ); +} + +/** + * Parse a query string into an associative array. + * + * If multiple values are found for the same key, the value of that key + * value pair will become an array. This function does not parse nested + * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will + * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']). + * + * @param string $str Query string to parse + * @param bool|string $urlEncoding How the query string is encoded + * + * @return array + */ +function parse_query($str, $urlEncoding = true) +{ + $result = []; + + if ($str === '') { + return $result; + } + + if ($urlEncoding === true) { + $decoder = function ($value) { + return rawurldecode(str_replace('+', ' ', $value)); + }; + } elseif ($urlEncoding == PHP_QUERY_RFC3986) { + $decoder = 'rawurldecode'; + } elseif ($urlEncoding == PHP_QUERY_RFC1738) { + $decoder = 'urldecode'; + } else { + $decoder = function ($str) { return $str; }; + } + + foreach (explode('&', $str) as $kvp) { + $parts = explode('=', $kvp, 2); + $key = $decoder($parts[0]); + $value = isset($parts[1]) ? $decoder($parts[1]) : null; + if (!isset($result[$key])) { + $result[$key] = $value; + } else { + if (!is_array($result[$key])) { + $result[$key] = [$result[$key]]; + } + $result[$key][] = $value; + } + } + + return $result; +} + +/** + * Build a query string from an array of key value pairs. + * + * This function can use the return value of parse_query() to build a query + * string. This function does not modify the provided keys when an array is + * encountered (like http_build_query would). + * + * @param array $params Query string parameters. + * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 + * to encode using RFC3986, or PHP_QUERY_RFC1738 + * to encode using RFC1738. + * @return string + */ +function build_query(array $params, $encoding = PHP_QUERY_RFC3986) +{ + if (!$params) { + return ''; + } + + if ($encoding === false) { + $encoder = function ($str) { return $str; }; + } elseif ($encoding === PHP_QUERY_RFC3986) { + $encoder = 'rawurlencode'; + } elseif ($encoding === PHP_QUERY_RFC1738) { + $encoder = 'urlencode'; + } else { + throw new \InvalidArgumentException('Invalid type'); + } + + $qs = ''; + foreach ($params as $k => $v) { + $k = $encoder($k); + if (!is_array($v)) { + $qs .= $k; + if ($v !== null) { + $qs .= '=' . $encoder($v); + } + $qs .= '&'; + } else { + foreach ($v as $vv) { + $qs .= $k; + if ($vv !== null) { + $qs .= '=' . $encoder($vv); + } + $qs .= '&'; + } + } + } + + return $qs ? (string) substr($qs, 0, -1) : ''; +} + +/** + * Determines the mimetype of a file by looking at its extension. + * + * @param $filename + * + * @return null|string + */ +function mimetype_from_filename($filename) +{ + return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION)); +} + +/** + * Maps a file extensions to a mimetype. + * + * @param $extension string The file extension. + * + * @return string|null + * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types + */ +function mimetype_from_extension($extension) +{ + static $mimetypes = [ + '7z' => 'application/x-7z-compressed', + 'aac' => 'audio/x-aac', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'asc' => 'text/plain', + 'asf' => 'video/x-ms-asf', + 'atom' => 'application/atom+xml', + 'avi' => 'video/x-msvideo', + 'bmp' => 'image/bmp', + 'bz2' => 'application/x-bzip2', + 'cer' => 'application/pkix-cert', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'css' => 'text/css', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'deb' => 'application/x-debian-package', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dvi' => 'application/x-dvi', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'etx' => 'text/x-setext', + 'flac' => 'audio/flac', + 'flv' => 'video/x-flv', + 'gif' => 'image/gif', + 'gz' => 'application/gzip', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ini' => 'text/plain', + 'iso' => 'application/x-iso9660-image', + 'jar' => 'application/java-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'latex' => 'application/x-latex', + 'log' => 'text/plain', + 'm4a' => 'audio/mp4', + 'm4v' => 'video/mp4', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mov' => 'video/quicktime', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4v' => 'video/mp4', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'pbm' => 'image/x-portable-bitmap', + 'pdf' => 'application/pdf', + 'pgm' => 'image/x-portable-graymap', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'ppm' => 'image/x-portable-pixmap', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'ps' => 'application/postscript', + 'qt' => 'video/quicktime', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'svg' => 'image/svg+xml', + 'swf' => 'application/x-shockwave-flash', + 'tar' => 'application/x-tar', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'torrent' => 'application/x-bittorrent', + 'ttf' => 'application/x-font-ttf', + 'txt' => 'text/plain', + 'wav' => 'audio/x-wav', + 'webm' => 'video/webm', + 'wma' => 'audio/x-ms-wma', + 'wmv' => 'video/x-ms-wmv', + 'woff' => 'application/x-font-woff', + 'wsdl' => 'application/wsdl+xml', + 'xbm' => 'image/x-xbitmap', + 'xls' => 'application/vnd.ms-excel', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xml' => 'application/xml', + 'xpm' => 'image/x-xpixmap', + 'xwd' => 'image/x-xwindowdump', + 'yaml' => 'text/yaml', + 'yml' => 'text/yaml', + 'zip' => 'application/zip', + ]; + + $extension = strtolower($extension); + + return isset($mimetypes[$extension]) + ? $mimetypes[$extension] + : null; +} + +/** + * Parses an HTTP message into an associative array. + * + * The array contains the "start-line" key containing the start line of + * the message, "headers" key containing an associative array of header + * array values, and a "body" key containing the body of the message. + * + * @param string $message HTTP request or response to parse. + * + * @return array + * @internal + */ +function _parse_message($message) +{ + if (!$message) { + throw new \InvalidArgumentException('Invalid message'); + } + + // Iterate over each line in the message, accounting for line endings + $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE); + $result = ['start-line' => array_shift($lines), 'headers' => [], 'body' => '']; + array_shift($lines); + + for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) { + $line = $lines[$i]; + // If two line breaks were encountered, then this is the end of body + if (empty($line)) { + if ($i < $totalLines - 1) { + $result['body'] = implode('', array_slice($lines, $i + 2)); + } + break; + } + if (strpos($line, ':')) { + $parts = explode(':', $line, 2); + $key = trim($parts[0]); + $value = isset($parts[1]) ? trim($parts[1]) : ''; + $result['headers'][$key][] = $value; + } + } + + return $result; +} + +/** + * Constructs a URI for an HTTP request message. + * + * @param string $path Path from the start-line + * @param array $headers Array of headers (each value an array). + * + * @return string + * @internal + */ +function _parse_request_uri($path, array $headers) +{ + $hostKey = array_filter(array_keys($headers), function ($k) { + return strtolower($k) === 'host'; + }); + + // If no host is found, then a full URI cannot be constructed. + if (!$hostKey) { + return $path; + } + + $host = $headers[reset($hostKey)][0]; + $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; + + return $scheme . '://' . $host . '/' . ltrim($path, '/'); +} + +/** @internal */ +function _caseless_remove($keys, array $data) +{ + $result = []; + + foreach ($keys as &$key) { + $key = strtolower($key); + } + + foreach ($data as $k => $v) { + if (!in_array(strtolower($k), $keys)) { + $result[$k] = $v; + } + } + + return $result; +} diff --git a/Upyun/vendor/guzzlehttp/psr7/src/functions_include.php b/Upyun/vendor/guzzlehttp/psr7/src/functions_include.php new file mode 100644 index 0000000..96a4a83 --- /dev/null +++ b/Upyun/vendor/guzzlehttp/psr7/src/functions_include.php @@ -0,0 +1,6 @@ +=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/Upyun/vendor/psr/http-message/src/MessageInterface.php b/Upyun/vendor/psr/http-message/src/MessageInterface.php new file mode 100644 index 0000000..dd46e5e --- /dev/null +++ b/Upyun/vendor/psr/http-message/src/MessageInterface.php @@ -0,0 +1,187 @@ +getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return string[][] Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders(); + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($name); + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader($name); + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine($name); + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withHeader($name, $value); + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader($name, $value); + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return static + */ + public function withoutHeader($name); + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody(); + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * new body stream. + * + * @param StreamInterface $body Body. + * @return static + * @throws \InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body); +} diff --git a/Upyun/vendor/psr/http-message/src/RequestInterface.php b/Upyun/vendor/psr/http-message/src/RequestInterface.php new file mode 100644 index 0000000..a96d4fd --- /dev/null +++ b/Upyun/vendor/psr/http-message/src/RequestInterface.php @@ -0,0 +1,129 @@ +getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams(); + + /** + * Return an instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URI stored by the + * request, nor the values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return static + */ + public function withQueryParams(array $query); + + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles(); + + /** + * Create a new instance with the specified uploaded files. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param array $uploadedFiles An array tree of UploadedFileInterface instances. + * @return static + * @throws \InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles); + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + */ + public function getParsedBody(); + + /** + * Return an instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return static + * @throws \InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data); + + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return array Attributes derived from the request. + */ + public function getAttributes(); + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute($name, $default = null); + + /** + * Return an instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return static + */ + public function withAttribute($name, $value); + + /** + * Return an instance that removes the specified derived request attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return static + */ + public function withoutAttribute($name); +} diff --git a/Upyun/vendor/psr/http-message/src/StreamInterface.php b/Upyun/vendor/psr/http-message/src/StreamInterface.php new file mode 100644 index 0000000..f68f391 --- /dev/null +++ b/Upyun/vendor/psr/http-message/src/StreamInterface.php @@ -0,0 +1,158 @@ + + * [user-info@]host[:port] + * + * + * If the port component is not set or is the standard port for the current + * scheme, it SHOULD NOT be included. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * @return string The URI authority, in "[user-info@]host[:port]" format. + */ + public function getAuthority(); + + /** + * Retrieve the user information component of the URI. + * + * If no user information is present, this method MUST return an empty + * string. + * + * If a user is present in the URI, this will return that value; + * additionally, if the password is also present, it will be appended to the + * user value, with a colon (":") separating the values. + * + * The trailing "@" character is not part of the user information and MUST + * NOT be added. + * + * @return string The URI user information, in "username[:password]" format. + */ + public function getUserInfo(); + + /** + * Retrieve the host component of the URI. + * + * If no host is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.2.2. + * + * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 + * @return string The URI host. + */ + public function getHost(); + + /** + * Retrieve the port component of the URI. + * + * If a port is present, and it is non-standard for the current scheme, + * this method MUST return it as an integer. If the port is the standard port + * used with the current scheme, this method SHOULD return null. + * + * If no port is present, and no scheme is present, this method MUST return + * a null value. + * + * If no port is present, but a scheme is present, this method MAY return + * the standard port for that scheme, but SHOULD return null. + * + * @return null|int The URI port. + */ + public function getPort(); + + /** + * Retrieve the path component of the URI. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * Normally, the empty path "" and absolute path "/" are considered equal as + * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically + * do this normalization because in contexts with a trimmed base path, e.g. + * the front controller, this difference becomes significant. It's the task + * of the user to handle both "" and "/". + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.3. + * + * As an example, if the value should include a slash ("/") not intended as + * delimiter between path segments, that value MUST be passed in encoded + * form (e.g., "%2F") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.3 + * @return string The URI path. + */ + public function getPath(); + + /** + * Retrieve the query string of the URI. + * + * If no query string is present, this method MUST return an empty string. + * + * The leading "?" character is not part of the query and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.4. + * + * As an example, if a value in a key/value pair of the query string should + * include an ampersand ("&") not intended as a delimiter between values, + * that value MUST be passed in encoded form (e.g., "%26") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.4 + * @return string The URI query string. + */ + public function getQuery(); + + /** + * Retrieve the fragment component of the URI. + * + * If no fragment is present, this method MUST return an empty string. + * + * The leading "#" character is not part of the fragment and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.5. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.5 + * @return string The URI fragment. + */ + public function getFragment(); + + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * @return static A new instance with the specified scheme. + * @throws \InvalidArgumentException for invalid or unsupported schemes. + */ + public function withScheme($scheme); + + /** + * Return an instance with the specified user information. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified user information. + * + * Password is optional, but the user information MUST include the + * user; an empty string for the user is equivalent to removing user + * information. + * + * @param string $user The user name to use for authority. + * @param null|string $password The password associated with $user. + * @return static A new instance with the specified user information. + */ + public function withUserInfo($user, $password = null); + + /** + * Return an instance with the specified host. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified host. + * + * An empty host value is equivalent to removing the host. + * + * @param string $host The hostname to use with the new instance. + * @return static A new instance with the specified host. + * @throws \InvalidArgumentException for invalid hostnames. + */ + public function withHost($host); + + /** + * Return an instance with the specified port. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified port. + * + * Implementations MUST raise an exception for ports outside the + * established TCP and UDP port ranges. + * + * A null value provided for the port is equivalent to removing the port + * information. + * + * @param null|int $port The port to use with the new instance; a null value + * removes the port information. + * @return static A new instance with the specified port. + * @throws \InvalidArgumentException for invalid ports. + */ + public function withPort($port); + + /** + * Return an instance with the specified path. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified path. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * If the path is intended to be domain-relative rather than path relative then + * it must begin with a slash ("/"). Paths not starting with a slash ("/") + * are assumed to be relative to some base path known to the application or + * consumer. + * + * Users can provide both encoded and decoded path characters. + * Implementations ensure the correct encoding as outlined in getPath(). + * + * @param string $path The path to use with the new instance. + * @return static A new instance with the specified path. + * @throws \InvalidArgumentException for invalid paths. + */ + public function withPath($path); + + /** + * Return an instance with the specified query string. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified query string. + * + * Users can provide both encoded and decoded query characters. + * Implementations ensure the correct encoding as outlined in getQuery(). + * + * An empty query string value is equivalent to removing the query string. + * + * @param string $query The query string to use with the new instance. + * @return static A new instance with the specified query string. + * @throws \InvalidArgumentException for invalid query strings. + */ + public function withQuery($query); + + /** + * Return an instance with the specified URI fragment. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified URI fragment. + * + * Users can provide both encoded and decoded fragment characters. + * Implementations ensure the correct encoding as outlined in getFragment(). + * + * An empty fragment value is equivalent to removing the fragment. + * + * @param string $fragment The fragment to use with the new instance. + * @return static A new instance with the specified fragment. + */ + public function withFragment($fragment); + + /** + * Return the string representation as a URI reference. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @return string + */ + public function __toString(); +} diff --git "a/\346\210\252\345\233\276.png" "b/\346\210\252\345\233\276.png" new file mode 100644 index 0000000000000000000000000000000000000000..39a4c84c4ef47f20d85d9ecf6b99f7c2e1cba71f GIT binary patch literal 63692 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 z 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 literal 0 HcmV?d00001