├── .gitignore ├── .travis.yml ├── README.md ├── build └── travis │ ├── apache │ └── virtual.host │ └── scripts │ ├── apache2-configure.sh │ ├── apache2-vhost.sh │ └── apt-get.sh ├── composer.json ├── phpunit.travis.xml ├── phpunit.xml.dist ├── src ├── AbstractMessage.php ├── AbstractRequest.php ├── Helper │ ├── HeaderHelper.php │ ├── ResponseHelper.php │ ├── ServerHelper.php │ └── StreamHelper.php ├── HttpClient.php ├── HttpClientInterface.php ├── Request.php ├── Response.php ├── ServerRequest.php ├── Stream │ ├── PhpInputStream.php │ ├── Stream.php │ └── StringStream.php ├── Transport │ ├── AbstractTransport.php │ ├── CurlTransport.php │ ├── StreamTransport.php │ ├── TransportInterface.php │ └── cacert.pem ├── UploadedFile.php └── Uri │ ├── AbstractUri.php │ ├── PsrUri.php │ ├── Uri.php │ ├── UriHelper.php │ └── UriInterface.php └── test ├── AbstractBaseTestCase.php ├── AbstractMessageTest.php ├── AbstractRequestTest.php ├── HttpClientTest.php ├── Mock └── MockTransport.php ├── RequestTest.php ├── ResponseTest.php ├── ServerRequestTest.php ├── Stream ├── StreamTest.php └── StringStreamTest.php ├── Stub ├── StubMessage.php ├── StubRequest.php ├── download_stub.txt └── http_stub.php ├── Transport ├── AbstractTransportTest.php ├── CurlTransportTest.php ├── StreamTransportTest.php └── downloaded.tmp ├── UploadedFileTest.php └── Uri ├── PsrUriTest.php ├── UriHelperTest.php └── UriTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Development system files # 2 | .* 3 | !/.gitignore 4 | !/.travis.yml 5 | 6 | # Composer # 7 | vendor/* 8 | composer.lock 9 | 10 | # Test # 11 | phpunit.xml 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: true 4 | 5 | php: 6 | - 5.3 7 | - 5.4 8 | - 5.5 9 | - 5.6 10 | - 7.0 11 | - 7.1 12 | - 7.2 13 | 14 | matrix: 15 | allow_failures: 16 | - php: 7.0 17 | - php: 7.1 18 | - php: 7.2 19 | 20 | before_install: 21 | - composer self-update 22 | - sh -e build/travis/scripts/apt-get.sh 23 | - sh -e build/travis/scripts/apache2-vhost.sh 24 | - sh -e build/travis/scripts/apache2-configure.sh 25 | 26 | before_script: 27 | - composer update --dev 28 | - phpenv rehash 29 | 30 | script: 31 | - phpunit --configuration phpunit.travis.xml 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The PSR7 Http Implementation [![Analytics](https://ga-beacon.appspot.com/UA-48372917-1/http/readme)](https://github.com/igrigorik/ga-beacon) 2 | 3 | [![Build Status](https://travis-ci.org/asika32764/http.svg)](https://travis-ci.org/asika32764/http) 4 | [![Latest Stable Version](https://poser.pugx.org/asika/http/v/stable)](https://packagist.org/packages/asika/http) 5 | [![Total Downloads](https://poser.pugx.org/asika/http/downloads)](https://packagist.org/packages/asika/http) 6 | [![Latest Unstable Version](https://poser.pugx.org/asika/http/v/unstable)](https://packagist.org/packages/asika/http) 7 | [![License](https://poser.pugx.org/asika/http/license)](https://packagist.org/packages/asika/http) 8 | 9 | This package provides PSR7 standard Http message objects, Uri objects, Stream objects and Client request object. **(PHP 5.3 Compatible)** 10 | 11 | > Some parts of this package based on [phly/http](https://github.com/phly/http) and [joomla/http](https://github.com/joomla-framework/http) 12 | 13 | **Project deprecated, please see [windwalker/http](https://github.com/ventoviro/windwalker-http)** 14 | 15 | ## Installation via Composer 16 | 17 | Add this to the require block in your `composer.json`. 18 | 19 | ``` json 20 | { 21 | "require": { 22 | "asika/http": "~1.0" 23 | } 24 | } 25 | ``` 26 | 27 | ## Make A Request 28 | 29 | HttpClient is a simple class to make restful request. 30 | 31 | ``` php 32 | use Asika\Http\HttpClient; 33 | 34 | $http = new HttpClient; 35 | 36 | $response = $http->get('http://example.com/?foo=bar'); 37 | 38 | // This is PSR7 ResponseInterface 39 | (string) $response->getBody(); 40 | ``` 41 | 42 | ### Other Methods 43 | 44 | ``` php 45 | $http = new HttpClient; 46 | 47 | // The post data can be query string or array 48 | $response = $http->post('http://example.com/?foo=bar', array('post_data' => 'data')); 49 | $response = $http->put('http://example.com/?foo=bar', array('post_data' => 'data')); 50 | $response = $http->patch('http://example.com/?foo=bar', array('post_data' => 'data')); 51 | $response = $http->delete('http://example.com/?foo=bar', array('post_data' => 'data')); 52 | 53 | $response = $http->head('http://example.com/?foo=bar'); 54 | $response = $http->trace('http://example.com/?foo=bar'); 55 | $response = $http->options('http://example.com/?foo=bar'); 56 | 57 | // With headers 58 | $response = $http->get('http://example.com/', null, array('X-Foo' => 'Bar')); 59 | 60 | // Use request() 61 | $response = $http->request('POST', 'http://example.com/?foo=bar', 'this=is&post=data'); 62 | ``` 63 | 64 | ### Use Psr RequestInterface to Make Request 65 | 66 | Psr7 Request is a immutable object, you have to get the return object every operation. 67 | 68 | ``` php 69 | use Asika\Http\Request; 70 | 71 | $request = new Request; 72 | 73 | // Note: You have to get the return value. 74 | // Every change will return new object. 75 | $request = $request->withRequestTarget('http://example.com/flower/sakura') 76 | ->withMethod('POST') 77 | ->withAddedHeader('Authorization', 'Bearer ' . $token) 78 | ->withAddedHeader('Content-Type', 'application/text'); 79 | 80 | // OR 81 | $request = new Request( 82 | 'http://example.com/flower/sakura', 83 | 'POST', 84 | 'php://memory', 85 | array( 86 | 'Authorization' => 'Bearer ' . $token, 87 | 'Content-Type' => 'application/json', 88 | ) 89 | ); 90 | 91 | // This is a POST request so we write post data to body 92 | $request->getBody()->write('this=is&post=data'); 93 | 94 | $http = new HttpClient; 95 | 96 | // Send request 97 | $response = $http->send($request); 98 | ``` 99 | 100 | Use Uri and Json output. 101 | 102 | ``` php 103 | use Asika\Http\Request; 104 | use Asika\Http\Uri\PsrUri; 105 | 106 | $request = (new Request) 107 | ->withUri(new PsrUri('http://example.com')) 108 | ->withMethod('POST') 109 | ->withAddedHeader('Authorization', 'Bearer ' . $token) 110 | ->withAddedHeader('Content-Type', 'application/json') // Use JSON 111 | 112 | // Note: Request will ignore path and query in Uri 113 | // So we have to set RequestTarget here 114 | ->withRequestTarget('/path/of/uri?flower=sakura'); 115 | 116 | // If you want to set a non-origin-form request target, set the 117 | // request-target explicitly: 118 | $request = $request->withRequestTarget((string) $uri); // absolute-form 119 | $request = $request->withRequestTarget($uri->getAuthority(); // authority-form 120 | $request = $request->withRequestTarget('*'); // asterisk-form 121 | 122 | // This is JSON request so we encode data here 123 | $request->getBody()->write(json_encode($data)); 124 | $response = $http->send($request); 125 | 126 | $response->getStatusCode(); // 200 is OK 127 | ``` 128 | 129 | ### Custom Transports and Options 130 | 131 | Now support Curl and Steam 2 transports. 132 | 133 | ``` php 134 | use Asika\Http\Transport\CurlTransport; 135 | 136 | $options = array( 137 | 'certpath' => '/custom/cert.pem' 138 | ); 139 | 140 | $transport = new CurlTransport($options); 141 | 142 | // Set transport when client new 143 | $http = new HttpClient(array(), $transport); 144 | ``` 145 | 146 | Set custom CURL options: 147 | 148 | ``` php 149 | $options = array( 150 | 'options' => array( 151 | CURLOPT_SSL_VERIFYHOST => false, 152 | CURLOPT_SSL_VERIFYPEER => true 153 | ) 154 | ); 155 | 156 | $httpOptions = array( 157 | 'headers' => array( 158 | 'X-Foo' => 'Bar' 159 | ) 160 | ); 161 | 162 | $http = new HttpClient($httpOptions, new CurlTransport($options)); 163 | ``` 164 | 165 | ### Download Remote File 166 | 167 | ``` php 168 | $http = new HttpClient; 169 | 170 | $dest = '/path/to/local/file.zip'; 171 | 172 | $response = $http->download('http://example.com/file.zip', $dest); 173 | 174 | if ($response->getStatusCode() != 200) 175 | { 176 | // Error 177 | } 178 | ``` 179 | 180 | ### Response Interface 181 | 182 | Response object holds a Stream object to store returned string. 183 | 184 | ``` php 185 | // The return value is: 'FOO BAR' 186 | $body = $response->getBody(); 187 | 188 | // Simply to string 189 | (string) $body; // FOO BAR 190 | 191 | $body->seek(2); 192 | $body->getContents(); // O BAR 193 | 194 | $body->rewind(); 195 | $body->read(5); // FOO B 196 | 197 | $body->getSize(); // 7 198 | ``` 199 | 200 | ## Uri 201 | 202 | `Uri` is a simple Uri object to modify URL but not Psr UriInterface. 203 | 204 | The methods provided in the `Uri` class allow you to manipulate all aspects of a uri. For example, suppose you wanted to set a new uri, add in a port, and then also post a username and password to authenticate a .htaccess security file. You could use the following syntax: 205 | 206 | ``` php 207 | // new uri object 208 | $uri = new Asika\Http\Uri\Uri; 209 | 210 | $uri->setHost('http://localhost'); 211 | $uri->setPort('8888'); 212 | $uri->setUser('myUser'); 213 | $uri->setPass('myPass'); 214 | 215 | echo $uri->__toString(); 216 | ``` 217 | 218 | This will output: 219 | 220 | ``` 221 | myUser:myPass@http://localhost:8888 222 | ``` 223 | 224 | If you wanted to add a specific filepath after the host you could use the `setPath()` method: 225 | 226 | ``` php 227 | // set path 228 | $uri->setPath('path/to/file.php'); 229 | ``` 230 | 231 | Which will output 232 | 233 | ``` 234 | myUser:myPass@http://localhost:8888path/to/file.php 235 | ``` 236 | 237 | Adding a URL query: 238 | 239 | ``` php 240 | // url query 241 | $uri->setQuery('foo=bar'); 242 | ``` 243 | 244 | Output: 245 | 246 | ``` 247 | myUser:myPass@http://localhost:8888path/to/file.php?foo=bar 248 | ``` 249 | 250 | ### PsrUri 251 | 252 | `PsrUri` is a Uri object implemented the Psr UriInterface. 253 | 254 | This object is also immutable, so we must get return value as new object every change. 255 | 256 | ``` php 257 | $uri = (new PsrUri('http://example.com')) 258 | ->withScheme('https') 259 | ->withUserInfo('user', 'pass') 260 | ->withPath('/path/to/target') 261 | ->withQuery('flower=sakura') 262 | ->withFragment('#hash'); 263 | 264 | (string) $uri; // https://user:pass@example.com/path/to/target?flower=sakura#fragment 265 | ``` 266 | 267 | ## Stream 268 | 269 | Stream is a powerful stream wrapper. 270 | 271 | Read write data to memory: 272 | 273 | ``` php 274 | $stream = new Stream('php://memory', 'wb+'); 275 | 276 | $stream->write('Foo Bar'); 277 | 278 | $stream->rewind(); // Back to begin 279 | 280 | // Now we take something we wrote into memory 281 | 282 | $stream->__toString(); // get: Foo Bar 283 | 284 | // OR 285 | 286 | $stream->rewind(); 287 | $stream->getContents(); // get: Foo Bar 288 | ``` 289 | 290 | Read data from `php://input` 291 | 292 | ``` php 293 | $stream = new PhpInputSteam; 294 | 295 | $data = $stream->__toString(); // foo=bar 296 | 297 | $query = \Asika\Http\Uri\UriHelper::parseQuery($data); // array('foo' => 'bar') 298 | ``` 299 | 300 | Read file: 301 | 302 | ``` php 303 | $stream = new Stream('/path/to/file.txt', 'r+'); 304 | 305 | $stream->__toString(); // Read 306 | 307 | $steam->seek($stream->getSize()); 308 | $steam->write('new string'); // Write 309 | ``` 310 | 311 | Quick copy stream. 312 | 313 | ``` php 314 | // Remote source 315 | $src = new Stream('http://example/test.txt'); 316 | 317 | // Local store 318 | $dest = new Stream('/path/to/local/test.txt'); 319 | 320 | // Do copy 321 | \Asika\Http\Helper\StreamHelper::copy($src, $dest); 322 | 323 | // Get Data 324 | $dest->rewind(); 325 | $data = $dest->getContents(); 326 | ``` 327 | 328 | See: [Psr7 StreamInterface](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md#13-streams) 329 | / [API](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md#34-psrhttpmessagestreaminterface) 330 | 331 | ## Other Http Message Objects 332 | 333 | ### `ServerRequest` 334 | 335 | A Request object to store server data, like: `$_SERVER`, `$_COOKIE`, `$_REQUEST` etc. 336 | 337 | ### `UploadedFile` 338 | 339 | An object to store uploaded files, see: [Uploaded files interface](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md#16-uploaded-files) 340 | 341 | ``` php 342 | $files = array(); 343 | 344 | foreach ($_FILE as $name => $file) 345 | { 346 | $files[$name] = new UploadedFile($file['tmp_name'], $file['size'], $file['error'], $file['name'], $file['type']); 347 | } 348 | 349 | $request = new ServerRequest( 350 | $_SERVER, 351 | $_GET, 352 | $_POST, 353 | $_COOKIE, 354 | $files 355 | ); 356 | ``` 357 | 358 | ## More About Psr 7 359 | 360 | [PSR7 HTTP message interfaces](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md) / 361 | [HTTP Message Meta Document](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message-meta.md) 362 | -------------------------------------------------------------------------------- /build/travis/apache/virtual.host: -------------------------------------------------------------------------------- 1 | 2 | ServerName %hostname% 3 | ServerAdmin github@babdev.com 4 | 5 | DocumentRoot %basedir% 6 | 7 | 8 | DirectoryIndex app.php 9 | Options -Indexes FollowSymLinks SymLinksifOwnerMatch 10 | AllowOverride All 11 | Allow from All 12 | 13 | 14 | -------------------------------------------------------------------------------- /build/travis/scripts/apache2-configure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | VHOSTNAME="virtual.host" 4 | if [ "$1" ] 5 | then 6 | VHOSTNAME="$1" 7 | fi 8 | 9 | echo "---> Applying $(tput bold ; tput setaf 2)apache2 configuration$(tput sgr0)" 10 | echo "--> Enabling virtual host $(tput setaf 2)$VHOSTNAME$(tput sgr0)" 11 | sudo a2enmod rewrite 12 | sudo a2ensite $VHOSTNAME 13 | 14 | echo "---> Restarting $(tput bold ; tput setaf 2)apache2$(tput sgr0)" 15 | 16 | sudo /etc/init.d/apache2 restart 17 | -------------------------------------------------------------------------------- /build/travis/scripts/apache2-vhost.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | BASEDIR=$(dirname $0) 4 | BASEDIR=$(readlink -f "$BASEDIR/..") 5 | ROOTDIR=$(readlink -f "$BASEDIR/../..") 6 | 7 | VHOSTNAME="virtual.host" 8 | if [ "$1" ] 9 | then 10 | VHOSTNAME="$1" 11 | fi 12 | 13 | DOCROOT="$ROOTDIR" 14 | if [ "$2" ] 15 | then 16 | DOCROOT="$2" 17 | fi 18 | 19 | CONFIGFILE="$BASEDIR/apache/virtual.host" 20 | if [ "$3" ] 21 | then 22 | CONFIGFILE="$3" 23 | fi 24 | 25 | echo "---> Starting $(tput bold ; tput setaf 2)virtual host creation$(tput sgr0)" 26 | echo "---> Virtualhost name : $(tput bold ; tput setaf 3)$VHOSTNAME$(tput sgr0)" 27 | echo "---> Document root : $(tput bold ; tput setaf 3)$DOCROOT$(tput sgr0)" 28 | echo "---> Configuration file : $(tput bold ; tput setaf 3)$CONFIGFILE$(tput sgr0)" 29 | 30 | sed s?%basedir%?$DOCROOT? "$CONFIGFILE" | sed s/%hostname%/$VHOSTNAME/ > $VHOSTNAME 31 | sudo mv $VHOSTNAME /etc/apache2/sites-available/$VHOSTNAME 32 | 33 | echo "---> $(tput bold ; tput setaf 2)Adding host to /etc/hosts$(tput sgr0) :" 34 | echo "127.0.0.1 $VHOSTNAME" | sudo tee -a /etc/hosts 35 | -------------------------------------------------------------------------------- /build/travis/scripts/apt-get.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo $FOO 4 | 5 | EXTRA_PACKETS="apache2 libapache2-mod-php5 php5-mysql" 6 | if [ "$1" ] 7 | then 8 | EXTRA_PACKETS="$EXTRA_PACKETS $1" 9 | fi 10 | 11 | echo "---> Starting $(tput bold ; tput setaf 2)packets installation$(tput sgr0)" 12 | echo "---> Packets to install : $(tput bold ; tput setaf 3)$EXTRA_PACKETS$(tput sgr0)" 13 | 14 | sudo apt-get update 15 | sudo apt-get install -y --force-yes $EXTRA_PACKETS -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asika/http", 3 | "description": "PSR HTTP Message implementations. (PHP 5.3 Compatible)", 4 | "keywords": ["asika", "psr7", "uri", "http", "request", "response", "client", "restful", "stream"], 5 | "homepage": "https://github.com/asika32764/asika-http", 6 | "license": "LGPL-2.0+", 7 | "require": { 8 | "php": ">=5.3.10", 9 | "psr/http-message": "1.*", 10 | "windwalker/uri": "~2.0" 11 | }, 12 | "require-dev": { 13 | "windwalker/environment": "~2.0", 14 | "windwalker/test": "~2.0" 15 | }, 16 | "minimum-stability": "beta", 17 | "autoload": { 18 | "psr-4": { 19 | "Asika\\Http\\": "src", 20 | "Asika\\Http\\Test\\": "test" 21 | } 22 | }, 23 | "provide": { 24 | "psr/http-message-implementation": "~1.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /phpunit.travis.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | test 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | test 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/AbstractMessage.php: -------------------------------------------------------------------------------- 1 | protocol; 60 | } 61 | 62 | /** 63 | * Return an instance with the specified HTTP protocol version. 64 | * 65 | * The version string MUST contain only the HTTP version number (e.g., 66 | * "1.1", "1.0"). 67 | * 68 | * This method MUST be implemented in such a way as to retain the 69 | * immutability of the message, and MUST return an instance that has the 70 | * new protocol version. 71 | * 72 | * @param string $version HTTP protocol version 73 | * 74 | * @return static 75 | */ 76 | public function withProtocolVersion($version) 77 | { 78 | $new = clone $this; 79 | $new->protocol = $version; 80 | 81 | return $new; 82 | } 83 | 84 | /** 85 | * Retrieves all message header values. 86 | * 87 | * The keys represent the header name as it will be sent over the wire, and 88 | * each value is an array of strings associated with the header. 89 | * 90 | * // Represent the headers as a string 91 | * foreach ($message->getHeaders() as $name => $values) { 92 | * echo $name . ": " . implode(", ", $values); 93 | * } 94 | * 95 | * // Emit headers iteratively: 96 | * foreach ($message->getHeaders() as $name => $values) { 97 | * foreach ($values as $value) { 98 | * header(sprintf('%s: %s', $name, $value), false); 99 | * } 100 | * } 101 | * 102 | * While header names are not case-sensitive, getHeaders() will preserve the 103 | * exact case in which headers were originally specified. 104 | * 105 | * @return array Returns an associative array of the message's headers. Each 106 | * key MUST be a header name, and each value MUST be an array of strings 107 | * for that header. 108 | */ 109 | public function getHeaders() 110 | { 111 | return $this->headers; 112 | } 113 | 114 | /** 115 | * Checks if a header exists by the given case-insensitive name. 116 | * 117 | * @param string $name Case-insensitive header field name. 118 | * 119 | * @return bool Returns true if any header names match the given header 120 | * name using a case-insensitive string comparison. Returns false if 121 | * no matching header name is found in the message. 122 | */ 123 | public function hasHeader($name) 124 | { 125 | return array_key_exists(strtolower($name), $this->headerNames); 126 | } 127 | 128 | /** 129 | * Retrieves a message header value by the given case-insensitive name. 130 | * 131 | * This method returns an array of all the header values of the given 132 | * case-insensitive header name. 133 | * 134 | * If the header does not appear in the message, this method MUST return an 135 | * empty array. 136 | * 137 | * @param string $name Case-insensitive header field name. 138 | * 139 | * @return string[] An array of string values as provided for the given 140 | * header. If the header does not appear in the message, this method MUST 141 | * return an empty array. 142 | */ 143 | public function getHeader($name) 144 | { 145 | if (!$this->hasHeader($name)) 146 | { 147 | return array(); 148 | } 149 | 150 | $name = $this->getHeaderName($name); 151 | 152 | return (array) $this->headers[$name]; 153 | } 154 | 155 | /** 156 | * Retrieves a comma-separated string of the values for a single header. 157 | * 158 | * This method returns all of the header values of the given 159 | * case-insensitive header name as a string concatenated together using 160 | * a comma. 161 | * 162 | * NOTE: Not all header values may be appropriately represented using 163 | * comma concatenation. For such headers, use getHeader() instead 164 | * and supply your own delimiter when concatenating. 165 | * 166 | * If the header does not appear in the message, this method MUST return 167 | * an empty string. 168 | * 169 | * @param string $name Case-insensitive header field name. 170 | * 171 | * @return string A string of values as provided for the given header 172 | * concatenated together using a comma. If the header does not appear in 173 | * the message, this method MUST return an empty string. 174 | */ 175 | public function getHeaderLine($name) 176 | { 177 | $value = $this->getHeader($name); 178 | 179 | if (!$value) 180 | { 181 | return ''; 182 | } 183 | 184 | return implode(',', $value); 185 | } 186 | 187 | /** 188 | * Return an instance with the provided value replacing the specified header. 189 | * 190 | * While header names are case-insensitive, the casing of the header will 191 | * be preserved by this function, and returned from getHeaders(). 192 | * 193 | * This method MUST be implemented in such a way as to retain the 194 | * immutability of the message, and MUST return an instance that has the 195 | * new and/or updated header and value. 196 | * 197 | * @param string $name Case-insensitive header field name. 198 | * @param string|string[] $value Header value(s). 199 | * 200 | * @return static 201 | * @throws \InvalidArgumentException for invalid header names or values. 202 | */ 203 | public function withHeader($name, $value) 204 | { 205 | $new = $this->createHeader($name); 206 | 207 | $new = $new->withAddedHeader($name, $value); 208 | 209 | return $new; 210 | } 211 | 212 | /** 213 | * Return an instance with the specified header appended with the given value. 214 | * 215 | * Existing values for the specified header will be maintained. The new 216 | * value(s) will be appended to the existing list. If the header did not 217 | * exist previously, it will be added. 218 | * 219 | * This method MUST be implemented in such a way as to retain the 220 | * immutability of the message, and MUST return an instance that has the 221 | * new header and/or value. 222 | * 223 | * @param string $name Case-insensitive header field name to add. 224 | * @param mixed $value Header value(s). 225 | * 226 | * @return static 227 | * @throws \InvalidArgumentException for invalid header names or values. 228 | */ 229 | public function withAddedHeader($name, $value) 230 | { 231 | $value = HeaderHelper::allToArray($value); 232 | 233 | if (!HeaderHelper::arrayOnlyContainsString($value)) 234 | { 235 | throw new \InvalidArgumentException('Header values should ony have string.'); 236 | } 237 | 238 | if (!HeaderHelper::isValidName($name)) 239 | { 240 | throw new \InvalidArgumentException('Invalid header name'); 241 | } 242 | 243 | $new = clone $this; 244 | 245 | if (!$this->hasHeader($name)) 246 | { 247 | $new = $new->createHeader($name); 248 | } 249 | 250 | $name = $new->getHeaderName($name); 251 | 252 | $new->headers[$name] = array_merge($new->headers[$name], $value); 253 | 254 | return $new; 255 | } 256 | 257 | /** 258 | * Return an instance without the specified header. 259 | * 260 | * Header resolution MUST be done without case-sensitivity. 261 | * 262 | * This method MUST be implemented in such a way as to retain the 263 | * immutability of the message, and MUST return an instance that removes 264 | * the named header. 265 | * 266 | * @param string $name Case-insensitive header field name to remove. 267 | * 268 | * @return static 269 | */ 270 | public function withoutHeader($name) 271 | { 272 | if (!$this->hasHeader($name)) 273 | { 274 | return clone $this; 275 | } 276 | 277 | $normalized = strtolower($name); 278 | $original = $this->headerNames[$normalized]; 279 | 280 | $new = clone $this; 281 | 282 | unset($new->headers[$original], $new->headerNames[$normalized]); 283 | 284 | return $new; 285 | } 286 | 287 | /** 288 | * Gets the body of the message. 289 | * 290 | * @return StreamInterface Returns the body as a stream. 291 | */ 292 | public function getBody() 293 | { 294 | return $this->stream; 295 | } 296 | 297 | /** 298 | * Return an instance with the specified message body. 299 | * 300 | * The body MUST be a StreamInterface object. 301 | * 302 | * This method MUST be implemented in such a way as to retain the 303 | * immutability of the message, and MUST return a new instance that has the 304 | * new body stream. 305 | * 306 | * @param StreamInterface $body Body. 307 | * 308 | * @return static 309 | * @throws \InvalidArgumentException When the body is not valid. 310 | */ 311 | public function withBody(StreamInterface $body) 312 | { 313 | $new = clone $this; 314 | 315 | $new->stream = $body; 316 | 317 | return $new; 318 | } 319 | 320 | /** 321 | * createHeader 322 | * 323 | * @param string $name 324 | * 325 | * @return static 326 | */ 327 | protected function createHeader($name) 328 | { 329 | $new = clone $this; 330 | 331 | $normalized = strtolower($name); 332 | $new->headerNames[$normalized] = $name; 333 | $new->headers[$name] = array(); 334 | 335 | return $new; 336 | } 337 | 338 | /** 339 | * getHeaderName 340 | * 341 | * @param string $name 342 | * 343 | * @return string 344 | */ 345 | protected function getHeaderName($name) 346 | { 347 | $normalized = strtolower($name); 348 | 349 | return $this->headerNames[$normalized]; 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /src/AbstractRequest.php: -------------------------------------------------------------------------------- 1 | $value) 86 | { 87 | $value = HeaderHelper::allToArray($value); 88 | 89 | if (!HeaderHelper::arrayOnlyContainsString($value)) 90 | { 91 | throw new \InvalidArgumentException('Header values should ony have string.'); 92 | } 93 | 94 | if (!HeaderHelper::isValidName($name)) 95 | { 96 | throw new \InvalidArgumentException('Invalid header name'); 97 | } 98 | 99 | $normalized = strtolower($name); 100 | $this->headerNames[$normalized] = $name; 101 | $this->headers[$name] = $value; 102 | } 103 | 104 | $this->stream = $body; 105 | $this->method = $this->validateMethod($method); 106 | $this->uri = $uri; 107 | } 108 | 109 | /** 110 | * Retrieves the message's request target. 111 | * 112 | * Retrieves the message's request-target either as it will appear (for 113 | * clients), as it appeared at request (for servers), or as it was 114 | * specified for the instance (see withRequestTarget()). 115 | * 116 | * In most cases, this will be the origin-form of the composed URI, 117 | * unless a value was provided to the concrete implementation (see 118 | * withRequestTarget() below). 119 | * 120 | * If no URI is available, and no request-target has been specifically 121 | * provided, this method MUST return the string "/". 122 | * 123 | * @return string 124 | */ 125 | public function getRequestTarget() 126 | { 127 | if ($this->requestTarget !== null) 128 | { 129 | return $this->requestTarget; 130 | } 131 | 132 | if (!$this->uri) 133 | { 134 | return '/'; 135 | } 136 | 137 | $target = $this->uri->getPath(); 138 | 139 | if ($this->uri->getQuery()) 140 | { 141 | $target .= '?' . $this->uri->getQuery(); 142 | } 143 | 144 | if (empty($target)) 145 | { 146 | $target = '/'; 147 | } 148 | 149 | return $target; 150 | } 151 | 152 | /** 153 | * Return an instance with the specific request-target. 154 | * 155 | * If the request needs a non-origin-form request-target — e.g., for 156 | * specifying an absolute-form, authority-form, or asterisk-form — 157 | * this method may be used to create an instance with the specified 158 | * request-target, verbatim. 159 | * 160 | * This method MUST be implemented in such a way as to retain the 161 | * immutability of the message, and MUST return an instance that has the 162 | * changed request target. 163 | * 164 | * @link http://tools.ietf.org/html/rfc7230#section-2.7 (for the various 165 | * request-target forms allowed in request messages) 166 | * 167 | * @param mixed $requestTarget 168 | * 169 | * @return static 170 | * @throws \InvalidArgumentException if the request target is invalid. 171 | */ 172 | public function withRequestTarget($requestTarget) 173 | { 174 | if (preg_match('/\s/', $requestTarget)) 175 | { 176 | throw new \InvalidArgumentException('RequestTarget cannot contain whitespace.'); 177 | } 178 | 179 | $new = clone $this; 180 | $new->requestTarget = $requestTarget; 181 | 182 | return $new; 183 | } 184 | 185 | /** 186 | * Retrieves the HTTP method of the request. 187 | * 188 | * @return string Returns the request method. 189 | */ 190 | public function getMethod() 191 | { 192 | return $this->method; 193 | } 194 | 195 | /** 196 | * Return an instance with the provided HTTP method. 197 | * 198 | * While HTTP method names are typically all uppercase characters, HTTP 199 | * method names are case-sensitive and thus implementations SHOULD NOT 200 | * modify the given string. 201 | * 202 | * This method MUST be implemented in such a way as to retain the 203 | * immutability of the message, and MUST return an instance that has the 204 | * changed request method. 205 | * 206 | * @param string $method Case-sensitive method. 207 | * 208 | * @return static 209 | * @throws \InvalidArgumentException for invalid HTTP methods. 210 | */ 211 | public function withMethod($method) 212 | { 213 | $method = $this->validateMethod($method); 214 | 215 | $new = clone $this; 216 | 217 | $new->method = $method; 218 | 219 | return $new; 220 | } 221 | 222 | /** 223 | * Retrieves the URI instance. 224 | * 225 | * This method MUST return a UriInterface instance. 226 | * 227 | * @link http://tools.ietf.org/html/rfc3986#section-4.3 228 | * @return UriInterface Returns a UriInterface instance 229 | * representing the URI of the request. 230 | */ 231 | public function getUri() 232 | { 233 | return $this->uri; 234 | } 235 | 236 | /** 237 | * Returns an instance with the provided URI. 238 | * 239 | * This method MUST update the Host header of the returned request by 240 | * default if the URI contains a host component. If the URI does not 241 | * contain a host component, any pre-existing Host header MUST be carried 242 | * over to the returned request. 243 | * 244 | * You can opt-in to preserving the original state of the Host header by 245 | * setting `$preserveHost` to `true`. When `$preserveHost` is set to 246 | * `true`, this method interacts with the Host header in the following ways: 247 | * 248 | * - If the the Host header is missing or empty, and the new URI contains 249 | * a host component, this method MUST update the Host header in the returned 250 | * request. 251 | * - If the Host header is missing or empty, and the new URI does not contain a 252 | * host component, this method MUST NOT update the Host header in the returned 253 | * request. 254 | * - If a Host header is present and non-empty, this method MUST NOT update 255 | * the Host header in the returned request. 256 | * 257 | * This method MUST be implemented in such a way as to retain the 258 | * immutability of the message, and MUST return an instance that has the 259 | * new UriInterface instance. 260 | * 261 | * @link http://tools.ietf.org/html/rfc3986#section-4.3 262 | * 263 | * @param UriInterface $uri New request URI to use. 264 | * @param bool $preserveHost Preserve the original state of the Host header. 265 | * 266 | * @return static 267 | */ 268 | public function withUri(UriInterface $uri, $preserveHost = false) 269 | { 270 | $new = clone $this; 271 | $new->uri = $uri; 272 | 273 | if ($preserveHost) 274 | { 275 | return $new; 276 | } 277 | 278 | if (!$uri->getHost()) 279 | { 280 | return $new; 281 | } 282 | 283 | $host = $uri->getHost(); 284 | 285 | if ($uri->getPort()) 286 | { 287 | $host .= ':' . $uri->getPort(); 288 | } 289 | 290 | $new->headerNames['host'] = 'Host'; 291 | $new->headers['Host'] = array($host); 292 | 293 | return $new; 294 | } 295 | 296 | /** 297 | * validateMethod 298 | * 299 | * @param string $method 300 | * 301 | * @return string 302 | */ 303 | protected function validateMethod($method) 304 | { 305 | if ($method === null) 306 | { 307 | return $method; 308 | } 309 | 310 | if (!is_string($method)) 311 | { 312 | throw new \InvalidArgumentException('Method should be a string.'); 313 | } 314 | 315 | $method = strtoupper($method); 316 | 317 | if (!in_array($method, $this->allowMethods)) 318 | { 319 | throw new \InvalidArgumentException('Invalid HTTP method: ' . $method); 320 | } 321 | 322 | return $method; 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/Helper/HeaderHelper.php: -------------------------------------------------------------------------------- 1 | 254) 83 | { 84 | continue; 85 | } 86 | 87 | $string .= $value[$i]; 88 | } 89 | 90 | return $string; 91 | } 92 | 93 | /** 94 | * Validate a header value. 95 | * 96 | * Per RFC 7230, only VISIBLE ASCII characters, spaces, and horizontal 97 | * tabs are allowed in values; header continuations MUST consist of 98 | * a single CRLF sequence followed by a space or horizontal tab. 99 | * 100 | * @param string $value 101 | * 102 | * @return boolean 103 | * 104 | * @see http://en.wikipedia.org/wiki/HTTP_response_splitting 105 | */ 106 | public static function isValidValue($value) 107 | { 108 | $value = (string) $value; 109 | 110 | // Look for: 111 | // \n not preceded by \r, OR 112 | // \r not followed by \n, OR 113 | // \r\n not followed by space or horizontal tab; these are all CRLF attacks 114 | if (preg_match("#(?:(?:(? 254) 133 | { 134 | return false; 135 | } 136 | } 137 | 138 | return true; 139 | } 140 | 141 | /** 142 | * allToArray 143 | * 144 | * @param mixed $value 145 | * 146 | * @return array 147 | */ 148 | public static function allToArray($value) 149 | { 150 | if ($value instanceof \Traversable) 151 | { 152 | $value = iterator_to_array($value); 153 | } 154 | 155 | if (is_object($value)) 156 | { 157 | $value = get_object_vars($value); 158 | } 159 | 160 | $value = (array) $value; 161 | 162 | foreach ($value as $k => $v) 163 | { 164 | if (!static::isValidValue($v)) 165 | { 166 | throw new \InvalidArgumentException('Value :' . $value . ' is invalid.'); 167 | } 168 | } 169 | 170 | return $value; 171 | } 172 | 173 | /** 174 | * arrayOnlyContainsString 175 | * 176 | * @param array $array 177 | * 178 | * @return bool 179 | */ 180 | public static function arrayOnlyContainsString(array $array) 181 | { 182 | foreach ($array as $value) 183 | { 184 | if (!is_string((string) $value)) 185 | { 186 | return false; 187 | } 188 | } 189 | 190 | return true; 191 | } 192 | 193 | /** 194 | * toHeaderLine 195 | * 196 | * @param array $headers 197 | * @param bool $toString 198 | * 199 | * @return array|string 200 | */ 201 | public static function toHeaderLine($headers, $toString = false) 202 | { 203 | $headerArray = array(); 204 | 205 | foreach ($headers as $key => $value) 206 | { 207 | $value = is_array($value) ? implode(',', $value) : implode(',', $value); 208 | 209 | $headerArray[] = $key . ': ' . $value; 210 | } 211 | 212 | if ($toString) 213 | { 214 | $headerArray = implode($headerArray, "\r\n"); 215 | } 216 | 217 | return $headerArray; 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/Helper/ResponseHelper.php: -------------------------------------------------------------------------------- 1 | 'Continue', 26 | 101 => 'Switching Protocols', 27 | 102 => 'Processing', 28 | // SUCCESS CODES 29 | 200 => 'OK', 30 | 201 => 'Created', 31 | 202 => 'Accepted', 32 | 203 => 'Non-Authoritative Information', 33 | 204 => 'No Content', 34 | 205 => 'Reset Content', 35 | 206 => 'Partial Content', 36 | 207 => 'Multi-status', 37 | 208 => 'Already Reported', 38 | // REDIRECTION CODES 39 | 300 => 'Multiple Choices', 40 | 301 => 'Moved Permanently', 41 | 302 => 'Found', 42 | 303 => 'See Other', 43 | 304 => 'Not Modified', 44 | 305 => 'Use Proxy', 45 | 306 => 'Switch Proxy', // Deprecated 46 | 307 => 'Temporary Redirect', 47 | // CLIENT ERROR 48 | 400 => 'Bad Request', 49 | 401 => 'Unauthorized', 50 | 402 => 'Payment Required', 51 | 403 => 'Forbidden', 52 | 404 => 'Not Found', 53 | 405 => 'Method Not Allowed', 54 | 406 => 'Not Acceptable', 55 | 407 => 'Proxy Authentication Required', 56 | 408 => 'Request Time-out', 57 | 409 => 'Conflict', 58 | 410 => 'Gone', 59 | 411 => 'Length Required', 60 | 412 => 'Precondition Failed', 61 | 413 => 'Request Entity Too Large', 62 | 414 => 'Request-URI Too Large', 63 | 415 => 'Unsupported Media Type', 64 | 416 => 'Requested range not satisfiable', 65 | 417 => 'Expectation Failed', 66 | 418 => 'I\'m a teapot', 67 | 422 => 'Unprocessable Entity', 68 | 423 => 'Locked', 69 | 424 => 'Failed Dependency', 70 | 425 => 'Unordered Collection', 71 | 426 => 'Upgrade Required', 72 | 428 => 'Precondition Required', 73 | 429 => 'Too Many Requests', 74 | 431 => 'Request Header Fields Too Large', 75 | // SERVER ERROR 76 | 500 => 'Internal Server Error', 77 | 501 => 'Not Implemented', 78 | 502 => 'Bad Gateway', 79 | 503 => 'Service Unavailable', 80 | 504 => 'Gateway Time-out', 81 | 505 => 'HTTP Version not supported', 82 | 506 => 'Variant Also Negotiates', 83 | 507 => 'Insufficient Storage', 84 | 508 => 'Loop Detected', 85 | 511 => 'Network Authentication Required', 86 | ); 87 | 88 | /** 89 | * getPhrase 90 | * 91 | * @param integer $code 92 | * 93 | * @return string 94 | */ 95 | public static function getPhrase($code) 96 | { 97 | if (isset(static::$phrases[$code])) 98 | { 99 | return static::$phrases[$code]; 100 | } 101 | 102 | return null; 103 | } 104 | 105 | /** 106 | * Validate a status code. 107 | * 108 | * @param int|string $code 109 | * 110 | * @return integer 111 | * 112 | * @throws \InvalidArgumentException on an invalid status code. 113 | */ 114 | public static function validateStatus($code) 115 | { 116 | $code = (int) $code; 117 | 118 | if ($code < 100 || $code >= 600) 119 | { 120 | throw new \InvalidArgumentException('Invalid status code: ' . $code); 121 | } 122 | 123 | return $code; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Helper/ServerHelper.php: -------------------------------------------------------------------------------- 1 | isSeekable()) 32 | { 33 | $src->rewind(); 34 | } 35 | 36 | while (!$src->eof()) 37 | { 38 | $dest->write($src->read(4096)); 39 | } 40 | } 41 | 42 | /** 43 | * Copy a stream to target resource. 44 | * 45 | * @param StreamInterface $src The source stream to copy. 46 | * @param string $dest The target stream, if is a path or resource, will auto create Stream object. 47 | * 48 | * @return void 49 | */ 50 | public static function copyTo(StreamInterface $src, $dest) 51 | { 52 | $destStream = $dest instanceof StreamInterface ? $dest : new Stream($dest, Stream::MODE_READ_WRITE_RESET); 53 | 54 | static::copy($src, $destStream); 55 | 56 | $destStream->close(); 57 | } 58 | 59 | /** 60 | * Copy a stream to target resource. 61 | * 62 | * @param string $src The source stream to copy, if is a path or resource, will auto create Stream object. 63 | * @param StreamInterface $dest The target stream. 64 | * 65 | * @return void 66 | */ 67 | public static function copyFrom($src, StreamInterface $dest) 68 | { 69 | $srcStream = $src instanceof StreamInterface ? $src : new Stream($src, Stream::MODE_READ_WRITE_RESET); 70 | 71 | static::copy($srcStream, $dest); 72 | 73 | $srcStream->close(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/HttpClientInterface.php: -------------------------------------------------------------------------------- 1 | getHeaders() as $name => $values) { 28 | * echo $name . ": " . implode(", ", $values); 29 | * } 30 | * 31 | * // Emit headers iteratively: 32 | * foreach ($message->getHeaders() as $name => $values) { 33 | * foreach ($values as $value) { 34 | * header(sprintf('%s: %s', $name, $value), false); 35 | * } 36 | * } 37 | * 38 | * While header names are not case-sensitive, getHeaders() will preserve the 39 | * exact case in which headers were originally specified. 40 | * 41 | * @return array Returns an associative array of the message's headers. Each 42 | * key MUST be a header name, and each value MUST be an array of strings 43 | * for that header. 44 | */ 45 | public function getHeaders() 46 | { 47 | $headers = $this->headers; 48 | 49 | if (!$this->hasHeader('host') && ($this->uri && $this->uri->getHost())) 50 | { 51 | $headers['Host'] = array($this->getHostFromUri()); 52 | } 53 | 54 | return $headers; 55 | } 56 | 57 | /** 58 | * Retrieves a message header value by the given case-insensitive name. 59 | * 60 | * This method returns an array of all the header values of the given 61 | * case-insensitive header name. 62 | * 63 | * If the header does not appear in the message, this method MUST return an 64 | * empty array. 65 | * 66 | * @param string $name Case-insensitive header field name. 67 | * 68 | * @return string[] An array of string values as provided for the given 69 | * header. If the header does not appear in the message, this method MUST 70 | * return an empty array. 71 | */ 72 | public function getHeader($name) 73 | { 74 | if (!$this->hasHeader($name)) 75 | { 76 | if (strtolower($name) === 'host' && ($this->uri && $this->uri->getHost())) 77 | { 78 | return array($this->getHostFromUri()); 79 | } 80 | 81 | return array(); 82 | } 83 | 84 | $name = $this->getHeaderName($name); 85 | 86 | return (array) $this->headers[$name]; 87 | } 88 | 89 | /** 90 | * Checks if a header exists by the given case-insensitive name. 91 | * 92 | * @param string $name Case-insensitive header field name. 93 | * 94 | * @return bool Returns true if any header names match the given header 95 | * name using a case-insensitive string comparison. Returns false if 96 | * no matching header name is found in the message. 97 | */ 98 | public function hasHeader($name) 99 | { 100 | if (strtolower($name) === 'host' && ($this->uri && $this->uri->getHost())) 101 | { 102 | $this->headerNames['host'] = $name; 103 | $this->headers[$name] = array($this->getHostFromUri()); 104 | } 105 | 106 | return parent::hasHeader($name); 107 | } 108 | 109 | /** 110 | * Retrieve the host from the URI instance 111 | * 112 | * @return string 113 | */ 114 | protected function getHostFromUri() 115 | { 116 | $host = $this->uri->getHost(); 117 | $host .= $this->uri->getPort() ? ':' . $this->uri->getPort() : ''; 118 | 119 | return $host; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Response.php: -------------------------------------------------------------------------------- 1 | $value) 54 | { 55 | $value = HeaderHelper::allToArray($value); 56 | 57 | if (!HeaderHelper::arrayOnlyContainsString($value)) 58 | { 59 | throw new \InvalidArgumentException('Header values should ony have string.'); 60 | } 61 | 62 | if (!HeaderHelper::isValidName($name)) 63 | { 64 | throw new \InvalidArgumentException('Invalid header name'); 65 | } 66 | 67 | $normalized = strtolower($name); 68 | $this->headerNames[$normalized] = $name; 69 | $this->headers[$name] = $value; 70 | } 71 | 72 | $this->stream = $body; 73 | $this->statusCode = $status; 74 | } 75 | 76 | /** 77 | * Gets the response status code. 78 | * 79 | * The status code is a 3-digit integer result code of the server's attempt 80 | * to understand and satisfy the request. 81 | * 82 | * @return int Status code. 83 | */ 84 | public function getStatusCode() 85 | { 86 | return $this->statusCode; 87 | } 88 | 89 | /** 90 | * Return an instance with the specified status code and, optionally, reason phrase. 91 | * 92 | * If no reason phrase is specified, implementations MAY choose to default 93 | * to the RFC 7231 or IANA recommended reason phrase for the response's 94 | * status code. 95 | * 96 | * This method MUST be implemented in such a way as to retain the 97 | * immutability of the message, and MUST return an instance that has the 98 | * updated status and reason phrase. 99 | * 100 | * @link http://tools.ietf.org/html/rfc7231#section-6 101 | * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml 102 | * 103 | * @param int $code The 3-digit integer result code to set. 104 | * @param string $reasonPhrase The reason phrase to use with the 105 | * provided status code; if none is provided, implementations MAY 106 | * use the defaults as suggested in the HTTP specification. 107 | * 108 | * @return static 109 | * @throws \InvalidArgumentException For invalid status code arguments. 110 | */ 111 | public function withStatus($code, $reasonPhrase = '') 112 | { 113 | $code = ResponseHelper::validateStatus($code); 114 | 115 | $new = clone $this; 116 | $new->statusCode = (int) $code; 117 | $new->reasonPhrase = $reasonPhrase; 118 | 119 | return $new; 120 | } 121 | 122 | /** 123 | * Gets the response reason phrase associated with the status code. 124 | * 125 | * Because a reason phrase is not a required element in a response 126 | * status line, the reason phrase value MAY be null. Implementations MAY 127 | * choose to return the default RFC 7231 recommended reason phrase (or those 128 | * listed in the IANA HTTP Status Code Registry) for the response's 129 | * status code. 130 | * 131 | * @link http://tools.ietf.org/html/rfc7231#section-6 132 | * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml 133 | * 134 | * @return string Reason phrase; must return an empty string if none present. 135 | */ 136 | public function getReasonPhrase() 137 | { 138 | if (!$this->reasonPhrase) 139 | { 140 | $this->reasonPhrase = ResponseHelper::getPhrase($this->statusCode); 141 | } 142 | 143 | return $this->reasonPhrase; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Stream/PhpInputStream.php: -------------------------------------------------------------------------------- 1 | reachedEof) 62 | { 63 | return $this->cache; 64 | } 65 | 66 | $this->getContents(); 67 | 68 | return $this->cache; 69 | } 70 | 71 | /** 72 | * Returns whether or not the stream is writable. 73 | * 74 | * @return bool 75 | */ 76 | public function isWritable() 77 | { 78 | return false; 79 | } 80 | 81 | /** 82 | * Read data from the stream. 83 | * 84 | * @param int $length Read up to $length bytes from the object and return 85 | * them. Fewer than $length bytes may be returned if underlying stream 86 | * call returns fewer bytes. 87 | * 88 | * @return string Returns the data read from the stream, or an empty string 89 | * if no bytes are available. 90 | * 91 | * @throws \RuntimeException if an error occurs. 92 | */ 93 | public function read($length) 94 | { 95 | $content = parent::read($length); 96 | 97 | if ($content && !$this->reachedEof) 98 | { 99 | $this->cache .= $content; 100 | } 101 | 102 | if ($this->eof()) 103 | { 104 | $this->reachedEof = true; 105 | } 106 | 107 | return $content; 108 | } 109 | 110 | /** 111 | * Returns the remaining contents in a string 112 | * 113 | * @return string 114 | * 115 | * @throws \RuntimeException if unable to read or an error occurs while reading. 116 | */ 117 | public function getContents($maxLength = -1) 118 | { 119 | if ($this->reachedEof) 120 | { 121 | return $this->cache; 122 | } 123 | 124 | $this->cache .= $contents = stream_get_contents($this->resource, $maxLength); 125 | 126 | if ($maxLength === -1 || $this->eof()) 127 | { 128 | $this->reachedEof = true; 129 | } 130 | 131 | return $contents; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Stream/Stream.php: -------------------------------------------------------------------------------- 1 | attach($stream, $mode); 50 | } 51 | 52 | /** 53 | * Reads all data from the stream into a string, from the beginning to end. 54 | * 55 | * This method MUST attempt to seek to the beginning of the stream before 56 | * reading data and read the stream until the end is reached. 57 | * 58 | * Warning: This could attempt to load a large amount of data into memory. 59 | * 60 | * This method MUST NOT raise an exception in order to conform with PHP's 61 | * string casting operations. 62 | * 63 | * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring 64 | * @return string 65 | */ 66 | public function __toString() 67 | { 68 | if (!$this->isReadable()) 69 | { 70 | return ''; 71 | } 72 | 73 | try 74 | { 75 | $this->rewind(); 76 | 77 | return $this->getContents(); 78 | } 79 | catch (\Exception $e) 80 | { 81 | return (string) $e; 82 | } 83 | } 84 | 85 | /** 86 | * Closes the stream and any underlying resources. 87 | * 88 | * @return void 89 | */ 90 | public function close() 91 | { 92 | if (! $this->resource) 93 | { 94 | return; 95 | } 96 | 97 | $resource = $this->detach(); 98 | 99 | fclose($resource); 100 | } 101 | 102 | /** 103 | * Method to attach resource into object. 104 | * 105 | * @param string|resource $stream The stream resource cursor. 106 | * @param string $mode Mode with which to open stream 107 | * 108 | * @return static Return self to support chaining. 109 | */ 110 | public function attach($stream, $mode = 'r') 111 | { 112 | $this->stream = $stream; 113 | 114 | if (is_resource($stream)) 115 | { 116 | $this->resource = $stream; 117 | } 118 | elseif (is_string($stream)) 119 | { 120 | $this->resource = fopen($stream, $mode); 121 | } 122 | else 123 | { 124 | throw new \InvalidArgumentException('Invalid resource.'); 125 | } 126 | 127 | return $this; 128 | } 129 | 130 | /** 131 | * Separates any underlying resources from the stream. 132 | * 133 | * After the stream has been detached, the stream is in an unusable state. 134 | * 135 | * @return resource|null Underlying PHP stream, if any 136 | */ 137 | public function detach() 138 | { 139 | $resource = $this->resource; 140 | 141 | $this->resource = null; 142 | $this->stream = null; 143 | 144 | return $resource; 145 | } 146 | 147 | /** 148 | * Get the size of the stream if known. 149 | * 150 | * @return int|null Returns the size in bytes if known, or null if unknown. 151 | */ 152 | public function getSize() 153 | { 154 | if (!is_resource($this->resource)) 155 | { 156 | return null; 157 | } 158 | 159 | $stats = fstat($this->resource); 160 | 161 | return $stats['size']; 162 | } 163 | 164 | /** 165 | * Returns the current position of the file read/write pointer 166 | * 167 | * @return int Position of the file pointer 168 | * @throws \RuntimeException on error. 169 | */ 170 | public function tell() 171 | { 172 | if (!is_resource($this->resource)) 173 | { 174 | throw new \RuntimeException('No resource available.'); 175 | } 176 | 177 | $result = ftell($this->resource); 178 | 179 | if (!is_int($result)) 180 | { 181 | throw new \RuntimeException('Error occurred during tell operation'); 182 | } 183 | 184 | return $result; 185 | } 186 | 187 | /** 188 | * Returns true if the stream is at the end of the stream. 189 | * 190 | * @return bool 191 | */ 192 | public function eof() 193 | { 194 | if (!is_resource($this->resource)) 195 | { 196 | return true; 197 | } 198 | 199 | return feof($this->resource); 200 | } 201 | 202 | /** 203 | * Returns whether or not the stream is seekable. 204 | * 205 | * @return bool 206 | */ 207 | public function isSeekable() 208 | { 209 | if (!is_resource($this->resource)) 210 | { 211 | return false; 212 | } 213 | 214 | $meta = stream_get_meta_data($this->resource); 215 | 216 | return $meta['seekable']; 217 | } 218 | 219 | /** 220 | * Seek to a position in the stream. 221 | * 222 | * @link http://www.php.net/manual/en/function.fseek.php 223 | * 224 | * @param int $offset Stream offset 225 | * @param int $whence Specifies how the cursor position will be calculated 226 | * based on the seek offset. Valid values are identical to the built-in 227 | * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to 228 | * offset bytes SEEK_CUR: Set position to current location plus offset 229 | * SEEK_END: Set position to end-of-stream plus offset. 230 | * 231 | * @return boolean 232 | * 233 | * @throws \RuntimeException on failure. 234 | */ 235 | public function seek($offset, $whence = SEEK_SET) 236 | { 237 | if (!is_resource($this->resource)) 238 | { 239 | throw new \RuntimeException('No resource available.'); 240 | } 241 | 242 | if (!$this->isSeekable()) 243 | { 244 | throw new \RuntimeException('Stream is not seekable'); 245 | } 246 | 247 | $result = fseek($this->resource, $offset, $whence); 248 | 249 | if ($result !== 0) 250 | { 251 | throw new \RuntimeException('Error seeking within stream'); 252 | } 253 | 254 | return true; 255 | } 256 | 257 | /** 258 | * Seek to the beginning of the stream. 259 | * 260 | * If the stream is not seekable, this method will raise an exception; 261 | * otherwise, it will perform a seek(0). 262 | * 263 | * @see seek() 264 | * @link http://www.php.net/manual/en/function.fseek.php 265 | * @throws \RuntimeException on failure. 266 | */ 267 | public function rewind() 268 | { 269 | return $this->seek(0); 270 | } 271 | 272 | /** 273 | * Returns whether or not the stream is writable. 274 | * 275 | * @return bool 276 | */ 277 | public function isWritable() 278 | { 279 | if (!is_resource($this->resource)) 280 | { 281 | return false; 282 | } 283 | 284 | $meta = stream_get_meta_data($this->resource); 285 | 286 | return is_writable($meta['uri']); 287 | } 288 | 289 | /** 290 | * Write data to the stream. 291 | * 292 | * @param string $string The string that is to be written. 293 | * 294 | * @return int Returns the number of bytes written to the stream. 295 | * @throws \RuntimeException on failure. 296 | */ 297 | public function write($string) 298 | { 299 | if (!is_resource($this->resource)) 300 | { 301 | throw new \RuntimeException('No resource available.'); 302 | } 303 | 304 | $result = fwrite($this->resource, $string); 305 | 306 | if ($result === false) 307 | { 308 | throw new \RuntimeException('Error writing to stream'); 309 | } 310 | 311 | return $result; 312 | } 313 | 314 | /** 315 | * Returns whether or not the stream is readable. 316 | * 317 | * @return bool 318 | */ 319 | public function isReadable() 320 | { 321 | if (!is_resource($this->resource)) 322 | { 323 | return false; 324 | } 325 | 326 | $meta = stream_get_meta_data($this->resource); 327 | $mode = $meta['mode']; 328 | 329 | return (strstr($mode, 'r') || strstr($mode, '+')); 330 | } 331 | 332 | /** 333 | * Read data from the stream. 334 | * 335 | * @param int $length Read up to $length bytes from the object and return 336 | * them. Fewer than $length bytes may be returned if underlying stream 337 | * call returns fewer bytes. 338 | * 339 | * @return string Returns the data read from the stream, or an empty string 340 | * if no bytes are available. 341 | * @throws \RuntimeException if an error occurs. 342 | */ 343 | public function read($length) 344 | { 345 | if (!is_resource($this->resource)) 346 | { 347 | throw new \RuntimeException('No resource available.'); 348 | } 349 | 350 | if (! $this->isReadable()) 351 | { 352 | throw new \RuntimeException('Stream is not readable'); 353 | } 354 | 355 | $result = fread($this->resource, $length); 356 | 357 | if ($result === false) 358 | { 359 | throw new \RuntimeException('Error reading stream'); 360 | } 361 | 362 | return $result; 363 | } 364 | 365 | /** 366 | * Returns the remaining contents in a string 367 | * 368 | * @return string 369 | * @throws \RuntimeException if unable to read or an error occurs while 370 | * reading. 371 | */ 372 | public function getContents() 373 | { 374 | if (!$this->isReadable()) 375 | { 376 | return ''; 377 | } 378 | 379 | $result = stream_get_contents($this->resource); 380 | 381 | if ($result === false) 382 | { 383 | throw new \RuntimeException('Error reading from stream'); 384 | } 385 | 386 | return $result; 387 | } 388 | 389 | /** 390 | * Get stream metadata as an associative array or retrieve a specific key. 391 | * 392 | * The keys returned are identical to the keys returned from PHP's 393 | * stream_get_meta_data() function. 394 | * 395 | * @link http://php.net/manual/en/function.stream-get-meta-data.php 396 | * 397 | * @param string $key Specific metadata to retrieve. 398 | * 399 | * @return array|mixed|null Returns an associative array if no key is 400 | * provided. Returns a specific key value if a key is provided and the 401 | * value is found, or null if the key is not found. 402 | */ 403 | public function getMetadata($key = null) 404 | { 405 | $metadata = stream_get_meta_data($this->resource); 406 | 407 | if ($key === null) 408 | { 409 | return $metadata; 410 | } 411 | 412 | if (!array_key_exists($key, $metadata)) 413 | { 414 | return null; 415 | } 416 | 417 | return $metadata[$key]; 418 | } 419 | 420 | /** 421 | * Method to get property Resource 422 | * 423 | * @return resource 424 | */ 425 | public function getResource() 426 | { 427 | return $this->resource; 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /src/Stream/StringStream.php: -------------------------------------------------------------------------------- 1 | 'string', 41 | 'stream_type' => 'STDIO', 42 | 'mode' => 'r+b', 43 | 'unread_bytes' => 0, 44 | 'seekable' => true, 45 | 'uri' => '', 46 | 'timed_out' => false, 47 | 'blocked' => true, 48 | 'eof' => false, 49 | ); 50 | 51 | /** 52 | * Property seekable. 53 | * 54 | * @var boolean 55 | */ 56 | protected $seekable = true; 57 | 58 | /** 59 | * Property writable. 60 | * 61 | * @var boolean 62 | */ 63 | protected $writable = true; 64 | 65 | /** 66 | * Class init. 67 | * 68 | * @param string $stream The stream resource cursor. 69 | * @param string $mode Mode with which to open stream 70 | */ 71 | public function __construct($stream = '', $mode = 'rb+') 72 | { 73 | $this->attach($stream, $mode); 74 | } 75 | 76 | /** 77 | * Closes the stream and any underlying resources. 78 | * 79 | * @return void 80 | */ 81 | public function close() 82 | { 83 | $this->detach(); 84 | } 85 | 86 | /** 87 | * Method to attach resource into object. 88 | * 89 | * @param string|resource $stream The stream resource cursor. 90 | * @param string $mode Mode with which to open stream 91 | * 92 | * @return static Return self to support chaining. 93 | */ 94 | public function attach($stream, $mode = 'rb+') 95 | { 96 | $this->stream = $stream; 97 | 98 | if (is_resource($stream)) 99 | { 100 | throw new \InvalidArgumentException('StringStream do not support resource.'); 101 | } 102 | 103 | if (is_array($stream) || (is_object($stream) && !is_callable(array($stream, '__toString')))) 104 | { 105 | throw new \InvalidArgumentException('StringStream only support string as resource.'); 106 | } 107 | 108 | if (strpos('+', $mode) === false) 109 | { 110 | $this->writable = false; 111 | } 112 | 113 | $this->resource = (string) $stream; 114 | 115 | return $this; 116 | } 117 | 118 | /** 119 | * Separates any underlying resources from the stream. 120 | * 121 | * After the stream has been detached, the stream is in an unusable state. 122 | * 123 | * @return resource|null Underlying PHP stream, if any 124 | */ 125 | public function detach() 126 | { 127 | $resource = $this->resource; 128 | 129 | $this->resource = null; 130 | $this->stream = null; 131 | $this->position = 0; 132 | $this->seekable = true; 133 | $this->writable = true; 134 | 135 | return $resource; 136 | } 137 | 138 | /** 139 | * Get the size of the stream if known. 140 | * 141 | * @return int|null Returns the size in bytes if known, or null if unknown. 142 | */ 143 | public function getSize() 144 | { 145 | if ($this->resource === null) 146 | { 147 | return null; 148 | } 149 | 150 | return strlen($this->resource); 151 | } 152 | 153 | /** 154 | * Returns the current position of the file read/write pointer 155 | * 156 | * @return int Position of the file pointer 157 | * @throws \RuntimeException on error. 158 | */ 159 | public function tell() 160 | { 161 | if ($this->resource === null) 162 | { 163 | throw new \RuntimeException('No resource set.'); 164 | } 165 | 166 | return $this->position; 167 | } 168 | 169 | /** 170 | * Returns true if the stream is at the end of the stream. 171 | * 172 | * @return bool 173 | */ 174 | public function eof() 175 | { 176 | return $this->position == $this->getSize(); 177 | } 178 | 179 | /** 180 | * Returns whether or not the stream is seekable. 181 | * 182 | * @return bool 183 | */ 184 | public function isSeekable() 185 | { 186 | return $this->seekable; 187 | } 188 | 189 | /** 190 | * Seek to a position in the stream. 191 | * 192 | * @link http://www.php.net/manual/en/function.fseek.php 193 | * 194 | * @param int $offset Stream offset 195 | * @param int $whence Specifies how the cursor position will be calculated 196 | * based on the seek offset. Valid values are identical to the built-in 197 | * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to 198 | * offset bytes SEEK_CUR: Set position to current location plus offset 199 | * SEEK_END: Set position to end-of-stream plus offset. 200 | * 201 | * @return boolean 202 | * 203 | * @throws \RuntimeException on failure. 204 | */ 205 | public function seek($offset, $whence = SEEK_SET) 206 | { 207 | if (!$this->isSeekable()) 208 | { 209 | throw new \RuntimeException('Stream is not seekable'); 210 | } 211 | 212 | if ($whence == SEEK_SET) 213 | { 214 | $this->position = $offset; 215 | } 216 | elseif ($whence == SEEK_CUR) 217 | { 218 | $this->position += $offset; 219 | } 220 | elseif ($whence == SEEK_END) 221 | { 222 | $this->position = $this->getSize(); 223 | $this->position += $offset; 224 | } 225 | 226 | if ($this->position < 0) 227 | { 228 | throw new \RuntimeException('Position should not less than 0.'); 229 | } 230 | 231 | return true; 232 | } 233 | 234 | /** 235 | * Seek to the beginning of the stream. 236 | * 237 | * If the stream is not seekable, this method will raise an exception; 238 | * otherwise, it will perform a seek(0). 239 | * 240 | * @see seek() 241 | * @link http://www.php.net/manual/en/function.fseek.php 242 | * @throws \RuntimeException on failure. 243 | */ 244 | public function rewind() 245 | { 246 | return $this->seek(0); 247 | } 248 | 249 | /** 250 | * Returns whether or not the stream is writable. 251 | * 252 | * @return bool 253 | */ 254 | public function isWritable() 255 | { 256 | return $this->writable; 257 | } 258 | 259 | /** 260 | * Write data to the stream. 261 | * 262 | * @param string $string The string that is to be written. 263 | * 264 | * @return int Returns the number of bytes written to the stream. 265 | * @throws \RuntimeException on failure. 266 | */ 267 | public function write($string) 268 | { 269 | $length = strlen($string); 270 | 271 | $start = substr($this->resource, 0, $this->position); 272 | $end = substr($this->resource, $this->position + $length); 273 | 274 | $this->resource = $start . $string . $end; 275 | 276 | $this->position = strlen($start . $string); 277 | 278 | return $string; 279 | } 280 | 281 | /** 282 | * Returns whether or not the stream is readable. 283 | * 284 | * @return bool 285 | */ 286 | public function isReadable() 287 | { 288 | return true; 289 | } 290 | 291 | /** 292 | * Read data from the stream. 293 | * 294 | * @param int $length Read up to $length bytes from the object and return 295 | * them. Fewer than $length bytes may be returned if underlying stream 296 | * call returns fewer bytes. 297 | * 298 | * @return string Returns the data read from the stream, or an empty string 299 | * if no bytes are available. 300 | * @throws \RuntimeException if an error occurs. 301 | */ 302 | public function read($length) 303 | { 304 | $result = substr($this->resource, $this->position, $length); 305 | 306 | $this->position += $length; 307 | 308 | return $result; 309 | } 310 | 311 | /** 312 | * Returns the remaining contents in a string 313 | * 314 | * @return string 315 | * @throws \RuntimeException if unable to read or an error occurs while 316 | * reading. 317 | */ 318 | public function getContents() 319 | { 320 | if ($this->resource === null) 321 | { 322 | return ''; 323 | } 324 | 325 | $result = substr($this->resource, $this->position); 326 | 327 | if ($result === false) 328 | { 329 | return ''; 330 | } 331 | 332 | return $result; 333 | } 334 | 335 | /** 336 | * Get stream metadata as an associative array or retrieve a specific key. 337 | * 338 | * The keys returned are identical to the keys returned from PHP's 339 | * stream_get_meta_data() function. 340 | * 341 | * @link http://php.net/manual/en/function.stream-get-meta-data.php 342 | * 343 | * @param string $key Specific metadata to retrieve. 344 | * 345 | * @return array|mixed|null Returns an associative array if no key is 346 | * provided. Returns a specific key value if a key is provided and the 347 | * value is found, or null if the key is not found. 348 | */ 349 | public function getMetadata($key = null) 350 | { 351 | $metadata = $this->metadata; 352 | 353 | $metadata['eof'] = $this->eof(); 354 | $metadata['seekable'] = $this->isSeekable(); 355 | $metadata['unread_bytes'] = $this->getSize() - $this->position; 356 | $metadata['mode'] = 'rb'; 357 | 358 | if ($this->isWritable()) 359 | { 360 | $metadata['mode'] = 'r+b'; 361 | } 362 | 363 | if ($key === null) 364 | { 365 | return $metadata; 366 | } 367 | 368 | if (!array_key_exists($key, $metadata)) 369 | { 370 | return null; 371 | } 372 | 373 | return $metadata[$key]; 374 | } 375 | 376 | /** 377 | * Method to get property Resource 378 | * 379 | * @return resource 380 | */ 381 | public function getResource() 382 | { 383 | return $this->resource; 384 | } 385 | 386 | /** 387 | * Method to set property seekable 388 | * 389 | * @param boolean $seekable 390 | * 391 | * @return static Return self to support chaining. 392 | */ 393 | public function seekable($seekable) 394 | { 395 | $this->seekable = (boolean) $seekable; 396 | 397 | return $this; 398 | } 399 | 400 | /** 401 | * Method to set property writable 402 | * 403 | * @param boolean $writable 404 | * 405 | * @return static Return self to support chaining. 406 | */ 407 | public function writable($writable) 408 | { 409 | $this->writable = (boolean) $writable; 410 | 411 | return $this; 412 | } 413 | } 414 | -------------------------------------------------------------------------------- /src/Transport/AbstractTransport.php: -------------------------------------------------------------------------------- 1 | setOptions($options); 38 | } 39 | 40 | /** 41 | * Send a request to the server and return a Response object with the response. 42 | * 43 | * @param RequestInterface $request The request object to send. 44 | * 45 | * @return ResponseInterface 46 | * 47 | * @since 2.1 48 | */ 49 | public function request(RequestInterface $request) 50 | { 51 | $uri = $request->getUri() 52 | ->withPath('') 53 | ->withQuery('') 54 | ->withFragment(''); 55 | 56 | $uri = $uri . $request->getRequestTarget(); 57 | 58 | $request = $request->withRequestTarget($uri); 59 | 60 | return $this->doRequest($request); 61 | } 62 | 63 | /** 64 | * Send a request to the server and return a Response object with the response. 65 | * 66 | * @param RequestInterface $request The request object to store request params. 67 | * 68 | * @return ResponseInterface 69 | * 70 | * @since 2.1 71 | */ 72 | abstract protected function doRequest(RequestInterface $request); 73 | 74 | /** 75 | * Get option value. 76 | * 77 | * @param string $name Option name. 78 | * @param mixed $default The default value if not exists. 79 | * 80 | * @return mixed The found value or default value. 81 | */ 82 | public function getOption($name, $default = null) 83 | { 84 | if (!isset($this->options[$name])) 85 | { 86 | return $default; 87 | } 88 | 89 | return $this->options[$name]; 90 | } 91 | 92 | /** 93 | * Set option value. 94 | * 95 | * @param string $name Option name. 96 | * @param mixed $value The value you want to set in. 97 | * 98 | * @return static Return self to support chaining. 99 | */ 100 | public function setOption($name, $value) 101 | { 102 | $this->options[$name] = $value; 103 | 104 | return $this; 105 | } 106 | 107 | /** 108 | * Method to get property Options 109 | * 110 | * @return array 111 | */ 112 | public function getOptions() 113 | { 114 | return $this->options; 115 | } 116 | 117 | /** 118 | * Method to set property options 119 | * 120 | * @param array $options 121 | * 122 | * @return static Return self to support chaining. 123 | */ 124 | public function setOptions($options) 125 | { 126 | if ($options instanceof \Traversable) 127 | { 128 | $options = iterator_to_array($options); 129 | } 130 | 131 | if (is_object($options)) 132 | { 133 | $options = get_object_vars($options); 134 | } 135 | 136 | $this->options = (array) $options; 137 | 138 | return $this; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/Transport/CurlTransport.php: -------------------------------------------------------------------------------- 1 | getMethod(); 40 | 41 | // Don't wait for body when $method is HEAD 42 | $options[CURLOPT_NOBODY] = ($request->getMethod() === 'HEAD'); 43 | 44 | // Initialize the certificate store 45 | $options[CURLOPT_CAINFO] = $this->getOption('certpath', __DIR__ . '/cacert.pem'); 46 | 47 | // Set HTTP Version 48 | switch ($request->getProtocolVersion()) 49 | { 50 | case '1.0': 51 | $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; 52 | break; 53 | 54 | case '1.1': 55 | $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; 56 | break; 57 | 58 | case '2.0': 59 | if (defined('CURL_HTTP_VERSION_2_0')) 60 | { 61 | $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0; 62 | } 63 | } 64 | 65 | // If data exists let's encode it and make sure our Content-type header is set. 66 | $data = (string) $request->getBody(); 67 | 68 | if (isset($data)) 69 | { 70 | // If the data is a scalar value simply add it to the cURL post fields. 71 | if (is_scalar($data) || strpos($request->getHeaderLine('Content-Type'), 'multipart/form-data') === 0) 72 | { 73 | $options[CURLOPT_POSTFIELDS] = $data; 74 | } 75 | else 76 | // Otherwise we need to encode the value first. 77 | { 78 | $options[CURLOPT_POSTFIELDS] = http_build_query($data); 79 | } 80 | 81 | if (!$request->getHeaderLine('Content-Type')) 82 | { 83 | $request = $request->withHeader('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8'); 84 | } 85 | 86 | // Add the relevant headers. 87 | if (is_scalar($options[CURLOPT_POSTFIELDS])) 88 | { 89 | $request = $request->withHeader('Content-Length', strlen($options[CURLOPT_POSTFIELDS])); 90 | } 91 | } 92 | 93 | // Build the headers string for the request. 94 | if ($headers = $request->getHeaders()) 95 | { 96 | // Add the headers string into the stream context options array. 97 | $options[CURLOPT_HTTPHEADER] = HeaderHelper::toHeaderLine($headers); 98 | } 99 | 100 | // If an explicit timeout is given user it. 101 | if ($timeout = $this->getOption('timeout')) 102 | { 103 | $options[CURLOPT_TIMEOUT] = (int) $timeout; 104 | $options[CURLOPT_CONNECTTIMEOUT] = (int) $timeout; 105 | } 106 | 107 | // If an explicit user agent is given use it. 108 | if ($userAgent = $this->getOption('userAgent')) 109 | { 110 | $options[CURLOPT_USERAGENT] = $userAgent; 111 | } 112 | 113 | // Set the request URL. 114 | $options[CURLOPT_URL] = (string) $request->getRequestTarget(); 115 | 116 | // We want our headers. :-) 117 | $options[CURLOPT_HEADER] = true; 118 | 119 | // Return it... echoing it would be tacky. 120 | $options[CURLOPT_RETURNTRANSFER] = true; 121 | 122 | // Override the Expect header to prevent cURL from confusing itself in its own stupidity. 123 | // Link: http://the-stickman.com/web-development/php-and-curl-disabling-100-continue-header/ 124 | $options[CURLOPT_HTTPHEADER][] = 'Expect:'; 125 | 126 | /* 127 | * Follow redirects if server config allows 128 | * @deprecated safe_mode is removed in PHP 5.4, check will be dropped when PHP 5.3 support is dropped 129 | */ 130 | if (!ini_get('safe_mode') && !ini_get('open_basedir')) 131 | { 132 | $options[CURLOPT_FOLLOWLOCATION] = (bool) isset($this->options['follow_location']) ? $this->options['follow_location'] : true; 133 | } 134 | 135 | // Set any custom transport options 136 | if ($this->getOption('options')) 137 | { 138 | foreach ((array) $this->getOption('options') as $key => $value) 139 | { 140 | $options[$key] = $value; 141 | } 142 | } 143 | 144 | // Set the cURL options. 145 | curl_setopt_array($ch, $options); 146 | 147 | // Execute the request and close the connection. 148 | $content = curl_exec($ch); 149 | 150 | if (!$this->getOption('allow_empty_result', false) && !trim($content)) 151 | { 152 | $message = curl_error($ch); 153 | 154 | // Error but nothing from cURL? Create our own 155 | $message = $message ? : 'No HTTP response received'; 156 | 157 | throw new \RuntimeException($message); 158 | } 159 | 160 | // Get the request information. 161 | $info = curl_getinfo($ch); 162 | 163 | // Close the connection. 164 | curl_close($ch); 165 | 166 | return $this->getResponse($content, $info); 167 | } 168 | 169 | /** 170 | * Method to get a response object from a server response. 171 | * 172 | * @param string $content The complete server response, including headers 173 | * as a string if the response has no errors. 174 | * @param array $info The cURL request information. 175 | * 176 | * @return Response 177 | * 178 | * @since 1.0 179 | * @throws \UnexpectedValueException 180 | */ 181 | protected function getResponse($content, $info) 182 | { 183 | // Create the response object. 184 | $return = new Response; 185 | 186 | // Get the number of redirects that occurred. 187 | $redirects = isset($info['redirect_count']) ? $info['redirect_count'] : 0; 188 | 189 | /* 190 | * Split the response into headers and body. If cURL encountered redirects, the headers for the redirected requests will 191 | * also be included. So we split the response into header + body + the number of redirects and only use the last two 192 | * sections which should be the last set of headers and the actual body. 193 | */ 194 | $response = explode("\r\n\r\n", $content, 2 + $redirects); 195 | 196 | // Set the body for the response. 197 | $return->getBody()->write(array_pop($response)); 198 | 199 | $return->getBody()->rewind(); 200 | 201 | // Get the last set of response headers as an array. 202 | $headers = explode("\r\n", array_pop($response)); 203 | 204 | // Get the response code from the first offset of the response headers. 205 | preg_match('/[0-9]{3}/', array_shift($headers), $matches); 206 | 207 | $code = count($matches) ? $matches[0] : null; 208 | 209 | if (is_numeric($code)) 210 | { 211 | $return = $return->withStatus($code); 212 | } 213 | 214 | // No valid response code was detected. 215 | else 216 | { 217 | throw new \UnexpectedValueException('No HTTP response code found.'); 218 | } 219 | 220 | // Add the response headers to the response object. 221 | foreach ($headers as $header) 222 | { 223 | $pos = strpos($header, ':'); 224 | 225 | $return = $return->withHeader(trim(substr($header, 0, $pos)), trim(substr($header, ($pos + 1)))); 226 | } 227 | 228 | return $return; 229 | } 230 | 231 | /** 232 | * Use stream to download file. 233 | * 234 | * @param RequestInterface $request The request object to store request params. 235 | * @param string|StreamInterface $dest The dest path to store file. 236 | * 237 | * @return ResponseInterface 238 | * @since 2.1 239 | */ 240 | public function download(RequestInterface $request, $dest) 241 | { 242 | if (!$dest) 243 | { 244 | throw new \InvalidArgumentException('Target file path is empty.'); 245 | } 246 | 247 | $response = $this->request($request); 248 | 249 | file_put_contents($dest, $response->getBody()->__toString()); 250 | 251 | return $response; 252 | } 253 | 254 | /** 255 | * Method to check if HTTP transport layer available for using 256 | * 257 | * @return boolean True if available else false 258 | * 259 | * @since 2.1 260 | */ 261 | public static function isSupported() 262 | { 263 | return function_exists('curl_init') && is_callable('curl_init'); 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/Transport/StreamTransport.php: -------------------------------------------------------------------------------- 1 | $request->getMethod()); 39 | 40 | // Set HTTP Version 41 | $options['protocol_version'] = $request->getProtocolVersion(); 42 | 43 | // If data exists let's encode it and make sure our Content-Type header is set. 44 | $data = (string) $request->getBody(); 45 | 46 | if (isset($data)) 47 | { 48 | // If the data is a scalar value simply add it to the stream context options. 49 | if (is_scalar($data)) 50 | { 51 | $options['content'] = $data; 52 | } 53 | else 54 | // Otherwise we need to encode the value first. 55 | { 56 | $options['content'] = http_build_query($data); 57 | } 58 | 59 | if (!$request->getHeader('Content-Type')) 60 | { 61 | $request = $request->withHeader('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8'); 62 | } 63 | 64 | // Add the relevant headers. 65 | $request = $request->withHeader('Content-Length', strlen($options['content'])); 66 | } 67 | 68 | // Speed up stream get URL 69 | // @see http://stackoverflow.com/questions/3629504/php-file-get-contents-very-slow-when-using-full-url 70 | // @see http://stackoverflow.com/questions/13679976/how-to-speed-up-file-get-contents 71 | $request = $request->withHeader('Connection', 'Close'); 72 | 73 | // Build the headers string for the request. 74 | if ($headers = $request->getHeaders()) 75 | { 76 | // Add the headers string into the stream context options array. 77 | $options['header'] = HeaderHelper::toHeaderLine($headers, true); 78 | } 79 | 80 | // If an explicit timeout is given user it. 81 | if ($this->getOption('timeout')) 82 | { 83 | $options['timeout'] = (int) $this->getOption('timeout'); 84 | } 85 | 86 | // If an explicit user agent is given use it. 87 | if ($this->getOption('userAgent')) 88 | { 89 | $options['user_agent'] = $this->getOption('userAgent'); 90 | } 91 | 92 | // Ignore HTTP errors so that we can capture them. 93 | $options['ignore_errors'] = 1; 94 | 95 | // Follow redirects. 96 | $options['follow_location'] = (int) $this->getOption('follow_location', 1); 97 | 98 | foreach ((array) $this->getOption('options') as $key => $value) 99 | { 100 | $options[$key] = $value; 101 | } 102 | 103 | // Create the stream context for the request. 104 | $context = stream_context_create(array('http' => $options)); 105 | 106 | // Capture PHP errors 107 | $php_errormsg = ''; 108 | $track_errors = ini_get('track_errors'); 109 | ini_set('track_errors', true); 110 | 111 | $connection = @fopen($request->getRequestTarget(), Stream::MODE_READ_ONLY_FROM_BEGIN, false, $context); 112 | 113 | if (!$connection) 114 | { 115 | if (!$php_errormsg) 116 | { 117 | // Error but nothing from php? Create our own 118 | $php_errormsg = sprintf('Could not connect to resource: %s', $request->getRequestTarget()); 119 | } 120 | 121 | // Restore error tracking to give control to the exception handler 122 | ini_set('track_errors', $track_errors); 123 | 124 | throw new \RuntimeException($php_errormsg); 125 | } 126 | 127 | $stream = new Stream($connection); 128 | 129 | if ($dest = $this->getOption('target_file')) 130 | { 131 | $content = ''; 132 | StreamHelper::copyTo($stream, $dest); 133 | } 134 | else 135 | { 136 | $content = $stream->getContents(); 137 | } 138 | 139 | $metadata = $stream->getMetadata(); 140 | 141 | $stream->close(); 142 | 143 | if (isset($metadata['wrapper_data']['headers'])) 144 | { 145 | $headers = $metadata['wrapper_data']['headers']; 146 | } 147 | elseif (isset($metadata['wrapper_data'])) 148 | { 149 | $headers = $metadata['wrapper_data']; 150 | } 151 | else 152 | { 153 | $headers = array(); 154 | } 155 | 156 | return $this->getResponse($headers, $content); 157 | } 158 | 159 | /** 160 | * Method to get a response object from a server response. 161 | * 162 | * @param array $headers The response headers as an array. 163 | * @param string $body The response body as a string. 164 | * 165 | * @return Response 166 | * 167 | * @since 2.1 168 | * @throws \UnexpectedValueException 169 | */ 170 | protected function getResponse(array $headers, $body) 171 | { 172 | // Create the response object. 173 | $return = new Response; 174 | 175 | // Set the body for the response. 176 | $return->getBody()->write($body); 177 | 178 | $return->getBody()->rewind(); 179 | 180 | // Get the response code from the first offset of the response headers. 181 | preg_match('/[0-9]{3}/', array_shift($headers), $matches); 182 | $code = $matches[0]; 183 | 184 | if (is_numeric($code)) 185 | { 186 | $return = $return->withStatus($code); 187 | } 188 | // No valid response code was detected. 189 | else 190 | { 191 | throw new \UnexpectedValueException('No HTTP response code found.'); 192 | } 193 | // Add the response headers to the response object. 194 | foreach ($headers as $header) 195 | { 196 | $pos = strpos($header, ':'); 197 | 198 | $return = $return->withHeader(trim(substr($header, 0, $pos)), trim(substr($header, ($pos + 1)))); 199 | } 200 | 201 | return $return; 202 | } 203 | 204 | /** 205 | * Use stream to download file. 206 | * 207 | * @param RequestInterface $request The request object to store request params. 208 | * @param string|StreamInterface $dest The dest path to store file. 209 | * 210 | * @return ResponseInterface 211 | * @since 2.1 212 | */ 213 | public function download(RequestInterface $request, $dest) 214 | { 215 | if (!$dest) 216 | { 217 | throw new \InvalidArgumentException('Target file path is emptty.'); 218 | } 219 | 220 | $dest = $dest instanceof StreamInterface ? $dest : new Stream($dest, Stream::MODE_READ_WRITE_RESET); 221 | 222 | $this->setOption('target_file', $dest); 223 | 224 | $response = $this->request($request); 225 | 226 | $this->setOption('target_file', null); 227 | 228 | $dest->close(); 229 | 230 | return $response; 231 | } 232 | 233 | /** 234 | * Method to check if HTTP transport layer available for using 235 | * 236 | * @return boolean True if available else false 237 | * 238 | * @since 1.0 239 | */ 240 | public static function isSupported() 241 | { 242 | return function_exists('fopen') && is_callable('fopen') && ini_get('allow_url_fopen'); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/Transport/TransportInterface.php: -------------------------------------------------------------------------------- 1 | file = $file; 92 | $this->stream = new Stream($file, Stream::MODE_READ_WRITE_FROM_BEGIN); 93 | } 94 | elseif (is_resource($file)) 95 | { 96 | $this->stream = new Stream($file); 97 | } 98 | elseif ($file instanceof StreamInterface) 99 | { 100 | $this->stream = $file; 101 | } 102 | 103 | if (!$this->file && !$this->stream) 104 | { 105 | throw new \InvalidArgumentException('Invalid stream or file provided for UploadedFile'); 106 | } 107 | 108 | if (!is_int($size)) 109 | { 110 | throw new \InvalidArgumentException('Size should be integer.'); 111 | } 112 | 113 | if (!is_int($error) || 0 > $error || 8 < $error 114 | ) { 115 | throw new \InvalidArgumentException('Error status must be integer or an UPLOAD_ERR_* constant.'); 116 | } 117 | 118 | if ($clientFilename !== null && !is_string($clientFilename)) 119 | { 120 | throw new \InvalidArgumentException('Client filename must be null or string'); 121 | } 122 | 123 | if ($clientMediaType !== null && !is_string($clientMediaType)) { 124 | throw new \InvalidArgumentException('Media type must be null or a string'); 125 | } 126 | 127 | $this->error = $error; 128 | $this->size = $size; 129 | $this->clientFilename = $clientFilename; 130 | $this->clientMediaType = $clientMediaType; 131 | 132 | $this->sapi = PHP_SAPI; 133 | } 134 | 135 | /** 136 | * Retrieve a stream representing the uploaded file. 137 | * 138 | * This method MUST return a StreamInterface instance, representing the 139 | * uploaded file. The purpose of this method is to allow utilizing native PHP 140 | * stream functionality to manipulate the file upload, such as 141 | * stream_copy_to_stream() (though the result will need to be decorated in a 142 | * native PHP stream wrapper to work with such functions). 143 | * 144 | * If the moveTo() method has been called previously, this method MUST raise 145 | * an exception. 146 | * 147 | * @return StreamInterface Stream representation of the uploaded file. 148 | * 149 | * @throws \RuntimeException in cases when no stream is available or can be 150 | * created. 151 | */ 152 | public function getStream() 153 | { 154 | if ($this->moved) 155 | { 156 | throw new \RuntimeException('The file has already moved.'); 157 | } 158 | 159 | if ($this->stream instanceof StreamInterface) 160 | { 161 | return $this->stream; 162 | } 163 | 164 | $this->stream = new Stream($this->file); 165 | 166 | return $this->stream; 167 | } 168 | 169 | /** 170 | * Move the uploaded file to a new location. 171 | * 172 | * Use this method as an alternative to move_uploaded_file(). This method is 173 | * guaranteed to work in both SAPI and non-SAPI environments. 174 | * Implementations must determine which environment they are in, and use the 175 | * appropriate method (move_uploaded_file(), rename(), or a stream 176 | * operation) to perform the operation. 177 | * 178 | * $targetPath may be an absolute path, or a relative path. If it is a 179 | * relative path, resolution should be the same as used by PHP's rename() 180 | * function. 181 | * 182 | * The original file or stream MUST be removed on completion. 183 | * 184 | * If this method is called more than once, any subsequent calls MUST raise 185 | * an exception. 186 | * 187 | * When used in an SAPI environment where $_FILES is populated, when writing 188 | * files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be 189 | * used to ensure permissions and upload status are verified correctly. 190 | * 191 | * If you wish to move to a stream, use getStream(), as SAPI operations 192 | * cannot guarantee writing to stream destinations. 193 | * 194 | * @see http://php.net/is_uploaded_file 195 | * @see http://php.net/move_uploaded_file 196 | * 197 | * @param string $targetPath Path to which to move the uploaded file. 198 | * 199 | * @throws \InvalidArgumentException if the $path specified is invalid. 200 | * @throws \RuntimeException on any error during the move operation, or on 201 | * the second or subsequent call to the method. 202 | */ 203 | public function moveTo($targetPath) 204 | { 205 | $targetPath = (string) $targetPath; 206 | 207 | if (empty($targetPath)) 208 | { 209 | throw new \InvalidArgumentException('Target path must be a non-empty string'); 210 | } 211 | 212 | if ($this->moved) 213 | { 214 | throw new \RuntimeException('Cannot move file, it has already moved!'); 215 | } 216 | 217 | $sapi = $this->getSapi(); 218 | 219 | // If we sent a stream or is CLI, use stream to write file. 220 | if (!$sapi || $sapi == 'cli' || !$this->file) 221 | { 222 | $this->writeFile($targetPath); 223 | } 224 | // If we sent a plain string as file path, use move_uploaded_file() 225 | else 226 | { 227 | if (move_uploaded_file($this->file, $targetPath) === false) 228 | { 229 | throw new \RuntimeException('Error moving uploaded file'); 230 | } 231 | } 232 | } 233 | 234 | /** 235 | * Retrieve the file size. 236 | * 237 | * Implementations SHOULD return the value stored in the "size" key of 238 | * the file in the $_FILES array if available, as PHP calculates this based 239 | * on the actual size transmitted. 240 | * 241 | * @return int|null The file size in bytes or null if unknown. 242 | */ 243 | public function getSize() 244 | { 245 | return $this->size; 246 | } 247 | 248 | /** 249 | * Retrieve the error associated with the uploaded file. 250 | * 251 | * The return value MUST be one of PHP's UPLOAD_ERR_XXX constants. 252 | * 253 | * If the file was uploaded successfully, this method MUST return 254 | * UPLOAD_ERR_OK. 255 | * 256 | * Implementations SHOULD return the value stored in the "error" key of 257 | * the file in the $_FILES array. 258 | * 259 | * @see http://php.net/manual/en/features.file-upload.errors.php 260 | * 261 | * @return int One of PHP's UPLOAD_ERR_XXX constants. 262 | */ 263 | public function getError() 264 | { 265 | return $this->error; 266 | } 267 | 268 | /** 269 | * Retrieve the filename sent by the client. 270 | * 271 | * Do not trust the value returned by this method. A client could send 272 | * a malicious filename with the intention to corrupt or hack your 273 | * application. 274 | * 275 | * Implementations SHOULD return the value stored in the "name" key of 276 | * the file in the $_FILES array. 277 | * 278 | * @return string|null The filename sent by the client or null if none 279 | * was provided. 280 | */ 281 | public function getClientFilename() 282 | { 283 | return $this->clientFilename; 284 | } 285 | 286 | /** 287 | * Retrieve the media type sent by the client. 288 | * 289 | * Do not trust the value returned by this method. A client could send 290 | * a malicious media type with the intention to corrupt or hack your 291 | * application. 292 | * 293 | * Implementations SHOULD return the value stored in the "type" key of 294 | * the file in the $_FILES array. 295 | * 296 | * @return string|null The media type sent by the client or null if none 297 | * was provided. 298 | */ 299 | public function getClientMediaType() 300 | { 301 | return $this->clientMediaType; 302 | } 303 | 304 | /** 305 | * Write internal stream to given path 306 | * 307 | * @param string $path 308 | */ 309 | protected function writeFile($path) 310 | { 311 | $handle = fopen($path, Stream::MODE_READ_WRITE_RESET); 312 | 313 | if ($handle === false) 314 | { 315 | throw new \RuntimeException('Unable to write to path: ' . $path); 316 | } 317 | 318 | $this->stream->rewind(); 319 | 320 | while (!$this->stream->eof()) 321 | { 322 | fwrite($handle, $this->stream->read(4096)); 323 | } 324 | 325 | fclose($handle); 326 | } 327 | 328 | /** 329 | * Method to get property Sapi 330 | * 331 | * @return string 332 | */ 333 | public function getSapi() 334 | { 335 | return $this->sapi; 336 | } 337 | 338 | /** 339 | * Method to set property sapi 340 | * 341 | * @param string $sapi 342 | * 343 | * @return static Return self to support chaining. 344 | */ 345 | public function setSapi($sapi) 346 | { 347 | $this->sapi = $sapi; 348 | 349 | return $this; 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /src/Uri/AbstractUri.php: -------------------------------------------------------------------------------- 1 | parse($uri); 98 | } 99 | } 100 | 101 | /** 102 | * Magic method to get the string representation of the URI object. 103 | * 104 | * @return string 105 | * 106 | * @since 2.0 107 | */ 108 | public function __toString() 109 | { 110 | return $this->toString(); 111 | } 112 | 113 | /** 114 | * Returns full uri string. 115 | * 116 | * @param array $parts An array specifying the parts to render. 117 | * 118 | * @return string The rendered URI string. 119 | * 120 | * @since 2.0 121 | */ 122 | public function toString(array $parts = array('scheme', 'user', 'pass', 'host', 'port', 'path', 'query', 'fragment')) 123 | { 124 | // Make sure the query is created 125 | $query = $this->getQuery(); 126 | 127 | $uri = ''; 128 | $uri .= in_array('scheme', $parts) ? (!empty($this->scheme) ? $this->scheme . '://' : '') : ''; 129 | $uri .= in_array('user', $parts) ? $this->user : ''; 130 | $uri .= in_array('pass', $parts) ? (!empty($this->pass) ? ':' : '') . $this->pass . (!empty($this->user) ? '@' : '') : ''; 131 | $uri .= in_array('host', $parts) ? $this->host : ''; 132 | $uri .= in_array('port', $parts) ? (!empty($this->port) ? ':' : '') . $this->port : ''; 133 | $uri .= in_array('path', $parts) ? $this->path ? '/' . ltrim($this->path, '/') : '' : ''; 134 | $uri .= in_array('query', $parts) ? (!empty($query) ? '?' . $query : '') : ''; 135 | $uri .= in_array('fragment', $parts) ? (!empty($this->fragment) ? '#' . $this->fragment : '') : ''; 136 | 137 | return $uri; 138 | } 139 | 140 | /** 141 | * Checks if variable exists. 142 | * 143 | * @param string $name Name of the query variable to check. 144 | * 145 | * @return boolean True if the variable exists. 146 | * 147 | * @since 2.0 148 | */ 149 | public function hasVar($name) 150 | { 151 | return array_key_exists($name, $this->vars); 152 | } 153 | 154 | /** 155 | * Returns a query variable by name. 156 | * 157 | * @param string $name Name of the query variable to get. 158 | * @param string $default Default value to return if the variable is not set. 159 | * 160 | * @return array Query variables. 161 | * 162 | * @since 2.0 163 | */ 164 | public function getVar($name, $default = null) 165 | { 166 | if (array_key_exists($name, $this->vars)) 167 | { 168 | return $this->vars[$name]; 169 | } 170 | 171 | return $default; 172 | } 173 | 174 | /** 175 | * Returns flat query string. 176 | * 177 | * @param boolean $toArray True to return the query as a key => value pair array. 178 | * 179 | * @return string Query string. 180 | * 181 | * @since 2.0 182 | */ 183 | public function getQuery($toArray = false) 184 | { 185 | if ($toArray) 186 | { 187 | return $this->vars; 188 | } 189 | 190 | // If the query is empty build it first 191 | if (is_null($this->query)) 192 | { 193 | $this->query = UriHelper::buildQuery($this->vars); 194 | } 195 | 196 | return $this->query; 197 | } 198 | 199 | /** 200 | * Get URI scheme (protocol) 201 | * ie. http, https, ftp, etc... 202 | * 203 | * @return string The URI scheme. 204 | * 205 | * @since 2.0 206 | */ 207 | public function getScheme() 208 | { 209 | return $this->scheme; 210 | } 211 | 212 | /** 213 | * Get URI username 214 | * Returns the username, or null if no username was specified. 215 | * 216 | * @return string The URI username. 217 | * 218 | * @since 2.0 219 | */ 220 | public function getUser() 221 | { 222 | return $this->user; 223 | } 224 | 225 | /** 226 | * Get URI password 227 | * Returns the password, or null if no password was specified. 228 | * 229 | * @return string The URI password. 230 | * 231 | * @since 2.0 232 | */ 233 | public function getPass() 234 | { 235 | return $this->pass; 236 | } 237 | 238 | /** 239 | * Retrieve the user information component of the URI. 240 | * 241 | * If no user information is present, this method MUST return an empty 242 | * string. 243 | * 244 | * If a user is present in the URI, this will return that value; 245 | * additionally, if the password is also present, it will be appended to the 246 | * user value, with a colon (":") separating the values. 247 | * 248 | * The trailing "@" character is not part of the user information and MUST 249 | * NOT be added. 250 | * 251 | * @return string The URI user information, in "username[:password]" format. 252 | * 253 | * @since 2.1 254 | */ 255 | public function getUserInfo() 256 | { 257 | $info = $this->user; 258 | 259 | if ($info && $this->pass) 260 | { 261 | $info .= ':' . $this->pass; 262 | } 263 | 264 | return $info; 265 | } 266 | 267 | /** 268 | * Get URI host 269 | * Returns the hostname/ip or null if no hostname/ip was specified. 270 | * 271 | * @return string The URI host. 272 | * 273 | * @since 2.0 274 | */ 275 | public function getHost() 276 | { 277 | return $this->host; 278 | } 279 | 280 | /** 281 | * Get URI port 282 | * Returns the port number, or null if no port was specified. 283 | * 284 | * @return integer The URI port number. 285 | * 286 | * @since 2.0 287 | */ 288 | public function getPort() 289 | { 290 | return (isset($this->port)) ? $this->port : null; 291 | } 292 | 293 | /** 294 | * Gets the URI path string. 295 | * 296 | * @return string The URI path string. 297 | * 298 | * @since 2.0 299 | */ 300 | public function getPath() 301 | { 302 | return $this->path; 303 | } 304 | 305 | /** 306 | * Get the URI archor string 307 | * Everything after the "#". 308 | * 309 | * @return string The URI anchor string. 310 | * 311 | * @since 2.0 312 | */ 313 | public function getFragment() 314 | { 315 | return $this->fragment; 316 | } 317 | 318 | /** 319 | * Checks whether the current URI is using HTTPS. 320 | * 321 | * @return boolean True if using SSL via HTTPS. 322 | * 323 | * @since 2.0 324 | */ 325 | public function isSSL() 326 | { 327 | return $this->getScheme() == 'https' ? true : false; 328 | } 329 | 330 | 331 | 332 | /** 333 | * Parse a given URI and populate the class fields. 334 | * 335 | * @param string $uri The URI string to parse. 336 | * 337 | * @return boolean True on success. 338 | * 339 | * @since 2.0 340 | */ 341 | protected function parse($uri) 342 | { 343 | // Set the original URI to fall back on 344 | $this->uri = $uri; 345 | 346 | /* 347 | * Parse the URI and populate the object fields. If URI is parsed properly, 348 | * set method return value to true. 349 | */ 350 | 351 | $parts = UriHelper::parseUrl($uri); 352 | 353 | $retval = ($parts) ? true : false; 354 | 355 | // We need to replace & with & for parse_str to work right... 356 | if (isset($parts['query']) && strpos($parts['query'], '&')) 357 | { 358 | $parts['query'] = str_replace('&', '&', $parts['query']); 359 | } 360 | 361 | $this->scheme = isset($parts['scheme']) ? $parts['scheme'] : null; 362 | $this->user = isset($parts['user']) ? $parts['user'] : null; 363 | $this->pass = isset($parts['pass']) ? $parts['pass'] : null; 364 | $this->host = isset($parts['host']) ? $parts['host'] : null; 365 | $this->port = isset($parts['port']) ? $parts['port'] : null; 366 | $this->path = isset($parts['path']) ? $parts['path'] : null; 367 | $this->query = isset($parts['query']) ? $parts['query'] : null; 368 | $this->fragment = isset($parts['fragment']) ? $parts['fragment'] : null; 369 | 370 | // Parse the query 371 | if (isset($parts['query'])) 372 | { 373 | $this->vars = UriHelper::parseQuery($parts['query']); 374 | } 375 | 376 | return $retval; 377 | } 378 | 379 | /** 380 | * getUri 381 | * 382 | * @return string 383 | */ 384 | public function getOriginal() 385 | { 386 | return $this->uri; 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /src/Uri/PsrUri.php: -------------------------------------------------------------------------------- 1 | 80, 41 | 'https' => 443, 42 | 'ftp' => 21, 43 | 'sftp' => 22 44 | ); 45 | 46 | /** 47 | * Constructor. 48 | * You can pass a URI string to the constructor to initialise a specific URI. 49 | * 50 | * @param string $uri The optional URI string 51 | * 52 | * @since 2.0 53 | */ 54 | public function __construct($uri = '') 55 | { 56 | if (!is_string($uri)) 57 | { 58 | throw new \InvalidArgumentException('URI should be a string'); 59 | } 60 | 61 | parent::__construct($uri); 62 | } 63 | 64 | /** 65 | * Retrieve the authority component of the URI. 66 | * 67 | * If no authority information is present, this method MUST return an empty 68 | * string. 69 | * 70 | * The authority syntax of the URI is: 71 | * 72 | *
 73 | 	 * [user-info@]host[:port]
 74 | 	 * 
75 | * 76 | * If the port component is not set or is the standard port for the current 77 | * scheme, it SHOULD NOT be included. 78 | * 79 | * @see https://tools.ietf.org/html/rfc3986#section-3.2 80 | * 81 | * @return string The URI authority, in "[user-info@]host[:port]" format. 82 | */ 83 | public function getAuthority() 84 | { 85 | if (empty($this->host)) 86 | { 87 | return ''; 88 | } 89 | 90 | $authority = $this->host; 91 | 92 | $userInfo = $this->getUserInfo(); 93 | 94 | if ($userInfo) 95 | { 96 | $authority = $userInfo . '@' . $authority; 97 | } 98 | 99 | if (!$this->isStandardPort($this->scheme, $this->host, $this->port)) 100 | { 101 | $authority .= ':' . $this->port; 102 | } 103 | 104 | return $authority; 105 | } 106 | 107 | /** 108 | * Return an instance with the specified scheme. 109 | * 110 | * This method MUST retain the state of the current instance, and return 111 | * an instance that contains the specified scheme. 112 | * 113 | * Implementations MUST support the schemes "http" and "https" case 114 | * insensitively, and MAY accommodate other schemes if required. 115 | * 116 | * An empty scheme is equivalent to removing the scheme. 117 | * 118 | * @param string $scheme The scheme to use with the new instance. 119 | * 120 | * @return static A new instance with the specified scheme. 121 | * 122 | * @throws \InvalidArgumentException for invalid or unsupported schemes. 123 | */ 124 | public function withScheme($scheme) 125 | { 126 | $scheme = UriHelper::filterScheme($scheme); 127 | 128 | $new = clone $this; 129 | $new->scheme = $scheme; 130 | 131 | return $new; 132 | } 133 | 134 | /** 135 | * Return an instance with the specified user information. 136 | * 137 | * This method MUST retain the state of the current instance, and return 138 | * an instance that contains the specified user information. 139 | * 140 | * Password is optional, but the user information MUST include the 141 | * user; an empty string for the user is equivalent to removing user 142 | * information. 143 | * 144 | * @param string $user The user name to use for authority. 145 | * @param string $password The password associated with $user. 146 | * 147 | * @return static A new instance with the specified user information. 148 | */ 149 | public function withUserInfo($user, $password = null) 150 | { 151 | $new = clone $this; 152 | $new->user = $user; 153 | $new->pass = $password; 154 | 155 | return $new; 156 | } 157 | 158 | /** 159 | * Return an instance with the specified host. 160 | * 161 | * This method MUST retain the state of the current instance, and return 162 | * an instance that contains the specified host. 163 | * 164 | * An empty host value is equivalent to removing the host. 165 | * 166 | * @param string $host The hostname to use with the new instance. 167 | * 168 | * @return static A new instance with the specified host. 169 | * 170 | * @throws \InvalidArgumentException for invalid hostnames. 171 | */ 172 | public function withHost($host) 173 | { 174 | $new = clone $this; 175 | $new->host = $host; 176 | 177 | return $new; 178 | } 179 | 180 | /** 181 | * Return an instance with the specified port. 182 | * 183 | * This method MUST retain the state of the current instance, and return 184 | * an instance that contains the specified port. 185 | * 186 | * Implementations MUST raise an exception for ports outside the 187 | * established TCP and UDP port ranges. 188 | * 189 | * A null value provided for the port is equivalent to removing the port 190 | * information. 191 | * 192 | * @param int $port The port to use with the new instance; a null value 193 | * removes the port information. 194 | * 195 | * @return static A new instance with the specified port. 196 | * @throws \InvalidArgumentException for invalid ports. 197 | */ 198 | public function withPort($port) 199 | { 200 | if (is_object($port) || is_array($port)) 201 | { 202 | throw new \InvalidArgumentException('Invalid port type.'); 203 | } 204 | 205 | $port = (int) $port; 206 | 207 | if ($port < 1 || $port > 65535) 208 | { 209 | throw new \InvalidArgumentException(sprintf('Number of "%d" is not a valid TCP/UDP port', $port)); 210 | } 211 | 212 | $new = clone $this; 213 | $new->port = $port; 214 | 215 | return $new; 216 | } 217 | 218 | /** 219 | * Return an instance with the specified path. 220 | * 221 | * This method MUST retain the state of the current instance, and return 222 | * an instance that contains the specified path. 223 | * 224 | * The path can either be empty or absolute (starting with a slash) or 225 | * rootless (not starting with a slash). Implementations MUST support all 226 | * three syntaxes. 227 | * 228 | * If the path is intended to be domain-relative rather than path relative then 229 | * it must begin with a slash ("/"). Paths not starting with a slash ("/") 230 | * are assumed to be relative to some base path known to the application or 231 | * consumer. 232 | * 233 | * Users can provide both encoded and decoded path characters. 234 | * Implementations ensure the correct encoding as outlined in getPath(). 235 | * 236 | * @param string $path The path to use with the new instance. 237 | * 238 | * @return static A new instance with the specified path. 239 | * @throws \InvalidArgumentException for invalid paths. 240 | */ 241 | public function withPath($path) 242 | { 243 | if (!is_string($path)) 244 | { 245 | throw new \InvalidArgumentException('URI Path should be a string'); 246 | } 247 | 248 | $path = (string) $path; 249 | 250 | if (strpos($path, '?') !== false || strpos($path, '#') !== false ) 251 | { 252 | throw new \InvalidArgumentException('Path should not contain `?` and `#` symbols.'); 253 | } 254 | 255 | $path = UriHelper::cleanPath($path); 256 | $path = UriHelper::filterPath($path); 257 | 258 | $new = clone $this; 259 | $new->path = $path; 260 | 261 | return $new; 262 | } 263 | 264 | /** 265 | * Return an instance with the specified query string. 266 | * 267 | * This method MUST retain the state of the current instance, and return 268 | * an instance that contains the specified query string. 269 | * 270 | * Users can provide both encoded and decoded query characters. 271 | * Implementations ensure the correct encoding as outlined in getQuery(). 272 | * 273 | * An empty query string value is equivalent to removing the query string. 274 | * 275 | * @param string|array $query The query string to use with the new instance. 276 | * 277 | * @return static A new instance with the specified query string. 278 | * @throws \InvalidArgumentException for invalid query strings. 279 | */ 280 | public function withQuery($query) 281 | { 282 | if (!is_string($query)) 283 | { 284 | throw new \InvalidArgumentException('URI query should be a string or array'); 285 | } 286 | 287 | $query = UriHelper::filterQuery($query); 288 | 289 | $new = clone $this; 290 | $new->vars = UriHelper::parseQuery($query); 291 | $new->query = $query; 292 | 293 | return $new; 294 | } 295 | 296 | /** 297 | * Return an instance with the specified URI fragment. 298 | * 299 | * This method MUST retain the state of the current instance, and return 300 | * an instance that contains the specified URI fragment. 301 | * 302 | * Users can provide both encoded and decoded fragment characters. 303 | * Implementations ensure the correct encoding as outlined in getFragment(). 304 | * 305 | * An empty fragment value is equivalent to removing the fragment. 306 | * 307 | * @param string $fragment The fragment to use with the new instance. 308 | * 309 | * @return static A new instance with the specified fragment. 310 | */ 311 | public function withFragment($fragment) 312 | { 313 | $fragment = UriHelper::filterFragment($fragment); 314 | 315 | $new = clone $this; 316 | $new->fragment = $fragment; 317 | 318 | return $new; 319 | } 320 | 321 | /** 322 | * Is a given port non-standard for the current scheme? 323 | * 324 | * @param string $scheme 325 | * @param string $host 326 | * @param int $port 327 | * 328 | * @return boolean 329 | */ 330 | protected function isStandardPort($scheme, $host, $port) 331 | { 332 | if (!$scheme) 333 | { 334 | return false; 335 | } 336 | 337 | if (!$host || !$port) 338 | { 339 | return true; 340 | } 341 | 342 | return (isset($this->standardSchemes[$scheme]) && $port == $this->standardSchemes[$scheme]); 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /src/Uri/Uri.php: -------------------------------------------------------------------------------- 1 | vars[$name]) ? $this->vars[$name] : null; 32 | 33 | $this->vars[$name] = $value; 34 | 35 | // Empty the query 36 | $this->query = null; 37 | 38 | return $tmp; 39 | } 40 | 41 | /** 42 | * Removes an item from the query string variables if it exists. 43 | * 44 | * @param string $name Name of variable to remove. 45 | * 46 | * @return void 47 | * 48 | * @since 2.0 49 | */ 50 | public function delVar($name) 51 | { 52 | if (array_key_exists($name, $this->vars)) 53 | { 54 | unset($this->vars[$name]); 55 | 56 | // Empty the query 57 | $this->query = null; 58 | } 59 | } 60 | 61 | /** 62 | * Sets the query to a supplied string in format: 63 | * foo=bar&x=y 64 | * 65 | * @param mixed $query The query string or array. 66 | * 67 | * @return void 68 | * 69 | * @since 2.0 70 | */ 71 | public function setQuery($query) 72 | { 73 | if (is_array($query)) 74 | { 75 | $this->vars = $query; 76 | } 77 | else 78 | { 79 | if (strpos($query, '&') !== false) 80 | { 81 | $query = str_replace('&', '&', $query); 82 | } 83 | 84 | parse_str($query, $this->vars); 85 | } 86 | 87 | // Empty the query 88 | $this->query = null; 89 | } 90 | 91 | /** 92 | * Set URI scheme (protocol) 93 | * ie. http, https, ftp, etc... 94 | * 95 | * @param string $scheme The URI scheme. 96 | * 97 | * @return void 98 | * 99 | * @since 2.0 100 | */ 101 | public function setScheme($scheme) 102 | { 103 | $this->scheme = $scheme; 104 | } 105 | 106 | /** 107 | * Set URI username. 108 | * 109 | * @param string $user The URI username. 110 | * 111 | * @return void 112 | * 113 | * @since 2.0 114 | */ 115 | public function setUser($user) 116 | { 117 | $this->user = $user; 118 | } 119 | 120 | /** 121 | * Set URI password. 122 | * 123 | * @param string $pass The URI password. 124 | * 125 | * @return void 126 | * 127 | * @since 2.0 128 | */ 129 | public function setPass($pass) 130 | { 131 | $this->pass = $pass; 132 | } 133 | 134 | /** 135 | * Set URI host. 136 | * 137 | * @param string $host The URI host. 138 | * 139 | * @return void 140 | * 141 | * @since 2.0 142 | */ 143 | public function setHost($host) 144 | { 145 | $this->host = $host; 146 | } 147 | 148 | /** 149 | * Set URI port. 150 | * 151 | * @param integer $port The URI port number. 152 | * 153 | * @return void 154 | * 155 | * @since 2.0 156 | */ 157 | public function setPort($port) 158 | { 159 | $this->port = $port; 160 | } 161 | 162 | /** 163 | * Set the URI path string. 164 | * 165 | * @param string $path The URI path string. 166 | * 167 | * @return void 168 | * 169 | * @since 2.0 170 | */ 171 | public function setPath($path) 172 | { 173 | $this->path = UriHelper::cleanPath($path); 174 | } 175 | 176 | /** 177 | * Set the URI anchor string 178 | * everything after the "#". 179 | * 180 | * @param string $anchor The URI anchor string. 181 | * 182 | * @return void 183 | * 184 | * @since 2.0 185 | */ 186 | public function setFragment($anchor) 187 | { 188 | $this->fragment = $anchor; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/Uri/UriHelper.php: -------------------------------------------------------------------------------- 1 | value pairs to return as a query string. 40 | * 41 | * @return string The resulting query string. 42 | * 43 | * @see parse_str() 44 | * @since 2.0 45 | */ 46 | public static function buildQuery(array $params) 47 | { 48 | return urldecode(http_build_query($params, '', '&')); 49 | } 50 | 51 | /** 52 | * Does a UTF-8 safe version of PHP parse_url function 53 | * 54 | * @param string $url URL to parse 55 | * 56 | * @return mixed Associative array or false if badly formed URL. 57 | * 58 | * @see http://us3.php.net/manual/en/function.parse-url.php 59 | * @since 2.0 60 | */ 61 | public static function parseUrl($url) 62 | { 63 | $result = false; 64 | 65 | // Build arrays of values we need to decode before parsing 66 | $entities = array('%21', '%2A', '%27', '%28', '%29', '%3B', '%3A', '%40', '%26', '%3D', '%24', '%2C', '%2F', '%3F', '%23', '%5B', '%5D'); 67 | $replacements = array('!', '*', "'", "(", ")", ";", ":", "@", "&", "=", "$", ",", "/", "?", "#", "[", "]"); 68 | 69 | // Create encoded URL with special URL characters decoded so it can be parsed 70 | // All other characters will be encoded 71 | $encodedURL = str_replace($entities, $replacements, urlencode($url)); 72 | 73 | // Parse the encoded URL 74 | $encodedParts = parse_url($encodedURL); 75 | 76 | // Now, decode each value of the resulting array 77 | if ($encodedParts) 78 | { 79 | foreach ($encodedParts as $key => $value) 80 | { 81 | $result[$key] = urldecode(str_replace($replacements, $entities, $value)); 82 | } 83 | } 84 | 85 | return $result; 86 | } 87 | 88 | /** 89 | * parseQuery 90 | * 91 | * @param string $query 92 | * 93 | * @return mixed 94 | */ 95 | public static function parseQuery($query) 96 | { 97 | parse_str($query, $vars); 98 | 99 | return $vars; 100 | } 101 | 102 | /** 103 | * filterScheme 104 | * 105 | * @param string $scheme 106 | * 107 | * @return string 108 | */ 109 | public static function filterScheme($scheme) 110 | { 111 | $scheme = strtolower($scheme); 112 | $scheme = preg_replace('#:(//)?$#', '', $scheme); 113 | 114 | if (empty($scheme)) 115 | { 116 | return ''; 117 | } 118 | 119 | return $scheme; 120 | } 121 | 122 | /** 123 | * Filter a query string to ensure it is propertly encoded. 124 | * 125 | * Ensures that the values in the query string are properly urlencoded. 126 | * 127 | * @param string $query 128 | * 129 | * @return string 130 | */ 131 | public static function filterQuery($query) 132 | { 133 | if (! empty($query) && strpos($query, '?') === 0) 134 | { 135 | $query = substr($query, 1); 136 | } 137 | 138 | $parts = explode('&', $query); 139 | foreach ($parts as $index => $part) 140 | { 141 | list($key, $value) = static::splitQueryValue($part); 142 | 143 | if ($value === null) 144 | { 145 | $parts[$index] = static::filterQueryOrFragment($key); 146 | 147 | continue; 148 | } 149 | 150 | $parts[$index] = sprintf( 151 | '%s=%s', 152 | static::filterQueryOrFragment($key), 153 | static::filterQueryOrFragment($value) 154 | ); 155 | } 156 | 157 | return implode('&', $parts); 158 | } 159 | 160 | /** 161 | * Split a query value into a key/value tuple. 162 | * 163 | * @param string $value 164 | * 165 | * @return array A value with exactly two elements, key and value 166 | */ 167 | public static function splitQueryValue($value) 168 | { 169 | $data = explode('=', $value, 2); 170 | 171 | if (1 === count($data)) 172 | { 173 | $data[] = null; 174 | } 175 | 176 | return $data; 177 | } 178 | 179 | /** 180 | * Filter a query string key or value, or a fragment. 181 | * 182 | * @param string $value 183 | * 184 | * @return string 185 | */ 186 | public static function filterQueryOrFragment($value) 187 | { 188 | return preg_replace_callback( 189 | '/(?:[^' . static::CHAR_UNRESERVED . static::CHAR_SUB_DELIMS . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/', 190 | function($matches) 191 | { 192 | return rawurlencode($matches[0]); 193 | }, 194 | $value 195 | ); 196 | } 197 | 198 | /** 199 | * Filter a fragment value to ensure it is properly encoded. 200 | * 201 | * @param string $fragment 202 | * 203 | * @return string 204 | */ 205 | public static function filterFragment($fragment) 206 | { 207 | if (null === $fragment) 208 | { 209 | $fragment = ''; 210 | } 211 | 212 | if (! empty($fragment) && strpos($fragment, '#') === 0) 213 | { 214 | $fragment = substr($fragment, 1); 215 | } 216 | 217 | return static::filterQueryOrFragment($fragment); 218 | } 219 | 220 | /** 221 | * Filters the path of a URI to ensure it is properly encoded. 222 | * 223 | * @param string $path 224 | * 225 | * @return string 226 | */ 227 | public static function filterPath($path) 228 | { 229 | return preg_replace_callback( 230 | '/(?:[^' . self::CHAR_UNRESERVED . ':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/', 231 | function($matches) 232 | { 233 | return rawurlencode($matches[0]); 234 | }, 235 | $path 236 | ); 237 | } 238 | 239 | /** 240 | * Resolves //, ../ and ./ from a path and returns 241 | * the result. Eg: 242 | * 243 | * /foo/bar/../boo.php => /foo/boo.php 244 | * /foo/bar/../../boo.php => /boo.php 245 | * /foo/bar/.././/boo.php => /foo/boo.php 246 | * 247 | * @param string $path The URI path to clean. 248 | * 249 | * @return string Cleaned and resolved URI path. 250 | * 251 | * @since 2.0 252 | */ 253 | public static function cleanPath($path) 254 | { 255 | $path = explode('/', preg_replace('#(/+)#', '/', $path)); 256 | 257 | for ($i = 0, $n = count($path); $i < $n; $i++) 258 | { 259 | if ($path[$i] == '.' || $path[$i] == '..') 260 | { 261 | if (($path[$i] == '.') || ($path[$i] == '..' && $i == 1 && $path[0] == '')) 262 | { 263 | unset($path[$i]); 264 | $path = array_values($path); 265 | $i--; 266 | $n--; 267 | } 268 | elseif ($path[$i] == '..' && ($i > 1 || ($i == 1 && $path[0] != ''))) 269 | { 270 | unset($path[$i]); 271 | unset($path[$i - 1]); 272 | $path = array_values($path); 273 | $i -= 2; 274 | $n -= 2; 275 | } 276 | } 277 | } 278 | 279 | return implode('/', $path); 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/Uri/UriInterface.php: -------------------------------------------------------------------------------- 1 | value pair array. 67 | * 68 | * @return string Query string. 69 | * 70 | * @since 2.0 71 | */ 72 | public function getQuery($toArray = false); 73 | 74 | /** 75 | * Get URI scheme (protocol) 76 | * ie. http, https, ftp, etc... 77 | * 78 | * @return string The URI scheme. 79 | * 80 | * @since 2.0 81 | */ 82 | public function getScheme(); 83 | 84 | /** 85 | * Get URI username 86 | * Returns the username, or null if no username was specified. 87 | * 88 | * @return string The URI username. 89 | * 90 | * @since 2.0 91 | */ 92 | public function getUser(); 93 | 94 | /** 95 | * Get URI password 96 | * Returns the password, or null if no password was specified. 97 | * 98 | * @return string The URI password. 99 | * 100 | * @since 2.0 101 | */ 102 | public function getPass(); 103 | 104 | /** 105 | * Get URI host 106 | * Returns the hostname/ip or null if no hostname/ip was specified. 107 | * 108 | * @return string The URI host. 109 | * 110 | * @since 2.0 111 | */ 112 | public function getHost(); 113 | 114 | /** 115 | * Get URI port 116 | * Returns the port number, or null if no port was specified. 117 | * 118 | * @return integer The URI port number. 119 | * 120 | * @since 2.0 121 | */ 122 | public function getPort(); 123 | 124 | /** 125 | * Gets the URI path string. 126 | * 127 | * @return string The URI path string. 128 | * 129 | * @since 2.0 130 | */ 131 | public function getPath(); 132 | 133 | /** 134 | * Get the URI archor string 135 | * Everything after the "#". 136 | * 137 | * @return string The URI anchor string. 138 | * 139 | * @since 2.0 140 | */ 141 | public function getFragment(); 142 | 143 | /** 144 | * Checks whether the current URI is using HTTPS. 145 | * 146 | * @return boolean True if using SSL via HTTPS. 147 | * 148 | * @since 2.0 149 | */ 150 | public function isSSL(); 151 | } 152 | -------------------------------------------------------------------------------- /test/AbstractBaseTestCase.php: -------------------------------------------------------------------------------- 1 | assertEquals( 36 | StringHelper::clean($expected), 37 | StringHelper::clean($actual), 38 | $message, 39 | $delta, 40 | $maxDepth, 41 | $canonicalize, 42 | $ignoreCase 43 | ); 44 | } 45 | 46 | /** 47 | * assertStringDataEquals 48 | * 49 | * @param string $expected 50 | * @param string $actual 51 | * @param string $message 52 | * @param int $delta 53 | * @param int $maxDepth 54 | * @param bool $canonicalize 55 | * @param bool $ignoreCase 56 | * 57 | * @return void 58 | */ 59 | public function assertStringSafeEquals($expected, $actual, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) 60 | { 61 | $this->assertEquals( 62 | trim(StringHelper::removeCRLF($expected)), 63 | trim(StringHelper::removeCRLF($actual)), 64 | $message, 65 | $delta, 66 | $maxDepth, 67 | $canonicalize, 68 | $ignoreCase 69 | ); 70 | } 71 | 72 | /** 73 | * assertExpectedException 74 | * 75 | * @param callable $closure 76 | * @param string $class 77 | * @param string $msg 78 | * @param int $code 79 | * @param string $message 80 | * 81 | * @return void 82 | */ 83 | public function assertExpectedException($closure, $class = 'Exception', $msg = null, $code = null, $message = '') 84 | { 85 | if (is_object($class)) 86 | { 87 | $class = get_class($class); 88 | } 89 | 90 | try 91 | { 92 | $closure(); 93 | } 94 | catch (\Exception $e) 95 | { 96 | $this->assertInstanceOf($class, $e, $message); 97 | 98 | if ($msg) 99 | { 100 | $this->assertStringStartsWith($msg, $e->getMessage(), $message); 101 | } 102 | 103 | if ($code) 104 | { 105 | $this->assertEquals($code, $e->getCode(), $message); 106 | } 107 | 108 | return; 109 | } 110 | 111 | $this->fail('No exception caught.'); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /test/AbstractMessageTest.php: -------------------------------------------------------------------------------- 1 | message = new StubMessage; 38 | } 39 | 40 | /** 41 | * Tears down the fixture, for example, closes a network connection. 42 | * This method is called after a test is executed. 43 | * 44 | * @return void 45 | */ 46 | protected function tearDown() 47 | { 48 | } 49 | 50 | /** 51 | * Method to test getProtocolVersion(). 52 | * 53 | * @return void 54 | * 55 | * @covers Asika\Http\AbstractMessage::getProtocolVersion 56 | * @covers Asika\Http\AbstractMessage::withProtocolVersion 57 | */ 58 | public function testWithAndSetProtocolVersion() 59 | { 60 | $this->assertEquals('1.1', $this->message->getProtocolVersion()); 61 | 62 | $message = $this->message->withProtocolVersion('1.0'); 63 | 64 | $this->assertNotSame($this->message, $message); 65 | $this->assertEquals('1.0', $message->getProtocolVersion()); 66 | } 67 | 68 | /** 69 | * Method to test getHeader(). 70 | * 71 | * @return void 72 | * 73 | * @covers Asika\Http\AbstractMessage::getHeader 74 | * @covers Asika\Http\AbstractMessage::withHeader 75 | */ 76 | public function testWithAndGetHeader() 77 | { 78 | $message = $this->message->withHeader('Content-Type', 'text/json'); 79 | 80 | $this->assertNotSame($this->message, $message); 81 | $this->assertEquals(array('text/json'), $message->getHeader('Content-Type')); 82 | $this->assertEquals(array('text/json'), $message->getHeader('content-type')); 83 | 84 | $message = $this->message->withHeader('X-Foo', array('Foo', 'Bar')); 85 | 86 | $this->assertNotSame($this->message, $message); 87 | $this->assertEquals(array('Foo', 'Bar'), $message->getHeader('X-Foo')); 88 | } 89 | 90 | /** 91 | * Method to test hasHeader(). 92 | * 93 | * @return void 94 | * 95 | * @covers Asika\Http\AbstractMessage::hasHeader 96 | */ 97 | public function testHasHeader() 98 | { 99 | $this->assertFalse($this->message->hasHeader('X-Foo')); 100 | 101 | $message = $this->message->withHeader('Content-Type', 'text/json'); 102 | 103 | $this->assertTrue($message->hasHeader('Content-Type')); 104 | $this->assertTrue($message->hasHeader('content-type')); 105 | } 106 | 107 | /** 108 | * Method to test getHeaders(). 109 | * 110 | * @return void 111 | * 112 | * @covers Asika\Http\AbstractMessage::getHeaders 113 | */ 114 | public function testGetHeaders() 115 | { 116 | $this->assertEquals(array(), $this->message->getHeaders()); 117 | 118 | $message = $this->message->withHeader('X-Foo', array('Foo', 'Bar')); 119 | $message = $message->withHeader('X-Bar', array('Flower', 'Sakura')); 120 | 121 | $expected = array( 122 | 'X-Foo' => array('Foo', 'Bar'), 123 | 'X-Bar' => array('Flower', 'Sakura'), 124 | ); 125 | 126 | $this->assertEquals($expected, $message->getHeaders()); 127 | } 128 | 129 | /** 130 | * Method to test getHeaderLine(). 131 | * 132 | * @return void 133 | * 134 | * @covers Asika\Http\AbstractMessage::getHeaderLine 135 | */ 136 | public function testGetHeaderLine() 137 | { 138 | $this->assertEquals('', $this->message->getHeaderLine('X-Foo')); 139 | 140 | $message = $this->message->withHeader('X-Foo', array('Foo', 'Bar')); 141 | 142 | $this->assertEquals('Foo,Bar', $message->getHeaderLine('X-Foo')); 143 | $this->assertEquals('Foo,Bar', $message->getHeaderLine('x-foo')); 144 | $this->assertSame('', $message->getHeaderLine('x-bar')); 145 | } 146 | 147 | /** 148 | * Method to test withAddedHeader(). 149 | * 150 | * @return void 151 | * 152 | * @covers Asika\Http\AbstractMessage::withAddedHeader 153 | */ 154 | public function testWithAddedHeader() 155 | { 156 | $message = $this->message->withAddedHeader('X-Foo', 'One'); 157 | 158 | $this->assertNotSame($this->message, $message); 159 | $this->assertEquals(array('One'), $message->getHeader('X-Foo')); 160 | 161 | $message = $message->withAddedHeader('X-Foo', 'Two'); 162 | 163 | $this->assertEquals(array('One', 'Two'), $message->getHeader('X-Foo')); 164 | 165 | $message = $message->withAddedHeader('X-Foo', array('Three', 'Four')); 166 | 167 | $this->assertEquals(array('One', 'Two', 'Three', 'Four'), $message->getHeader('X-Foo')); 168 | } 169 | 170 | /** 171 | * Method to test withoutHeader(). 172 | * 173 | * @return void 174 | * 175 | * @covers Asika\Http\AbstractMessage::withoutHeader 176 | */ 177 | public function testWithoutHeader() 178 | { 179 | $message = $this->message->withAddedHeader('X-Foo', 'One'); 180 | 181 | $this->assertNotSame($this->message, $message); 182 | $this->assertEquals(array('One'), $message->getHeader('X-Foo')); 183 | 184 | $message2 = $message->withoutHeader('X-Foo'); 185 | 186 | $this->assertNotSame($this->message, $message2); 187 | $this->assertEquals(array(), $message2->getHeader('X-Foo')); 188 | 189 | $message3 = $message->withoutHeader('x-foo'); 190 | 191 | $this->assertNotSame($this->message, $message3); 192 | $this->assertEquals(array(), $message3->getHeader('X-Foo')); 193 | } 194 | 195 | /** 196 | * Method to test getBody(). 197 | * 198 | * @return void 199 | * 200 | * @covers Asika\Http\AbstractMessage::getBody 201 | */ 202 | public function testWithAndGetBody() 203 | { 204 | $message = $this->message->withBody(new Stream); 205 | 206 | $this->assertNotSame($this->message, $message); 207 | $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $message->getBody()); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /test/AbstractRequestTest.php: -------------------------------------------------------------------------------- 1 | instance = new StubRequest; 39 | } 40 | 41 | /** 42 | * Tears down the fixture, for example, closes a network connection. 43 | * This method is called after a test is executed. 44 | * 45 | * @return void 46 | */ 47 | protected function tearDown() 48 | { 49 | } 50 | 51 | /** 52 | * testConstruct 53 | * 54 | * @return void 55 | */ 56 | public function testConstruct() 57 | { 58 | // Test no params 59 | $request = new StubRequest; 60 | 61 | $this->assertInstanceOf('Asika\Http\Uri\PsrUri', $request->getUri()); 62 | $this->assertEquals('', (string) $request->getUri()); 63 | $this->assertNull($request->getMethod()); 64 | $this->assertInstanceOf('Asika\Http\Stream\Stream', $request->getBody()); 65 | $this->assertEquals('php://memory', $request->getBody()->getMetadata('uri')); 66 | $this->assertEquals(array(), $request->getHeaders()); 67 | 68 | // Test with params 69 | $uri = 'http://example.com/?foo=bar#baz'; 70 | $method = 'post'; 71 | $body = fopen($tmpfile = tempnam(sys_get_temp_dir(), 'windwalker'), 'wb+'); 72 | $headers = array( 73 | 'X-Foo' => array('Flower', 'Sakura'), 74 | 'Content-Type' => 'application/json' 75 | ); 76 | 77 | $request = new StubRequest($uri, $method, $body, $headers); 78 | 79 | $this->assertInstanceOf('Asika\Http\Uri\PsrUri', $request->getUri()); 80 | $this->assertEquals('http://example.com/?foo=bar#baz', (string) $request->getUri()); 81 | $this->assertEquals('POST', $request->getMethod()); 82 | $this->assertInstanceOf('Asika\Http\Stream\Stream', $request->getBody()); 83 | $this->assertEquals($tmpfile, $request->getBody()->getMetadata('uri')); 84 | $this->assertEquals(array('Flower', 'Sakura'), $request->getHeader('x-foo')); 85 | $this->assertEquals(array('application/json'), $request->getHeader('content-type')); 86 | 87 | fclose($body); 88 | 89 | // Test with object params 90 | $uri = new PsrUri('http://example.com/flower/sakura?foo=bar#baz'); 91 | $body = new Stream; 92 | $request = new StubRequest($uri, null, $body); 93 | 94 | $this->assertSame($uri, $request->getUri()); 95 | $this->assertSame($body, $request->getBody()); 96 | } 97 | 98 | /** 99 | * Method to test getRequestTarget(). 100 | * 101 | * @return void 102 | * 103 | * @covers Asika\Http\AbstractRequest::getRequestTarget 104 | * @covers Asika\Http\AbstractRequest::withRequestTarget 105 | */ 106 | public function testWithAndGetRequestTarget() 107 | { 108 | $this->assertEquals('/', $this->instance->getRequestTarget()); 109 | 110 | $request = $this->instance->withUri(new PsrUri('http://example.com/flower/sakura?foo=bar#baz')); 111 | 112 | $this->assertNotSame($request, $this->instance); 113 | $this->assertEquals('/flower/sakura?foo=bar', (string) $request->getRequestTarget()); 114 | 115 | $request = $request->withUri(new PsrUri('http://example.com')); 116 | 117 | $this->assertEquals('/', (string) $request->getRequestTarget()); 118 | 119 | $request = $request->withRequestTarget('*'); 120 | 121 | $this->assertEquals('*', $request->getRequestTarget()); 122 | } 123 | 124 | /** 125 | * Method to test getMethod(). 126 | * 127 | * @return void 128 | * 129 | * @covers Asika\Http\AbstractRequest::getMethod 130 | * @covers Asika\Http\AbstractRequest::withMethod 131 | */ 132 | public function testWithAndGetMethod() 133 | { 134 | $this->assertNull($this->instance->getMethod()); 135 | 136 | $request = $this->instance->withMethod('patch'); 137 | 138 | $this->assertNotSame($request, $this->instance); 139 | $this->assertEquals('PATCH', $request->getMethod()); 140 | 141 | $this->assertExpectedException(function() use ($request) 142 | { 143 | $request->withMethod('FLY'); 144 | }, new \InvalidArgumentException); 145 | } 146 | 147 | /** 148 | * Method to test getUri(). 149 | * 150 | * @return void 151 | * 152 | * @covers Asika\Http\AbstractRequest::getUri 153 | * @covers Asika\Http\AbstractRequest::withUri 154 | */ 155 | public function testWithAndGetUri() 156 | { 157 | $this->assertInstanceOf('Asika\Http\Uri\PsrUri', $this->instance->getUri()); 158 | $this->assertEquals('', (string) $this->instance->getUri()); 159 | 160 | $request = $this->instance->withUri(new PsrUri('http://example.com/flower/sakura?foo=bar#baz'), true); 161 | 162 | $this->assertNotSame($request, $this->instance); 163 | $this->assertEquals('http://example.com/flower/sakura?foo=bar#baz', (string) $request->getUri()); 164 | $this->assertEquals(array(), $request->getHeader('host')); 165 | 166 | $request = $this->instance->withUri(new PsrUri('http://windwalker.io/flower/sakura?foo=bar#baz')); 167 | 168 | $this->assertEquals('http://windwalker.io/flower/sakura?foo=bar#baz', (string) $request->getUri()); 169 | $this->assertEquals(array('windwalker.io'), $request->getHeader('host')); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /test/Mock/MockTransport.php: -------------------------------------------------------------------------------- 1 | request = $request; 43 | 44 | return $this->doRequest($request); 45 | } 46 | 47 | /** 48 | * Send a request to the server and return a Response object with the response. 49 | * 50 | * @param RequestInterface $request The request object to store request params. 51 | * 52 | * @return ResponseInterface 53 | * 54 | * @since 2.1 55 | */ 56 | protected function doRequest(RequestInterface $request) 57 | { 58 | return new Response; 59 | } 60 | 61 | /** 62 | * Method to check if HTTP transport layer available for using 63 | * 64 | * @return boolean True if available else false 65 | * 66 | * @since 2.1 67 | */ 68 | public static function isSupported() 69 | { 70 | return true; 71 | } 72 | 73 | /** 74 | * Use stream to download file. 75 | * 76 | * @param RequestInterface $request The request object to store request params. 77 | * @param string|StreamInterface $dest The dest path to store file. 78 | * 79 | * @return ResponseInterface 80 | * @since 2.1 81 | */ 82 | public function download(RequestInterface $request, $dest) 83 | { 84 | $this->setOption('target_file', $dest); 85 | 86 | return $this->request($request); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /test/RequestTest.php: -------------------------------------------------------------------------------- 1 | instance = new Request; 37 | } 38 | 39 | /** 40 | * Tears down the fixture, for example, closes a network connection. 41 | * This method is called after a test is executed. 42 | * 43 | * @return void 44 | */ 45 | protected function tearDown() 46 | { 47 | } 48 | 49 | /** 50 | * Method to test getHeaders(). 51 | * 52 | * @return void 53 | * 54 | * @covers Asika\Http\Request::getHeaders 55 | */ 56 | public function testGetHeaders() 57 | { 58 | $this->assertEquals(array(), $this->instance->getHeaders()); 59 | 60 | $request = $this->instance->withUri(new PsrUri('http://windwalker.io/flower/sakura')); 61 | 62 | $this->assertEquals(array('Host' => array('windwalker.io')), $request->getHeaders()); 63 | } 64 | 65 | /** 66 | * Method to test getHeader(). 67 | * 68 | * @return void 69 | * 70 | * @covers Asika\Http\Request::getHeader 71 | */ 72 | public function testGetHeader() 73 | { 74 | $this->assertEquals(array(), $this->instance->getHeader('host')); 75 | 76 | $request = $this->instance->withUri(new PsrUri('http://windwalker.io/flower/sakura')); 77 | 78 | $this->assertEquals(array('windwalker.io'), $request->getHeader('host')); 79 | } 80 | 81 | /** 82 | * Method to test hasHeader(). 83 | * 84 | * @return void 85 | * 86 | * @covers Asika\Http\Request::hasHeader 87 | */ 88 | public function testHasHeader() 89 | { 90 | $request = new \Asika\Http\Request('http://example.com/foo', 'GET'); 91 | 92 | $this->assertTrue($request->hasHeader('host')); 93 | $this->assertTrue($request->hasHeader('Host')); 94 | $this->assertFalse($request->hasHeader('X-Foo')); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/ResponseTest.php: -------------------------------------------------------------------------------- 1 | instance = new Response; 37 | } 38 | 39 | /** 40 | * Tears down the fixture, for example, closes a network connection. 41 | * This method is called after a test is executed. 42 | * 43 | * @return void 44 | */ 45 | protected function tearDown() 46 | { 47 | } 48 | 49 | public function testConstruct() 50 | { 51 | // Test no params 52 | $res = new Response; 53 | 54 | $this->assertInstanceOf('Asika\Http\Stream\Stream', $res->getBody()); 55 | $this->assertEquals('php://memory', $res->getBody()->getMetadata('uri')); 56 | $this->assertEquals(200, $res->getStatusCode()); 57 | $this->assertEquals(array(), $res->getHeaders()); 58 | 59 | // Test with params 60 | $body = fopen($tmpfile = tempnam(sys_get_temp_dir(), 'http'), 'wb+'); 61 | $headers = array( 62 | 'X-Foo' => array('Flower', 'Sakura'), 63 | 'Content-Type' => 'application/json' 64 | ); 65 | 66 | $res = new Response($body, 404, $headers); 67 | 68 | $this->assertInstanceOf('Asika\Http\Stream\Stream', $res->getBody()); 69 | $this->assertEquals($tmpfile, $res->getBody()->getMetadata('uri')); 70 | $this->assertEquals(array('Flower', 'Sakura'), $res->getHeader('x-foo')); 71 | $this->assertEquals(array('application/json'), $res->getHeader('content-type')); 72 | 73 | fclose($body); 74 | 75 | // Test with object params 76 | $body = new Stream; 77 | $res = new Response($body); 78 | 79 | $this->assertSame($body, $res->getBody()); 80 | } 81 | 82 | /** 83 | * Method to test getStatusCode(). 84 | * 85 | * @return void 86 | * 87 | * @covers Asika\Http\Response::getStatusCode 88 | * @covers Asika\Http\Response::withStatus 89 | */ 90 | public function testWithAndGetStatusCode() 91 | { 92 | $this->assertEquals(200, $this->instance->getStatusCode()); 93 | 94 | $res = $this->instance->withStatus(403); 95 | 96 | $this->assertNotSame($res, $this->instance); 97 | $this->assertEquals(403, $res->getStatusCode()); 98 | 99 | $res = $res->withStatus(500, 'Unknown error'); 100 | 101 | $this->assertEquals(500, $res->getStatusCode()); 102 | $this->assertEquals('Unknown error', $res->getReasonPhrase()); 103 | } 104 | 105 | /** 106 | * Method to test getReasonPhrase(). 107 | * 108 | * @return void 109 | * 110 | * @covers Asika\Http\Response::getReasonPhrase 111 | */ 112 | public function testGetReasonPhrase() 113 | { 114 | $res = new Response; 115 | 116 | $res = $res->withStatus(200); 117 | 118 | $this->assertEquals('OK', $res->getReasonPhrase()); 119 | 120 | $res = $res->withStatus(400); 121 | 122 | $this->assertEquals('Bad Request', $res->getReasonPhrase()); 123 | 124 | $res = $res->withStatus(404); 125 | 126 | $this->assertEquals('Not Found', $res->getReasonPhrase()); 127 | 128 | $res = $res->withStatus(500); 129 | 130 | $this->assertEquals('Internal Server Error', $res->getReasonPhrase()); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /test/ServerRequestTest.php: -------------------------------------------------------------------------------- 1 | instance = new ServerRequest; 39 | } 40 | 41 | /** 42 | * Tears down the fixture, for example, closes a network connection. 43 | * This method is called after a test is executed. 44 | * 45 | * @return void 46 | */ 47 | protected function tearDown() 48 | { 49 | } 50 | 51 | /** 52 | * testConstruct 53 | * 54 | * @return void 55 | */ 56 | public function testConstruct() 57 | { 58 | $server = array( 59 | 'foo' => 'bar', 60 | 'baz' => 'bat', 61 | ); 62 | 63 | $server['server'] = true; 64 | 65 | $files = array( 66 | 'files' => new UploadedFile('php://temp', 0), 67 | ); 68 | 69 | $uri = new PsrUri('http://example.com'); 70 | $method = 'POST'; 71 | $headers = array( 72 | 'Host' => array('example.com'), 73 | ); 74 | 75 | $request = new ServerRequest( 76 | $server, 77 | $files, 78 | $uri, 79 | $method, 80 | 'php://memory', 81 | $headers 82 | ); 83 | 84 | $this->assertEquals($server, $request->getServerParams()); 85 | $this->assertEquals($files, $request->getUploadedFiles()); 86 | 87 | $this->assertSame($uri, $request->getUri()); 88 | $this->assertEquals($method, $request->getMethod()); 89 | $this->assertEquals($headers, $request->getHeaders()); 90 | 91 | $body = $request->getBody(); 92 | $stream = TestHelper::getValue($body, 'stream'); 93 | 94 | $this->assertEquals('php://memory', $stream); 95 | } 96 | 97 | /** 98 | * Method to test getServerParams(). 99 | * 100 | * @return void 101 | * 102 | * @covers Asika\Http\ServerRequest::getServerParams 103 | * @TODO Implement testGetServerParams(). 104 | */ 105 | public function testGetServerParams() 106 | { 107 | // Remove the following lines when you implement this test. 108 | $this->markTestIncomplete( 109 | 'This test has not been implemented yet.' 110 | ); 111 | } 112 | 113 | /** 114 | * Method to test getCookieParams(). 115 | * 116 | * @return void 117 | * 118 | * @covers Asika\Http\ServerRequest::getCookieParams 119 | * @TODO Implement testGetCookieParams(). 120 | */ 121 | public function testGetCookieParams() 122 | { 123 | // Remove the following lines when you implement this test. 124 | $this->markTestIncomplete( 125 | 'This test has not been implemented yet.' 126 | ); 127 | } 128 | 129 | /** 130 | * Method to test withCookieParams(). 131 | * 132 | * @return void 133 | * 134 | * @covers Asika\Http\ServerRequest::withCookieParams 135 | * @TODO Implement testWithCookieParams(). 136 | */ 137 | public function testWithCookieParams() 138 | { 139 | // Remove the following lines when you implement this test. 140 | $this->markTestIncomplete( 141 | 'This test has not been implemented yet.' 142 | ); 143 | } 144 | 145 | /** 146 | * Method to test getQueryParams(). 147 | * 148 | * @return void 149 | * 150 | * @covers Asika\Http\ServerRequest::getQueryParams 151 | * @TODO Implement testGetQueryParams(). 152 | */ 153 | public function testGetQueryParams() 154 | { 155 | // Remove the following lines when you implement this test. 156 | $this->markTestIncomplete( 157 | 'This test has not been implemented yet.' 158 | ); 159 | } 160 | 161 | /** 162 | * Method to test withQueryParams(). 163 | * 164 | * @return void 165 | * 166 | * @covers Asika\Http\ServerRequest::withQueryParams 167 | * @TODO Implement testWithQueryParams(). 168 | */ 169 | public function testWithQueryParams() 170 | { 171 | // Remove the following lines when you implement this test. 172 | $this->markTestIncomplete( 173 | 'This test has not been implemented yet.' 174 | ); 175 | } 176 | 177 | /** 178 | * Method to test getUploadedFiles(). 179 | * 180 | * @return void 181 | * 182 | * @covers Asika\Http\ServerRequest::getUploadedFiles 183 | * @TODO Implement testGetUploadedFiles(). 184 | */ 185 | public function testGetUploadedFiles() 186 | { 187 | // Remove the following lines when you implement this test. 188 | $this->markTestIncomplete( 189 | 'This test has not been implemented yet.' 190 | ); 191 | } 192 | 193 | /** 194 | * Method to test withUploadedFiles(). 195 | * 196 | * @return void 197 | * 198 | * @covers Asika\Http\ServerRequest::withUploadedFiles 199 | * @TODO Implement testWithUploadedFiles(). 200 | */ 201 | public function testWithUploadedFiles() 202 | { 203 | // Remove the following lines when you implement this test. 204 | $this->markTestIncomplete( 205 | 'This test has not been implemented yet.' 206 | ); 207 | } 208 | 209 | /** 210 | * Method to test getParsedBody(). 211 | * 212 | * @return void 213 | * 214 | * @covers Asika\Http\ServerRequest::getParsedBody 215 | * @TODO Implement testGetParsedBody(). 216 | */ 217 | public function testGetParsedBody() 218 | { 219 | // Remove the following lines when you implement this test. 220 | $this->markTestIncomplete( 221 | 'This test has not been implemented yet.' 222 | ); 223 | } 224 | 225 | /** 226 | * Method to test withParsedBody(). 227 | * 228 | * @return void 229 | * 230 | * @covers Asika\Http\ServerRequest::withParsedBody 231 | * @TODO Implement testWithParsedBody(). 232 | */ 233 | public function testWithParsedBody() 234 | { 235 | // Remove the following lines when you implement this test. 236 | $this->markTestIncomplete( 237 | 'This test has not been implemented yet.' 238 | ); 239 | } 240 | 241 | /** 242 | * Method to test getAttributes(). 243 | * 244 | * @return void 245 | * 246 | * @covers Asika\Http\ServerRequest::getAttributes 247 | * @TODO Implement testGetAttributes(). 248 | */ 249 | public function testGetAttributes() 250 | { 251 | // Remove the following lines when you implement this test. 252 | $this->markTestIncomplete( 253 | 'This test has not been implemented yet.' 254 | ); 255 | } 256 | 257 | /** 258 | * Method to test getAttribute(). 259 | * 260 | * @return void 261 | * 262 | * @covers Asika\Http\ServerRequest::getAttribute 263 | * @TODO Implement testGetAttribute(). 264 | */ 265 | public function testGetAttribute() 266 | { 267 | // Remove the following lines when you implement this test. 268 | $this->markTestIncomplete( 269 | 'This test has not been implemented yet.' 270 | ); 271 | } 272 | 273 | /** 274 | * Method to test withAttribute(). 275 | * 276 | * @return void 277 | * 278 | * @covers Asika\Http\ServerRequest::withAttribute 279 | * @TODO Implement testWithAttribute(). 280 | */ 281 | public function testWithAttribute() 282 | { 283 | // Remove the following lines when you implement this test. 284 | $this->markTestIncomplete( 285 | 'This test has not been implemented yet.' 286 | ); 287 | } 288 | 289 | /** 290 | * Method to test withoutAttribute(). 291 | * 292 | * @return void 293 | * 294 | * @covers Asika\Http\ServerRequest::withoutAttribute 295 | * @TODO Implement testWithoutAttribute(). 296 | */ 297 | public function testWithoutAttribute() 298 | { 299 | // Remove the following lines when you implement this test. 300 | $this->markTestIncomplete( 301 | 'This test has not been implemented yet.' 302 | ); 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /test/Stream/StreamTest.php: -------------------------------------------------------------------------------- 1 | instance = new Stream('php://memory', Stream::MODE_READ_WRITE_RESET); 45 | } 46 | 47 | /** 48 | * Tears down the fixture, for example, closes a network connection. 49 | * This method is called after a test is executed. 50 | * 51 | * @return void 52 | */ 53 | protected function tearDown() 54 | { 55 | if ($this->tmpnam && is_file($this->tmpnam)) 56 | { 57 | unlink($this->tmpnam); 58 | } 59 | } 60 | 61 | public function testConstruct() 62 | { 63 | $resource = fopen('php://memory', Stream::MODE_READ_WRITE_RESET); 64 | $stream = new Stream($resource); 65 | 66 | $this->assertInstanceOf('Asika\Http\Stream\Stream', $stream); 67 | 68 | $stream = new Stream; 69 | 70 | $this->assertInternalType('resource', TestHelper::getValue($stream, 'resource')); 71 | $this->assertEquals('php://memory', TestHelper::getValue($stream, 'stream')); 72 | } 73 | 74 | /** 75 | * Method to test __toString(). 76 | * 77 | * @return void 78 | * 79 | * @covers Asika\Http\Stream::__toString 80 | */ 81 | public function test__toString() 82 | { 83 | $message = 'foo bar'; 84 | 85 | $this->instance->write($message); 86 | 87 | $this->assertEquals($message, (string) $this->instance); 88 | 89 | // Not readable should return empty string 90 | $this->createTempFile(); 91 | 92 | file_put_contents($this->tmpnam, 'FOO BAR'); 93 | 94 | $stream = new Stream($this->tmpnam, 'w'); 95 | 96 | $this->assertEquals('', $stream->__toString()); 97 | } 98 | 99 | /** 100 | * Method to test close(). 101 | * 102 | * @return void 103 | * 104 | * @covers Asika\Http\Stream::close 105 | */ 106 | public function testClose() 107 | { 108 | $this->createTempFile(); 109 | 110 | $resource = fopen($this->tmpnam, Stream::MODE_READ_WRITE_RESET); 111 | 112 | $stream = new Stream($resource); 113 | $stream->write('Foo Bar'); 114 | 115 | $stream->close(); 116 | 117 | $this->assertFalse(is_resource($resource)); 118 | $this->assertAttributeEmpty('resource', $stream); 119 | $this->assertEquals('', (string) $stream); 120 | } 121 | 122 | /** 123 | * Method to test detach(). 124 | * 125 | * @return void 126 | * 127 | * @covers Asika\Http\Stream::detach 128 | */ 129 | public function testDetach() 130 | { 131 | $resource = fopen('php://memory', Stream::MODE_READ_WRITE_RESET); 132 | $stream = new Stream($resource); 133 | 134 | $this->assertSame($resource, $stream->detach()); 135 | $this->assertAttributeEmpty('resource', $stream); 136 | $this->assertAttributeEmpty('stream', $stream); 137 | } 138 | 139 | /** 140 | * Method to test getSize(). 141 | * 142 | * @return void 143 | * 144 | * @covers Asika\Http\Stream::getSize 145 | */ 146 | public function testGetSize() 147 | { 148 | $this->createTempFile(); 149 | 150 | file_put_contents($this->tmpnam, 'FOO BAR'); 151 | $resource = fopen($this->tmpnam, Stream::MODE_READ_ONLY_FROM_BEGIN); 152 | 153 | $stream = new Stream($resource); 154 | 155 | $this->assertEquals(7, $stream->getSize()); 156 | } 157 | 158 | /** 159 | * Method to test tell(). 160 | * 161 | * @return void 162 | * 163 | * @covers Asika\Http\Stream::tell 164 | */ 165 | public function testTell() 166 | { 167 | $this->createTempFile(); 168 | 169 | file_put_contents($this->tmpnam, 'FOO BAR'); 170 | $resource = fopen($this->tmpnam, Stream::MODE_READ_WRITE_RESET); 171 | 172 | $stream = new Stream($resource); 173 | 174 | fseek($resource, 2); 175 | 176 | $this->assertEquals(2, $stream->tell()); 177 | 178 | $stream->detach(); 179 | 180 | $this->assertExpectedException(function() use ($stream) 181 | { 182 | $stream->tell(); 183 | }, new \RuntimeException); 184 | } 185 | 186 | /** 187 | * Method to test eof(). 188 | * 189 | * @return void 190 | * 191 | * @covers Asika\Http\Stream::eof 192 | */ 193 | public function testEof() 194 | { 195 | $this->createTempFile(); 196 | file_put_contents($this->tmpnam, 'FOO BAR'); 197 | $resource = fopen($this->tmpnam, Stream::MODE_READ_ONLY_FROM_BEGIN); 198 | $stream = new Stream($resource); 199 | 200 | fseek($resource, 2); 201 | $this->assertFalse($stream->eof()); 202 | 203 | while (! feof($resource)) 204 | { 205 | fread($resource, 4096); 206 | } 207 | 208 | $this->assertTrue($stream->eof()); 209 | } 210 | 211 | /** 212 | * Method to test isSeekable(). 213 | * 214 | * @return void 215 | * 216 | * @covers Asika\Http\Stream::isSeekable 217 | */ 218 | public function testIsSeekable() 219 | { 220 | $this->createTempFile(); 221 | 222 | file_put_contents($this->tmpnam, 'FOO BAR'); 223 | $resource = fopen($this->tmpnam, Stream::MODE_READ_WRITE_RESET); 224 | $stream = new Stream($resource); 225 | 226 | $this->assertTrue($stream->isSeekable()); 227 | } 228 | 229 | /** 230 | * Method to test seek(). 231 | * 232 | * @return void 233 | * 234 | * @covers Asika\Http\Stream::seek 235 | */ 236 | public function testSeek() 237 | { 238 | $this->createTempFile(); 239 | file_put_contents($this->tmpnam, 'FOO BAR'); 240 | 241 | $resource = fopen($this->tmpnam, Stream::MODE_READ_ONLY_FROM_BEGIN); 242 | $stream = new Stream($resource); 243 | 244 | $this->assertTrue($stream->seek(2)); 245 | $this->assertEquals(2, $stream->tell()); 246 | 247 | $this->assertTrue($stream->seek(2, SEEK_CUR)); 248 | $this->assertEquals(4, $stream->tell()); 249 | 250 | $this->assertTrue($stream->seek(-1, SEEK_END)); 251 | $this->assertEquals(6, $stream->tell()); 252 | } 253 | 254 | /** 255 | * Method to test rewind(). 256 | * 257 | * @return void 258 | * 259 | * @covers Asika\Http\Stream::rewind 260 | */ 261 | public function testRewind() 262 | { 263 | $this->createTempFile(); 264 | file_put_contents($this->tmpnam, 'FOO BAR'); 265 | $resource = fopen($this->tmpnam, Stream::MODE_READ_WRITE_RESET); 266 | 267 | $stream = new Stream($resource); 268 | 269 | $this->assertTrue($stream->seek(2)); 270 | 271 | $stream->rewind(); 272 | 273 | $this->assertEquals(0, $stream->tell()); 274 | } 275 | 276 | /** 277 | * Method to test isWritable(). 278 | * 279 | * @return void 280 | * 281 | * @covers Asika\Http\Stream::isWritable 282 | */ 283 | public function testIsWritable() 284 | { 285 | $stream = new Stream('php://memory', Stream::MODE_READ_ONLY_FROM_BEGIN); 286 | 287 | $this->assertFalse($stream->isWritable()); 288 | } 289 | 290 | /** 291 | * Method to test write(). 292 | * 293 | * @return void 294 | * 295 | * @covers Asika\Http\Stream::write 296 | */ 297 | public function testWrite() 298 | { 299 | $this->createTempFile(); 300 | $resource = fopen($this->tmpnam, Stream::MODE_READ_WRITE_RESET); 301 | 302 | $stream = new Stream($resource); 303 | $stream->write('flower'); 304 | 305 | $this->assertEquals('flower', file_get_contents($this->tmpnam)); 306 | 307 | $stream->write(' bloom'); 308 | 309 | $this->assertEquals('flower bloom', file_get_contents($this->tmpnam)); 310 | 311 | $stream->seek(4); 312 | 313 | $stream->write('test'); 314 | 315 | $this->assertEquals('flowtestloom', file_get_contents($this->tmpnam)); 316 | } 317 | 318 | /** 319 | * Method to test isReadable(). 320 | * 321 | * @return void 322 | * 323 | * @covers Asika\Http\Stream::isReadable 324 | */ 325 | public function testIsReadable() 326 | { 327 | $this->createTempFile(); 328 | 329 | $stream = new Stream($this->tmpnam, Stream::MODE_WRITE_ONLY_RESET); 330 | $this->assertFalse($stream->isReadable()); 331 | } 332 | 333 | /** 334 | * Method to test read(). 335 | * 336 | * @return void 337 | * 338 | * @covers Asika\Http\Stream::read 339 | */ 340 | public function testRead() 341 | { 342 | $this->createTempFile(); 343 | file_put_contents($this->tmpnam, 'FOO BAR'); 344 | $resource = fopen($this->tmpnam, Stream::MODE_READ_ONLY_FROM_BEGIN); 345 | 346 | $stream = new Stream($resource); 347 | 348 | $this->assertEquals('FO', $stream->read(2)); 349 | $this->assertEquals('O B', $stream->read(3)); 350 | 351 | $stream->rewind(); 352 | 353 | $this->assertEquals('FOO', $stream->read(3)); 354 | } 355 | 356 | /** 357 | * Method to test getContents(). 358 | * 359 | * @return void 360 | * 361 | * @covers Asika\Http\Stream::getContents 362 | */ 363 | public function testGetContents() 364 | { 365 | $this->createTempFile(); 366 | file_put_contents($this->tmpnam, 'FOO BAR'); 367 | $resource = fopen($this->tmpnam, Stream::MODE_READ_ONLY_FROM_BEGIN); 368 | 369 | $stream = new Stream($resource); 370 | 371 | $this->assertEquals('FOO BAR', $stream->getContents()); 372 | } 373 | 374 | /** 375 | * Method to test getMetadata(). 376 | * 377 | * @return void 378 | * 379 | * @covers Asika\Http\Stream::getMetadata 380 | */ 381 | public function testGetMetadata() 382 | { 383 | $this->createTempFile(); 384 | $resource = fopen($this->tmpnam, Stream::MODE_READ_WRITE_FROM_BEGIN); 385 | $this->instance->attach($resource); 386 | 387 | $this->assertEquals(stream_get_meta_data($resource), $this->instance->getMetadata()); 388 | 389 | $this->assertEquals(Stream::MODE_READ_WRITE_FROM_BEGIN, $this->instance->getMetadata('mode')); 390 | 391 | fclose($resource); 392 | } 393 | 394 | /** 395 | * createTempFile 396 | * 397 | * @return string 398 | */ 399 | protected function createTempFile() 400 | { 401 | return $this->tmpnam = tempnam(sys_get_temp_dir(), 'http'); 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /test/Stream/StringStreamTest.php: -------------------------------------------------------------------------------- 1 | instance = new StringStream('', Stream::MODE_READ_WRITE_FROM_BEGIN); 46 | } 47 | 48 | /** 49 | * Tears down the fixture, for example, closes a network connection. 50 | * This method is called after a test is executed. 51 | * 52 | * @return void 53 | */ 54 | protected function tearDown() 55 | { 56 | if ($this->tmpnam && is_file($this->tmpnam)) 57 | { 58 | unlink($this->tmpnam); 59 | } 60 | } 61 | 62 | /** 63 | * testConstruct 64 | * 65 | * @return void 66 | */ 67 | public function testConstruct() 68 | { 69 | $stringObject = $this->getMockBuilder('stdClass') 70 | ->setMethods(array('__toString')) 71 | ->getMock(); 72 | 73 | $stringObject->expects($this->once()) 74 | ->method('__toString') 75 | ->willReturn('FOO'); 76 | 77 | $stream = new StringStream($stringObject); 78 | 79 | $this->assertEquals('FOO', TestHelper::getValue($stream, 'resource')); 80 | $this->assertInternalType('object', TestHelper::getValue($stream, 'stream')); 81 | } 82 | 83 | /** 84 | * Method to test __toString(). 85 | * 86 | * @return void 87 | * 88 | * @covers Asika\Http\Stream\StringStream::__toString 89 | */ 90 | public function test__toString() 91 | { 92 | $message = 'foo bar'; 93 | 94 | $this->instance->write($message); 95 | 96 | $this->assertEquals($message, (string) $this->instance); 97 | } 98 | 99 | /** 100 | * Method to test close(). 101 | * 102 | * @return void 103 | * 104 | * @covers Asika\Http\Stream\StringStream::close 105 | */ 106 | public function testClose() 107 | { 108 | $stream = new StringStream; 109 | $stream->write('Foo Bar'); 110 | 111 | $stream->close(); 112 | 113 | $this->assertEmpty($stream->getResource()); 114 | $this->assertAttributeEmpty('resource', $stream); 115 | $this->assertEquals('', (string) $stream); 116 | } 117 | 118 | /** 119 | * Method to test detach(). 120 | * 121 | * @return void 122 | * 123 | * @covers Asika\Http\Stream\StringStream::detach 124 | */ 125 | public function testDetach() 126 | { 127 | $stream = new StringStream('flower'); 128 | 129 | $this->assertEquals('flower', $stream->detach()); 130 | $this->assertAttributeEmpty('resource', $stream); 131 | $this->assertAttributeEmpty('stream', $stream); 132 | } 133 | 134 | /** 135 | * Method to test getSize(). 136 | * 137 | * @return void 138 | * 139 | * @covers Asika\Http\Stream\StringStream::getSize 140 | */ 141 | public function testGetSize() 142 | { 143 | $stream = new StringStream('FOO BAR'); 144 | 145 | $this->assertEquals(7, $stream->getSize()); 146 | } 147 | 148 | /** 149 | * Method to test tell(). 150 | * 151 | * @return void 152 | * 153 | * @covers Asika\Http\Stream\StringStream::tell 154 | */ 155 | public function testTell() 156 | { 157 | $stream = new StringStream('FOO BAR'); 158 | 159 | $stream->seek(2); 160 | 161 | $this->assertEquals(2, $stream->tell()); 162 | 163 | $stream->detach(); 164 | 165 | $this->assertExpectedException(function() use ($stream) 166 | { 167 | $stream->tell(); 168 | }, new \RuntimeException); 169 | } 170 | 171 | /** 172 | * Method to test eof(). 173 | * 174 | * @return void 175 | * 176 | * @covers Asika\Http\Stream\StringStream::eof 177 | */ 178 | public function testEof() 179 | { 180 | $stream = new StringStream('FOO BAR'); 181 | 182 | $stream->seek(2); 183 | $this->assertFalse($stream->eof()); 184 | 185 | $stream->seek(7); 186 | $this->assertTrue($stream->eof()); 187 | } 188 | 189 | /** 190 | * Method to test isSeekable(). 191 | * 192 | * @return void 193 | * 194 | * @covers Asika\Http\Stream\StringStream::isSeekable 195 | */ 196 | public function testIsSeekable() 197 | { 198 | $stream = new StringStream('FOO BAR'); 199 | 200 | $this->assertTrue($stream->isSeekable()); 201 | 202 | $stream->seekable(false); 203 | 204 | $this->assertFalse($stream->isSeekable()); 205 | } 206 | 207 | /** 208 | * Method to test seek(). 209 | * 210 | * @return void 211 | * 212 | * @covers Asika\Http\Stream\StringStream::seek 213 | */ 214 | public function testSeek() 215 | { 216 | $stream = new StringStream('FOO BAR'); 217 | 218 | $this->assertTrue($stream->seek(2)); 219 | $this->assertEquals(2, $stream->tell()); 220 | 221 | $this->assertTrue($stream->seek(2, SEEK_CUR)); 222 | $this->assertEquals(4, $stream->tell()); 223 | 224 | $this->assertTrue($stream->seek(-1, SEEK_END)); 225 | $this->assertEquals(6, $stream->tell()); 226 | } 227 | 228 | /** 229 | * Method to test rewind(). 230 | * 231 | * @return void 232 | * 233 | * @covers Asika\Http\Stream\StringStream::rewind 234 | */ 235 | public function testRewind() 236 | { 237 | $stream = new StringStream('FOO BAR'); 238 | 239 | $this->assertTrue($stream->seek(2)); 240 | 241 | $stream->rewind(); 242 | 243 | $this->assertEquals(0, $stream->tell()); 244 | } 245 | 246 | /** 247 | * Method to test isWritable(). 248 | * 249 | * @return void 250 | * 251 | * @covers Asika\Http\Stream\StringStream::isWritable 252 | */ 253 | public function testIsWritable() 254 | { 255 | $stream = new StringStream('php://memory', Stream::MODE_READ_ONLY_FROM_BEGIN); 256 | 257 | $this->assertFalse($stream->isWritable()); 258 | } 259 | 260 | /** 261 | * Method to test write(). 262 | * 263 | * @return void 264 | * 265 | * @covers Asika\Http\Stream\StringStream::write 266 | */ 267 | public function testWrite() 268 | { 269 | $stream = new StringStream(''); 270 | $stream->write('flower'); 271 | 272 | $this->assertEquals('flower', $stream->getResource()); 273 | 274 | $stream->write(' bloom'); 275 | 276 | $this->assertEquals('flower bloom', $stream->getResource()); 277 | 278 | $stream->seek(4); 279 | 280 | $stream->write('test'); 281 | 282 | $this->assertEquals('flowtestloom', $stream->getResource()); 283 | } 284 | 285 | /** 286 | * Method to test isReadable(). 287 | * 288 | * @return void 289 | * 290 | * @covers Asika\Http\Stream\StringStream::isReadable 291 | */ 292 | public function testIsReadable() 293 | { 294 | $stream = new StringStream; 295 | $this->assertTrue($stream->isReadable()); 296 | } 297 | 298 | /** 299 | * Method to test read(). 300 | * 301 | * @return void 302 | * 303 | * @covers Asika\Http\Stream\StringStream::read 304 | */ 305 | public function testRead() 306 | { 307 | $stream = new StringStream('FOO BAR'); 308 | 309 | $this->assertEquals('FO', $stream->read(2)); 310 | $this->assertEquals('O B', $stream->read(3)); 311 | 312 | $stream->rewind(); 313 | 314 | $this->assertEquals('FOO', $stream->read(3)); 315 | } 316 | 317 | /** 318 | * Method to test getContents(). 319 | * 320 | * @return void 321 | * 322 | * @covers Asika\Http\Stream\StringStream::getContents 323 | */ 324 | public function testGetContents() 325 | { 326 | $stream = new StringStream('FOO BAR'); 327 | 328 | $this->assertEquals('FOO BAR', $stream->getContents()); 329 | 330 | $stream = new StringStream('FOO BAR'); 331 | 332 | $stream->seek(2); 333 | 334 | $this->assertEquals('O BAR', $stream->getContents()); 335 | } 336 | 337 | /** 338 | * Method to test getMetadata(). 339 | * 340 | * @return void 341 | * 342 | * @covers Asika\Http\Stream\StringStream::getMetadata 343 | */ 344 | public function testGetMetadata() 345 | { 346 | $this->assertInternalType('array', $this->instance->getMetadata()); 347 | 348 | $this->assertTrue($this->instance->getMetadata('seekable')); 349 | $this->assertEquals('rb', $this->instance->getMetadata('mode')); 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /test/Stub/StubMessage.php: -------------------------------------------------------------------------------- 1 | $value) 13 | { 14 | if ($key == 'GLOBALS' || $key == 'globals' || $key == 'value') 15 | { 16 | continue; 17 | } 18 | 19 | $globals[$key] = $value; 20 | } 21 | 22 | parse_str(file_get_contents('php://input'), $globals['data']); 23 | 24 | header('Content-Type: text/json'); 25 | echo json_encode($globals); 26 | -------------------------------------------------------------------------------- /test/Transport/AbstractTransportTest.php: -------------------------------------------------------------------------------- 1 | array() 33 | ); 34 | 35 | /** 36 | * Test instance. 37 | * 38 | * @var AbstractTransport 39 | */ 40 | protected $instance; 41 | 42 | /** 43 | * Property downloadFile. 44 | * 45 | * @var string 46 | */ 47 | protected $destFile; 48 | 49 | /** 50 | * setUpBeforeClass 51 | * 52 | * @return void 53 | */ 54 | public static function setUpBeforeClass() 55 | { 56 | if (!defined('ASIKA_HTTP_TEST_URL')) 57 | { 58 | static::markTestSkipped('No ASIKA_HTTP_TEST_URL provided'); 59 | } 60 | } 61 | 62 | /** 63 | * Sets up the fixture, for example, opens a network connection. 64 | * This method is called before a test is executed. 65 | * 66 | * @return void 67 | */ 68 | protected function setUp() 69 | { 70 | if (!$this->instance->isSupported()) 71 | { 72 | $this->markTestSkipped(get_class($this->instance) . ' driver not supported.'); 73 | } 74 | 75 | $this->destFile = __DIR__ . '/downloaded.tmp'; 76 | } 77 | 78 | /** 79 | * createRequest 80 | * 81 | * @param StreamInterface $stream 82 | * 83 | * @return Request 84 | */ 85 | protected function createRequest($stream = null) 86 | { 87 | return new Request($stream ? : new StringStream); 88 | } 89 | 90 | /** 91 | * testRequestGet 92 | * 93 | * @return void 94 | */ 95 | public function testRequestGet() 96 | { 97 | $request = $this->createRequest(); 98 | 99 | $request = $request->withUri(new PsrUri(ASIKA_HTTP_TEST_URL)) 100 | ->withMethod('GET'); 101 | 102 | $response = $this->instance->request($request); 103 | 104 | $this->assertEquals(200, $response->getStatusCode()); 105 | $this->assertJson($response->getBody()->getContents()); 106 | 107 | $request = $this->createRequest(); 108 | 109 | $request = $request->withUri(new PsrUri(ASIKA_HTTP_TEST_URL . '?foo=bar&baz[3]=yoo')) 110 | ->withMethod('GET'); 111 | 112 | $response = $this->instance->request($request); 113 | 114 | $data = json_decode($response->getBody()->getContents(), true); 115 | $this->assertEquals(array('foo' => 'bar', 'baz' => array(3 => 'yoo')), $data['_GET']); 116 | } 117 | 118 | /** 119 | * testBadDomainGet 120 | * 121 | * @return void 122 | * 123 | * @expectedException \RuntimeException 124 | */ 125 | public function testBadDomainGet() 126 | { 127 | $request = $this->createRequest(); 128 | 129 | $request = $request->withUri(new PsrUri('http://not.exists.url/flower.sakura')) 130 | ->withMethod('GET'); 131 | 132 | $this->instance->request($request); 133 | } 134 | 135 | /** 136 | * testBadPathGet 137 | * 138 | * @return void 139 | */ 140 | public function testBadPathGet() 141 | { 142 | $request = $this->createRequest(); 143 | 144 | $request = $request->withUri(new PsrUri(dirname(ASIKA_HTTP_TEST_URL) . '/wrong.php')) 145 | ->withMethod('POST'); 146 | 147 | $request->getBody()->write(UriHelper::buildQuery(array('foo' => 'bar'))); 148 | 149 | $response = $this->instance->request($request); 150 | 151 | $this->assertEquals(404, $response->getStatusCode()); 152 | $this->assertEquals('Not Found', $response->getReasonPhrase()); 153 | } 154 | 155 | /** 156 | * testRequestPost 157 | * 158 | * @return void 159 | */ 160 | public function testRequestPost() 161 | { 162 | $request = $this->createRequest(); 163 | 164 | $request = $request->withUri(new PsrUri(ASIKA_HTTP_TEST_URL)) 165 | ->withMethod('POST'); 166 | 167 | $request->getBody()->write(UriHelper::buildQuery(array('foo' => 'bar'))); 168 | 169 | $response = $this->instance->request($request); 170 | 171 | $data = json_decode($response->getBody()->getContents(), true); 172 | 173 | $this->assertEquals(array('foo' => 'bar'), $data['_POST']); 174 | } 175 | 176 | /** 177 | * testRequestPut 178 | * 179 | * @return void 180 | */ 181 | public function testRequestPut() 182 | { 183 | $request = $this->createRequest(); 184 | 185 | $request = $request->withUri(new PsrUri(ASIKA_HTTP_TEST_URL)) 186 | ->withMethod('PUT'); 187 | 188 | $request->getBody()->write(UriHelper::buildQuery(array('foo' => 'bar'))); 189 | 190 | $response = $this->instance->request($request); 191 | 192 | $data = json_decode($response->getBody()->getContents(), true); 193 | 194 | $this->assertEquals(array('foo' => 'bar'), $data['data']); 195 | $this->assertEquals('PUT', $data['_SERVER']['REQUEST_METHOD']); 196 | } 197 | 198 | /** 199 | * testRequestCredentials 200 | * 201 | * @return void 202 | */ 203 | public function testRequestCredentials() 204 | { 205 | $request = $this->createRequest(); 206 | 207 | $uri = new PsrUri(ASIKA_HTTP_TEST_URL); 208 | $uri = $uri->withUserInfo('username', 'pass1234'); 209 | 210 | $request = $request->withUri($uri) 211 | ->withMethod('GET'); 212 | 213 | $response = $this->instance->request($request); 214 | 215 | $data = json_decode($response->getBody()->getContents(), true); 216 | 217 | $this->assertEquals('username', $data['_SERVER']['PHP_AUTH_USER']); 218 | $this->assertEquals('pass1234', $data['_SERVER']['PHP_AUTH_PW']); 219 | } 220 | 221 | /** 222 | * testRequestPostScalar 223 | * 224 | * @return void 225 | */ 226 | public function testRequestPostScalar() 227 | { 228 | $request = $this->createRequest(); 229 | 230 | $request = $request->withUri(new PsrUri(ASIKA_HTTP_TEST_URL . '?foo=bar')) 231 | ->withMethod('POST'); 232 | 233 | $request->getBody()->write('flower=sakura'); 234 | 235 | $response = $this->instance->request($request); 236 | 237 | $data = json_decode($response->getBody()->getContents(), true); 238 | 239 | $this->assertEquals(array('foo' => 'bar'), $data['_GET']); 240 | $this->assertEquals(array('flower' => 'sakura'), $data['_POST']); 241 | } 242 | 243 | public function testDownload() 244 | { 245 | $this->unlinkDownloaded(); 246 | 247 | $this->assertFileNotExists((string) $this->destFile); 248 | 249 | $request = $this->createRequest(new Stream); 250 | 251 | $src = dirname(ASIKA_HTTP_TEST_URL) . '/download_stub.txt'; 252 | 253 | $request = $request->withUri(new PsrUri($src)) 254 | ->withMethod('GET'); 255 | 256 | $response = $this->instance->download($request, $this->destFile); 257 | 258 | $this->assertEquals('This is test download file.', trim(file_get_contents($this->destFile))); 259 | } 260 | 261 | /** 262 | * unlinkDownloaded 263 | * 264 | * @return void 265 | */ 266 | protected function unlinkDownloaded() 267 | { 268 | if (is_file($this->destFile)) 269 | { 270 | unlink($this->destFile); 271 | } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /test/Transport/CurlTransportTest.php: -------------------------------------------------------------------------------- 1 | array(CURLOPT_SSL_VERIFYPEER => false) 27 | ); 28 | 29 | /** 30 | * Test instance. 31 | * 32 | * @var CurlTransport 33 | */ 34 | protected $instance; 35 | 36 | /** 37 | * Sets up the fixture, for example, opens a network connection. 38 | * This method is called before a test is executed. 39 | * 40 | * @return void 41 | */ 42 | protected function setUp() 43 | { 44 | $this->instance = new CurlTransport; 45 | 46 | parent::setUp(); 47 | } 48 | 49 | /** 50 | * Tears down the fixture, for example, closes a network connection. 51 | * This method is called after a test is executed. 52 | * 53 | * @return void 54 | */ 55 | protected function tearDown() 56 | { 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/Transport/StreamTransportTest.php: -------------------------------------------------------------------------------- 1 | array(CURLOPT_SSL_VERIFYPEER => false) 28 | ); 29 | 30 | /** 31 | * Test instance. 32 | * 33 | * @var CurlTransport 34 | */ 35 | protected $instance; 36 | 37 | /** 38 | * Sets up the fixture, for example, opens a network connection. 39 | * This method is called before a test is executed. 40 | * 41 | * @return void 42 | */ 43 | protected function setUp() 44 | { 45 | $this->instance = new StreamTransport; 46 | 47 | parent::setUp(); 48 | } 49 | 50 | /** 51 | * Tears down the fixture, for example, closes a network connection. 52 | * This method is called after a test is executed. 53 | * 54 | * @return void 55 | */ 56 | protected function tearDown() 57 | { 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/Transport/downloaded.tmp: -------------------------------------------------------------------------------- 1 | This is test download file. 2 | -------------------------------------------------------------------------------- /test/UploadedFileTest.php: -------------------------------------------------------------------------------- 1 | tmpFile) && file_exists($this->tmpFile)) 48 | { 49 | unlink($this->tmpFile); 50 | } 51 | } 52 | 53 | /** 54 | * Method to test getStream(). 55 | * 56 | * @return void 57 | * 58 | * @covers Asika\Http\UploadedFile::getStream 59 | * @TODO Implement testGetStream(). 60 | */ 61 | public function testGetStream() 62 | { 63 | // Remove the following lines when you implement this test. 64 | $this->markTestIncomplete( 65 | 'This test has not been implemented yet.' 66 | ); 67 | } 68 | 69 | /** 70 | * Method to test moveTo(). 71 | * 72 | * @return void 73 | * 74 | * @covers Asika\Http\UploadedFile::moveTo 75 | */ 76 | public function testMoveTo() 77 | { 78 | $stream = new Stream('php://temp', 'wb+'); 79 | $stream->write('Foo bar!'); 80 | $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); 81 | 82 | $this->tmpFile = $to = sys_get_temp_dir() . '/http-' . uniqid(); 83 | 84 | $this->assertFalse(is_file($to)); 85 | 86 | $upload->moveTo($to); 87 | 88 | $this->assertTrue(is_file($to)); 89 | 90 | $contents = file_get_contents($to); 91 | 92 | $this->assertEquals($stream->__toString(), $contents); 93 | 94 | // Send string 95 | $uploadFile = sys_get_temp_dir() . '/upload-' . uniqid(); 96 | file_put_contents($uploadFile, 'Foo bar!'); 97 | $upload = new UploadedFile($uploadFile, 0, UPLOAD_ERR_OK); 98 | 99 | $this->tmpFile = $to = sys_get_temp_dir() . '/http-' . uniqid(); 100 | 101 | $this->assertFalse(is_file($to)); 102 | 103 | $upload->moveTo($to); 104 | 105 | $this->assertTrue(is_file($to)); 106 | 107 | $contents = file_get_contents($to); 108 | 109 | $this->assertEquals('Foo bar!', $contents); 110 | 111 | @unlink($uploadFile); 112 | } 113 | 114 | /** 115 | * testMoveInNotCli 116 | * 117 | * @return void 118 | */ 119 | public function testMoveInNotCli() 120 | { 121 | // Send stream 122 | $stream = new Stream('php://temp', 'wb+'); 123 | $stream->write('Foo bar!'); 124 | $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); 125 | $upload->setSapi('cgi'); 126 | 127 | $this->tmpFile = $to = sys_get_temp_dir() . '/http-' . uniqid(); 128 | 129 | $this->assertFalse(is_file($to)); 130 | 131 | $upload->moveTo($to); 132 | 133 | $this->assertTrue(is_file($to)); 134 | 135 | $contents = file_get_contents($to); 136 | 137 | $this->assertEquals($stream->__toString(), $contents); 138 | 139 | // Send string 140 | $uploadFile = sys_get_temp_dir() . '/upload-' . uniqid(); 141 | file_put_contents($uploadFile, 'Foo bar!'); 142 | $upload = new UploadedFile($uploadFile, 0, UPLOAD_ERR_OK); 143 | $upload->setSapi('cgi'); 144 | 145 | $this->tmpFile = $to = sys_get_temp_dir() . '/http-' . uniqid(); 146 | 147 | $this->assertFalse(is_file($to)); 148 | 149 | $this->assertExpectedException(function() use ($upload, $to) 150 | { 151 | $upload->moveTo($to); 152 | }, 'RuntimeException', 'Error moving uploaded file'); 153 | } 154 | 155 | /** 156 | * Method to test getSize(). 157 | * 158 | * @return void 159 | * 160 | * @covers Asika\Http\UploadedFile::getSize 161 | * @TODO Implement testGetSize(). 162 | */ 163 | public function testGetSize() 164 | { 165 | // Remove the following lines when you implement this test. 166 | $this->markTestIncomplete( 167 | 'This test has not been implemented yet.' 168 | ); 169 | } 170 | 171 | /** 172 | * Method to test getError(). 173 | * 174 | * @return void 175 | * 176 | * @covers Asika\Http\UploadedFile::getError 177 | * @TODO Implement testGetError(). 178 | */ 179 | public function testGetError() 180 | { 181 | // Remove the following lines when you implement this test. 182 | $this->markTestIncomplete( 183 | 'This test has not been implemented yet.' 184 | ); 185 | } 186 | 187 | /** 188 | * Method to test getClientFilename(). 189 | * 190 | * @return void 191 | * 192 | * @covers Asika\Http\UploadedFile::getClientFilename 193 | * @TODO Implement testGetClientFilename(). 194 | */ 195 | public function testGetClientFilename() 196 | { 197 | // Remove the following lines when you implement this test. 198 | $this->markTestIncomplete( 199 | 'This test has not been implemented yet.' 200 | ); 201 | } 202 | 203 | /** 204 | * Method to test getClientMediaType(). 205 | * 206 | * @return void 207 | * 208 | * @covers Asika\Http\UploadedFile::getClientMediaType 209 | * @TODO Implement testGetClientMediaType(). 210 | */ 211 | public function testGetClientMediaType() 212 | { 213 | // Remove the following lines when you implement this test. 214 | $this->markTestIncomplete( 215 | 'This test has not been implemented yet.' 216 | ); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /test/Uri/PsrUriTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('https', $uri->getScheme()); 30 | $this->assertEquals('user:pass', $uri->getUserInfo()); 31 | $this->assertEquals('local.example.com', $uri->getHost()); 32 | $this->assertEquals(3001, $uri->getPort()); 33 | $this->assertEquals('user:pass@local.example.com:3001', $uri->getAuthority()); 34 | $this->assertEquals('/foo', $uri->getPath()); 35 | $this->assertEquals('bar=baz', $uri->getQuery()); 36 | $this->assertEquals('quz', $uri->getFragment()); 37 | } 38 | 39 | /** 40 | * testToString 41 | * 42 | * @return void 43 | */ 44 | public function testToString() 45 | { 46 | $url = 'https://user:pass@local.example.com:3001/foo?bar=baz#quz'; 47 | $uri = new PsrUri($url); 48 | $this->assertEquals($url, (string) $uri); 49 | } 50 | 51 | /** 52 | * testWithScheme 53 | * 54 | * @return void 55 | */ 56 | public function testWithScheme() 57 | { 58 | $uri = new PsrUri('https://user:pass@local.example.com:3001/foo?bar=baz#quz'); 59 | $new = $uri->withScheme('http'); 60 | $this->assertNotSame($uri, $new); 61 | $this->assertEquals('http', $new->getScheme()); 62 | $this->assertEquals('http://user:pass@local.example.com:3001/foo?bar=baz#quz', (string) $new); 63 | 64 | $new = $uri->withScheme('https://'); 65 | $this->assertEquals('https', $new->getScheme()); 66 | } 67 | 68 | /** 69 | * testWithUserInfo 70 | * 71 | * @return void 72 | */ 73 | public function testWithUserInfo() 74 | { 75 | // User 76 | $uri = new PsrUri('https://user:pass@local.example.com:3001/foo?bar=baz#quz'); 77 | $new = $uri->withUserInfo('flower'); 78 | $this->assertNotSame($uri, $new); 79 | $this->assertEquals('flower', $new->getUserInfo()); 80 | $this->assertEquals('https://flower@local.example.com:3001/foo?bar=baz#quz', (string) $new); 81 | 82 | // User & Password 83 | $uri = new PsrUri('https://user:pass@local.example.com:3001/foo?bar=baz#quz'); 84 | $new = $uri->withUserInfo('flower', 'sakura'); 85 | $this->assertNotSame($uri, $new); 86 | $this->assertEquals('flower:sakura', $new->getUserInfo()); 87 | $this->assertEquals('https://flower:sakura@local.example.com:3001/foo?bar=baz#quz', (string) $new); 88 | } 89 | 90 | /** 91 | * testWithHost 92 | * 93 | * @return void 94 | */ 95 | public function testWithHost() 96 | { 97 | $uri = new PsrUri('https://user:pass@local.example.com:3001/foo?bar=baz#quz'); 98 | $new = $uri->withHost('windwalker.io'); 99 | $this->assertNotSame($uri, $new); 100 | $this->assertEquals('windwalker.io', $new->getHost()); 101 | $this->assertEquals('https://user:pass@windwalker.io:3001/foo?bar=baz#quz', (string) $new); 102 | } 103 | 104 | /** 105 | * testWithPort 106 | * 107 | * @return void 108 | */ 109 | public function testWithPort() 110 | { 111 | $uri = new PsrUri('https://user:pass@local.example.com:3001/foo?bar=baz#quz'); 112 | $new = $uri->withPort(3000); 113 | $this->assertNotSame($uri, $new); 114 | $this->assertEquals(3000, $new->getPort()); 115 | $this->assertEquals( 116 | sprintf('https://user:pass@local.example.com:%d/foo?bar=baz#quz', 3000), 117 | (string) $new 118 | ); 119 | } 120 | 121 | /** 122 | * testWithPath 123 | * 124 | * @return void 125 | */ 126 | public function testWithPath() 127 | { 128 | $uri = new PsrUri('https://user:pass@local.example.com:3001/foo?bar=baz#quz'); 129 | $new = $uri->withPath('/bar/baz'); 130 | $this->assertNotSame($uri, $new); 131 | $this->assertEquals('/bar/baz', $new->getPath()); 132 | $this->assertEquals('https://user:pass@local.example.com:3001/bar/baz?bar=baz#quz', (string) $new); 133 | 134 | $uri = new PsrUri('http://example.com'); 135 | $new = $uri->withPath('foo/bar'); 136 | $this->assertEquals('foo/bar', $new->getPath()); 137 | 138 | $uri = new PsrUri('http://example.com'); 139 | $new = $uri->withPath('foo/bar'); 140 | $this->assertEquals('http://example.com/foo/bar', $new->__toString()); 141 | 142 | // Encoded 143 | $uri = $uri->withPath('/foo^bar'); 144 | $expected = '/foo%5Ebar'; 145 | $this->assertEquals($expected, $uri->getPath()); 146 | 147 | // Not double encoded 148 | $uri = $uri->withPath('/foo%5Ebar'); 149 | $expected = '/foo%5Ebar'; 150 | $this->assertEquals($expected, $uri->getPath()); 151 | } 152 | 153 | /** 154 | * testWithQuery 155 | * 156 | * @return void 157 | */ 158 | public function testWithQuery() 159 | { 160 | $uri = new PsrUri('https://user:pass@local.example.com:3001/foo?bar=baz#quz'); 161 | $new = $uri->withQuery('baz=bat'); 162 | $this->assertNotSame($uri, $new); 163 | $this->assertEquals('baz=bat', $new->getQuery()); 164 | $this->assertEquals('https://user:pass@local.example.com:3001/foo?baz=bat#quz', (string) $new); 165 | 166 | // Strip query symbol 167 | $uri = new PsrUri('http://example.com'); 168 | $new = $uri->withQuery('?foo=bar'); 169 | $this->assertEquals('foo=bar', $new->getQuery()); 170 | } 171 | 172 | /** 173 | * testWithFragment 174 | * 175 | * @return void 176 | */ 177 | public function testWithFragment() 178 | { 179 | $uri = new PsrUri('https://user:pass@local.example.com:3001/foo?bar=baz#quz'); 180 | $new = $uri->withFragment('qat'); 181 | $this->assertNotSame($uri, $new); 182 | $this->assertEquals('qat', $new->getFragment()); 183 | $this->assertEquals('https://user:pass@local.example.com:3001/foo?bar=baz#qat', (string) $new); 184 | 185 | $uri = new PsrUri('http://example.com'); 186 | $new = $uri->withFragment('#/foo/bar'); 187 | $this->assertEquals('/foo/bar', $new->getFragment()); 188 | } 189 | 190 | /** 191 | * authorityProvider 192 | * 193 | * @return array 194 | */ 195 | public function authorityProvider() 196 | { 197 | return array( 198 | 'host-only' => array('http://foo.com/bar', 'foo.com'), 199 | 'host-port' => array('http://foo.com:3000/bar', 'foo.com:3000'), 200 | 'user-host' => array('http://me@foo.com/bar', 'me@foo.com'), 201 | 'user-host-port' => array('http://me@foo.com:3000/bar', 'me@foo.com:3000'), 202 | ); 203 | } 204 | 205 | /** 206 | * testAuthority 207 | * 208 | * @dataProvider authorityProvider 209 | * 210 | * @param string $url 211 | * @param string $expected 212 | */ 213 | public function testAuthority($url, $expected) 214 | { 215 | $uri = new PsrUri($url); 216 | $this->assertEquals($expected, $uri->getAuthority()); 217 | } 218 | 219 | public function queryStringsForEncoding() 220 | { 221 | return array( 222 | 'key-only' => array('k^ey', 'k%5Eey'), 223 | 'key-value' => array('k^ey=valu`', 'k%5Eey=valu%60'), 224 | 'array-key-only' => array('key[]', 'key%5B%5D'), 225 | 'array-key-value' => array('key[]=valu`', 'key%5B%5D=valu%60'), 226 | 'complex' => array('k^ey&key[]=valu`&f<>=`bar', 'k%5Eey&key%5B%5D=valu%60&f%3C%3E=%60bar'), 227 | ); 228 | } 229 | 230 | /** 231 | * @dataProvider queryStringsForEncoding 232 | * 233 | * @param string $query 234 | * @param string $expected 235 | */ 236 | public function testQueryEncoded($query, $expected) 237 | { 238 | $uri = new PsrUri; 239 | $uri = $uri->withQuery($query); 240 | $this->assertEquals($expected, $uri->getQuery()); 241 | 242 | // No double encoded 243 | $uri = $uri->withQuery($expected); 244 | $this->assertEquals($expected, $uri->getQuery()); 245 | } 246 | 247 | /** 248 | * testFragmentEncoded 249 | * 250 | * @return void 251 | */ 252 | public function testFragmentEncoded() 253 | { 254 | $uri = new PsrUri; 255 | $uri = $uri->withFragment('/p^th?key^=`bar#b@z'); 256 | $expected = '/p%5Eth?key%5E=%60bar%23b@z'; 257 | $this->assertEquals($expected, $uri->getFragment()); 258 | 259 | // No double encoded 260 | $expected = '/p%5Eth?key%5E=%60bar%23b@z'; 261 | $uri = $uri->withFragment($expected); 262 | $this->assertEquals($expected, $uri->getFragment()); 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /test/Uri/UriHelperTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($expected, $actual, 'Line: ' . __LINE__ . ' Results should be equal'); 34 | 35 | // Test all parts of query 36 | $url = 'https://john:doe@www.google.com:80/folder/page.html#id?var=kay&var2=key&true'; 37 | $expected = parse_url($url); 38 | $actual = UriHelper::parseUrl($url); 39 | $this->assertEquals($expected, $actual, 'Line: ' . __LINE__ . ' Results should be equal'); 40 | 41 | // Test special characters in URL 42 | $url = 'http://Windwalker.org/mytestpath/È'; 43 | $expected = parse_url($url); 44 | 45 | // Fix up path for UTF-8 characters 46 | $expected['path'] = '/mytestpath/È'; 47 | $actual = UriHelper::parseUrl($url); 48 | $this->assertEquals($expected, $actual, 'Line: ' . __LINE__ . ' Results should be equal'); 49 | 50 | // Test special characters in URL 51 | $url = 'http://mydomain.com/!*\'();:@&=+$,/?%#[]" \\'; 52 | $expected = parse_url($url); 53 | $actual = UriHelper::parseUrl($url); 54 | $this->assertEquals($expected, $actual, 'Line: ' . __LINE__ . ' Results should be equal'); 55 | 56 | // Test url encoding in URL 57 | $url = 'http://mydomain.com/%21%2A%27%28%29%3B%3A%40%26%3D%24%2C%2F%3F%25%23%5B%22%20%5C'; 58 | $expected = parse_url($url); 59 | $actual = UriHelper::parseUrl($url); 60 | $this->assertEquals($expected, $actual, 'Line: ' . __LINE__ . ' Results should be equal'); 61 | 62 | // Test a mix of the above 63 | $url = 'http://john:doe@mydomain.com:80/%È21%25È3*%('; 64 | $expected = parse_url($url); 65 | 66 | // Fix up path for UTF-8 characters 67 | $expected['path'] = '/%È21%25È3*%('; 68 | $actual = UriHelper::parseUrl($url); 69 | $this->assertEquals($expected, $actual, 'Line: ' . __LINE__ . ' Results should be equal'); 70 | 71 | // Test invalild URL 72 | $url = 'http:///mydomain.com'; 73 | $expected = parse_url($url); 74 | $actual = UriHelper::parseUrl($url); 75 | $this->assertEquals($expected, $actual, 'Line: ' . __LINE__ . ' Results should be equal'); 76 | } 77 | } 78 | --------------------------------------------------------------------------------