1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
14: namespace Cake\Http\Client;
15:
16:
17:
18: use Cake\Http\Cookie\CookieCollection as CookiesCollection;
19: use Cake\Http\Cookie\CookieInterface;
20: use Psr\Http\Message\ResponseInterface;
21: use RuntimeException;
22: use Zend\Diactoros\MessageTrait;
23: use Zend\Diactoros\Stream;
24:
25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82:
83: class Response extends Message implements ResponseInterface
84: {
85: use MessageTrait;
86:
87: 88: 89: 90: 91:
92: protected $code;
93:
94: 95: 96: 97: 98:
99: protected $cookies;
100:
101: 102: 103: 104: 105:
106: protected $reasonPhrase;
107:
108: 109: 110: 111: 112:
113: protected $_xml;
114:
115: 116: 117: 118: 119:
120: protected $_json;
121:
122: 123: 124: 125: 126:
127: protected $_exposedProperties = [
128: 'cookies' => '_getCookies',
129: 'body' => '_getBody',
130: 'code' => 'code',
131: 'json' => '_getJson',
132: 'xml' => '_getXml',
133: 'headers' => '_getHeaders',
134: ];
135:
136: 137: 138: 139: 140: 141:
142: protected $_deprecatedMagicProperties = [
143: 'cookies' => 'getCookies()',
144: 'body' => 'getStringBody()',
145: 'json' => 'getJson()',
146: 'xml' => 'getXml()',
147: 'headers' => 'getHeaders()',
148: ];
149:
150: 151: 152: 153: 154: 155:
156: public function __construct($headers = [], $body = '')
157: {
158: $this->_parseHeaders($headers);
159: if ($this->getHeaderLine('Content-Encoding') === 'gzip') {
160: $body = $this->_decodeGzipBody($body);
161: }
162: $stream = new Stream('php://memory', 'wb+');
163: $stream->write($body);
164: $stream->rewind();
165: $this->stream = $stream;
166: }
167:
168: 169: 170: 171: 172: 173: 174: 175: 176: 177:
178: protected function _decodeGzipBody($body)
179: {
180: if (!function_exists('gzinflate')) {
181: throw new RuntimeException('Cannot decompress gzip response body without gzinflate()');
182: }
183: $offset = 0;
184:
185: if (substr($body, 0, 2) === "\x1f\x8b") {
186: $offset = 2;
187: }
188:
189: if (substr($body, $offset, 1) === "\x08") {
190: return gzinflate(substr($body, $offset + 8));
191: }
192: }
193:
194: 195: 196: 197: 198: 199: 200: 201: 202:
203: protected function _parseHeaders($headers)
204: {
205: foreach ($headers as $key => $value) {
206: if (substr($value, 0, 5) === 'HTTP/') {
207: preg_match('/HTTP\/([\d.]+) ([0-9]+)(.*)/i', $value, $matches);
208: $this->protocol = $matches[1];
209: $this->code = (int)$matches[2];
210: $this->reasonPhrase = trim($matches[3]);
211: continue;
212: }
213: if (strpos($value, ':') === false) {
214: continue;
215: }
216: list($name, $value) = explode(':', $value, 2);
217: $value = trim($value);
218: $name = trim($name);
219:
220: $normalized = strtolower($name);
221:
222: if (isset($this->headers[$name])) {
223: $this->headers[$name][] = $value;
224: } else {
225: $this->headers[$name] = (array)$value;
226: $this->headerNames[$normalized] = $name;
227: }
228: }
229: }
230:
231: 232: 233: 234: 235:
236: public function isOk()
237: {
238: $codes = [
239: static::STATUS_OK,
240: static::STATUS_CREATED,
241: static::STATUS_ACCEPTED,
242: static::STATUS_NON_AUTHORITATIVE_INFORMATION,
243: static::STATUS_NO_CONTENT
244: ];
245:
246: return in_array($this->code, $codes);
247: }
248:
249: 250: 251: 252: 253:
254: public function isRedirect()
255: {
256: $codes = [
257: static::STATUS_MOVED_PERMANENTLY,
258: static::STATUS_FOUND,
259: static::STATUS_SEE_OTHER,
260: static::STATUS_TEMPORARY_REDIRECT,
261: ];
262:
263: return (
264: in_array($this->code, $codes) &&
265: $this->getHeaderLine('Location')
266: );
267: }
268:
269: 270: 271: 272: 273: 274:
275: public function statusCode()
276: {
277: deprecationWarning(
278: 'Response::statusCode() is deprecated. ' .
279: 'Use Response::getStatusCode() instead.'
280: );
281:
282: return $this->code;
283: }
284:
285: 286: 287: 288: 289:
290: public function getStatusCode()
291: {
292: return $this->code;
293: }
294:
295: 296: 297: 298: 299: 300: 301:
302: public function withStatus($code, $reasonPhrase = '')
303: {
304: $new = clone $this;
305: $new->code = $code;
306: $new->reasonPhrase = $reasonPhrase;
307:
308: return $new;
309: }
310:
311: 312: 313: 314: 315:
316: public function getReasonPhrase()
317: {
318: return $this->reasonPhrase;
319: }
320:
321: 322: 323: 324: 325: 326:
327: public function encoding()
328: {
329: deprecationWarning(
330: 'Response::encoding() is deprecated. ' .
331: 'Use Response::getEncoding() instead.'
332: );
333:
334: return $this->getEncoding();
335: }
336:
337: 338: 339: 340: 341:
342: public function getEncoding()
343: {
344: $content = $this->getHeaderLine('content-type');
345: if (!$content) {
346: return null;
347: }
348: preg_match('/charset\s?=\s?[\'"]?([a-z0-9-_]+)[\'"]?/i', $content, $matches);
349: if (empty($matches[1])) {
350: return null;
351: }
352:
353: return $matches[1];
354: }
355:
356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366:
367: public function header($name = null)
368: {
369: deprecationWarning(
370: 'Response::header() is deprecated. ' .
371: 'Use Response::getHeader() and getHeaderLine() instead.'
372: );
373:
374: if ($name === null) {
375: return $this->_getHeaders();
376: }
377: $header = $this->getHeader($name);
378: if (count($header) === 1) {
379: return $header[0];
380: }
381:
382: return $header;
383: }
384:
385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398:
399: public function cookie($name = null, $all = false)
400: {
401: deprecationWarning(
402: 'Response::cookie() is deprecated. ' .
403: 'Use Response::getCookie(), getCookieData() or getCookies() instead.'
404: );
405:
406: if ($name === null) {
407: return $this->getCookies();
408: }
409: if ($all) {
410: return $this->getCookieData($name);
411: }
412:
413: return $this->getCookie($name);
414: }
415:
416: 417: 418: 419: 420:
421: public function getCookies()
422: {
423: return $this->_getCookies();
424: }
425:
426: 427: 428: 429: 430: 431: 432: 433:
434: public function getCookieCollection()
435: {
436: $this->buildCookieCollection();
437:
438: return $this->cookies;
439: }
440:
441: 442: 443: 444: 445: 446:
447: public function getCookie($name)
448: {
449: $this->buildCookieCollection();
450: if (!$this->cookies->has($name)) {
451: return null;
452: }
453:
454: return $this->cookies->get($name)->getValue();
455: }
456:
457: 458: 459: 460: 461: 462:
463: public function getCookieData($name)
464: {
465: $this->buildCookieCollection();
466:
467: if (!$this->cookies->has($name)) {
468: return null;
469: }
470:
471: $cookie = $this->cookies->get($name);
472:
473: return $this->convertCookieToArray($cookie);
474: }
475:
476: 477: 478: 479: 480: 481: 482: 483: 484:
485: protected function convertCookieToArray(CookieInterface $cookie)
486: {
487: return [
488: 'name' => $cookie->getName(),
489: 'value' => $cookie->getValue(),
490: 'path' => $cookie->getPath(),
491: 'domain' => $cookie->getDomain(),
492: 'secure' => $cookie->isSecure(),
493: 'httponly' => $cookie->isHttpOnly(),
494: 'expires' => $cookie->getFormattedExpires()
495: ];
496: }
497:
498: 499: 500: 501: 502:
503: protected function buildCookieCollection()
504: {
505: if ($this->cookies) {
506: return;
507: }
508: $this->cookies = CookiesCollection::createFromHeader($this->getHeader('Set-Cookie'));
509: }
510:
511: 512: 513: 514: 515:
516: protected function _getCookies()
517: {
518: $this->buildCookieCollection();
519:
520: $cookies = [];
521: foreach ($this->cookies as $cookie) {
522: $cookies[$cookie->getName()] = $this->convertCookieToArray($cookie);
523: }
524:
525: return $cookies;
526: }
527:
528: 529: 530: 531: 532: 533:
534: public function version()
535: {
536: deprecationWarning(
537: 'Response::version() is deprecated. ' .
538: 'Use Response::getProtocolVersion() instead.'
539: );
540:
541: return $this->protocol;
542: }
543:
544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560:
561: public function body($parser = null)
562: {
563: deprecationWarning(
564: 'Response::body() is deprecated. Use getStringBody()/getJson()/getXml() instead.'
565: );
566:
567: $stream = $this->stream;
568: $stream->rewind();
569: if ($parser) {
570: return $parser($stream->getContents());
571: }
572:
573: return $stream->getContents();
574: }
575:
576: 577: 578: 579: 580:
581: public function getStringBody()
582: {
583: return $this->_getBody();
584: }
585:
586: 587: 588: 589: 590:
591: public function getJson()
592: {
593: return $this->_getJson();
594: }
595:
596: 597: 598: 599: 600:
601: protected function _getJson()
602: {
603: if ($this->_json) {
604: return $this->_json;
605: }
606:
607: return $this->_json = json_decode($this->_getBody(), true);
608: }
609:
610: 611: 612: 613: 614:
615: public function getXml()
616: {
617: return $this->_getXml();
618: }
619:
620: 621: 622: 623: 624:
625: protected function _getXml()
626: {
627: if ($this->_xml) {
628: return $this->_xml;
629: }
630: libxml_use_internal_errors();
631: $data = simplexml_load_string($this->_getBody());
632: if ($data) {
633: $this->_xml = $data;
634:
635: return $this->_xml;
636: }
637:
638: return null;
639: }
640:
641: 642: 643: 644: 645:
646: protected function _getHeaders()
647: {
648: $out = [];
649: foreach ($this->headers as $key => $values) {
650: $out[$key] = implode(',', $values);
651: }
652:
653: return $out;
654: }
655:
656: 657: 658: 659: 660:
661: protected function _getBody()
662: {
663: $this->stream->rewind();
664:
665: return $this->stream->getContents();
666: }
667:
668: 669: 670: 671: 672: 673:
674: public function __get($name)
675: {
676: if (!isset($this->_exposedProperties[$name])) {
677: return false;
678: }
679: $key = $this->_exposedProperties[$name];
680: if (substr($key, 0, 4) === '_get') {
681: deprecationWarning(sprintf(
682: 'Response::%s is deprecated. Use Response::%s instead.',
683: $name,
684: $this->_deprecatedMagicProperties[$name]
685: ));
686:
687: return $this->{$key}();
688: }
689:
690: if ($key === 'code') {
691: deprecationWarning(
692: 'Response::code() is deprecated. ' .
693: 'Use Response::getStatusCode() instead.'
694: );
695: }
696:
697: return $this->{$key};
698: }
699:
700: 701: 702: 703: 704: 705:
706: public function __isset($name)
707: {
708: if (!isset($this->_exposedProperties[$name])) {
709: return false;
710: }
711: $key = $this->_exposedProperties[$name];
712: if (substr($key, 0, 4) === '_get') {
713: deprecationWarning(sprintf(
714: 'Response::%s is deprecated. Use Response::%s instead.',
715: $name,
716: $this->_deprecatedMagicProperties[$name]
717: ));
718:
719: $val = $this->{$key}();
720:
721: return $val !== null;
722: }
723:
724: if ($key === 'code') {
725: deprecationWarning(
726: 'Response::code() is deprecated. ' .
727: 'Use Response::getStatusCode() instead.'
728: );
729: }
730:
731: return isset($this->{$key});
732: }
733: }
734:
735:
736: class_alias('Cake\Http\Client\Response', 'Cake\Network\Http\Response');
737: