├── .github
└── workflows
│ └── run-tests.yml
├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml
├── run-tests.sh
├── src
├── Laravel
│ └── cURL.php
├── Request.php
├── Response.php
├── cURL.php
└── cURLException.php
└── tests
├── functional
└── cURLTest.php
├── server
├── digest-auth.php
├── echo.php
├── failure.php
├── success.php
└── upload.php
└── unit
├── RequestTest.php
└── ResponseTest.php
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: run-tests
2 |
3 | 'on':
4 | push:
5 | branches:
6 | - master
7 | - develop
8 | tags:
9 | - '**'
10 | pull_request:
11 | branches:
12 | - '**'
13 | schedule:
14 | - cron: '0 8 1 * *'
15 |
16 | jobs:
17 | phpunit:
18 | runs-on: ubuntu-latest
19 | strategy:
20 | matrix:
21 | php-version:
22 | - '5.6'
23 | - '7.0'
24 | - '7.1'
25 | - '7.2'
26 | steps:
27 | - uses: actions/checkout@v2
28 | - uses: shivammathur/setup-php@v2
29 | with:
30 | php-version: ${{ matrix.php-version }}
31 | tools: composer
32 | - run: composer install --dev
33 | - run: ./run-tests.sh -v
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | /composer.lock
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013-2015 Andreas Lutro
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # php-curl
2 |
3 | [](https://travis-ci.org/anlutro/php-curl)
4 | [](https://github.com/anlutro/php-curl/releases)
5 | [](https://github.com/anlutro/php-curl/branches/active)
6 | [](http://opensource.org/licenses/MIT)
7 |
8 |
9 | The smallest possible OOP wrapper for PHP's curl capabilities.
10 |
11 | Note that this is **not** meant as a high-level abstraction. You should still know how "pure PHP" curl works, you need to know the curl options to set, and you need to know some HTTP basics.
12 |
13 | If you're looking for a more user-friendly abstraction, check out [Guzzle](http://guzzle.readthedocs.org/en/latest/).
14 |
15 | ## Installation
16 |
17 | $ composer require anlutro/curl
18 |
19 | ## Usage
20 |
21 | ```php
22 | $curl = new anlutro\cURL\cURL;
23 |
24 | $response = $curl->get('http://www.google.com');
25 |
26 | // easily build an url with a query string
27 | $url = $curl->buildUrl('http://www.google.com', ['s' => 'curl']);
28 | $response = $curl->get($url);
29 |
30 | // the post, put and patch methods takes an array of POST data
31 | $response = $curl->post($url, ['api_key' => 'my_key', 'post' => 'data']);
32 |
33 | // add "json" to the start of the method to convert the data to a JSON string
34 | // and send the header "Content-Type: application/json"
35 | $response = $curl->jsonPost($url, ['post' => 'data']);
36 |
37 | // if you don't want any conversion to be done to the provided data, for example
38 | // if you want to post an XML string, add "raw" to the start of the method
39 | $response = $curl->rawPost($url, 'rawPost($url, ['file' => $file]);
44 |
45 | // a response object is returned
46 | var_dump($response->statusCode); // response status code integer (for example, 200)
47 | var_dump($response->statusText); // full response status (for example, '200 OK')
48 | echo $response->body;
49 | var_dump($response->headers); // array of headers
50 | var_dump($response->info); // array of curl info
51 | ```
52 |
53 | If you need to send headers or set [cURL options](http://php.net/manual/en/function.curl-setopt.php) you can manipulate a request object directly. `send()` finalizes the request and returns the result.
54 |
55 | ```php
56 | // newRequest, newJsonRequest and newRawRequest returns a Request object
57 | $request = $curl->newRequest('post', $url, ['foo' => 'bar'])
58 | ->setHeader('Accept-Charset', 'utf-8')
59 | ->setHeader('Accept-Language', 'en-US')
60 | ->setOption(CURLOPT_CAINFO, '/path/to/cert')
61 | ->setOption(CURLOPT_FOLLOWLOCATION, true);
62 | $response = $request->send();
63 | ```
64 |
65 | You can also use `setHeaders(array $headers)` and `setOptions(array $options)` to replace all the existing options.
66 |
67 | Note that some curl options are reset when you call `send()`. Look at the source code of the method `cURL::prepareMethod` for a full overview of which options are overwritten.
68 |
69 | HTTP basic authentication:
70 |
71 | ```php
72 | $request = $curl->newRequest('post', $url, ['foo' => 'bar'])
73 | ->setUser($username)->setPass($password);
74 | ```
75 |
76 | ### Laravel
77 |
78 | The package comes with a facade you can use if you prefer the static method calls over dependency injection.
79 |
80 | You do **not** need to add a service provider.
81 |
82 | Optionally, add `'cURL' => 'anlutro\cURL\Laravel\cURL'` to the array of aliases in `config/app.php`.
83 |
84 | Replace `$curl->` with `cURL::` in the examples above.
85 |
86 | ## Contact
87 |
88 | Open an issue on GitHub if you have any problems or suggestions.
89 |
90 | ## License
91 |
92 | The contents of this repository is released under the [MIT license](http://opensource.org/licenses/MIT).
93 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "anlutro/curl",
3 | "description": "Simple OOP cURL wrapper.",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Andreas Lutro",
8 | "email": "anlutro@gmail.com"
9 | }
10 | ],
11 | "require": {
12 | "php": ">=5.3.0"
13 | },
14 | "require-dev": {
15 | "mockery/mockery": "0.9.*",
16 | "phpunit/phpunit": "4.*"
17 | },
18 | "autoload": {
19 | "psr-4": {
20 | "anlutro\\cURL\\": "src/"
21 | }
22 | },
23 | "config": {
24 | "preferred-install": "dist"
25 | },
26 | "minimum-stability": "dev",
27 | "prefer-stable": true
28 | }
29 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | ./tests/unit
16 |
17 |
18 | ./tests/functional
19 |
20 |
21 |
--------------------------------------------------------------------------------
/run-tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if [ "$TRAVIS_PHP_VERSION" = "" ]; then
4 | php_version=$(php --version | grep -oh 'PHP ([0-9]\.[0-9])' | sed 's/PHP //')
5 | else
6 | php_version=$TRAVIS_PHP_VERSION
7 | fi
8 |
9 | if [ "$php_version" != "5.3" ] && [ "$php_version" != "hhvm" ]; then
10 | php -S localhost:8080 -t tests/server > /dev/null 2>&1 &
11 | php_pid=$!
12 | export CURL_TEST_SERVER_RUNNING=1
13 | fi
14 |
15 | if [ -e vendor/bin/phpunit ]; then
16 | phpunit=vendor/bin/phpunit
17 | else
18 | phpunit=phpunit
19 | fi
20 |
21 | $phpunit $@
22 | ret=$?
23 |
24 | if [ $CURL_TEST_SERVER_RUNNING ]; then
25 | kill $php_pid
26 | fi
27 |
28 | exit $ret
29 |
--------------------------------------------------------------------------------
/src/Laravel/cURL.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://opensource.org/licenses/MIT
7 | * @package PHP cURL
8 | */
9 |
10 | namespace anlutro\cURL\Laravel;
11 |
12 | use Illuminate\Support\Facades\Facade;
13 |
14 | /**
15 | * cURL facade class.
16 | */
17 | class cURL extends Facade
18 | {
19 | public static function getFacadeAccessor()
20 | {
21 | return 'anlutro\cURL\cURL';
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Request.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://opensource.org/licenses/MIT
7 | * @package PHP cURL
8 | */
9 |
10 | namespace anlutro\cURL;
11 |
12 | /**
13 | * cURL request representation class.
14 | */
15 | class Request
16 | {
17 | /**
18 | * ENCODING_* constants, used for specifying encoding options
19 | */
20 | const ENCODING_QUERY = 0;
21 | const ENCODING_JSON = 1;
22 | const ENCODING_RAW = 2;
23 |
24 | /**
25 | * Allowed methods => allows postdata
26 | *
27 | * @var array
28 | */
29 | public static $methods = array(
30 | 'get' => false,
31 | 'head' => false,
32 | 'post' => true,
33 | 'put' => true,
34 | 'patch' => true,
35 | 'delete' => true,
36 | 'options' => false,
37 | );
38 |
39 | /**
40 | * cURL wrapper instance
41 | *
42 | * @var cURL
43 | */
44 | private $curl;
45 |
46 | /**
47 | * The HTTP method to use. Defaults to GET.
48 | *
49 | * @var string
50 | */
51 | private $method = 'get';
52 |
53 | /**
54 | * The URL the request is sent to.
55 | *
56 | * @var string
57 | */
58 | private $url = '';
59 |
60 | /**
61 | * The headers sent with the request.
62 | *
63 | * @var array
64 | */
65 | private $headers = array();
66 |
67 | /**
68 | * The cookies sent with the request.
69 | *
70 | * @var array
71 | */
72 | private $cookies = array();
73 |
74 | /**
75 | * POST data sent with the request.
76 | *
77 | * @var mixed
78 | */
79 | private $data = array();
80 |
81 | /**
82 | * Optional cURL options.
83 | *
84 | * @var array
85 | */
86 | private $options = array();
87 |
88 | /**
89 | * Username to authenticate the request of cURL.
90 | *
91 | * @var array
92 | */
93 | private $user = '';
94 |
95 | /**
96 | * Password to authenticate the request of cURL.
97 | *
98 | * @var array
99 | */
100 | private $pass = '';
101 |
102 | /**
103 | * The type of processing to perform to encode the POST data
104 | *
105 | * @var int
106 | */
107 | private $encoding = Request::ENCODING_QUERY;
108 |
109 | /**
110 | * @param cURL $curl
111 | */
112 | public function __construct(cURL $curl)
113 | {
114 | $this->curl = $curl;
115 | }
116 |
117 | /**
118 | * Set the HTTP method of the request.
119 | *
120 | * @param string $method
121 | */
122 | public function setMethod($method)
123 | {
124 | $method = strtolower($method);
125 |
126 | if (!array_key_exists($method, static::$methods)) {
127 | throw new \InvalidArgumentException("Method [$method] not a valid HTTP method.");
128 | }
129 |
130 | if ($this->data && !static::$methods[$method]) {
131 | throw new \LogicException('Request has POST data, but tried changing HTTP method to one that does not allow POST data');
132 | }
133 |
134 | $this->method = $method;
135 |
136 | return $this;
137 | }
138 |
139 | /**
140 | * Get the HTTP method of the request.
141 | *
142 | * @return string
143 | */
144 | public function getMethod()
145 | {
146 | return $this->method;
147 | }
148 |
149 | /**
150 | * Set the URL of the request.
151 | *
152 | * @param string $url
153 | */
154 | public function setUrl($url)
155 | {
156 | $this->url = $url;
157 |
158 | return $this;
159 | }
160 |
161 | /**
162 | * Get the URL of the request.
163 | *
164 | * @return string
165 | */
166 | public function getUrl()
167 | {
168 | return $this->url;
169 | }
170 |
171 | /**
172 | * Set a specific header to be sent with the request.
173 | *
174 | * @param string $key Can also be a string in "foo: bar" format
175 | * @param mixed $value
176 | * @param boolean $preserveCase
177 | */
178 | public function setHeader($key, $value = null, $preserveCase = false)
179 | {
180 | if ($value === null) {
181 | list($key, $value) = explode(':', $value, 2);
182 | }
183 |
184 | if (!$preserveCase) {
185 | $key = strtolower($key);
186 | }
187 |
188 | $key = trim($key);
189 | $this->headers[$key] = trim($value);
190 |
191 | return $this;
192 | }
193 |
194 | /**
195 | * Set the headers to be sent with the request.
196 | *
197 | * Pass an associative array - e.g. ['Content-Type' => 'application/json']
198 | * and the correct header formatting - e.g. 'Content-Type: application/json'
199 | * will be done for you when the request is sent.
200 | *
201 | * @param array $headers
202 | */
203 | public function setHeaders(array $headers)
204 | {
205 | $this->headers = array();
206 |
207 | foreach ($headers as $key => $value) {
208 | $this->setHeader($key, $value);
209 | }
210 |
211 | return $this;
212 | }
213 |
214 | /**
215 | * Get a specific header from the request.
216 | *
217 | * @param string $key
218 | *
219 | * @return mixed
220 | */
221 | public function getHeader($key)
222 | {
223 | $key = strtolower($key);
224 |
225 | return isset($this->headers[$key]) ? $this->headers[$key] : null;
226 | }
227 |
228 | /**
229 | * Get the headers to be sent with the request.
230 | *
231 | * @return array
232 | */
233 | public function getHeaders()
234 | {
235 | return $this->headers;
236 | }
237 |
238 | /**
239 | * Set a cookie.
240 | *
241 | * @param string $key
242 | * @param string $value
243 | */
244 | public function setCookie($key, $value)
245 | {
246 | $this->cookies[$key] = $value;
247 | $this->updateCookieHeader();
248 |
249 | return $this;
250 | }
251 |
252 | /**
253 | * Replace the request's cookies.
254 | *
255 | * @param array $cookies
256 | */
257 | public function setCookies(array $cookies)
258 | {
259 | $this->cookies = $cookies;
260 | $this->updateCookieHeader();
261 |
262 | return $this;
263 | }
264 |
265 | /**
266 | * Read the request cookies and set the cookie header.
267 | *
268 | * @return void
269 | */
270 | private function updateCookieHeader()
271 | {
272 | $strings = array();
273 |
274 | foreach ($this->cookies as $key => $value) {
275 | $strings[] = "{$key}={$value}";
276 | }
277 |
278 | $this->setHeader('cookie', implode('; ', $strings));
279 | }
280 |
281 | /**
282 | * Get a specific cookie from the request.
283 | *
284 | * @param string $key
285 | *
286 | * @return string|null
287 | */
288 | public function getCookie($key)
289 | {
290 | return isset($this->cookies[$key]) ? $this->cookies[$key] : null;
291 | }
292 |
293 | /**
294 | * Get all the request's cookies.
295 | *
296 | * @return string[]
297 | */
298 | public function getCookies()
299 | {
300 | return $this->cookies;
301 | }
302 |
303 | /**
304 | * Format the headers to an array of 'key: val' which can be passed to
305 | * curl_setopt.
306 | *
307 | * @return array
308 | */
309 | public function formatHeaders()
310 | {
311 | $headers = array();
312 |
313 | foreach ($this->headers as $key => $val) {
314 | if (is_string($key)) {
315 | $headers[] = $key . ': ' . $val;
316 | } else {
317 | $headers[] = $val;
318 | }
319 | }
320 |
321 | return $headers;
322 | }
323 |
324 | /**
325 | * Set the POST data to be sent with the request.
326 | *
327 | * @param mixed $data
328 | */
329 | public function setData($data)
330 | {
331 | if ($data && !static::$methods[$this->method]) {
332 | throw new \InvalidArgumentException("HTTP method [$this->method] does not allow POST data.");
333 | }
334 |
335 | $this->data = $data;
336 |
337 | return $this;
338 | }
339 |
340 | /**
341 | * Check whether the request has any data.
342 | *
343 | * @return boolean
344 | */
345 | public function hasData()
346 | {
347 | return static::$methods[$this->method] && (bool) $this->encodeData();
348 | }
349 |
350 | /**
351 | * Get the POST data to be sent with the request.
352 | *
353 | * @return mixed
354 | */
355 | public function getData()
356 | {
357 | return $this->data;
358 | }
359 |
360 | /**
361 | * Set the encoding to use on the POST data, and (possibly) associated Content-Type headers
362 | *
363 | * @param int $encoding a Request::ENCODING_* constant
364 | */
365 | public function setEncoding($encoding)
366 | {
367 | $encoding = intval($encoding);
368 |
369 | if (
370 | $encoding !== static::ENCODING_QUERY &&
371 | $encoding !== static::ENCODING_JSON &&
372 | $encoding !== static::ENCODING_RAW
373 | ) {
374 | throw new \InvalidArgumentException("Encoding [$encoding] not a known Request::ENCODING_* constant");
375 | }
376 |
377 | if ($encoding === static::ENCODING_JSON && !$this->getHeader('Content-Type')) {
378 | $this->setHeader('Content-Type', 'application/json');
379 | }
380 |
381 | $this->encoding = $encoding;
382 |
383 | return $this;
384 | }
385 |
386 | /**
387 | * Get the current encoding which will be used on the POST data
388 | *
389 | * @return int a Request::ENCODING_* constant
390 | */
391 | public function getEncoding()
392 | {
393 | return $this->encoding;
394 | }
395 |
396 | /**
397 | * Encode the POST data as a string.
398 | *
399 | * @return string
400 | */
401 | public function encodeData()
402 | {
403 | switch ($this->encoding) {
404 | case static::ENCODING_JSON:
405 | return json_encode($this->data);
406 | case static::ENCODING_QUERY:
407 | return (!is_null($this->data) ? http_build_query($this->data) : '');
408 | case static::ENCODING_RAW:
409 | return $this->data;
410 | default:
411 | $msg = "Encoding [$this->encoding] not a known Request::ENCODING_* constant";
412 | throw new \UnexpectedValueException($msg);
413 | }
414 | }
415 |
416 | /**
417 | * Set a specific curl option for the request.
418 | *
419 | * @param int $key
420 | * @param mixed $value
421 | */
422 | public function setOption($key, $value)
423 | {
424 | $this->options[$key] = $value;
425 |
426 | return $this;
427 | }
428 |
429 | /**
430 | * Set the cURL options for the request.
431 | *
432 | * @param array $options
433 | */
434 | public function setOptions(array $options)
435 | {
436 | $this->options = $options;
437 |
438 | return $this;
439 | }
440 |
441 | /**
442 | * Get a specific curl option from the request.
443 | *
444 | * @param int $key
445 | *
446 | * @return mixed
447 | */
448 | public function getOption($key)
449 | {
450 | return isset($this->options[$key]) ? $this->options[$key] : null;
451 | }
452 |
453 | /**
454 | * Get the cURL options for the request.
455 | *
456 | * @return array
457 | */
458 | public function getOptions()
459 | {
460 | return $this->options;
461 | }
462 |
463 | /**
464 | * Set the HTTP basic username and password.
465 | *
466 | * @param string $user
467 | * @param string $pass
468 | *
469 | * @return string
470 | */
471 | public function auth($user, $pass)
472 | {
473 | $this->user = $user;
474 | $this->pass = $pass;
475 |
476 | return $this;
477 | }
478 |
479 | /**
480 | * Set an username to authenticate the request of curl.
481 | *
482 | * @param string $user
483 | *
484 | * @return static
485 | */
486 | public function setUser($user)
487 | {
488 | $this->user = $user;
489 |
490 | return $this;
491 | }
492 |
493 | /**
494 | * Set a password to authenticate the request of curl.
495 | *
496 | * @param string $pass
497 | *
498 | * @return static
499 | */
500 | public function setPass($pass)
501 | {
502 | $this->pass = $pass;
503 |
504 | return $this;
505 | }
506 |
507 | /**
508 | * If username and password is set, returns a string of 'username:password'.
509 | * If not, returns null.
510 | *
511 | * @return string|null
512 | */
513 | public function getUserAndPass()
514 | {
515 | if ($this->user) {
516 | return $this->user . ':' . $this->pass;
517 | }
518 |
519 | return null;
520 | }
521 |
522 | /**
523 | * Whether the request is JSON or not.
524 | *
525 | * @return boolean
526 | */
527 | public function isJson()
528 | {
529 | return $this->encoding === static::ENCODING_JSON;
530 | }
531 |
532 | /**
533 | * Send the request.
534 | *
535 | * @return Response
536 | */
537 | public function send()
538 | {
539 | return $this->curl->sendRequest($this);
540 | }
541 | }
542 |
--------------------------------------------------------------------------------
/src/Response.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://opensource.org/licenses/MIT
7 | * @package PHP cURL
8 | */
9 |
10 | namespace anlutro\cURL;
11 |
12 | /**
13 | * cURL response representation class.
14 | */
15 | class Response
16 | {
17 | /**
18 | * The response headers.
19 | *
20 | * @var array
21 | */
22 | public $headers = array();
23 |
24 | /**
25 | * The response body.
26 | *
27 | * @var string|null
28 | */
29 | public $body;
30 |
31 | /**
32 | * The results of curl_getinfo on the response request.
33 | *
34 | * @var array|false
35 | */
36 | public $info;
37 |
38 | /**
39 | * The response code including text, e.g. '200 OK'.
40 | *
41 | * @var string
42 | */
43 | public $statusText;
44 |
45 | /**
46 | * The response code.
47 | *
48 | * @var int
49 | */
50 | public $statusCode;
51 |
52 | /**
53 | * @param string|null $body
54 | * @param string $headers
55 | * @param mixed $info
56 | */
57 | public function __construct($body, $headers, $info = array())
58 | {
59 | $this->body = $body;
60 | $this->info = $info;
61 | $this->parseHeader($headers);
62 | }
63 |
64 | /**
65 | * Parse a header string.
66 | *
67 | * @param string $header
68 | *
69 | * @return void
70 | */
71 | protected function parseHeader($header)
72 | {
73 | if ($header === "") {
74 | throw new \UnexpectedValueException('Empty header string passed!');
75 | }
76 | $headers = explode("\r\n", trim($header));
77 | $this->parseHeaders($headers);
78 | }
79 |
80 | /**
81 | * Parse an array of headers.
82 | *
83 | * @param array $headers
84 | *
85 | * @return void
86 | */
87 | protected function parseHeaders(array $headers)
88 | {
89 | if (count($headers) === 0) {
90 | throw new \UnexpectedValueException('No headers passed!');
91 | }
92 |
93 | $this->headers = array();
94 |
95 | // find and set the HTTP status code and reason
96 | $firstHeader = array_shift($headers);
97 | if (!preg_match('/^HTTP\/\d(\.\d)? [0-9]{3}/', $firstHeader)) {
98 | throw new \UnexpectedValueException('Invalid response header');
99 | }
100 | list(, $status) = explode(' ', $firstHeader, 2);
101 | $code = explode(' ', $status);
102 | $code = (int) $code[0];
103 |
104 | // special handling for HTTP 100 responses
105 | if ($code === 100) {
106 | // remove empty header lines between 100 and actual HTTP status
107 | foreach ($headers as $idx => $header) {
108 | if ($header) {
109 | break;
110 | }
111 | }
112 |
113 | // start the process over with the 100 continue header stripped away
114 | return $this->parseHeaders(array_slice($headers, $idx));
115 | }
116 |
117 | // handle cases where CURLOPT_HTTPAUTH is being used, in which case
118 | // curl_exec may cause two HTTP responses
119 | if (
120 | array_key_exists(CURLINFO_HTTPAUTH_AVAIL, $this->info) &&
121 | $this->info[CURLINFO_HTTPAUTH_AVAIL] > 0 &&
122 | $code === 401
123 | ) {
124 | $foundAuthenticateHeader = false;
125 | $foundSecondHttpResponse = false;
126 | foreach ($headers as $idx => $header) {
127 | if ($foundAuthenticateHeader === false && strpos(strtolower($header), 'www-authenticate:') === 0) {
128 | $foundAuthenticateHeader = true;
129 | }
130 | if ($foundAuthenticateHeader && preg_match('/^HTTP\/\d(\.\d)? [0-9]{3}/', $header)) {
131 | $foundSecondHttpResponse = true;
132 | break;
133 | }
134 | }
135 |
136 | // discard the original response.
137 | if ($foundAuthenticateHeader && $foundSecondHttpResponse) {
138 | $headers = array_slice($headers, $idx);
139 | return $this->parseHeaders($headers);
140 | }
141 | }
142 |
143 | $this->statusText = $status;
144 | $this->statusCode = $code;
145 |
146 | foreach ($headers as $header) {
147 | // skip empty lines
148 | if (!$header) {
149 | continue;
150 | }
151 |
152 | $delimiter = strpos($header, ':');
153 | if (!$delimiter) {
154 | continue;
155 | }
156 |
157 | $key = trim(strtolower(substr($header, 0, $delimiter)));
158 | $val = ltrim(substr($header, $delimiter + 1));
159 |
160 | if (isset($this->headers[$key])) {
161 | if (is_array($this->headers[$key])) {
162 | $this->headers[$key][] = $val;
163 | } else {
164 | $this->headers[$key] = array($this->headers[$key], $val);
165 | }
166 | } else {
167 | $this->headers[$key] = $val;
168 | }
169 | }
170 | }
171 |
172 | /**
173 | * Get a specific header from the response.
174 | *
175 | * @param string $key
176 | *
177 | * @return mixed
178 | */
179 | public function getHeader($key)
180 | {
181 | $key = strtolower($key);
182 |
183 | return array_key_exists($key, $this->headers) ?
184 | $this->headers[$key] : null;
185 | }
186 |
187 | /**
188 | * Gets all the headers of the response.
189 | *
190 | * @return array
191 | */
192 | public function getHeaders()
193 | {
194 | return $this->headers;
195 | }
196 |
197 | /**
198 | * Get the response body.
199 | *
200 | * @return string
201 | */
202 | public function getBody()
203 | {
204 | // usually because CURLOPT_FILE is set
205 | if ($this->body === null) {
206 | throw new \UnexpectedValueException("Response has no body!");
207 | }
208 |
209 | return $this->body;
210 | }
211 |
212 | /**
213 | * Convert the response instance to an array.
214 | *
215 | * @return array
216 | */
217 | public function toArray()
218 | {
219 | return array(
220 | 'headers' => $this->headers,
221 | 'body' => $this->body,
222 | 'info' => $this->info
223 | );
224 | }
225 |
226 | /**
227 | * Convert the response object to a JSON string.
228 | *
229 | * @return string
230 | */
231 | public function toJson()
232 | {
233 | return json_encode($this->toArray());
234 | }
235 |
236 | /**
237 | * Convert the object to its string representation by returning the body.
238 | *
239 | * @return string
240 | */
241 | public function __toString()
242 | {
243 | return (string) $this->getBody();
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/src/cURL.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://opensource.org/licenses/MIT
7 | * @package PHP cURL
8 | */
9 |
10 | namespace anlutro\cURL;
11 |
12 | /**
13 | * cURL wrapper class.
14 | *
15 | * @method Response get(string $url) Execute a GET request
16 | * @method Response delete(string $url) Execute a DELETE request
17 | * @method Response head(string $url) Execute a HEAD request
18 | * @method Response post(string $url, mixed $data) Execute a POST request
19 | * @method Response put(string $url, mixed $data) Execute a PUT request
20 | * @method Response patch(string $url, mixed $data) Execute a PATCH request
21 | * @method Response jsonGet(string $url) Execute a JSON GET request
22 | * @method Response jsonDelete(string $url) Execute a JSON DELETE request
23 | * @method Response jsonHead(string $url) Execute a JSON HEAD request
24 | * @method Response jsonPost(string $url, mixed $data) Execute a JSON POST request
25 | * @method Response jsonPut(string $url, mixed $data) Execute a JSON PUT request
26 | * @method Response jsonPatch(string $url, mixed $data) Execute a JSON PATCH request
27 | * @method Response rawGet(string $url) Execute a raw GET request
28 | * @method Response rawDelete(string $url) Execute a raw DELETE request
29 | * @method Response rawHead(string $url) Execute a raw HEAD request
30 | * @method Response rawPost(string $url, mixed $data) Execute a raw POST request
31 | * @method Response rawPut(string $url, mixed $data) Execute a raw PUT request
32 | * @method Response rawPatch(string $url, mixed $data) Execute a raw PATCH request
33 | */
34 | class cURL
35 | {
36 | /**
37 | * The cURL resource.
38 | */
39 | protected $ch;
40 |
41 | /**
42 | * The request class to use.
43 | *
44 | * @var string
45 | */
46 | protected $requestClass = 'anlutro\cURL\Request';
47 |
48 | /**
49 | * The response class to use.
50 | *
51 | * @var string
52 | */
53 | protected $responseClass = 'anlutro\cURL\Response';
54 |
55 | /**
56 | * The default headers.
57 | *
58 | * @var array
59 | */
60 | protected $defaultHeaders = array();
61 |
62 | /**
63 | * The default curl options.
64 | *
65 | * @var array
66 | */
67 | protected $defaultOptions = array();
68 |
69 | /**
70 | * Get allowed methods.
71 | *
72 | * @return array
73 | */
74 | public function getAllowedMethods()
75 | {
76 | return Request::$methods;
77 | }
78 |
79 | /**
80 | * Set the request class.
81 | *
82 | * @param string $class
83 | */
84 | public function setRequestClass($class)
85 | {
86 | $this->requestClass = $class;
87 | }
88 |
89 | /**
90 | * Set the response class.
91 | *
92 | * @param string $class
93 | */
94 | public function setResponseClass($class)
95 | {
96 | $this->responseClass = $class;
97 | }
98 |
99 | /**
100 | * Set the default headers for every request.
101 | *
102 | * @param array $headers
103 | */
104 | public function setDefaultHeaders(array $headers)
105 | {
106 | $this->defaultHeaders = $headers;
107 | }
108 |
109 | /**
110 | * Get the default headers.
111 | *
112 | * @return array
113 | */
114 | public function getDefaultHeaders()
115 | {
116 | return $this->defaultHeaders;
117 | }
118 |
119 | /**
120 | * Set the default curl options for every request.
121 | *
122 | * @param array $options
123 | */
124 | public function setDefaultOptions(array $options)
125 | {
126 | $this->defaultOptions = $options;
127 | }
128 |
129 | /**
130 | * Get the default options.
131 | *
132 | * @return array
133 | */
134 | public function getDefaultOptions()
135 | {
136 | return $this->defaultOptions;
137 | }
138 |
139 | /**
140 | * Build an URL with an optional query string.
141 | *
142 | * @param string $url the base URL without any query string
143 | * @param array $query array of GET parameters
144 | *
145 | * @return string
146 | */
147 | public function buildUrl($url, array $query)
148 | {
149 | if (empty($query)) {
150 | return $url;
151 | }
152 |
153 | $parts = parse_url($url);
154 |
155 | $queryString = '';
156 | if (isset($parts['query']) && $parts['query']) {
157 | $queryString .= $parts['query'].'&'.http_build_query($query);
158 | } else {
159 | $queryString .= http_build_query($query);
160 | }
161 |
162 | $retUrl = $parts['scheme'].'://'.$parts['host'];
163 | if (isset($parts['port'])) {
164 | $retUrl .= ':'.$parts['port'];
165 | }
166 |
167 | if (isset($parts['path'])) {
168 | $retUrl .= $parts['path'];
169 | }
170 |
171 | if ($queryString) {
172 | $retUrl .= '?' . $queryString;
173 | }
174 |
175 | return $retUrl;
176 | }
177 |
178 | /**
179 | * Create a new response object and set its values.
180 | *
181 | * @param string $method get, post, etc
182 | * @param string $url
183 | * @param mixed $data POST data
184 | * @param int $encoding Request::ENCODING_* constant specifying how to process the POST data
185 | *
186 | * @return Request
187 | */
188 | public function newRequest($method, $url, $data = array(), $encoding = Request::ENCODING_QUERY)
189 | {
190 | $class = $this->requestClass;
191 | $request = new $class($this);
192 |
193 | if ($this->defaultHeaders) {
194 | $request->setHeaders($this->defaultHeaders);
195 | }
196 | if ($this->defaultOptions) {
197 | $request->setOptions($this->defaultOptions);
198 | }
199 | $request->setMethod($method);
200 | $request->setUrl($url);
201 | $request->setData($data);
202 | $request->setEncoding($encoding);
203 |
204 | return $request;
205 | }
206 |
207 | /**
208 | * Create a new JSON request and set its values.
209 | *
210 | * @param string $method get, post etc
211 | * @param string $url
212 | * @param mixed $data POST data
213 | *
214 | * @return Request
215 | */
216 | public function newJsonRequest($method, $url, $data = array())
217 | {
218 | return $this->newRequest($method, $url, $data, Request::ENCODING_JSON);
219 | }
220 |
221 | /**
222 | * Create a new raw request and set its values.
223 | *
224 | * @param string $method get, post etc
225 | * @param string $url
226 | * @param mixed $data request body
227 | *
228 | * @return Request
229 | */
230 | public function newRawRequest($method, $url, $data = '')
231 | {
232 | return $this->newRequest($method, $url, $data, Request::ENCODING_RAW);
233 | }
234 |
235 | /**
236 | * Prepare the curl resource for sending a request.
237 | *
238 | * @param Request $request
239 | *
240 | * @return void
241 | */
242 | public function prepareRequest(Request $request)
243 | {
244 | $this->ch = curl_init();
245 | curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
246 | curl_setopt($this->ch, CURLOPT_HEADER, true);
247 | if ($auth = $request->getUserAndPass()) {
248 | curl_setopt($this->ch, CURLOPT_USERPWD, $auth);
249 | }
250 | curl_setopt($this->ch, CURLOPT_URL, $request->getUrl());
251 |
252 | $options = $request->getOptions();
253 | if (!empty($options)) {
254 | curl_setopt_array($this->ch, $options);
255 | }
256 |
257 | $method = $request->getMethod();
258 | curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, strtoupper($method));
259 |
260 | curl_setopt($this->ch, CURLOPT_HTTPHEADER, $request->formatHeaders());
261 |
262 | if ($request->hasData()) {
263 | curl_setopt($this->ch, CURLOPT_POSTFIELDS, $request->encodeData());
264 | }
265 |
266 | if ($method === 'head') {
267 | curl_setopt($this->ch, CURLOPT_NOBODY, true);
268 | }
269 | }
270 |
271 | /**
272 | * Send a request.
273 | *
274 | * @param Request $request
275 | *
276 | * @return Response
277 | */
278 | public function sendRequest(Request $request)
279 | {
280 | $this->prepareRequest($request);
281 |
282 | $result = curl_exec($this->ch);
283 |
284 | if ($result === false) {
285 | $errno = curl_errno($this->ch);
286 | $errmsg = curl_error($this->ch);
287 | $msg = "cURL request failed with error [$errno]: $errmsg";
288 | curl_close($this->ch);
289 | throw new cURLException($request, $msg, $errno);
290 | }
291 |
292 | $response = $this->createResponseObject($result, $request);
293 |
294 | curl_close($this->ch);
295 |
296 | return $response;
297 | }
298 |
299 | /**
300 | * Extract the response info, header and body from a cURL response. Saves
301 | * the data in variables stored on the object.
302 | *
303 | * @param string $response
304 | * @param Request $request
305 | *
306 | * @return Response
307 | */
308 | protected function createResponseObject($response, Request $request)
309 | {
310 | $info = curl_getinfo($this->ch);
311 | $headerSize = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE);
312 | // needed for the Response class to know that it may have to parse 2 HTTP responses
313 | $info[CURLINFO_HTTPAUTH_AVAIL] = curl_getinfo($this->ch, CURLINFO_HTTPAUTH_AVAIL);
314 |
315 | if ($file = $request->getOption(CURLOPT_FILE)) {
316 | // file may be opened write-only, and even when it isn't,
317 | // seeking/reading seems to be buggy
318 | $fileMeta = stream_get_meta_data($file);
319 | $file = fopen($fileMeta['uri'], 'r');
320 | $headers = fread($file, $headerSize);
321 | fclose($file);
322 | $body = null;
323 | } else {
324 | $headers = substr($response, 0, $headerSize);
325 | $body = substr($response, $headerSize);
326 | }
327 |
328 | $class = $this->responseClass;
329 |
330 | return new $class($body, $headers, $info);
331 | }
332 |
333 | /**
334 | * Handle dynamic calls to the class.
335 | *
336 | * @param string $func
337 | * @param array $args
338 | *
339 | * @return mixed
340 | */
341 | public function __call($func, $args)
342 | {
343 | $method = strtolower($func);
344 |
345 | $encoding = Request::ENCODING_QUERY;
346 |
347 | if (substr($method, 0, 4) === 'json') {
348 | $encoding = Request::ENCODING_JSON;
349 | $method = substr($method, 4);
350 | } elseif (substr($method, 0, 3) === 'raw') {
351 | $encoding = Request::ENCODING_RAW;
352 | $method = substr($method, 3);
353 | }
354 |
355 | if (!array_key_exists($method, Request::$methods)) {
356 | throw new \BadMethodCallException("Method [$method] not a valid HTTP method.");
357 | }
358 |
359 | if (!isset($args[0])) {
360 | throw new \BadMethodCallException('Missing argument 1 ($url) for '.__CLASS__.'::'.$func);
361 | }
362 | $url = $args[0];
363 |
364 | if (isset($args[1])) {
365 | $data = $args[1];
366 | } else {
367 | $data = null;
368 | }
369 |
370 | $request = $this->newRequest($method, $url, $data, $encoding);
371 |
372 | return $this->sendRequest($request);
373 | }
374 | }
375 |
--------------------------------------------------------------------------------
/src/cURLException.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://opensource.org/licenses/MIT
7 | * @package PHP cURL
8 | */
9 |
10 | namespace anlutro\cURL;
11 |
12 | use Exception;
13 | use RuntimeException;
14 |
15 | class cURLException extends RuntimeException
16 | {
17 | /**
18 | * The request that triggered the exception.
19 | *
20 | * @var Request
21 | */
22 | protected $request;
23 |
24 | /**
25 | * Constructor.
26 | *
27 | * @param Request|null $request
28 | * @param string $message
29 | * @param integer $code
30 | */
31 | public function __construct(Request $request, $message = "", $code = 0)
32 | {
33 | parent::__construct($message, $code);
34 | $this->request = $request;
35 | }
36 |
37 | /**
38 | * Get the request that triggered the exception.
39 | *
40 | * @return Request
41 | */
42 | public function getRequest()
43 | {
44 | return $this->request;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/functional/cURLTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('The web server is not running.');
14 | }
15 | if (!extension_loaded('curl')) {
16 | $this->markTestSkipped('The curl extension is not installed.');
17 | }
18 | }
19 |
20 | private function makeCurl()
21 | {
22 | return new anlutro\cURL\cURL;
23 | }
24 |
25 | /** @test */
26 | public function successfulResponse()
27 | {
28 | $r = $this->makeCurl()->get(static::URL.'/success.php');
29 | $this->assertEquals(200, $r->statusCode);
30 | $this->assertEquals('200 OK', $r->statusText);
31 | $this->assertEquals('OK', $r->body);
32 | $this->assertNotNull($r->headers);
33 | $this->assertNotNull($r->info);
34 | }
35 |
36 | /** @test */
37 | public function failedResponse()
38 | {
39 | $r = $this->makeCurl()->get(static::URL.'/failure.php');
40 | $this->assertEquals(500, $r->statusCode);
41 | $this->assertEquals('500 Internal Server Error', $r->statusText);
42 | $this->assertEquals('Failure', $r->body);
43 | $this->assertNotNull($r->headers);
44 | $this->assertNotNull($r->info);
45 | }
46 |
47 | /** @test */
48 | public function queryRequestBody()
49 | {
50 | $r = $this->makeCurl()->post(static::URL.'/echo.php', array('foo' => 'bar'));
51 | $this->assertEquals('foo=bar', $r->body);
52 | }
53 |
54 | /** @test */
55 | public function queryRequestEmptyArrayBody()
56 | {
57 | $r = $this->makeCurl()->post(static::URL.'/echo.php', array());
58 | $this->assertEquals('', $r->body);
59 | }
60 |
61 | /** @test */
62 | public function queryRequestEmptyObjectBody()
63 | {
64 | $r = $this->makeCurl()->post(static::URL.'/echo.php', new \stdClass());
65 | $this->assertEquals('', $r->body);
66 | }
67 |
68 | /** @test */
69 | public function jsonRequestBody()
70 | {
71 | $r = $this->makeCurl()->jsonPost(static::URL.'/echo.php', array('foo' => 'bar'));
72 | $this->assertEquals('{"foo":"bar"}', $r->body);
73 | }
74 |
75 | /** @test */
76 | public function jsonRequestEmptyArrayBody()
77 | {
78 | $r = $this->makeCurl()->jsonPost(static::URL.'/echo.php', array());
79 | $this->assertEquals('[]', $r->body);
80 | }
81 |
82 | /** @test */
83 | public function jsonRequestEmptyObjectBody()
84 | {
85 | $r = $this->makeCurl()->jsonPost(static::URL.'/echo.php', new \stdClass());
86 | $this->assertEquals('{}', $r->body);
87 | }
88 |
89 | /** @test */
90 | public function rawRequestBody()
91 | {
92 | $r = $this->makeCurl()->rawPost(static::URL.'/echo.php', '');
93 | $this->assertEquals('', $r->body);
94 | }
95 |
96 | /** @test */
97 | public function rawRequestEmptyBody()
98 | {
99 | $r = $this->makeCurl()->rawPost(static::URL.'/echo.php', '');
100 | $this->assertEquals('', $r->body);
101 | }
102 |
103 | /** @test */
104 | public function fileUpload()
105 | {
106 | $file = __FILE__;
107 | if (function_exists('curl_file_create')) {
108 | $data = array('file' => curl_file_create($file));
109 | } else {
110 | $data = array('file' => '@'.$file);
111 | }
112 |
113 | $r = $this->makeCurl()->rawPost(static::URL.'/upload.php', $data);
114 | $this->assertEquals(basename($file)."\t".filesize($file)."\n", $r->body);
115 | }
116 |
117 | /** @test */
118 | public function digestAuth()
119 | {
120 | $curl = $this->makeCurl();
121 | $request = $curl->newRequest('get', static::URL . '/digest-auth.php');
122 | $request->auth('guest', 'guest');
123 | $request->setOption(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
124 | $response = $curl->sendRequest($request);
125 | $this->assertEquals(200, $response->statusCode);
126 | }
127 |
128 | /** @test */
129 | public function throwsExceptionOnCurlError()
130 | {
131 | $this->setExpectedException('anlutro\cURL\cURLException', 'cURL request failed with error [7]:');
132 | $this->makeCurl()->get('http://0.0.0.0');
133 | }
134 |
135 | /** @test */
136 | public function throwsExceptionWithMissingUrl()
137 | {
138 | $this->setExpectedException('BadMethodCallException', 'Missing argument 1 ($url) for anlutro\cURL\cURL::get');
139 | $this->makeCurl()->get();
140 | }
141 |
142 | /** @test */
143 | public function throwsExceptionWhenDataProvidedButNotAllowed()
144 | {
145 | $this->setExpectedException('InvalidArgumentException', 'HTTP method [options] does not allow POST data.');
146 | $this->makeCurl()->options('http://localhost', array('foo' => 'bar'));
147 | }
148 |
149 | /** @test */
150 | public function defaultHeadersAreAdded()
151 | {
152 | $curl = $this->makeCurl();
153 | $curl->setDefaultHeaders(array('foo' => 'bar'));
154 | $request = $curl->newRequest('post', 'does-not-matter');
155 | $this->assertEquals('bar', $request->getHeader('foo'));
156 | }
157 |
158 | /** @test */
159 | public function defaultOptionsAreAdded()
160 | {
161 | $curl = $this->makeCurl();
162 | $curl->setDefaultOptions(array('foo' => 'bar'));
163 | $request = $curl->newRequest('post', 'does-not-matter');
164 | $this->assertEquals('bar', $request->getOption('foo'));
165 | }
166 |
167 | /** @test */
168 | public function curloptFileWorks()
169 | {
170 | $r = $this->makeCurl()
171 | ->newRequest('get', static::URL.'/success.php')
172 | ->setOption(CURLOPT_FILE, $fh = tmpfile())
173 | ->send();
174 | $this->assertEquals(200, $r->statusCode);
175 | $this->assertEquals('200 OK', $r->statusText);
176 | $this->assertNotNull($r->headers);
177 | $this->assertNotNull($r->info);
178 | $this->assertNull($r->body);
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/tests/server/digest-auth.php:
--------------------------------------------------------------------------------
1 | password
7 | $users = array('admin' => 'mypass', 'guest' => 'guest');
8 |
9 |
10 | if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
11 | header('HTTP/1.1 401 Unauthorized');
12 | header('WWW-Authenticate: Digest realm="'.$realm.
13 | '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
14 |
15 | die('Text to send if user hits Cancel button');
16 | }
17 |
18 |
19 | // analyze the PHP_AUTH_DIGEST variable
20 | if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||
21 | !isset($users[$data['username']]))
22 | die('Wrong Credentials!');
23 |
24 |
25 | // generate the valid response
26 | $A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
27 | $A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
28 | $valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
29 |
30 | if ($data['response'] != $valid_response)
31 | die('Wrong Credentials!');
32 |
33 | // ok, valid username & password
34 | echo 'You are logged in as: ' . $data['username'];
35 |
36 |
37 | // function to parse the http auth header
38 | function http_digest_parse($txt)
39 | {
40 | // protect against missing data
41 | $needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
42 | $data = array();
43 | $keys = implode('|', array_keys($needed_parts));
44 |
45 | preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER);
46 |
47 | foreach ($matches as $m) {
48 | $data[$m[1]] = $m[3] ? $m[3] : $m[4];
49 | unset($needed_parts[$m[1]]);
50 | }
51 |
52 | return $needed_parts ? false : $data;
53 | }
54 |
--------------------------------------------------------------------------------
/tests/server/echo.php:
--------------------------------------------------------------------------------
1 | makeRequest();
16 |
17 | $r->setUrl('foo');
18 | $this->assertEquals('foo', $r->getUrl());
19 |
20 | $r->setMethod('post');
21 | $this->assertEquals('post', $r->getMethod());
22 |
23 | $r->setData(array('foo' => 'bar'));
24 | $this->assertEquals(array('foo' => 'bar'), $r->getData());
25 |
26 | $r->setOptions(array('bar' => 'baz'));
27 | $this->assertEquals(array('bar' => 'baz'), $r->getOptions());
28 |
29 | $r->setHeaders(array('baz' => 'foo'));
30 | $this->assertEquals(array('baz' => 'foo'), $r->getHeaders());
31 |
32 | $r->setHeader('bar', 'baz');
33 | $this->assertEquals(array('baz' => 'foo', 'bar' => 'baz'), $r->getHeaders());
34 | }
35 |
36 | /** @test */
37 | public function encodeData()
38 | {
39 | $r = $this->makeRequest();
40 | $r->setMethod('post');
41 |
42 | $r->setData(array('foo' => 'bar', 'bar' => 'baz'));
43 | $this->assertEquals('foo=bar&bar=baz', $r->encodeData());
44 |
45 | $r->setEncoding(Request::ENCODING_JSON);
46 | $this->assertEquals('{"foo":"bar","bar":"baz"}', $r->encodeData());
47 |
48 | $r->setEncoding(Request::ENCODING_RAW);
49 | $r->setData('ArbitraryValue');
50 | $this->assertEquals('ArbitraryValue', $r->encodeData());
51 | }
52 |
53 | /** @test */
54 | public function formatHeaders()
55 | {
56 | $r = $this->makeRequest();
57 |
58 | $r->setHeaders(array('foo' => 'bar', 'bar' => 'baz'));
59 | $this->assertEquals(array('foo: bar', 'bar: baz'), $r->formatHeaders());
60 |
61 | $r->setHeaders(array('foo: bar', 'bar: baz'));
62 | $this->assertEquals(array('foo: bar', 'bar: baz'), $r->formatHeaders());
63 | }
64 |
65 | /** @test */
66 | public function headersAreCaseInsensitive()
67 | {
68 | $r = $this->makeRequest();
69 |
70 | $r->setHeader('foo', 'bar');
71 | $r->setHeader('Foo', 'bar');
72 | $r->setHeader('FOO', 'bar');
73 | $this->assertEquals(array('foo' => 'bar'), $r->getHeaders());
74 | }
75 |
76 | /**
77 | * @test
78 | * @expectedException InvalidArgumentException
79 | */
80 | public function invalidMethod()
81 | {
82 | $this->makeRequest()->setMethod('foo');
83 | }
84 |
85 | /**
86 | * @test
87 | * @expectedException InvalidArgumentException
88 | */
89 | public function invalidEncoding()
90 | {
91 | $this->makeRequest()->setEncoding(999);
92 | }
93 |
94 | /** @test */
95 | public function userAndPass()
96 | {
97 | $r = $this->makeRequest();
98 | $this->assertEquals(null, $r->getUserAndPass());
99 | $r->setUser('foo');
100 | $this->assertEquals('foo:', $r->getUserAndPass());
101 | $r->setPass('bar');
102 | $this->assertEquals('foo:bar', $r->getUserAndPass());
103 | }
104 |
105 | /** @test */
106 | public function cookies()
107 | {
108 | $r = $this->makeRequest();
109 |
110 | $this->assertEquals(array(), $r->getCookies());
111 |
112 | $r->setCookie('foo', 'bar');
113 | $this->assertEquals(array('foo' => 'bar'), $r->getCookies());
114 | $this->assertEquals('bar', $r->getCookie('foo'));
115 | $this->assertEquals('foo=bar', $r->getHeader('cookie'));
116 |
117 | $r->setCookie('bar', 'baz');
118 | $this->assertEquals(array('foo' => 'bar', 'bar' => 'baz'), $r->getCookies());
119 | $this->assertEquals('baz', $r->getCookie('bar'));
120 | $this->assertEquals('foo=bar; bar=baz', $r->getHeader('cookie'));
121 |
122 | $r->setCookies(array('baz' => 'foo'));
123 | $this->assertEquals(array('baz' => 'foo'), $r->getCookies());
124 | $this->assertEquals('foo', $r->getCookie('baz'));
125 | $this->assertEquals('baz=foo', $r->getHeader('cookie'));
126 | }
127 |
128 | /** @test */
129 | public function emptyJsonGetRequestHasNoData()
130 | {
131 | $r = $this->makeRequest();
132 | $r->setEncoding(Request::ENCODING_JSON);
133 | $r->setMethod('get');
134 |
135 | $this->assertFalse($r->hasData());
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/tests/unit/ResponseTest.php:
--------------------------------------------------------------------------------
1 | makeResponse('', 'HTTP/1.1 200 OK');
16 | $this->assertEquals(200, $r->statusCode);
17 | $this->assertEquals('200 OK', $r->statusText);
18 |
19 | $r = $this->makeResponse('', 'HTTP/1.1 302 Found');
20 | $this->assertEquals(302, $r->statusCode);
21 | $this->assertEquals('302 Found', $r->statusText);
22 | }
23 |
24 | /** @test */
25 | public function parsesHttp2ResponseCorrectly()
26 | {
27 | $r = $this->makeResponse('', 'HTTP/2 200');
28 | $this->assertEquals(200, $r->statusCode);
29 | $this->assertEquals('200', $r->statusText);
30 | }
31 |
32 | /** @test */
33 | public function parsesHeaderStringCorrectly()
34 | {
35 | $header = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 0";
36 | $r = $this->makeResponse('', $header);
37 | $this->assertEquals('text/plain', $r->getHeader('content-type'));
38 | $this->assertEquals('0', $r->getHeader('content-length'));
39 | $this->assertEquals(null, $r->getHeader('x-nonexistant'));
40 | }
41 |
42 | /** @test */
43 | public function duplicateHeadersAreHandled()
44 | {
45 | $header = "HTTP/1.1 200 OK\r\nX-Var: A\r\nX-Var: B\r\nX-Var: C";
46 | $r = $this->makeResponse('', $header);
47 | $this->assertEquals(array('A', 'B', 'C'), $r->getHeader('X-Var'));
48 | }
49 |
50 | /** @test */
51 | public function httpContinueResponsesAreHandled()
52 | {
53 | $header = "HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\nx-var: foo";
54 | $r = $this->makeResponse('', $header);
55 | $this->assertEquals(200, $r->statusCode);
56 | $this->assertEquals('foo', $r->getHeader('x-var'));
57 | }
58 |
59 | /** @test */
60 | public function throwsExceptionIfHeaderDoesntStartWithHttpStatus()
61 | {
62 | $this->setExpectedException('UnexpectedValueException', 'Invalid response header');
63 | $this->makeResponse('', 'x-var: foo');
64 | }
65 |
66 | /** @test */
67 | public function httpUnauthorizedResponsesContainingMultipleStatusesAreHandled()
68 | {
69 | $header = "HTTP/1.1 401 Unauthorized\r\nwww-authenticate: digest something\r\n\r\nHTTP/1.1 200 OK\r\nx-var: foo";
70 | $r = $this->makeResponse('', $header, [CURLINFO_HTTPAUTH_AVAIL => CURLAUTH_DIGEST]);
71 | $this->assertEquals(200, $r->statusCode);
72 | $this->assertEquals('foo', $r->getHeader('x-var'));
73 | }
74 | }
75 |
--------------------------------------------------------------------------------