├── .ci ├── php5.6.ini ├── proxy.conf └── site.conf ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── AbstractMessage.php ├── Client.php ├── Client ├── Adapter │ ├── AdapterInterface.php │ ├── Curl.php │ ├── Exception │ │ ├── ExceptionInterface.php │ │ ├── InitializationException.php │ │ ├── InvalidArgumentException.php │ │ ├── OutOfRangeException.php │ │ ├── RuntimeException.php │ │ └── TimeoutException.php │ ├── Proxy.php │ ├── Socket.php │ ├── StreamInterface.php │ └── Test.php └── Exception │ ├── ExceptionInterface.php │ ├── InvalidArgumentException.php │ ├── OutOfRangeException.php │ └── RuntimeException.php ├── ClientStatic.php ├── Cookies.php ├── Exception ├── ExceptionInterface.php ├── InvalidArgumentException.php ├── OutOfRangeException.php └── RuntimeException.php ├── Header ├── AbstractAccept.php ├── AbstractDate.php ├── AbstractLocation.php ├── Accept.php ├── Accept │ └── FieldValuePart │ │ ├── AbstractFieldValuePart.php │ │ ├── AcceptFieldValuePart.php │ │ ├── CharsetFieldValuePart.php │ │ ├── EncodingFieldValuePart.php │ │ └── LanguageFieldValuePart.php ├── AcceptCharset.php ├── AcceptEncoding.php ├── AcceptLanguage.php ├── AcceptRanges.php ├── Age.php ├── Allow.php ├── AuthenticationInfo.php ├── Authorization.php ├── CacheControl.php ├── Connection.php ├── ContentDisposition.php ├── ContentEncoding.php ├── ContentLanguage.php ├── ContentLength.php ├── ContentLocation.php ├── ContentMD5.php ├── ContentRange.php ├── ContentSecurityPolicy.php ├── ContentTransferEncoding.php ├── ContentType.php ├── Cookie.php ├── Date.php ├── Etag.php ├── Exception │ ├── DomainException.php │ ├── ExceptionInterface.php │ ├── InvalidArgumentException.php │ └── RuntimeException.php ├── Expect.php ├── Expires.php ├── FeaturePolicy.php ├── From.php ├── GenericHeader.php ├── GenericMultiHeader.php ├── HeaderInterface.php ├── HeaderValue.php ├── Host.php ├── IfMatch.php ├── IfModifiedSince.php ├── IfNoneMatch.php ├── IfRange.php ├── IfUnmodifiedSince.php ├── KeepAlive.php ├── LastModified.php ├── Location.php ├── MaxForwards.php ├── MultipleHeaderInterface.php ├── Origin.php ├── Pragma.php ├── ProxyAuthenticate.php ├── ProxyAuthorization.php ├── Range.php ├── Referer.php ├── Refresh.php ├── RetryAfter.php ├── Server.php ├── SetCookie.php ├── TE.php ├── Trailer.php ├── TransferEncoding.php ├── Upgrade.php ├── UserAgent.php ├── Vary.php ├── Via.php ├── WWWAuthenticate.php └── Warning.php ├── HeaderLoader.php ├── Headers.php ├── PhpEnvironment ├── RemoteAddress.php ├── Request.php └── Response.php ├── Request.php ├── Response.php └── Response └── Stream.php /.ci/php5.6.ini: -------------------------------------------------------------------------------- 1 | always_populate_raw_post_data=-1 2 | -------------------------------------------------------------------------------- /.ci/proxy.conf: -------------------------------------------------------------------------------- 1 | 2 | ProxyRequests On 3 | 4 | ErrorLog ${APACHE_LOG_DIR}/error.log 5 | CustomLog ${APACHE_LOG_DIR}/access.log combined 6 | 7 | -------------------------------------------------------------------------------- /.ci/site.conf: -------------------------------------------------------------------------------- 1 | 2 | DocumentRoot %TRAVIS_BUILD_DIR%/test/Client/_files 3 | 4 | 5 | Options FollowSymLinks MultiViews ExecCGI 6 | AllowOverride All 7 | Require all granted 8 | 9 | 10 | # Wire up Apache to use Travis CI's php-fpm. 11 | 12 | AddHandler php%PHP_VERSION%-fcgi .php 13 | Action php%PHP_VERSION%-fcgi /php%PHP_VERSION%-fcgi 14 | Alias /php%PHP_VERSION%-fcgi /usr/lib/cgi-bin/php%PHP_VERSION%-fcgi 15 | FastCgiExternalServer /usr/lib/cgi-bin/php%PHP_VERSION%-fcgi -host 127.0.0.1:9000 -pass-header Authorization 16 | 17 | 18 | Require all granted 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2005-2019, Zend Technologies USA, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | - Neither the name of Zend Technologies USA, Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zend-http 2 | 3 | > ## Repository abandoned 2019-12-31 4 | > 5 | > This repository has moved to [laminas/laminas-http](https://github.com/laminas/laminas-http). 6 | 7 | [![Build Status](https://secure.travis-ci.org/zendframework/zend-http.svg?branch=master)](https://secure.travis-ci.org/zendframework/zend-http) 8 | [![Coverage Status](https://coveralls.io/repos/github/zendframework/zend-http/badge.svg?branch=master)](https://coveralls.io/github/zendframework/zend-http?branch=master) 9 | 10 | zend-http provides the HTTP message abstraction used by 11 | [zend-mvc](https://docs.zendframework.com/zend-mvc/), and also provides an 12 | extensible, adapter-driven HTTP client library. 13 | 14 | This library **does not** support [PSR-7](http://www.php-fig.org/psr/psr-7), as 15 | it predates that specification. For PSR-7 support, please see our 16 | [Diactoros component](https://docs.zendframework.com/zend-diactoros/). 17 | 18 | ## Installation 19 | 20 | Run the following to install this library: 21 | 22 | ```bash 23 | $ composer require zendframework/zend-http 24 | ``` 25 | 26 | ## Documentation 27 | 28 | Browse the documentation online at https://docs.zendframework.com/zend-http/ 29 | 30 | ## Support 31 | 32 | * [Issues](https://github.com/zendframework/zend-http/issues/) 33 | * [Chat](https://zendframework-slack.herokuapp.com/) 34 | * [Forum](https://discourse.zendframework.com/) 35 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zendframework/zend-http", 3 | "description": "Provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests", 4 | "license": "BSD-3-Clause", 5 | "keywords": [ 6 | "zf", 7 | "zend", 8 | "zendframework", 9 | "http", 10 | "HTTP client" 11 | ], 12 | "support": { 13 | "docs": "https://docs.zendframework.com/zend-http/", 14 | "issues": "https://github.com/zendframework/zend-http/issues", 15 | "source": "https://github.com/zendframework/zend-http", 16 | "rss": "https://github.com/zendframework/zend-http/releases.atom", 17 | "chat": "https://zendframework-slack.herokuapp.com", 18 | "forum": "https://discourse.zendframework.com/c/questions/components" 19 | }, 20 | "require": { 21 | "php": "^5.6 || ^7.0", 22 | "zendframework/zend-loader": "^2.5.1", 23 | "zendframework/zend-stdlib": "^3.2.1", 24 | "zendframework/zend-uri": "^2.5.2", 25 | "zendframework/zend-validator": "^2.10.1" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.3", 29 | "zendframework/zend-coding-standard": "~1.0.0", 30 | "zendframework/zend-config": "^3.1 || ^2.6" 31 | }, 32 | "suggest": { 33 | "paragonie/certainty": "For automated management of cacert.pem" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "Zend\\Http\\": "src/" 38 | } 39 | }, 40 | "autoload-dev": { 41 | "psr-4": { 42 | "ZendTest\\Http\\": "test/" 43 | } 44 | }, 45 | "config": { 46 | "sort-packages": true 47 | }, 48 | "extra": { 49 | "branch-alias": { 50 | "dev-master": "2.11.x-dev", 51 | "dev-develop": "2.12.x-dev" 52 | } 53 | }, 54 | "scripts": { 55 | "check": [ 56 | "@cs-check", 57 | "@test" 58 | ], 59 | "cs-check": "phpcs", 60 | "cs-fix": "phpcbf", 61 | "test": "phpunit --colors=always", 62 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/AbstractMessage.php: -------------------------------------------------------------------------------- 1 | version = $version; 53 | return $this; 54 | } 55 | 56 | /** 57 | * Return the HTTP version for this request 58 | * 59 | * @return string 60 | */ 61 | public function getVersion() 62 | { 63 | return $this->version; 64 | } 65 | 66 | /** 67 | * Provide an alternate Parameter Container implementation for headers in this object, 68 | * (this is NOT the primary API for value setting, for that see getHeaders()) 69 | * 70 | * @see getHeaders() 71 | * @param Headers $headers 72 | * @return $this 73 | */ 74 | public function setHeaders(Headers $headers) 75 | { 76 | $this->headers = $headers; 77 | return $this; 78 | } 79 | 80 | /** 81 | * Return the header container responsible for headers 82 | * 83 | * @return Headers 84 | */ 85 | public function getHeaders() 86 | { 87 | if ($this->headers === null || is_string($this->headers)) { 88 | // this is only here for fromString lazy loading 89 | $this->headers = (is_string($this->headers)) ? Headers::fromString($this->headers) : new Headers(); 90 | } 91 | 92 | return $this->headers; 93 | } 94 | 95 | /** 96 | * Allow PHP casting of this object 97 | * 98 | * @return string 99 | */ 100 | public function __toString() 101 | { 102 | return $this->toString(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Client/Adapter/AdapterInterface.php: -------------------------------------------------------------------------------- 1 | false, 35 | 'ssltransport' => 'ssl', 36 | 'sslcert' => null, 37 | 'sslpassphrase' => null, 38 | 'sslverifypeer' => true, 39 | 'sslcafile' => null, 40 | 'sslcapath' => null, 41 | 'sslallowselfsigned' => false, 42 | 'sslusecontext' => false, 43 | 'sslverifypeername' => true, 44 | 'proxy_host' => '', 45 | 'proxy_port' => 8080, 46 | 'proxy_user' => '', 47 | 'proxy_pass' => '', 48 | 'proxy_auth' => Client::AUTH_BASIC, 49 | ]; 50 | 51 | /** 52 | * Whether HTTPS CONNECT was already negotiated with the proxy or not 53 | * 54 | * @var bool 55 | */ 56 | protected $negotiated = false; 57 | 58 | /** 59 | * Set the configuration array for the adapter 60 | * 61 | * @param array $options 62 | */ 63 | public function setOptions($options = []) 64 | { 65 | if ($options instanceof Traversable) { 66 | $options = ArrayUtils::iteratorToArray($options); 67 | } 68 | if (! is_array($options)) { 69 | throw new AdapterException\InvalidArgumentException( 70 | 'Array or Zend\Config object expected, got ' . gettype($options) 71 | ); 72 | } 73 | 74 | //enforcing that the proxy keys are set in the form proxy_* 75 | foreach ($options as $k => $v) { 76 | if (preg_match('/^proxy[a-z]+/', $k)) { 77 | $options['proxy_' . substr($k, 5, strlen($k))] = $v; 78 | unset($options[$k]); 79 | } 80 | } 81 | 82 | parent::setOptions($options); 83 | } 84 | 85 | /** 86 | * Connect to the remote server 87 | * 88 | * Will try to connect to the proxy server. If no proxy was set, will 89 | * fall back to the target server (behave like regular Socket adapter) 90 | * 91 | * @param string $host 92 | * @param int $port 93 | * @param bool $secure 94 | * @throws AdapterException\RuntimeException 95 | */ 96 | public function connect($host, $port = 80, $secure = false) 97 | { 98 | // If no proxy is set, fall back to Socket adapter 99 | if (! $this->config['proxy_host']) { 100 | parent::connect($host, $port, $secure); 101 | return; 102 | } 103 | 104 | /* Url might require stream context even if proxy connection doesn't */ 105 | if ($secure) { 106 | $this->config['sslusecontext'] = true; 107 | $this->setSslCryptoMethod = false; 108 | } 109 | 110 | // Connect (a non-secure connection) to the proxy server 111 | parent::connect( 112 | $this->config['proxy_host'], 113 | $this->config['proxy_port'], 114 | false 115 | ); 116 | } 117 | 118 | /** 119 | * Send request to the proxy server 120 | * 121 | * @param string $method 122 | * @param \Zend\Uri\Uri $uri 123 | * @param string $httpVer 124 | * @param array $headers 125 | * @param string $body 126 | * @throws AdapterException\RuntimeException 127 | * @return string Request as string 128 | */ 129 | public function write($method, $uri, $httpVer = '1.1', $headers = [], $body = '') 130 | { 131 | // If no proxy is set, fall back to default Socket adapter 132 | if (! $this->config['proxy_host']) { 133 | return parent::write($method, $uri, $httpVer, $headers, $body); 134 | } 135 | 136 | // Make sure we're properly connected 137 | if (! $this->socket) { 138 | throw new AdapterException\RuntimeException('Trying to write but we are not connected'); 139 | } 140 | 141 | $host = $this->config['proxy_host']; 142 | $port = $this->config['proxy_port']; 143 | 144 | $isSecure = strtolower($uri->getScheme()) === 'https'; 145 | $connectedHost = ($isSecure ? $this->config['ssltransport'] : 'tcp') . '://' . $host; 146 | 147 | if ($this->connectedTo[1] !== $port || $this->connectedTo[0] !== $connectedHost) { 148 | throw new AdapterException\RuntimeException( 149 | 'Trying to write but we are connected to the wrong proxy server' 150 | ); 151 | } 152 | 153 | // Add Proxy-Authorization header 154 | if ($this->config['proxy_user'] && ! isset($headers['proxy-authorization'])) { 155 | $headers['proxy-authorization'] = Client::encodeAuthHeader( 156 | $this->config['proxy_user'], 157 | $this->config['proxy_pass'], 158 | $this->config['proxy_auth'] 159 | ); 160 | } 161 | 162 | // if we are proxying HTTPS, preform CONNECT handshake with the proxy 163 | if ($isSecure && ! $this->negotiated) { 164 | $this->connectHandshake($uri->getHost(), $uri->getPort(), $httpVer, $headers); 165 | $this->negotiated = true; 166 | } 167 | 168 | // Save request method for later 169 | $this->method = $method; 170 | 171 | if ($uri->getUserInfo()) { 172 | $headers['Authorization'] = 'Basic ' . base64_encode($uri->getUserInfo()); 173 | } 174 | 175 | $path = $uri->getPath(); 176 | $query = $uri->getQuery(); 177 | $path .= $query ? '?' . $query : ''; 178 | 179 | if (! $this->negotiated) { 180 | $path = $uri->getScheme() . '://' . $uri->getHost() . $path; 181 | } 182 | 183 | // Build request headers 184 | $request = sprintf('%s %s HTTP/%s%s', $method, $path, $httpVer, "\r\n"); 185 | 186 | // Add all headers to the request string 187 | foreach ($headers as $k => $v) { 188 | if (is_string($k)) { 189 | $v = $k . ': ' . $v; 190 | } 191 | $request .= $v . "\r\n"; 192 | } 193 | 194 | if (is_resource($body)) { 195 | $request .= "\r\n"; 196 | } else { 197 | // Add the request body 198 | $request .= "\r\n" . $body; 199 | } 200 | 201 | // Send the request 202 | ErrorHandler::start(); 203 | $test = fwrite($this->socket, $request); 204 | $error = ErrorHandler::stop(); 205 | if ($test === false) { 206 | throw new AdapterException\RuntimeException('Error writing request to proxy server', 0, $error); 207 | } 208 | 209 | if (is_resource($body)) { 210 | if (stream_copy_to_stream($body, $this->socket) == 0) { 211 | throw new AdapterException\RuntimeException('Error writing request to server'); 212 | } 213 | } 214 | 215 | return $request; 216 | } 217 | 218 | /** 219 | * Preform handshaking with HTTPS proxy using CONNECT method 220 | * 221 | * @param string $host 222 | * @param int $port 223 | * @param string $httpVer 224 | * @param array $headers 225 | * @throws AdapterException\RuntimeException 226 | */ 227 | protected function connectHandshake($host, $port = 443, $httpVer = '1.1', array &$headers = []) 228 | { 229 | $request = 'CONNECT ' . $host . ':' . $port . ' HTTP/' . $httpVer . "\r\n" 230 | . 'Host: ' . $host . "\r\n"; 231 | 232 | // Add the user-agent header 233 | if (isset($this->config['useragent'])) { 234 | $request .= 'User-agent: ' . $this->config['useragent'] . "\r\n"; 235 | } 236 | 237 | // If the proxy-authorization header is set, send it to proxy but remove 238 | // it from headers sent to target host 239 | if (isset($headers['proxy-authorization'])) { 240 | $request .= 'Proxy-authorization: ' . $headers['proxy-authorization'] . "\r\n"; 241 | unset($headers['proxy-authorization']); 242 | } 243 | 244 | $request .= "\r\n"; 245 | 246 | // Send the request 247 | ErrorHandler::start(); 248 | $test = fwrite($this->socket, $request); 249 | $error = ErrorHandler::stop(); 250 | if (! $test) { 251 | throw new AdapterException\RuntimeException('Error writing request to proxy server', 0, $error); 252 | } 253 | 254 | // Read response headers only 255 | $response = ''; 256 | $gotStatus = false; 257 | ErrorHandler::start(); 258 | while ($line = fgets($this->socket)) { 259 | $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false); 260 | if ($gotStatus) { 261 | $response .= $line; 262 | if (! rtrim($line)) { 263 | break; 264 | } 265 | } 266 | } 267 | ErrorHandler::stop(); 268 | 269 | // Check that the response from the proxy is 200 270 | if (Response::fromString($response)->getStatusCode() != 200) { 271 | throw new AdapterException\RuntimeException(sprintf( 272 | 'Unable to connect to HTTPS proxy. Server response: %s', 273 | $response 274 | )); 275 | } 276 | 277 | // If all is good, switch socket to secure mode. We have to fall back 278 | // through the different modes 279 | $modes = [ 280 | STREAM_CRYPTO_METHOD_TLS_CLIENT, 281 | STREAM_CRYPTO_METHOD_SSLv3_CLIENT, 282 | STREAM_CRYPTO_METHOD_SSLv23_CLIENT, 283 | STREAM_CRYPTO_METHOD_SSLv2_CLIENT, 284 | ]; 285 | 286 | $success = false; 287 | foreach ($modes as $mode) { 288 | $success = stream_socket_enable_crypto($this->socket, true, $mode); 289 | if ($success) { 290 | break; 291 | } 292 | } 293 | 294 | if (! $success) { 295 | throw new AdapterException\RuntimeException( 296 | 'Unable to connect to HTTPS server through proxy: could not negotiate secure connection.' 297 | ); 298 | } 299 | } 300 | 301 | /** 302 | * Close the connection to the server 303 | */ 304 | public function close() 305 | { 306 | parent::close(); 307 | $this->negotiated = false; 308 | } 309 | 310 | /** 311 | * Destructor: make sure the socket is disconnected 312 | */ 313 | public function __destruct() 314 | { 315 | if ($this->socket) { 316 | $this->close(); 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /src/Client/Adapter/StreamInterface.php: -------------------------------------------------------------------------------- 1 | nextRequestWillFail = (bool) $flag; 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * Set the configuration array for the adapter 75 | * 76 | * @param array|Traversable $options 77 | * @throws Exception\InvalidArgumentException 78 | */ 79 | public function setOptions($options = []) 80 | { 81 | if ($options instanceof Traversable) { 82 | $options = ArrayUtils::iteratorToArray($options); 83 | } 84 | 85 | if (! is_array($options)) { 86 | throw new Exception\InvalidArgumentException( 87 | 'Array or Traversable object expected, got ' . gettype($options) 88 | ); 89 | } 90 | 91 | foreach ($options as $k => $v) { 92 | $this->config[strtolower($k)] = $v; 93 | } 94 | } 95 | 96 | 97 | /** 98 | * Connect to the remote server 99 | * 100 | * @param string $host 101 | * @param int $port 102 | * @param bool $secure 103 | * @throws Exception\RuntimeException 104 | */ 105 | public function connect($host, $port = 80, $secure = false) 106 | { 107 | if ($this->nextRequestWillFail) { 108 | $this->nextRequestWillFail = false; 109 | throw new Exception\RuntimeException('Request failed'); 110 | } 111 | } 112 | 113 | /** 114 | * Send request to the remote server 115 | * 116 | * @param string $method 117 | * @param \Zend\Uri\Uri $uri 118 | * @param string $httpVer 119 | * @param array $headers 120 | * @param string $body 121 | * @return string Request as string 122 | */ 123 | public function write($method, $uri, $httpVer = '1.1', $headers = [], $body = '') 124 | { 125 | // Build request headers 126 | $path = $uri->getPath(); 127 | if (empty($path)) { 128 | $path = '/'; 129 | } 130 | $query = $uri->getQuery(); 131 | $path .= $query ? '?' . $query : ''; 132 | $request = $method . ' ' . $path . ' HTTP/' . $httpVer . "\r\n"; 133 | foreach ($headers as $k => $v) { 134 | if (is_string($k)) { 135 | $v = $k . ': ' . $v; 136 | } 137 | $request .= $v . "\r\n"; 138 | } 139 | 140 | // Add the request body 141 | $request .= "\r\n" . $body; 142 | 143 | // Do nothing - just return the request as string 144 | 145 | return $request; 146 | } 147 | 148 | /** 149 | * Return the response set in $this->setResponse() 150 | * 151 | * @return string 152 | */ 153 | public function read() 154 | { 155 | if ($this->responseIndex >= count($this->responses)) { 156 | $this->responseIndex = 0; 157 | } 158 | return $this->responses[$this->responseIndex++]; 159 | } 160 | 161 | /** 162 | * Close the connection (dummy) 163 | * 164 | */ 165 | public function close() 166 | { 167 | } 168 | 169 | /** 170 | * Set the HTTP response(s) to be returned by this adapter 171 | * 172 | * @param \Zend\Http\Response|array|string $response 173 | */ 174 | public function setResponse($response) 175 | { 176 | if ($response instanceof Response) { 177 | $response = $response->toString(); 178 | } 179 | 180 | $this->responses = (array) $response; 181 | $this->responseIndex = 0; 182 | } 183 | 184 | /** 185 | * Add another response to the response buffer. 186 | * 187 | * @param string|Response $response 188 | */ 189 | public function addResponse($response) 190 | { 191 | if ($response instanceof Response) { 192 | $response = $response->toString(); 193 | } 194 | 195 | $this->responses[] = $response; 196 | } 197 | 198 | /** 199 | * Sets the position of the response buffer. Selects which 200 | * response will be returned on the next call to read(). 201 | * 202 | * @param int $index 203 | * @throws Exception\OutOfRangeException 204 | */ 205 | public function setResponseIndex($index) 206 | { 207 | if ($index < 0 || $index >= count($this->responses)) { 208 | throw new Exception\OutOfRangeException( 209 | 'Index out of range of response buffer size' 210 | ); 211 | } 212 | $this->responseIndex = $index; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/Client/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | setUri($url); 52 | $request->setMethod(Request::METHOD_GET); 53 | 54 | if (! empty($query) && is_array($query)) { 55 | $request->getQuery()->fromArray($query); 56 | } 57 | 58 | if (! empty($headers) && is_array($headers)) { 59 | $request->getHeaders()->addHeaders($headers); 60 | } 61 | 62 | if (! empty($body)) { 63 | $request->setContent($body); 64 | } 65 | 66 | return static::getStaticClient($clientOptions)->send($request); 67 | } 68 | 69 | /** 70 | * HTTP POST METHOD (static) 71 | * 72 | * @param string $url 73 | * @param array $params 74 | * @param array $headers 75 | * @param mixed $body 76 | * @param array|Traversable $clientOptions 77 | * @throws Exception\InvalidArgumentException 78 | * @return Response|bool 79 | */ 80 | public static function post($url, $params, $headers = [], $body = null, $clientOptions = null) 81 | { 82 | if (empty($url)) { 83 | return false; 84 | } 85 | 86 | $request = new Request(); 87 | $request->setUri($url); 88 | $request->setMethod(Request::METHOD_POST); 89 | 90 | if (! empty($params) && is_array($params)) { 91 | $request->getPost()->fromArray($params); 92 | } else { 93 | throw new Exception\InvalidArgumentException('The array of post parameters is empty'); 94 | } 95 | 96 | if (! isset($headers['Content-Type'])) { 97 | $headers['Content-Type'] = Client::ENC_URLENCODED; 98 | } 99 | 100 | if (! empty($headers) && is_array($headers)) { 101 | $request->getHeaders()->addHeaders($headers); 102 | } 103 | 104 | if (! empty($body)) { 105 | $request->setContent($body); 106 | } 107 | 108 | return static::getStaticClient($clientOptions)->send($request); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 'D, d M Y H:i:s \G\M\T', 60 | self::DATE_RFC1036 => 'D, d M y H:i:s \G\M\T', 61 | self::DATE_ANSIC => 'D M j H:i:s Y', 62 | ]; 63 | 64 | /** 65 | * Create date-based header from string 66 | * 67 | * @param string $headerLine 68 | * @return static 69 | * @throws Exception\InvalidArgumentException 70 | */ 71 | public static function fromString($headerLine) 72 | { 73 | $dateHeader = new static(); 74 | 75 | list($name, $date) = GenericHeader::splitHeaderLine($headerLine); 76 | 77 | // check to ensure proper header type for this factory 78 | if (strtolower($name) !== strtolower($dateHeader->getFieldName())) { 79 | throw new Exception\InvalidArgumentException( 80 | 'Invalid header line for "' . $dateHeader->getFieldName() . '" header string' 81 | ); 82 | } 83 | 84 | $dateHeader->setDate($date); 85 | 86 | return $dateHeader; 87 | } 88 | 89 | /** 90 | * Create date-based header from strtotime()-compatible string 91 | * 92 | * @param int|string $time 93 | * @return static 94 | * @throws Exception\InvalidArgumentException 95 | */ 96 | public static function fromTimeString($time) 97 | { 98 | return static::fromTimestamp(strtotime($time)); 99 | } 100 | 101 | /** 102 | * Create date-based header from Unix timestamp 103 | * 104 | * @param int $time 105 | * @return static 106 | * @throws Exception\InvalidArgumentException 107 | */ 108 | public static function fromTimestamp($time) 109 | { 110 | $dateHeader = new static(); 111 | 112 | if (! $time || ! is_numeric($time)) { 113 | throw new Exception\InvalidArgumentException( 114 | 'Invalid time for "' . $dateHeader->getFieldName() . '" header string' 115 | ); 116 | } 117 | 118 | $dateHeader->setDate(new DateTime('@' . $time)); 119 | 120 | return $dateHeader; 121 | } 122 | 123 | /** 124 | * Set date output format 125 | * 126 | * @param int $format 127 | * @throws Exception\InvalidArgumentException 128 | */ 129 | public static function setDateFormat($format) 130 | { 131 | if (! isset(static::$dateFormats[$format])) { 132 | throw new Exception\InvalidArgumentException(sprintf( 133 | 'No constant defined for provided date format: %s', 134 | $format 135 | )); 136 | } 137 | 138 | static::$dateFormat = static::$dateFormats[$format]; 139 | } 140 | 141 | /** 142 | * Return current date output format 143 | * 144 | * @return string 145 | */ 146 | public static function getDateFormat() 147 | { 148 | return static::$dateFormat; 149 | } 150 | 151 | /** 152 | * Set the date for this header, this can be a string or an instance of \DateTime 153 | * 154 | * @param string|DateTime $date 155 | * @return $this 156 | * @throws Exception\InvalidArgumentException 157 | */ 158 | public function setDate($date) 159 | { 160 | if (is_string($date)) { 161 | try { 162 | $date = new DateTime($date, new DateTimeZone('GMT')); 163 | } catch (\Exception $e) { 164 | throw new Exception\InvalidArgumentException( 165 | sprintf('Invalid date passed as string (%s)', (string) $date), 166 | $e->getCode(), 167 | $e 168 | ); 169 | } 170 | } elseif (! ($date instanceof DateTime)) { 171 | throw new Exception\InvalidArgumentException('Date must be an instance of \DateTime or a string'); 172 | } 173 | 174 | $date->setTimezone(new DateTimeZone('GMT')); 175 | $this->date = $date; 176 | 177 | return $this; 178 | } 179 | 180 | /** 181 | * Return date for this header 182 | * 183 | * @return string 184 | */ 185 | public function getDate() 186 | { 187 | return $this->date()->format(static::$dateFormat); 188 | } 189 | 190 | /** 191 | * Return date for this header as an instance of \DateTime 192 | * 193 | * @return DateTime 194 | */ 195 | public function date() 196 | { 197 | if ($this->date === null) { 198 | $this->date = new DateTime(null, new DateTimeZone('GMT')); 199 | } 200 | return $this->date; 201 | } 202 | 203 | /** 204 | * Compare provided date to date for this header 205 | * Returns < 0 if date in header is less than $date; > 0 if it's greater, and 0 if they are equal. 206 | * @see \strcmp() 207 | * 208 | * @param string|DateTime $date 209 | * @return int 210 | * @throws Exception\InvalidArgumentException 211 | */ 212 | public function compareTo($date) 213 | { 214 | if (is_string($date)) { 215 | try { 216 | $date = new DateTime($date, new DateTimeZone('GMT')); 217 | } catch (\Exception $e) { 218 | throw new Exception\InvalidArgumentException( 219 | sprintf('Invalid Date passed as string (%s)', (string) $date), 220 | $e->getCode(), 221 | $e 222 | ); 223 | } 224 | } elseif (! ($date instanceof DateTime)) { 225 | throw new Exception\InvalidArgumentException('Date must be an instance of \DateTime or a string'); 226 | } 227 | 228 | $dateTimestamp = $date->getTimestamp(); 229 | $thisTimestamp = $this->date()->getTimestamp(); 230 | 231 | return ($thisTimestamp === $dateTimestamp) ? 0 : (($thisTimestamp > $dateTimestamp) ? 1 : -1); 232 | } 233 | 234 | /** 235 | * Get header value as formatted date 236 | * 237 | * @return string 238 | */ 239 | public function getFieldValue() 240 | { 241 | return $this->getDate(); 242 | } 243 | 244 | /** 245 | * Return header line 246 | * 247 | * @return string 248 | */ 249 | public function toString() 250 | { 251 | return $this->getFieldName() . ': ' . $this->getDate(); 252 | } 253 | 254 | /** 255 | * Allow casting to string 256 | * 257 | * @return string 258 | */ 259 | public function __toString() 260 | { 261 | return $this->toString(); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/Header/AbstractLocation.php: -------------------------------------------------------------------------------- 1 | getFieldName())) { 50 | throw new Exception\InvalidArgumentException( 51 | 'Invalid header line for "' . $locationHeader->getFieldName() . '" header string' 52 | ); 53 | } 54 | 55 | HeaderValue::assertValid($uri); 56 | $locationHeader->setUri(trim($uri)); 57 | 58 | return $locationHeader; 59 | } 60 | 61 | /** 62 | * Set the URI/URL for this header, this can be a string or an instance of Zend\Uri\Http 63 | * 64 | * @param string|UriInterface $uri 65 | * @return $this 66 | * @throws Exception\InvalidArgumentException 67 | */ 68 | public function setUri($uri) 69 | { 70 | if (is_string($uri)) { 71 | try { 72 | $uri = UriFactory::factory($uri); 73 | } catch (UriException\InvalidUriPartException $e) { 74 | throw new Exception\InvalidArgumentException( 75 | sprintf('Invalid URI passed as string (%s)', (string) $uri), 76 | $e->getCode(), 77 | $e 78 | ); 79 | } catch (UriException\InvalidArgumentException $e) { 80 | throw new Exception\InvalidArgumentException( 81 | sprintf('Invalid URI passed as string (%s)', (string) $uri), 82 | $e->getCode(), 83 | $e 84 | ); 85 | } 86 | } elseif (! ($uri instanceof UriInterface)) { 87 | throw new Exception\InvalidArgumentException('URI must be an instance of Zend\Uri\Http or a string'); 88 | } 89 | $this->uri = $uri; 90 | 91 | return $this; 92 | } 93 | 94 | /** 95 | * Return the URI for this header 96 | * 97 | * @return string 98 | */ 99 | public function getUri() 100 | { 101 | if ($this->uri instanceof UriInterface) { 102 | return $this->uri->toString(); 103 | } 104 | return $this->uri; 105 | } 106 | 107 | /** 108 | * Return the URI for this header as an instance of Zend\Uri\Http 109 | * 110 | * @return UriInterface 111 | */ 112 | public function uri() 113 | { 114 | if ($this->uri === null || is_string($this->uri)) { 115 | $this->uri = UriFactory::factory($this->uri); 116 | } 117 | return $this->uri; 118 | } 119 | 120 | /** 121 | * Get header value as URI string 122 | * 123 | * @return string 124 | */ 125 | public function getFieldValue() 126 | { 127 | return $this->getUri(); 128 | } 129 | 130 | /** 131 | * Output header line 132 | * 133 | * @return string 134 | */ 135 | public function toString() 136 | { 137 | return $this->getFieldName() . ': ' . $this->getUri(); 138 | } 139 | 140 | /** 141 | * Allow casting to string 142 | * 143 | * @return string 144 | */ 145 | public function __toString() 146 | { 147 | return $this->toString(); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/Header/Accept.php: -------------------------------------------------------------------------------- 1 | getFieldValue(); 42 | } 43 | 44 | /** 45 | * Add a media type, with the given priority 46 | * 47 | * @param string $type 48 | * @param int|float $priority 49 | * @param array $params 50 | * @return $this 51 | */ 52 | public function addMediaType($type, $priority = 1, array $params = []) 53 | { 54 | return $this->addType($type, $priority, $params); 55 | } 56 | 57 | /** 58 | * Does the header have the requested media type? 59 | * 60 | * @param string $type 61 | * @return bool 62 | */ 63 | public function hasMediaType($type) 64 | { 65 | return $this->hasType($type); 66 | } 67 | 68 | /** 69 | * Parse the keys contained in the header line 70 | * 71 | * @param string $fieldValuePart 72 | * @return FieldValuePart\AcceptFieldValuePart 73 | * @see \Zend\Http\Header\AbstractAccept::parseFieldValuePart() 74 | */ 75 | protected function parseFieldValuePart($fieldValuePart) 76 | { 77 | $raw = $fieldValuePart; 78 | if ($pos = strpos($fieldValuePart, '/')) { 79 | $type = trim(substr($fieldValuePart, 0, $pos)); 80 | } else { 81 | $type = trim($fieldValuePart); 82 | } 83 | 84 | $params = $this->getParametersFromFieldValuePart($fieldValuePart); 85 | 86 | if ($pos = strpos($fieldValuePart, ';')) { 87 | $fieldValuePart = trim(substr($fieldValuePart, 0, $pos)); 88 | } 89 | 90 | if (strpos($fieldValuePart, '/')) { 91 | $subtypeWhole = $format = $subtype = trim(substr($fieldValuePart, strpos($fieldValuePart, '/') + 1)); 92 | } else { 93 | $subtypeWhole = ''; 94 | $format = '*'; 95 | $subtype = '*'; 96 | } 97 | 98 | $pos = strpos($subtype, '+'); 99 | if (false !== $pos) { 100 | $format = trim(substr($subtype, $pos + 1)); 101 | $subtype = trim(substr($subtype, 0, $pos)); 102 | } 103 | 104 | $aggregated = [ 105 | 'typeString' => trim($fieldValuePart), 106 | 'type' => $type, 107 | 'subtype' => $subtype, 108 | 'subtypeRaw' => $subtypeWhole, 109 | 'format' => $format, 110 | 'priority' => isset($params['q']) ? $params['q'] : 1, 111 | 'params' => $params, 112 | 'raw' => trim($raw), 113 | ]; 114 | 115 | return new FieldValuePart\AcceptFieldValuePart((object) $aggregated); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Header/Accept/FieldValuePart/AbstractFieldValuePart.php: -------------------------------------------------------------------------------- 1 | internalValues = $internalValues; 35 | } 36 | 37 | /** 38 | * Set a Field Value Part this Field Value Part matched against. 39 | * 40 | * @param AbstractFieldValuePart $matchedAgainst 41 | * @return $this 42 | */ 43 | public function setMatchedAgainst(AbstractFieldValuePart $matchedAgainst) 44 | { 45 | $this->matchedAgainst = $matchedAgainst; 46 | return $this; 47 | } 48 | 49 | /** 50 | * Get a Field Value Part this Field Value Part matched against. 51 | * 52 | * @return AbstractFieldValuePart|null 53 | */ 54 | public function getMatchedAgainst() 55 | { 56 | return $this->matchedAgainst; 57 | } 58 | 59 | /** 60 | * @return object 61 | */ 62 | protected function getInternalValues() 63 | { 64 | return $this->internalValues; 65 | } 66 | 67 | /** 68 | * @return string $typeString 69 | */ 70 | public function getTypeString() 71 | { 72 | return $this->getInternalValues()->typeString; 73 | } 74 | 75 | /** 76 | * @return float $priority 77 | */ 78 | public function getPriority() 79 | { 80 | return (float) $this->getInternalValues()->priority; 81 | } 82 | 83 | /** 84 | * @return \stdClass $params 85 | */ 86 | public function getParams() 87 | { 88 | return (object) $this->getInternalValues()->params; 89 | } 90 | 91 | /** 92 | * @return string $raw 93 | */ 94 | public function getRaw() 95 | { 96 | return $this->getInternalValues()->raw; 97 | } 98 | 99 | /** 100 | * @param mixed $key 101 | * @return mixed 102 | */ 103 | public function __get($key) 104 | { 105 | return $this->getInternalValues()->$key; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Header/Accept/FieldValuePart/AcceptFieldValuePart.php: -------------------------------------------------------------------------------- 1 | getInternalValues()->subtype; 23 | } 24 | 25 | /** 26 | * @return string 27 | */ 28 | public function getSubtypeRaw() 29 | { 30 | return $this->getInternalValues()->subtypeRaw; 31 | } 32 | 33 | /** 34 | * @return string 35 | */ 36 | public function getFormat() 37 | { 38 | return $this->getInternalValues()->format; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Header/Accept/FieldValuePart/CharsetFieldValuePart.php: -------------------------------------------------------------------------------- 1 | getInternalValues()->type; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Header/Accept/FieldValuePart/EncodingFieldValuePart.php: -------------------------------------------------------------------------------- 1 | getInternalValues()->type; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Header/Accept/FieldValuePart/LanguageFieldValuePart.php: -------------------------------------------------------------------------------- 1 | getInternalValues()->typeString; 20 | } 21 | 22 | public function getPrimaryTag() 23 | { 24 | return $this->getInternalValues()->type; 25 | } 26 | 27 | public function getSubTag() 28 | { 29 | return $this->getInternalValues()->subtype; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Header/AcceptCharset.php: -------------------------------------------------------------------------------- 1 | getFieldValue(); 39 | } 40 | 41 | /** 42 | * Add a charset, with the given priority 43 | * 44 | * @param string $type 45 | * @param int|float $priority 46 | * @return $this 47 | */ 48 | public function addCharset($type, $priority = 1) 49 | { 50 | return $this->addType($type, $priority); 51 | } 52 | 53 | /** 54 | * Does the header have the requested charset? 55 | * 56 | * @param string $type 57 | * @return bool 58 | */ 59 | public function hasCharset($type) 60 | { 61 | return $this->hasType($type); 62 | } 63 | 64 | /** 65 | * Parse the keys contained in the header line 66 | * 67 | * @param string $fieldValuePart 68 | * @return \Zend\Http\Header\Accept\FieldValuePart\CharsetFieldValuePart 69 | * @see \Zend\Http\Header\AbstractAccept::parseFieldValuePart() 70 | */ 71 | protected function parseFieldValuePart($fieldValuePart) 72 | { 73 | $internalValues = parent::parseFieldValuePart($fieldValuePart); 74 | 75 | return new FieldValuePart\CharsetFieldValuePart($internalValues); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Header/AcceptEncoding.php: -------------------------------------------------------------------------------- 1 | getFieldValue(); 39 | } 40 | 41 | /** 42 | * Add an encoding, with the given priority 43 | * 44 | * @param string $type 45 | * @param int|float $priority 46 | * @return $this 47 | */ 48 | public function addEncoding($type, $priority = 1) 49 | { 50 | return $this->addType($type, $priority); 51 | } 52 | 53 | /** 54 | * Does the header have the requested encoding? 55 | * 56 | * @param string $type 57 | * @return bool 58 | */ 59 | public function hasEncoding($type) 60 | { 61 | return $this->hasType($type); 62 | } 63 | 64 | /** 65 | * Parse the keys contained in the header line 66 | * 67 | * @param string $fieldValuePart 68 | * @return \Zend\Http\Header\Accept\FieldValuePart\EncodingFieldValuePart 69 | * @see \Zend\Http\Header\AbstractAccept::parseFieldValuePart() 70 | */ 71 | protected function parseFieldValuePart($fieldValuePart) 72 | { 73 | $internalValues = parent::parseFieldValuePart($fieldValuePart); 74 | 75 | return new FieldValuePart\EncodingFieldValuePart($internalValues); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Header/AcceptLanguage.php: -------------------------------------------------------------------------------- 1 | getFieldValue(); 39 | } 40 | 41 | /** 42 | * Add a language, with the given priority 43 | * 44 | * @param string $type 45 | * @param int|float $priority 46 | * @return $this 47 | */ 48 | public function addLanguage($type, $priority = 1) 49 | { 50 | return $this->addType($type, $priority); 51 | } 52 | 53 | /** 54 | * Does the header have the requested language? 55 | * 56 | * @param string $type 57 | * @return bool 58 | */ 59 | public function hasLanguage($type) 60 | { 61 | return $this->hasType($type); 62 | } 63 | 64 | /** 65 | * Parse the keys contained in the header line 66 | * 67 | * @param string $fieldValuePart 68 | * @return \Zend\Http\Header\Accept\FieldValuePart\LanguageFieldValuePart 69 | * @see \Zend\Http\Header\AbstractAccept::parseFieldValuePart() 70 | */ 71 | protected function parseFieldValuePart($fieldValuePart) 72 | { 73 | $raw = $fieldValuePart; 74 | if ($pos = strpos($fieldValuePart, '-')) { 75 | $type = trim(substr($fieldValuePart, 0, $pos)); 76 | } else { 77 | $type = trim(substr($fieldValuePart, 0)); 78 | } 79 | 80 | $params = $this->getParametersFromFieldValuePart($fieldValuePart); 81 | 82 | if ($pos = strpos($fieldValuePart, ';')) { 83 | $fieldValuePart = $type = trim(substr($fieldValuePart, 0, $pos)); 84 | } 85 | 86 | if (strpos($fieldValuePart, '-')) { 87 | $subtypeWhole = $format = $subtype = trim(substr($fieldValuePart, strpos($fieldValuePart, '-') + 1)); 88 | } else { 89 | $subtypeWhole = ''; 90 | $format = '*'; 91 | $subtype = '*'; 92 | } 93 | 94 | $aggregated = [ 95 | 'typeString' => trim($fieldValuePart), 96 | 'type' => $type, 97 | 'subtype' => $subtype, 98 | 'subtypeRaw' => $subtypeWhole, 99 | 'format' => $format, 100 | 'priority' => isset($params['q']) ? $params['q'] : 1, 101 | 'params' => $params, 102 | 'raw' => trim($raw), 103 | ]; 104 | 105 | return new FieldValuePart\LanguageFieldValuePart((object) $aggregated); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Header/AcceptRanges.php: -------------------------------------------------------------------------------- 1 | setRangeUnit($rangeUnit); 37 | } 38 | } 39 | 40 | public function getFieldName() 41 | { 42 | return 'Accept-Ranges'; 43 | } 44 | 45 | public function getFieldValue() 46 | { 47 | return $this->getRangeUnit(); 48 | } 49 | 50 | public function setRangeUnit($rangeUnit) 51 | { 52 | HeaderValue::assertValid($rangeUnit); 53 | $this->rangeUnit = $rangeUnit; 54 | return $this; 55 | } 56 | 57 | public function getRangeUnit() 58 | { 59 | return (string) $this->rangeUnit; 60 | } 61 | 62 | public function toString() 63 | { 64 | return 'Accept-Ranges: ' . $this->getFieldValue(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Header/Age.php: -------------------------------------------------------------------------------- 1 | setDeltaSeconds($deltaSeconds); 47 | } 48 | } 49 | 50 | /** 51 | * Get header name 52 | * 53 | * @return string 54 | */ 55 | public function getFieldName() 56 | { 57 | return 'Age'; 58 | } 59 | 60 | /** 61 | * Get header value (number of seconds) 62 | * 63 | * @return string 64 | */ 65 | public function getFieldValue() 66 | { 67 | return (string) $this->getDeltaSeconds(); 68 | } 69 | 70 | /** 71 | * Set number of seconds 72 | * 73 | * @param int $delta 74 | * @return $this 75 | */ 76 | public function setDeltaSeconds($delta) 77 | { 78 | if (! is_int($delta) && ! is_numeric($delta)) { 79 | throw new Exception\InvalidArgumentException('Invalid delta provided'); 80 | } 81 | $this->deltaSeconds = (int) $delta; 82 | return $this; 83 | } 84 | 85 | /** 86 | * Get number of seconds 87 | * 88 | * @return int 89 | */ 90 | public function getDeltaSeconds() 91 | { 92 | return $this->deltaSeconds; 93 | } 94 | 95 | /** 96 | * Return header line 97 | * In case of overflow RFC states to set value of 2147483648 (2^31) 98 | * 99 | * @return string 100 | */ 101 | public function toString() 102 | { 103 | return 'Age: ' . (($this->deltaSeconds >= PHP_INT_MAX) ? '2147483648' : $this->deltaSeconds); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Header/Allow.php: -------------------------------------------------------------------------------- 1 | false, 28 | Request::METHOD_GET => true, 29 | Request::METHOD_HEAD => false, 30 | Request::METHOD_POST => true, 31 | Request::METHOD_PUT => false, 32 | Request::METHOD_DELETE => false, 33 | Request::METHOD_TRACE => false, 34 | Request::METHOD_CONNECT => false, 35 | Request::METHOD_PATCH => false, 36 | ]; 37 | 38 | /** 39 | * Create Allow header from header line 40 | * 41 | * @param string $headerLine 42 | * @return static 43 | * @throws Exception\InvalidArgumentException 44 | */ 45 | public static function fromString($headerLine) 46 | { 47 | list($name, $value) = GenericHeader::splitHeaderLine($headerLine); 48 | 49 | // check to ensure proper header type for this factory 50 | if (strtolower($name) !== 'allow') { 51 | throw new Exception\InvalidArgumentException('Invalid header line for Allow string: "' . $name . '"'); 52 | } 53 | 54 | $header = new static(); 55 | $header->disallowMethods(array_keys($header->getAllMethods())); 56 | $header->allowMethods(explode(',', $value)); 57 | 58 | return $header; 59 | } 60 | 61 | /** 62 | * Get header name 63 | * 64 | * @return string 65 | */ 66 | public function getFieldName() 67 | { 68 | return 'Allow'; 69 | } 70 | 71 | /** 72 | * Get comma-separated list of allowed methods 73 | * 74 | * @return string 75 | */ 76 | public function getFieldValue() 77 | { 78 | return implode(', ', array_keys($this->methods, true, true)); 79 | } 80 | 81 | /** 82 | * Get list of all defined methods 83 | * 84 | * @return array 85 | */ 86 | public function getAllMethods() 87 | { 88 | return $this->methods; 89 | } 90 | 91 | /** 92 | * Get list of allowed methods 93 | * 94 | * @return array 95 | */ 96 | public function getAllowedMethods() 97 | { 98 | return array_keys($this->methods, true, true); 99 | } 100 | 101 | /** 102 | * Allow methods or list of methods 103 | * 104 | * @param array|string $allowedMethods 105 | * @return $this 106 | */ 107 | public function allowMethods($allowedMethods) 108 | { 109 | foreach ((array) $allowedMethods as $method) { 110 | $method = trim(strtoupper($method)); 111 | if (preg_match('/\s/', $method)) { 112 | throw new Exception\InvalidArgumentException(sprintf( 113 | 'Unable to whitelist method; "%s" is not a valid method', 114 | $method 115 | )); 116 | } 117 | $this->methods[$method] = true; 118 | } 119 | 120 | return $this; 121 | } 122 | 123 | /** 124 | * Disallow methods or list of methods 125 | * 126 | * @param array|string $disallowedMethods 127 | * @return $this 128 | */ 129 | public function disallowMethods($disallowedMethods) 130 | { 131 | foreach ((array) $disallowedMethods as $method) { 132 | $method = trim(strtoupper($method)); 133 | if (preg_match('/\s/', $method)) { 134 | throw new Exception\InvalidArgumentException(sprintf( 135 | 'Unable to blacklist method; "%s" is not a valid method', 136 | $method 137 | )); 138 | } 139 | $this->methods[$method] = false; 140 | } 141 | 142 | return $this; 143 | } 144 | 145 | /** 146 | * Convenience alias for @see disallowMethods() 147 | * 148 | * @param array|string $disallowedMethods 149 | * @return $this 150 | */ 151 | public function denyMethods($disallowedMethods) 152 | { 153 | return $this->disallowMethods($disallowedMethods); 154 | } 155 | 156 | /** 157 | * Check whether method is allowed 158 | * 159 | * @param string $method 160 | * @return bool 161 | */ 162 | public function isAllowedMethod($method) 163 | { 164 | $method = trim(strtoupper($method)); 165 | 166 | // disallow unknown method 167 | if (! isset($this->methods[$method])) { 168 | $this->methods[$method] = false; 169 | } 170 | 171 | return $this->methods[$method]; 172 | } 173 | 174 | /** 175 | * Return header as string 176 | * 177 | * @return string 178 | */ 179 | public function toString() 180 | { 181 | return 'Allow: ' . $this->getFieldValue(); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/Header/AuthenticationInfo.php: -------------------------------------------------------------------------------- 1 | value = $value; 42 | } 43 | } 44 | 45 | public function getFieldName() 46 | { 47 | return 'Authentication-Info'; 48 | } 49 | 50 | public function getFieldValue() 51 | { 52 | return (string) $this->value; 53 | } 54 | 55 | public function toString() 56 | { 57 | return 'Authentication-Info: ' . $this->getFieldValue(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Header/Authorization.php: -------------------------------------------------------------------------------- 1 | value = $value; 42 | } 43 | } 44 | 45 | public function getFieldName() 46 | { 47 | return 'Authorization'; 48 | } 49 | 50 | public function getFieldValue() 51 | { 52 | return (string) $this->value; 53 | } 54 | 55 | public function toString() 56 | { 57 | return 'Authorization: ' . $this->getFieldValue(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Header/CacheControl.php: -------------------------------------------------------------------------------- 1 | $value) { 53 | $header->addDirective($key, $value); 54 | } 55 | 56 | return $header; 57 | } 58 | 59 | /** 60 | * Required from HeaderDescription interface 61 | * 62 | * @return string 63 | */ 64 | public function getFieldName() 65 | { 66 | return 'Cache-Control'; 67 | } 68 | 69 | /** 70 | * Checks if the internal directives array is empty 71 | * 72 | * @return bool 73 | */ 74 | public function isEmpty() 75 | { 76 | return empty($this->directives); 77 | } 78 | 79 | /** 80 | * Add a directive 81 | * For directives like 'max-age=60', $value = '60' 82 | * For directives like 'private', use the default $value = true 83 | * 84 | * @param string $key 85 | * @param string|bool $value 86 | * @return $this 87 | */ 88 | public function addDirective($key, $value = true) 89 | { 90 | HeaderValue::assertValid($key); 91 | if (! is_bool($value)) { 92 | HeaderValue::assertValid($value); 93 | } 94 | $this->directives[$key] = $value; 95 | return $this; 96 | } 97 | 98 | /** 99 | * Check the internal directives array for a directive 100 | * 101 | * @param string $key 102 | * @return bool 103 | */ 104 | public function hasDirective($key) 105 | { 106 | return array_key_exists($key, $this->directives); 107 | } 108 | 109 | /** 110 | * Fetch the value of a directive from the internal directive array 111 | * 112 | * @param string $key 113 | * @return string|null 114 | */ 115 | public function getDirective($key) 116 | { 117 | return array_key_exists($key, $this->directives) ? $this->directives[$key] : null; 118 | } 119 | 120 | /** 121 | * Remove a directive 122 | * 123 | * @param string $key 124 | * @return $this 125 | */ 126 | public function removeDirective($key) 127 | { 128 | unset($this->directives[$key]); 129 | return $this; 130 | } 131 | 132 | /** 133 | * Assembles the directives into a comma-delimited string 134 | * 135 | * @return string 136 | */ 137 | public function getFieldValue() 138 | { 139 | $parts = []; 140 | ksort($this->directives); 141 | foreach ($this->directives as $key => $value) { 142 | if (true === $value) { 143 | $parts[] = $key; 144 | } else { 145 | if (preg_match('#[^a-zA-Z0-9._-]#', $value)) { 146 | $value = '"' . $value . '"'; 147 | } 148 | $parts[] = $key . '=' . $value; 149 | } 150 | } 151 | return implode(', ', $parts); 152 | } 153 | 154 | /** 155 | * Returns a string representation of the HTTP Cache-Control header 156 | * 157 | * @return string 158 | */ 159 | public function toString() 160 | { 161 | return 'Cache-Control: ' . $this->getFieldValue(); 162 | } 163 | 164 | /** 165 | * Internal function for parsing the value part of a 166 | * HTTP Cache-Control header 167 | * 168 | * @param string $value 169 | * @throws Exception\InvalidArgumentException 170 | * @return array 171 | */ 172 | protected static function parseValue($value) 173 | { 174 | $value = trim($value); 175 | 176 | $directives = []; 177 | 178 | // handle empty string early so we don't need a separate start state 179 | if ($value == '') { 180 | return $directives; 181 | } 182 | 183 | $lastMatch = null; 184 | 185 | state_directive: 186 | switch (static::match(['[a-zA-Z][a-zA-Z_-]*'], $value, $lastMatch)) { 187 | case 0: 188 | $directive = $lastMatch; 189 | goto state_value; 190 | // intentional fall-through 191 | 192 | default: 193 | throw new Exception\InvalidArgumentException('expected DIRECTIVE'); 194 | } 195 | 196 | state_value: 197 | switch (static::match(['="[^"]*"', '=[^",\s;]*'], $value, $lastMatch)) { 198 | case 0: 199 | $directives[$directive] = substr($lastMatch, 2, -1); 200 | goto state_separator; 201 | // intentional fall-through 202 | 203 | case 1: 204 | $directives[$directive] = rtrim(substr($lastMatch, 1)); 205 | goto state_separator; 206 | // intentional fall-through 207 | 208 | default: 209 | $directives[$directive] = true; 210 | goto state_separator; 211 | } 212 | 213 | state_separator: 214 | switch (static::match(['\s*,\s*', '$'], $value, $lastMatch)) { 215 | case 0: 216 | goto state_directive; 217 | // intentional fall-through 218 | 219 | case 1: 220 | return $directives; 221 | 222 | default: 223 | throw new Exception\InvalidArgumentException('expected SEPARATOR or END'); 224 | } 225 | } 226 | 227 | /** 228 | * Internal function used by parseValue to match tokens 229 | * 230 | * @param array $tokens 231 | * @param string $string 232 | * @param string $lastMatch 233 | * @return int 234 | */ 235 | protected static function match($tokens, &$string, &$lastMatch) 236 | { 237 | // Ensure we have a string 238 | $value = (string) $string; 239 | 240 | foreach ($tokens as $i => $token) { 241 | if (preg_match('/^' . $token . '/', $value, $matches)) { 242 | $lastMatch = $matches[0]; 243 | $string = substr($value, strlen($matches[0])); 244 | return $i; 245 | } 246 | } 247 | return -1; 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/Header/Connection.php: -------------------------------------------------------------------------------- 1 | setValue(trim($value)); 44 | 45 | return $header; 46 | } 47 | 48 | /** 49 | * Set Connection header to define persistent connection 50 | * 51 | * @param bool $flag 52 | * @return $this 53 | */ 54 | public function setPersistent($flag) 55 | { 56 | $this->value = (bool) $flag 57 | ? self::CONNECTION_KEEP_ALIVE 58 | : self::CONNECTION_CLOSE; 59 | return $this; 60 | } 61 | 62 | /** 63 | * Get whether this connection is persistent 64 | * 65 | * @return bool 66 | */ 67 | public function isPersistent() 68 | { 69 | return ($this->value === self::CONNECTION_KEEP_ALIVE); 70 | } 71 | 72 | /** 73 | * Set arbitrary header value 74 | * RFC allows any token as value, 'close' and 'keep-alive' are commonly used 75 | * 76 | * @param string $value 77 | * @return $this 78 | */ 79 | public function setValue($value) 80 | { 81 | HeaderValue::assertValid($value); 82 | $this->value = strtolower($value); 83 | return $this; 84 | } 85 | 86 | /** 87 | * Connection header name 88 | * 89 | * @return string 90 | */ 91 | public function getFieldName() 92 | { 93 | return 'Connection'; 94 | } 95 | 96 | /** 97 | * Connection header value 98 | * 99 | * @return string 100 | */ 101 | public function getFieldValue() 102 | { 103 | return $this->value; 104 | } 105 | 106 | /** 107 | * Return header line 108 | * 109 | * @return string 110 | */ 111 | public function toString() 112 | { 113 | return 'Connection: ' . $this->getFieldValue(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Header/ContentDisposition.php: -------------------------------------------------------------------------------- 1 | value = $value; 42 | } 43 | } 44 | 45 | public function getFieldName() 46 | { 47 | return 'Content-Disposition'; 48 | } 49 | 50 | public function getFieldValue() 51 | { 52 | return (string) $this->value; 53 | } 54 | 55 | public function toString() 56 | { 57 | return 'Content-Disposition: ' . $this->getFieldValue(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Header/ContentEncoding.php: -------------------------------------------------------------------------------- 1 | value = $value; 41 | } 42 | } 43 | 44 | public function getFieldName() 45 | { 46 | return 'Content-Encoding'; 47 | } 48 | 49 | public function getFieldValue() 50 | { 51 | return (string) $this->value; 52 | } 53 | 54 | public function toString() 55 | { 56 | return 'Content-Encoding: ' . $this->getFieldValue(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Header/ContentLanguage.php: -------------------------------------------------------------------------------- 1 | value = $value; 42 | } 43 | } 44 | 45 | public function getFieldName() 46 | { 47 | return 'Content-Language'; 48 | } 49 | 50 | public function getFieldValue() 51 | { 52 | return (string) $this->value; 53 | } 54 | 55 | public function toString() 56 | { 57 | return 'Content-Language: ' . $this->getFieldValue(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Header/ContentLength.php: -------------------------------------------------------------------------------- 1 | value = $value; 42 | } 43 | } 44 | 45 | public function getFieldName() 46 | { 47 | return 'Content-Length'; 48 | } 49 | 50 | public function getFieldValue() 51 | { 52 | return (string) $this->value; 53 | } 54 | 55 | public function toString() 56 | { 57 | return 'Content-Length: ' . $this->getFieldValue(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Header/ContentLocation.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'Content-MD5'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'Content-MD5: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/ContentRange.php: -------------------------------------------------------------------------------- 1 | value = $value; 42 | } 43 | } 44 | 45 | public function getFieldName() 46 | { 47 | return 'Content-Range'; 48 | } 49 | 50 | public function getFieldValue() 51 | { 52 | return (string) $this->value; 53 | } 54 | 55 | public function toString() 56 | { 57 | return 'Content-Range: ' . $this->getFieldValue(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Header/ContentSecurityPolicy.php: -------------------------------------------------------------------------------- 1 | directives; 79 | } 80 | 81 | /** 82 | * Sets the directive to consist of the source list 83 | * 84 | * Reverses http://www.w3.org/TR/CSP/#parsing-1 85 | * 86 | * @param string $name The directive name. 87 | * @param array $sources The source list. 88 | * @return $this 89 | * @throws Exception\InvalidArgumentException If the name is not a valid directive name. 90 | */ 91 | public function setDirective($name, array $sources) 92 | { 93 | if (! in_array($name, $this->validDirectiveNames, true)) { 94 | throw new Exception\InvalidArgumentException(sprintf( 95 | '%s expects a valid directive name; received "%s"', 96 | __METHOD__, 97 | (string) $name 98 | )); 99 | } 100 | 101 | if ($name === 'block-all-mixed-content' 102 | || $name === 'upgrade-insecure-requests' 103 | ) { 104 | if ($sources) { 105 | throw new Exception\InvalidArgumentException(sprintf( 106 | 'Received value for %s directive; none expected', 107 | $name 108 | )); 109 | } 110 | 111 | $this->directives[$name] = ''; 112 | return $this; 113 | } 114 | 115 | if (empty($sources)) { 116 | if ('report-uri' === $name) { 117 | if (isset($this->directives[$name])) { 118 | unset($this->directives[$name]); 119 | } 120 | return $this; 121 | } 122 | 123 | $this->directives[$name] = "'none'"; 124 | return $this; 125 | } 126 | 127 | array_walk($sources, [__NAMESPACE__ . '\HeaderValue', 'assertValid']); 128 | $this->directives[$name] = implode(' ', $sources); 129 | 130 | return $this; 131 | } 132 | 133 | /** 134 | * Create Content Security Policy header from a given header line 135 | * 136 | * @param string $headerLine The header line to parse. 137 | * @return static 138 | * @throws Exception\InvalidArgumentException If the name field in the given header line does not match. 139 | */ 140 | public static function fromString($headerLine) 141 | { 142 | $header = new static(); 143 | $headerName = $header->getFieldName(); 144 | list($name, $value) = GenericHeader::splitHeaderLine($headerLine); 145 | // Ensure the proper header name 146 | if (strcasecmp($name, $headerName) != 0) { 147 | throw new Exception\InvalidArgumentException(sprintf( 148 | 'Invalid header line for %s string: "%s"', 149 | $headerName, 150 | $name 151 | )); 152 | } 153 | // As per http://www.w3.org/TR/CSP/#parsing 154 | $tokens = explode(';', $value); 155 | foreach ($tokens as $token) { 156 | $token = trim($token); 157 | if ($token) { 158 | list($directiveName, $directiveValue) = array_pad(explode(' ', $token, 2), 2, null); 159 | if (! isset($header->directives[$directiveName])) { 160 | $header->setDirective( 161 | $directiveName, 162 | $directiveValue === null ? [] : [$directiveValue] 163 | ); 164 | } 165 | } 166 | } 167 | return $header; 168 | } 169 | 170 | /** 171 | * Get the header name 172 | * 173 | * @return string 174 | */ 175 | public function getFieldName() 176 | { 177 | return 'Content-Security-Policy'; 178 | } 179 | 180 | /** 181 | * Get the header value 182 | * 183 | * @return string 184 | */ 185 | public function getFieldValue() 186 | { 187 | $directives = []; 188 | foreach ($this->directives as $name => $value) { 189 | $directives[] = sprintf('%s %s;', $name, $value); 190 | } 191 | return str_replace(' ;', ';', implode(' ', $directives)); 192 | } 193 | 194 | /** 195 | * Return the header as a string 196 | * 197 | * @return string 198 | */ 199 | public function toString() 200 | { 201 | return sprintf('%s: %s', $this->getFieldName(), $this->getFieldValue()); 202 | } 203 | 204 | public function toStringMultipleHeaders(array $headers) 205 | { 206 | $strings = [$this->toString()]; 207 | foreach ($headers as $header) { 208 | if (! $header instanceof ContentSecurityPolicy) { 209 | throw new Exception\RuntimeException( 210 | 'The ContentSecurityPolicy multiple header implementation can only' 211 | . ' accept an array of ContentSecurityPolicy headers' 212 | ); 213 | } 214 | $strings[] = $header->toString(); 215 | } 216 | 217 | return implode("\r\n", $strings) . "\r\n"; 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/Header/ContentTransferEncoding.php: -------------------------------------------------------------------------------- 1 | value = $value; 42 | } 43 | } 44 | 45 | public function getFieldName() 46 | { 47 | return 'Content-Transfer-Encoding'; 48 | } 49 | 50 | public function getFieldValue() 51 | { 52 | return (string) $this->value; 53 | } 54 | 55 | public function toString() 56 | { 57 | return 'Content-Transfer-Encoding: ' . $this->getFieldValue(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Header/Cookie.php: -------------------------------------------------------------------------------- 1 | getName(), $nvPairs)) { 33 | throw new Exception\InvalidArgumentException(sprintf( 34 | 'Two cookies with the same name were provided to %s', 35 | __METHOD__ 36 | )); 37 | } 38 | 39 | $nvPairs[$setCookie->getName()] = $setCookie->getValue(); 40 | } 41 | 42 | return new static($nvPairs); 43 | } 44 | 45 | public static function fromString($headerLine) 46 | { 47 | $header = new static(); 48 | 49 | list($name, $value) = GenericHeader::splitHeaderLine($headerLine); 50 | 51 | // check to ensure proper header type for this factory 52 | if (strtolower($name) !== 'cookie') { 53 | throw new Exception\InvalidArgumentException('Invalid header line for Server string: "' . $name . '"'); 54 | } 55 | 56 | $nvPairs = preg_split('#;\s*#', $value); 57 | 58 | $arrayInfo = []; 59 | foreach ($nvPairs as $nvPair) { 60 | $parts = explode('=', $nvPair, 2); 61 | if (count($parts) != 2) { 62 | throw new Exception\RuntimeException('Malformed Cookie header found'); 63 | } 64 | list($name, $value) = $parts; 65 | $arrayInfo[$name] = urldecode($value); 66 | } 67 | 68 | $header->exchangeArray($arrayInfo); 69 | 70 | return $header; 71 | } 72 | 73 | public function __construct(array $array = []) 74 | { 75 | parent::__construct($array, ArrayObject::ARRAY_AS_PROPS); 76 | } 77 | 78 | /** 79 | * @param bool $encodeValue 80 | * 81 | * @return $this 82 | */ 83 | public function setEncodeValue($encodeValue) 84 | { 85 | $this->encodeValue = (bool) $encodeValue; 86 | return $this; 87 | } 88 | 89 | /** 90 | * @return bool 91 | */ 92 | public function getEncodeValue() 93 | { 94 | return $this->encodeValue; 95 | } 96 | 97 | public function getFieldName() 98 | { 99 | return 'Cookie'; 100 | } 101 | 102 | public function getFieldValue() 103 | { 104 | $nvPairs = []; 105 | 106 | foreach ($this->flattenCookies($this) as $name => $value) { 107 | $nvPairs[] = $name . '=' . (($this->encodeValue) ? urlencode($value) : $value); 108 | } 109 | 110 | return implode('; ', $nvPairs); 111 | } 112 | 113 | protected function flattenCookies($data, $prefix = null) 114 | { 115 | $result = []; 116 | foreach ($data as $key => $value) { 117 | $key = $prefix ? $prefix . '[' . $key . ']' : $key; 118 | if (is_array($value)) { 119 | $result = array_merge($result, $this->flattenCookies($value, $key)); 120 | } else { 121 | $result[$key] = $value; 122 | } 123 | } 124 | 125 | return $result; 126 | } 127 | 128 | public function toString() 129 | { 130 | return 'Cookie: ' . $this->getFieldValue(); 131 | } 132 | 133 | /** 134 | * Get the cookie as a string, suitable for sending as a "Cookie" header in an 135 | * HTTP request 136 | * 137 | * @return string 138 | */ 139 | public function __toString() 140 | { 141 | return $this->toString(); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Header/Date.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'Etag'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'Etag: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/Exception/DomainException.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'Expect'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'Expect: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/Expires.php: -------------------------------------------------------------------------------- 1 | directives; 83 | } 84 | 85 | /** 86 | * Sets the directive to consist of the source list 87 | * 88 | * @param string $name The directive name. 89 | * @param string[] $sources The source list. 90 | * @return $this 91 | * @throws Exception\InvalidArgumentException If the name is not a valid directive name. 92 | */ 93 | public function setDirective($name, array $sources) 94 | { 95 | if (! in_array($name, $this->validDirectiveNames, true)) { 96 | throw new Exception\InvalidArgumentException(sprintf( 97 | '%s expects a valid directive name; received "%s"', 98 | __METHOD__, 99 | (string) $name 100 | )); 101 | } 102 | if (empty($sources)) { 103 | $this->directives[$name] = "'none'"; 104 | return $this; 105 | } 106 | 107 | array_walk($sources, [__NAMESPACE__ . '\HeaderValue', 'assertValid']); 108 | 109 | $this->directives[$name] = implode(' ', $sources); 110 | return $this; 111 | } 112 | 113 | /** 114 | * Create Feature Policy header from a given header line 115 | * 116 | * @param string $headerLine The header line to parse. 117 | * @return static 118 | * @throws Exception\InvalidArgumentException If the name field in the given header line does not match. 119 | */ 120 | public static function fromString($headerLine) 121 | { 122 | $header = new static(); 123 | $headerName = $header->getFieldName(); 124 | list($name, $value) = GenericHeader::splitHeaderLine($headerLine); 125 | // Ensure the proper header name 126 | if (strcasecmp($name, $headerName) !== 0) { 127 | throw new Exception\InvalidArgumentException(sprintf( 128 | 'Invalid header line for %s string: "%s"', 129 | $headerName, 130 | $name 131 | )); 132 | } 133 | // As per https://w3c.github.io/webappsec-feature-policy/#algo-parse-policy-directive 134 | $tokens = explode(';', $value); 135 | foreach ($tokens as $token) { 136 | $token = trim($token); 137 | if ($token) { 138 | list($directiveName, $directiveValue) = array_pad(explode(' ', $token, 2), 2, null); 139 | if (! isset($header->directives[$directiveName])) { 140 | $header->setDirective( 141 | $directiveName, 142 | $directiveValue === null ? [] : [$directiveValue] 143 | ); 144 | } 145 | } 146 | } 147 | 148 | return $header; 149 | } 150 | 151 | /** 152 | * Get the header name 153 | * 154 | * @return string 155 | */ 156 | public function getFieldName() 157 | { 158 | return 'Feature-Policy'; 159 | } 160 | 161 | /** 162 | * Get the header value 163 | * 164 | * @return string 165 | */ 166 | public function getFieldValue() 167 | { 168 | $directives = []; 169 | foreach ($this->directives as $name => $value) { 170 | $directives[] = sprintf('%s %s;', $name, $value); 171 | } 172 | return implode(' ', $directives); 173 | } 174 | 175 | /** 176 | * Return the header as a string 177 | * 178 | * @return string 179 | */ 180 | public function toString() 181 | { 182 | return sprintf('%s: %s', $this->getFieldName(), $this->getFieldValue()); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/Header/From.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'From'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'From: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/GenericHeader.php: -------------------------------------------------------------------------------- 1 | setFieldName($fieldName); 71 | } 72 | 73 | if ($fieldValue !== null) { 74 | $this->setFieldValue($fieldValue); 75 | } 76 | } 77 | 78 | /** 79 | * Set header field name 80 | * 81 | * @param string $fieldName 82 | * @return $this 83 | * @throws Exception\InvalidArgumentException If the name does not match with RFC 2616 format. 84 | */ 85 | public function setFieldName($fieldName) 86 | { 87 | if (! is_string($fieldName) || empty($fieldName)) { 88 | throw new Exception\InvalidArgumentException('Header name must be a string'); 89 | } 90 | 91 | /* 92 | * Following RFC 7230 section 3.2 93 | * 94 | * header-field = field-name ":" [ field-value ] 95 | * field-name = token 96 | * token = 1*tchar 97 | * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / 98 | * "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA 99 | */ 100 | if (! preg_match('/^[!#$%&\'*+\-\.\^_`|~0-9a-zA-Z]+$/', $fieldName)) { 101 | throw new Exception\InvalidArgumentException( 102 | 'Header name must be a valid RFC 7230 (section 3.2) field-name.' 103 | ); 104 | } 105 | 106 | $this->fieldName = $fieldName; 107 | return $this; 108 | } 109 | 110 | /** 111 | * Retrieve header field name 112 | * 113 | * @return string 114 | */ 115 | public function getFieldName() 116 | { 117 | return $this->fieldName; 118 | } 119 | 120 | /** 121 | * Set header field value 122 | * 123 | * @param string $fieldValue 124 | * @return $this 125 | */ 126 | public function setFieldValue($fieldValue) 127 | { 128 | $fieldValue = (string) $fieldValue; 129 | HeaderValue::assertValid($fieldValue); 130 | 131 | if (preg_match('/^\s+$/', $fieldValue)) { 132 | $fieldValue = ''; 133 | } 134 | 135 | $this->fieldValue = $fieldValue; 136 | return $this; 137 | } 138 | 139 | /** 140 | * Retrieve header field value 141 | * 142 | * @return string 143 | */ 144 | public function getFieldValue() 145 | { 146 | return $this->fieldValue; 147 | } 148 | 149 | /** 150 | * Cast to string as a well formed HTTP header line 151 | * 152 | * Returns in form of "NAME: VALUE\r\n" 153 | * 154 | * @return string 155 | */ 156 | public function toString() 157 | { 158 | return $this->getFieldName() . ': ' . $this->getFieldValue(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/Header/GenericMultiHeader.php: -------------------------------------------------------------------------------- 1 | getFieldName(); 31 | $values = [$this->getFieldValue()]; 32 | foreach ($headers as $header) { 33 | if (! $header instanceof static) { 34 | throw new Exception\InvalidArgumentException( 35 | 'This method toStringMultipleHeaders was expecting an array of headers of the same type' 36 | ); 37 | } 38 | $values[] = $header->getFieldValue(); 39 | } 40 | return $name . ': ' . implode(',', $values) . "\r\n"; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Header/HeaderInterface.php: -------------------------------------------------------------------------------- 1 | 254 48 | ) { 49 | continue; 50 | } 51 | 52 | $string .= $value[$i]; 53 | } 54 | 55 | return $string; 56 | } 57 | 58 | /** 59 | * Validate a header value. 60 | * 61 | * Per RFC 7230, only VISIBLE ASCII characters, spaces, and horizontal 62 | * tabs are allowed in values; only one whitespace character is allowed 63 | * between visible characters. 64 | * 65 | * @see http://en.wikipedia.org/wiki/HTTP_response_splitting 66 | * @param string $value 67 | * @return bool 68 | */ 69 | public static function isValid($value) 70 | { 71 | $value = (string) $value; 72 | $length = strlen($value); 73 | for ($i = 0; $i < $length; $i += 1) { 74 | $ascii = ord($value[$i]); 75 | 76 | // Non-visible, non-whitespace characters 77 | // 9 === horizontal tab 78 | // 32-126, 128-254 === visible 79 | // 127 === DEL 80 | // 255 === null byte 81 | if (($ascii < 32 && $ascii !== 9) 82 | || $ascii === 127 83 | || $ascii > 254 84 | ) { 85 | return false; 86 | } 87 | } 88 | 89 | return true; 90 | } 91 | 92 | /** 93 | * Assert a header value is valid. 94 | * 95 | * @param string $value 96 | * @throws Exception\RuntimeException for invalid values 97 | * @return void 98 | */ 99 | public static function assertValid($value) 100 | { 101 | if (! self::isValid($value)) { 102 | throw new Exception\InvalidArgumentException('Invalid header value'); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Header/Host.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'Host'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'Host: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/IfMatch.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'If-Match'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'If-Match: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/IfModifiedSince.php: -------------------------------------------------------------------------------- 1 | value = $value; 42 | } 43 | } 44 | 45 | public function getFieldName() 46 | { 47 | return 'If-None-Match'; 48 | } 49 | 50 | public function getFieldValue() 51 | { 52 | return (string) $this->value; 53 | } 54 | 55 | public function toString() 56 | { 57 | return 'If-None-Match: ' . $this->getFieldValue(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Header/IfRange.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'If-Range'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'If-Range: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/IfUnmodifiedSince.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'Keep-Alive'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'Keep-Alive: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/LastModified.php: -------------------------------------------------------------------------------- 1 | value = $value; 42 | } 43 | } 44 | 45 | public function getFieldName() 46 | { 47 | return 'Max-Forwards'; 48 | } 49 | 50 | public function getFieldValue() 51 | { 52 | return (string) $this->value; 53 | } 54 | 55 | public function toString() 56 | { 57 | return 'Max-Forwards: ' . $this->getFieldValue(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Header/MultipleHeaderInterface.php: -------------------------------------------------------------------------------- 1 | isValid()) { 34 | throw new Exception\InvalidArgumentException('Invalid header value for Origin key: "' . $name . '"'); 35 | } 36 | 37 | return new static($value); 38 | } 39 | 40 | /** 41 | * @param string|null $value 42 | */ 43 | public function __construct($value = null) 44 | { 45 | if ($value !== null) { 46 | HeaderValue::assertValid($value); 47 | $this->value = $value; 48 | } 49 | } 50 | 51 | public function getFieldName() 52 | { 53 | return 'Origin'; 54 | } 55 | 56 | public function getFieldValue() 57 | { 58 | return (string) $this->value; 59 | } 60 | 61 | public function toString() 62 | { 63 | return 'Origin: ' . $this->getFieldValue(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Header/Pragma.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'Pragma'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'Pragma: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/ProxyAuthenticate.php: -------------------------------------------------------------------------------- 1 | value = $value; 42 | } 43 | } 44 | 45 | public function getFieldName() 46 | { 47 | return 'Proxy-Authenticate'; 48 | } 49 | 50 | public function getFieldValue() 51 | { 52 | return (string) $this->value; 53 | } 54 | 55 | public function toString() 56 | { 57 | return 'Proxy-Authenticate: ' . $this->getFieldValue(); 58 | } 59 | 60 | public function toStringMultipleHeaders(array $headers) 61 | { 62 | $strings = [$this->toString()]; 63 | foreach ($headers as $header) { 64 | if (! $header instanceof ProxyAuthenticate) { 65 | throw new Exception\RuntimeException( 66 | 'The ProxyAuthenticate multiple header implementation can only accept' 67 | . ' an array of ProxyAuthenticate headers' 68 | ); 69 | } 70 | $strings[] = $header->toString(); 71 | } 72 | return implode("\r\n", $strings); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Header/ProxyAuthorization.php: -------------------------------------------------------------------------------- 1 | value = $value; 42 | } 43 | } 44 | 45 | public function getFieldName() 46 | { 47 | return 'Proxy-Authorization'; 48 | } 49 | 50 | public function getFieldValue() 51 | { 52 | return (string) $this->value; 53 | } 54 | 55 | public function toString() 56 | { 57 | return 'Proxy-Authorization: ' . $this->getFieldValue(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Header/Range.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'Range'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'Range: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/Referer.php: -------------------------------------------------------------------------------- 1 | uri->setFragment(null); 30 | 31 | return $this; 32 | } 33 | 34 | /** 35 | * Return header name 36 | * 37 | * @return string 38 | */ 39 | public function getFieldName() 40 | { 41 | return 'Referer'; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Header/Refresh.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'Refresh'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'Refresh: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/RetryAfter.php: -------------------------------------------------------------------------------- 1 | getFieldName())) { 40 | throw new Exception\InvalidArgumentException( 41 | 'Invalid header line for "' . $dateHeader->getFieldName() . '" header string' 42 | ); 43 | } 44 | 45 | if (is_numeric($date)) { 46 | $dateHeader->setDeltaSeconds($date); 47 | } else { 48 | $dateHeader->setDate($date); 49 | } 50 | 51 | return $dateHeader; 52 | } 53 | 54 | /** 55 | * Set number of seconds 56 | * 57 | * @param int $delta 58 | * @return $this 59 | */ 60 | public function setDeltaSeconds($delta) 61 | { 62 | $this->deltaSeconds = (int) $delta; 63 | return $this; 64 | } 65 | 66 | /** 67 | * Get number of seconds 68 | * 69 | * @return int 70 | */ 71 | public function getDeltaSeconds() 72 | { 73 | return $this->deltaSeconds; 74 | } 75 | 76 | /** 77 | * Get header name 78 | * 79 | * @return string 80 | */ 81 | public function getFieldName() 82 | { 83 | return 'Retry-After'; 84 | } 85 | 86 | /** 87 | * Returns date if it's set, or number of seconds 88 | * 89 | * @return int|string 90 | */ 91 | public function getFieldValue() 92 | { 93 | return ($this->date === null) ? $this->deltaSeconds : $this->getDate(); 94 | } 95 | 96 | /** 97 | * Return header line 98 | * 99 | * @return string 100 | */ 101 | public function toString() 102 | { 103 | return 'Retry-After: ' . $this->getFieldValue(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Header/Server.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'Server'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'Server: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/TE.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'TE'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'TE: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/Trailer.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'Trailer'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'Trailer: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/TransferEncoding.php: -------------------------------------------------------------------------------- 1 | value = $value; 41 | } 42 | } 43 | 44 | public function getFieldName() 45 | { 46 | return 'Transfer-Encoding'; 47 | } 48 | 49 | public function getFieldValue() 50 | { 51 | return (string) $this->value; 52 | } 53 | 54 | public function toString() 55 | { 56 | return 'Transfer-Encoding: ' . $this->getFieldValue(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Header/Upgrade.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'Upgrade'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'Upgrade: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/UserAgent.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'User-Agent'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'User-Agent: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/Vary.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'Vary'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'Vary: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/Via.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'Via'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'Via: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Header/WWWAuthenticate.php: -------------------------------------------------------------------------------- 1 | value = $value; 42 | } 43 | } 44 | 45 | public function getFieldName() 46 | { 47 | return 'WWW-Authenticate'; 48 | } 49 | 50 | public function getFieldValue() 51 | { 52 | return (string) $this->value; 53 | } 54 | 55 | public function toString() 56 | { 57 | return 'WWW-Authenticate: ' . $this->getFieldValue(); 58 | } 59 | 60 | public function toStringMultipleHeaders(array $headers) 61 | { 62 | $strings = [$this->toString()]; 63 | foreach ($headers as $header) { 64 | if (! $header instanceof WWWAuthenticate) { 65 | throw new Exception\RuntimeException( 66 | 'The WWWAuthenticate multiple header implementation can only' 67 | . ' accept an array of WWWAuthenticate headers' 68 | ); 69 | } 70 | $strings[] = $header->toString(); 71 | } 72 | return implode("\r\n", $strings); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Header/Warning.php: -------------------------------------------------------------------------------- 1 | value = $value; 39 | } 40 | } 41 | 42 | public function getFieldName() 43 | { 44 | return 'Warning'; 45 | } 46 | 47 | public function getFieldValue() 48 | { 49 | return (string) $this->value; 50 | } 51 | 52 | public function toString() 53 | { 54 | return 'Warning: ' . $this->getFieldValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/HeaderLoader.php: -------------------------------------------------------------------------------- 1 | Header\Accept::class, 22 | 'acceptcharset' => Header\AcceptCharset::class, 23 | 'acceptencoding' => Header\AcceptEncoding::class, 24 | 'acceptlanguage' => Header\AcceptLanguage::class, 25 | 'acceptranges' => Header\AcceptRanges::class, 26 | 'age' => Header\Age::class, 27 | 'allow' => Header\Allow::class, 28 | 'authenticationinfo' => Header\AuthenticationInfo::class, 29 | 'authorization' => Header\Authorization::class, 30 | 'cachecontrol' => Header\CacheControl::class, 31 | 'connection' => Header\Connection::class, 32 | 'contentdisposition' => Header\ContentDisposition::class, 33 | 'contentencoding' => Header\ContentEncoding::class, 34 | 'contentlanguage' => Header\ContentLanguage::class, 35 | 'contentlength' => Header\ContentLength::class, 36 | 'contentlocation' => Header\ContentLocation::class, 37 | 'contentmd5' => Header\ContentMD5::class, 38 | 'contentrange' => Header\ContentRange::class, 39 | 'contentsecuritypolicy' => Header\ContentSecurityPolicy::class, 40 | 'contenttransferencoding' => Header\ContentTransferEncoding::class, 41 | 'contenttype' => Header\ContentType::class, 42 | 'cookie' => Header\Cookie::class, 43 | 'date' => Header\Date::class, 44 | 'etag' => Header\Etag::class, 45 | 'expect' => Header\Expect::class, 46 | 'expires' => Header\Expires::class, 47 | 'featurepolicy' => Header\FeaturePolicy::class, 48 | 'from' => Header\From::class, 49 | 'host' => Header\Host::class, 50 | 'ifmatch' => Header\IfMatch::class, 51 | 'ifmodifiedsince' => Header\IfModifiedSince::class, 52 | 'ifnonematch' => Header\IfNoneMatch::class, 53 | 'ifrange' => Header\IfRange::class, 54 | 'ifunmodifiedsince' => Header\IfUnmodifiedSince::class, 55 | 'keepalive' => Header\KeepAlive::class, 56 | 'lastmodified' => Header\LastModified::class, 57 | 'location' => Header\Location::class, 58 | 'maxforwards' => Header\MaxForwards::class, 59 | 'origin' => Header\Origin::class, 60 | 'pragma' => Header\Pragma::class, 61 | 'proxyauthenticate' => Header\ProxyAuthenticate::class, 62 | 'proxyauthorization' => Header\ProxyAuthorization::class, 63 | 'range' => Header\Range::class, 64 | 'referer' => Header\Referer::class, 65 | 'refresh' => Header\Refresh::class, 66 | 'retryafter' => Header\RetryAfter::class, 67 | 'server' => Header\Server::class, 68 | 'setcookie' => Header\SetCookie::class, 69 | 'te' => Header\TE::class, 70 | 'trailer' => Header\Trailer::class, 71 | 'transferencoding' => Header\TransferEncoding::class, 72 | 'upgrade' => Header\Upgrade::class, 73 | 'useragent' => Header\UserAgent::class, 74 | 'vary' => Header\Vary::class, 75 | 'via' => Header\Via::class, 76 | 'warning' => Header\Warning::class, 77 | 'wwwauthenticate' => Header\WWWAuthenticate::class, 78 | ]; 79 | } 80 | -------------------------------------------------------------------------------- /src/PhpEnvironment/RemoteAddress.php: -------------------------------------------------------------------------------- 1 | useProxy = $useProxy; 53 | return $this; 54 | } 55 | 56 | /** 57 | * Checks proxy handling setting. 58 | * 59 | * @return bool Current setting value. 60 | */ 61 | public function getUseProxy() 62 | { 63 | return $this->useProxy; 64 | } 65 | 66 | /** 67 | * Set list of trusted proxy addresses 68 | * 69 | * @param array $trustedProxies 70 | * @return $this 71 | */ 72 | public function setTrustedProxies(array $trustedProxies) 73 | { 74 | $this->trustedProxies = $trustedProxies; 75 | return $this; 76 | } 77 | 78 | /** 79 | * Set the header to introspect for proxy IPs 80 | * 81 | * @param string $header 82 | * @return $this 83 | */ 84 | public function setProxyHeader($header = 'X-Forwarded-For') 85 | { 86 | $this->proxyHeader = $this->normalizeProxyHeader($header); 87 | return $this; 88 | } 89 | 90 | /** 91 | * Returns client IP address. 92 | * 93 | * @return string IP address. 94 | */ 95 | public function getIpAddress() 96 | { 97 | $ip = $this->getIpAddressFromProxy(); 98 | if ($ip) { 99 | return $ip; 100 | } 101 | 102 | // direct IP address 103 | if (isset($_SERVER['REMOTE_ADDR'])) { 104 | return $_SERVER['REMOTE_ADDR']; 105 | } 106 | 107 | return ''; 108 | } 109 | 110 | /** 111 | * Attempt to get the IP address for a proxied client 112 | * 113 | * @see http://tools.ietf.org/html/draft-ietf-appsawg-http-forwarded-10#section-5.2 114 | * @return false|string 115 | */ 116 | protected function getIpAddressFromProxy() 117 | { 118 | if (! $this->useProxy 119 | || (isset($_SERVER['REMOTE_ADDR']) && ! in_array($_SERVER['REMOTE_ADDR'], $this->trustedProxies)) 120 | ) { 121 | return false; 122 | } 123 | 124 | $header = $this->proxyHeader; 125 | if (! isset($_SERVER[$header]) || empty($_SERVER[$header])) { 126 | return false; 127 | } 128 | 129 | // Extract IPs 130 | $ips = explode(',', $_SERVER[$header]); 131 | // trim, so we can compare against trusted proxies properly 132 | $ips = array_map('trim', $ips); 133 | // remove trusted proxy IPs 134 | $ips = array_diff($ips, $this->trustedProxies); 135 | 136 | // Any left? 137 | if (empty($ips)) { 138 | return false; 139 | } 140 | 141 | // Since we've removed any known, trusted proxy servers, the right-most 142 | // address represents the first IP we do not know about -- i.e., we do 143 | // not know if it is a proxy server, or a client. As such, we treat it 144 | // as the originating IP. 145 | // @see http://en.wikipedia.org/wiki/X-Forwarded-For 146 | $ip = array_pop($ips); 147 | return $ip; 148 | } 149 | 150 | /** 151 | * Normalize a header string 152 | * 153 | * Normalizes a header string to a format that is compatible with 154 | * $_SERVER 155 | * 156 | * @param string $header 157 | * @return string 158 | */ 159 | protected function normalizeProxyHeader($header) 160 | { 161 | $header = strtoupper($header); 162 | $header = str_replace('-', '_', $header); 163 | if (0 !== strpos($header, 'HTTP_')) { 164 | $header = 'HTTP_' . $header; 165 | } 166 | return $header; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/PhpEnvironment/Response.php: -------------------------------------------------------------------------------- 1 | version) { 40 | $this->version = $this->detectVersion(); 41 | } 42 | return $this->version; 43 | } 44 | 45 | /** 46 | * Detect the current used protocol version. 47 | * If detection failed it falls back to version 1.0. 48 | * 49 | * @return string 50 | */ 51 | protected function detectVersion() 52 | { 53 | if (isset($_SERVER['SERVER_PROTOCOL']) && $_SERVER['SERVER_PROTOCOL'] == 'HTTP/1.1') { 54 | return self::VERSION_11; 55 | } 56 | 57 | return self::VERSION_10; 58 | } 59 | 60 | /** 61 | * @return bool 62 | */ 63 | public function headersSent() 64 | { 65 | return headers_sent(); 66 | } 67 | 68 | /** 69 | * @return bool 70 | */ 71 | public function contentSent() 72 | { 73 | return $this->contentSent; 74 | } 75 | 76 | /** 77 | * Send HTTP headers 78 | * 79 | * @return $this 80 | */ 81 | public function sendHeaders() 82 | { 83 | if ($this->headersSent()) { 84 | return $this; 85 | } 86 | 87 | $status = $this->renderStatusLine(); 88 | header($status); 89 | 90 | /** @var \Zend\Http\Header\HeaderInterface $header */ 91 | foreach ($this->getHeaders() as $header) { 92 | if ($header instanceof MultipleHeaderInterface) { 93 | header($header->toString(), false); 94 | continue; 95 | } 96 | header($header->toString()); 97 | } 98 | 99 | $this->headersSent = true; 100 | return $this; 101 | } 102 | 103 | /** 104 | * Send content 105 | * 106 | * @return $this 107 | */ 108 | public function sendContent() 109 | { 110 | if ($this->contentSent()) { 111 | return $this; 112 | } 113 | 114 | echo $this->getContent(); 115 | $this->contentSent = true; 116 | return $this; 117 | } 118 | 119 | /** 120 | * Send HTTP response 121 | * 122 | * @return $this 123 | */ 124 | public function send() 125 | { 126 | $this->sendHeaders() 127 | ->sendContent(); 128 | return $this; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Response/Stream.php: -------------------------------------------------------------------------------- 1 | contentLength = $contentLength; 64 | } 65 | 66 | /** 67 | * Get content length 68 | * 69 | * @return int|null 70 | */ 71 | public function getContentLength() 72 | { 73 | return $this->contentLength; 74 | } 75 | 76 | /** 77 | * Get the response as stream 78 | * 79 | * @return resource 80 | */ 81 | public function getStream() 82 | { 83 | return $this->stream; 84 | } 85 | 86 | /** 87 | * Set the response stream 88 | * 89 | * @param resource $stream 90 | * @return $this 91 | */ 92 | public function setStream($stream) 93 | { 94 | $this->stream = $stream; 95 | return $this; 96 | } 97 | 98 | /** 99 | * Get the cleanup trigger 100 | * 101 | * @return bool 102 | */ 103 | public function getCleanup() 104 | { 105 | return $this->cleanup; 106 | } 107 | 108 | /** 109 | * Set the cleanup trigger 110 | * 111 | * @param bool $cleanup 112 | */ 113 | public function setCleanup($cleanup = true) 114 | { 115 | $this->cleanup = $cleanup; 116 | } 117 | 118 | /** 119 | * Get file name associated with the stream 120 | * 121 | * @return string 122 | */ 123 | public function getStreamName() 124 | { 125 | return $this->streamName; 126 | } 127 | 128 | /** 129 | * Set file name associated with the stream 130 | * 131 | * @param string $streamName Name to set 132 | * @return $this 133 | */ 134 | public function setStreamName($streamName) 135 | { 136 | $this->streamName = $streamName; 137 | return $this; 138 | } 139 | 140 | /** 141 | * Create a new Zend\Http\Response\Stream object from a stream 142 | * 143 | * @param string $responseString 144 | * @param resource $stream 145 | * @return $this 146 | * @throws Exception\InvalidArgumentException 147 | * @throws Exception\OutOfRangeException 148 | */ 149 | public static function fromStream($responseString, $stream) 150 | { 151 | if (! is_resource($stream) || get_resource_type($stream) !== 'stream') { 152 | throw new Exception\InvalidArgumentException('A valid stream is required'); 153 | } 154 | 155 | $headerComplete = false; 156 | $headersString = ''; 157 | $responseArray = []; 158 | 159 | if ($responseString) { 160 | $responseArray = explode("\n", $responseString); 161 | } 162 | 163 | while (! empty($responseArray)) { 164 | $nextLine = array_shift($responseArray); 165 | $headersString .= $nextLine . "\n"; 166 | $nextLineTrimmed = trim($nextLine); 167 | if ($nextLineTrimmed == '') { 168 | $headerComplete = true; 169 | break; 170 | } 171 | } 172 | 173 | if (! $headerComplete) { 174 | while (false !== ($nextLine = fgets($stream))) { 175 | $headersString .= trim($nextLine) . "\r\n"; 176 | if ($nextLine == "\r\n" || $nextLine == "\n") { 177 | $headerComplete = true; 178 | break; 179 | } 180 | } 181 | } 182 | 183 | if (! $headerComplete) { 184 | throw new Exception\OutOfRangeException('End of header not found'); 185 | } 186 | 187 | /** @var Stream $response */ 188 | $response = static::fromString($headersString); 189 | 190 | if (is_resource($stream)) { 191 | $response->setStream($stream); 192 | } 193 | 194 | if (! empty($responseArray)) { 195 | $response->content = implode("\n", $responseArray); 196 | } 197 | 198 | $headers = $response->getHeaders(); 199 | foreach ($headers as $header) { 200 | if ($header instanceof \Zend\Http\Header\ContentLength) { 201 | $response->setContentLength((int) $header->getFieldValue()); 202 | $contentLength = $response->getContentLength(); 203 | if (strlen($response->content) > $contentLength) { 204 | throw new Exception\OutOfRangeException(sprintf( 205 | 'Too much content was extracted from the stream (%d instead of %d bytes)', 206 | strlen($response->content), 207 | $contentLength 208 | )); 209 | } 210 | break; 211 | } 212 | } 213 | 214 | return $response; 215 | } 216 | 217 | /** 218 | * Get the response body as string 219 | * 220 | * This method returns the body of the HTTP response (the content), as it 221 | * should be in it's readable version - that is, after decoding it (if it 222 | * was decoded), deflating it (if it was gzip compressed), etc. 223 | * 224 | * If you want to get the raw body (as transferred on wire) use 225 | * $this->getRawBody() instead. 226 | * 227 | * @return string 228 | */ 229 | public function getBody() 230 | { 231 | if ($this->stream !== null) { 232 | $this->readStream(); 233 | } 234 | return parent::getBody(); 235 | } 236 | 237 | /** 238 | * Get the raw response body (as transferred "on wire") as string 239 | * 240 | * If the body is encoded (with Transfer-Encoding, not content-encoding - 241 | * IE "chunked" body), gzip compressed, etc. it will not be decoded. 242 | * 243 | * @return string 244 | */ 245 | public function getRawBody() 246 | { 247 | if ($this->stream) { 248 | $this->readStream(); 249 | } 250 | return $this->content; 251 | } 252 | 253 | /** 254 | * Read stream content and return it as string 255 | * 256 | * Function reads the remainder of the body from the stream and closes the stream. 257 | * 258 | * @return string 259 | */ 260 | protected function readStream() 261 | { 262 | $contentLength = $this->getContentLength(); 263 | if (null !== $contentLength) { 264 | $bytes = $contentLength - $this->contentStreamed; 265 | } else { 266 | $bytes = -1; // Read the whole buffer 267 | } 268 | 269 | if (! is_resource($this->stream) || $bytes == 0) { 270 | return ''; 271 | } 272 | 273 | $this->content .= stream_get_contents($this->stream, $bytes); 274 | $this->contentStreamed += strlen($this->content); 275 | 276 | if ($this->getContentLength() == $this->contentStreamed) { 277 | $this->stream = null; 278 | } 279 | } 280 | 281 | /** 282 | * Destructor 283 | */ 284 | public function __destruct() 285 | { 286 | if (is_resource($this->stream)) { 287 | $this->stream = null; //Could be listened by others 288 | } 289 | if ($this->cleanup) { 290 | ErrorHandler::start(E_WARNING); 291 | unlink($this->streamName); 292 | ErrorHandler::stop(); 293 | } 294 | } 295 | } 296 | --------------------------------------------------------------------------------