Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 liveuser 1
<?php
2
 
3
namespace Guzzle\Http\Message;
4
 
5
use Guzzle\Common\Version;
6
use Guzzle\Common\ToArrayInterface;
7
use Guzzle\Common\Exception\RuntimeException;
8
use Guzzle\Http\EntityBodyInterface;
9
use Guzzle\Http\EntityBody;
10
use Guzzle\Http\Exception\BadResponseException;
11
use Guzzle\Http\RedirectPlugin;
12
use Guzzle\Parser\ParserRegistry;
13
 
14
/**
15
 * Guzzle HTTP response object
16
 */
17
class Response extends AbstractMessage implements \Serializable
18
{
19
    /**
20
     * @var array Array of reason phrases and their corresponding status codes
21
     */
22
    private static $statusTexts = array(
23
        100 => 'Continue',
24
        101 => 'Switching Protocols',
25
        102 => 'Processing',
26
        200 => 'OK',
27
        201 => 'Created',
28
        202 => 'Accepted',
29
        203 => 'Non-Authoritative Information',
30
        204 => 'No Content',
31
        205 => 'Reset Content',
32
        206 => 'Partial Content',
33
        207 => 'Multi-Status',
34
        208 => 'Already Reported',
35
        226 => 'IM Used',
36
        300 => 'Multiple Choices',
37
        301 => 'Moved Permanently',
38
        302 => 'Found',
39
        303 => 'See Other',
40
        304 => 'Not Modified',
41
        305 => 'Use Proxy',
42
        307 => 'Temporary Redirect',
43
        308 => 'Permanent Redirect',
44
        400 => 'Bad Request',
45
        401 => 'Unauthorized',
46
        402 => 'Payment Required',
47
        403 => 'Forbidden',
48
        404 => 'Not Found',
49
        405 => 'Method Not Allowed',
50
        406 => 'Not Acceptable',
51
        407 => 'Proxy Authentication Required',
52
        408 => 'Request Timeout',
53
        409 => 'Conflict',
54
        410 => 'Gone',
55
        411 => 'Length Required',
56
        412 => 'Precondition Failed',
57
        413 => 'Request Entity Too Large',
58
        414 => 'Request-URI Too Long',
59
        415 => 'Unsupported Media Type',
60
        416 => 'Requested Range Not Satisfiable',
61
        417 => 'Expectation Failed',
62
        422 => 'Unprocessable Entity',
63
        423 => 'Locked',
64
        424 => 'Failed Dependency',
65
        425 => 'Reserved for WebDAV advanced collections expired proposal',
66
        426 => 'Upgrade required',
67
        428 => 'Precondition Required',
68
        429 => 'Too Many Requests',
69
        431 => 'Request Header Fields Too Large',
70
        500 => 'Internal Server Error',
71
        501 => 'Not Implemented',
72
        502 => 'Bad Gateway',
73
        503 => 'Service Unavailable',
74
        504 => 'Gateway Timeout',
75
        505 => 'HTTP Version Not Supported',
76
        506 => 'Variant Also Negotiates (Experimental)',
77
        507 => 'Insufficient Storage',
78
        508 => 'Loop Detected',
79
        510 => 'Not Extended',
80
        511 => 'Network Authentication Required',
81
    );
82
 
83
    /** @var EntityBodyInterface The response body */
84
    protected $body;
85
 
86
    /** @var string The reason phrase of the response (human readable code) */
87
    protected $reasonPhrase;
88
 
89
    /** @var string The status code of the response */
90
    protected $statusCode;
91
 
92
    /** @var array Information about the request */
93
    protected $info = array();
94
 
95
    /** @var string The effective URL that returned this response */
96
    protected $effectiveUrl;
97
 
98
    /** @var array Cacheable response codes (see RFC 2616:13.4) */
99
    protected static $cacheResponseCodes = array(200, 203, 206, 300, 301, 410);
100
 
101
    /**
102
     * Create a new Response based on a raw response message
103
     *
104
     * @param string $message Response message
105
     *
106
     * @return self|bool Returns false on error
107
     */
108
    public static function fromMessage($message)
109
    {
110
        $data = ParserRegistry::getInstance()->getParser('message')->parseResponse($message);
111
        if (!$data) {
112
            return false;
113
        }
114
 
115
        $response = new static($data['code'], $data['headers'], $data['body']);
116
        $response->setProtocol($data['protocol'], $data['version'])
117
                 ->setStatus($data['code'], $data['reason_phrase']);
118
 
119
        // Set the appropriate Content-Length if the one set is inaccurate (e.g. setting to X)
120
        $contentLength = (string) $response->getHeader('Content-Length');
121
        $actualLength = strlen($data['body']);
122
        if (strlen($data['body']) > 0 && $contentLength != $actualLength) {
123
            $response->setHeader('Content-Length', $actualLength);
124
        }
125
 
126
        return $response;
127
    }
128
 
129
    /**
130
     * Construct the response
131
     *
132
     * @param string                              $statusCode The response status code (e.g. 200, 404, etc)
133
     * @param ToArrayInterface|array              $headers    The response headers
134
     * @param string|resource|EntityBodyInterface $body       The body of the response
135
     *
136
     * @throws BadResponseException if an invalid response code is given
137
     */
138
    public function __construct($statusCode, $headers = null, $body = null)
139
    {
140
        parent::__construct();
141
        $this->setStatus($statusCode);
142
        $this->body = EntityBody::factory($body !== null ? $body : '');
143
 
144
        if ($headers) {
145
            if (is_array($headers)) {
146
                $this->setHeaders($headers);
147
            } elseif ($headers instanceof ToArrayInterface) {
148
                $this->setHeaders($headers->toArray());
149
            } else {
150
                throw new BadResponseException('Invalid headers argument received');
151
            }
152
        }
153
    }
154
 
155
    /**
156
     * @return string
157
     */
158
    public function __toString()
159
    {
160
        return $this->getMessage();
161
    }
162
 
163
    public function serialize()
164
    {
165
        return json_encode(array(
166
            'status'  => $this->statusCode,
167
            'body'    => (string) $this->body,
168
            'headers' => $this->headers->toArray()
169
        ));
170
    }
171
 
172
    public function unserialize($serialize)
173
    {
174
        $data = json_decode($serialize, true);
175
        $this->__construct($data['status'], $data['headers'], $data['body']);
176
    }
177
 
178
    /**
179
     * Get the response entity body
180
     *
181
     * @param bool $asString Set to TRUE to return a string of the body rather than a full body object
182
     *
183
     * @return EntityBodyInterface|string
184
     */
185
    public function getBody($asString = false)
186
    {
187
        return $asString ? (string) $this->body : $this->body;
188
    }
189
 
190
    /**
191
     * Set the response entity body
192
     *
193
     * @param EntityBodyInterface|string $body Body to set
194
     *
195
     * @return self
196
     */
197
    public function setBody($body)
198
    {
199
        $this->body = EntityBody::factory($body);
200
 
201
        return $this;
202
    }
203
 
204
    /**
205
     * Set the protocol and protocol version of the response
206
     *
207
     * @param string $protocol Response protocol
208
     * @param string $version  Protocol version
209
     *
210
     * @return self
211
     */
212
    public function setProtocol($protocol, $version)
213
    {
214
        $this->protocol = $protocol;
215
        $this->protocolVersion = $version;
216
 
217
        return $this;
218
    }
219
 
220
    /**
221
     * Get the protocol used for the response (e.g. HTTP)
222
     *
223
     * @return string
224
     */
225
    public function getProtocol()
226
    {
227
        return $this->protocol;
228
    }
229
 
230
    /**
231
     * Get the HTTP protocol version
232
     *
233
     * @return string
234
     */
235
    public function getProtocolVersion()
236
    {
237
        return $this->protocolVersion;
238
    }
239
 
240
    /**
241
     * Get a cURL transfer information
242
     *
243
     * @param string $key A single statistic to check
244
     *
245
     * @return array|string|null Returns all stats if no key is set, a single stat if a key is set, or null if a key
246
     *                           is set and not found
247
     * @link http://www.php.net/manual/en/function.curl-getinfo.php
248
     */
249
    public function getInfo($key = null)
250
    {
251
        if ($key === null) {
252
            return $this->info;
253
        } elseif (array_key_exists($key, $this->info)) {
254
            return $this->info[$key];
255
        } else {
256
            return null;
257
        }
258
    }
259
 
260
    /**
261
     * Set the transfer information
262
     *
263
     * @param array $info Array of cURL transfer stats
264
     *
265
     * @return self
266
     */
267
    public function setInfo(array $info)
268
    {
269
        $this->info = $info;
270
 
271
        return $this;
272
    }
273
 
274
    /**
275
     * Set the response status
276
     *
277
     * @param int    $statusCode   Response status code to set
278
     * @param string $reasonPhrase Response reason phrase
279
     *
280
     * @return self
281
     * @throws BadResponseException when an invalid response code is received
282
     */
283
    public function setStatus($statusCode, $reasonPhrase = '')
284
    {
285
        $this->statusCode = (int) $statusCode;
286
 
287
        if (!$reasonPhrase && isset(self::$statusTexts[$this->statusCode])) {
288
            $this->reasonPhrase = self::$statusTexts[$this->statusCode];
289
        } else {
290
            $this->reasonPhrase = $reasonPhrase;
291
        }
292
 
293
        return $this;
294
    }
295
 
296
    /**
297
     * Get the response status code
298
     *
299
     * @return integer
300
     */
301
    public function getStatusCode()
302
    {
303
        return $this->statusCode;
304
    }
305
 
306
    /**
307
     * Get the entire response as a string
308
     *
309
     * @return string
310
     */
311
    public function getMessage()
312
    {
313
        $message = $this->getRawHeaders();
314
 
315
        // Only include the body in the message if the size is < 2MB
316
        $size = $this->body->getSize();
317
        if ($size < 2097152) {
318
            $message .= (string) $this->body;
319
        }
320
 
321
        return $message;
322
    }
323
 
324
    /**
325
     * Get the the raw message headers as a string
326
     *
327
     * @return string
328
     */
329
    public function getRawHeaders()
330
    {
331
        $headers = 'HTTP/1.1 ' . $this->statusCode . ' ' . $this->reasonPhrase . "\r\n";
332
        $lines = $this->getHeaderLines();
333
        if (!empty($lines)) {
334
            $headers .= implode("\r\n", $lines) . "\r\n";
335
        }
336
 
337
        return $headers . "\r\n";
338
    }
339
 
340
    /**
341
     * Get the response reason phrase- a human readable version of the numeric
342
     * status code
343
     *
344
     * @return string
345
     */
346
    public function getReasonPhrase()
347
    {
348
        return $this->reasonPhrase;
349
    }
350
 
351
    /**
352
     * Get the Accept-Ranges HTTP header
353
     *
354
     * @return string Returns what partial content range types this server supports.
355
     */
356
    public function getAcceptRanges()
357
    {
358
        return (string) $this->getHeader('Accept-Ranges');
359
    }
360
 
361
    /**
362
     * Calculate the age of the response
363
     *
364
     * @return integer
365
     */
366
    public function calculateAge()
367
    {
368
        $age = $this->getHeader('Age');
369
 
370
        if ($age === null && $this->getDate()) {
371
            $age = time() - strtotime($this->getDate());
372
        }
373
 
374
        return $age === null ? null : (int) (string) $age;
375
    }
376
 
377
    /**
378
     * Get the Age HTTP header
379
     *
380
     * @return integer|null Returns the age the object has been in a proxy cache in seconds.
381
     */
382
    public function getAge()
383
    {
384
        return (string) $this->getHeader('Age');
385
    }
386
 
387
    /**
388
     * Get the Allow HTTP header
389
     *
390
     * @return string|null Returns valid actions for a specified resource. To be used for a 405 Method not allowed.
391
     */
392
    public function getAllow()
393
    {
394
        return (string) $this->getHeader('Allow');
395
    }
396
 
397
    /**
398
     * Check if an HTTP method is allowed by checking the Allow response header
399
     *
400
     * @param string $method Method to check
401
     *
402
     * @return bool
403
     */
404
    public function isMethodAllowed($method)
405
    {
406
        $allow = $this->getHeader('Allow');
407
        if ($allow) {
408
            foreach (explode(',', $allow) as $allowable) {
409
                if (!strcasecmp(trim($allowable), $method)) {
410
                    return true;
411
                }
412
            }
413
        }
414
 
415
        return false;
416
    }
417
 
418
    /**
419
     * Get the Cache-Control HTTP header
420
     *
421
     * @return string
422
     */
423
    public function getCacheControl()
424
    {
425
        return (string) $this->getHeader('Cache-Control');
426
    }
427
 
428
    /**
429
     * Get the Connection HTTP header
430
     *
431
     * @return string
432
     */
433
    public function getConnection()
434
    {
435
        return (string) $this->getHeader('Connection');
436
    }
437
 
438
    /**
439
     * Get the Content-Encoding HTTP header
440
     *
441
     * @return string|null
442
     */
443
    public function getContentEncoding()
444
    {
445
        return (string) $this->getHeader('Content-Encoding');
446
    }
447
 
448
    /**
449
     * Get the Content-Language HTTP header
450
     *
451
     * @return string|null Returns the language the content is in.
452
     */
453
    public function getContentLanguage()
454
    {
455
        return (string) $this->getHeader('Content-Language');
456
    }
457
 
458
    /**
459
     * Get the Content-Length HTTP header
460
     *
461
     * @return integer Returns the length of the response body in bytes
462
     */
463
    public function getContentLength()
464
    {
465
        return (int) (string) $this->getHeader('Content-Length');
466
    }
467
 
468
    /**
469
     * Get the Content-Location HTTP header
470
     *
471
     * @return string|null Returns an alternate location for the returned data (e.g /index.htm)
472
     */
473
    public function getContentLocation()
474
    {
475
        return (string) $this->getHeader('Content-Location');
476
    }
477
 
478
    /**
479
     * Get the Content-Disposition HTTP header
480
     *
481
     * @return string|null Returns the Content-Disposition header
482
     */
483
    public function getContentDisposition()
484
    {
485
        return (string) $this->getHeader('Content-Disposition');
486
    }
487
 
488
    /**
489
     * Get the Content-MD5 HTTP header
490
     *
491
     * @return string|null Returns a Base64-encoded binary MD5 sum of the content of the response.
492
     */
493
    public function getContentMd5()
494
    {
495
        return (string) $this->getHeader('Content-MD5');
496
    }
497
 
498
    /**
499
     * Get the Content-Range HTTP header
500
     *
501
     * @return string Returns where in a full body message this partial message belongs (e.g. bytes 21010-47021/47022).
502
     */
503
    public function getContentRange()
504
    {
505
        return (string) $this->getHeader('Content-Range');
506
    }
507
 
508
    /**
509
     * Get the Content-Type HTTP header
510
     *
511
     * @return string Returns the mime type of this content.
512
     */
513
    public function getContentType()
514
    {
515
        return (string) $this->getHeader('Content-Type');
516
    }
517
 
518
    /**
519
     * Checks if the Content-Type is of a certain type.  This is useful if the
520
     * Content-Type header contains charset information and you need to know if
521
     * the Content-Type matches a particular type.
522
     *
523
     * @param string $type Content type to check against
524
     *
525
     * @return bool
526
     */
527
    public function isContentType($type)
528
    {
529
        return stripos($this->getHeader('Content-Type'), $type) !== false;
530
    }
531
 
532
    /**
533
     * Get the Date HTTP header
534
     *
535
     * @return string|null Returns the date and time that the message was sent.
536
     */
537
    public function getDate()
538
    {
539
        return (string) $this->getHeader('Date');
540
    }
541
 
542
    /**
543
     * Get the ETag HTTP header
544
     *
545
     * @return string|null Returns an identifier for a specific version of a resource, often a Message digest.
546
     */
547
    public function getEtag()
548
    {
549
        return (string) $this->getHeader('ETag');
550
    }
551
 
552
    /**
553
     * Get the Expires HTTP header
554
     *
555
     * @return string|null Returns the date/time after which the response is considered stale.
556
     */
557
    public function getExpires()
558
    {
559
        return (string) $this->getHeader('Expires');
560
    }
561
 
562
    /**
563
     * Get the Last-Modified HTTP header
564
     *
565
     * @return string|null Returns the last modified date for the requested object, in RFC 2822 format
566
     *                     (e.g. Tue, 15 Nov 1994 12:45:26 GMT)
567
     */
568
    public function getLastModified()
569
    {
570
        return (string) $this->getHeader('Last-Modified');
571
    }
572
 
573
    /**
574
     * Get the Location HTTP header
575
     *
576
     * @return string|null Used in redirection, or when a new resource has been created.
577
     */
578
    public function getLocation()
579
    {
580
        return (string) $this->getHeader('Location');
581
    }
582
 
583
    /**
584
     * Get the Pragma HTTP header
585
     *
586
     * @return Header|null Returns the implementation-specific headers that may have various effects anywhere along
587
     *                     the request-response chain.
588
     */
589
    public function getPragma()
590
    {
591
        return (string) $this->getHeader('Pragma');
592
    }
593
 
594
    /**
595
     * Get the Proxy-Authenticate HTTP header
596
     *
597
     * @return string|null Authentication to access the proxy (e.g. Basic)
598
     */
599
    public function getProxyAuthenticate()
600
    {
601
        return (string) $this->getHeader('Proxy-Authenticate');
602
    }
603
 
604
    /**
605
     * Get the Retry-After HTTP header
606
     *
607
     * @return int|null If an entity is temporarily unavailable, this instructs the client to try again after a
608
     *                  specified period of time.
609
     */
610
    public function getRetryAfter()
611
    {
612
        return (string) $this->getHeader('Retry-After');
613
    }
614
 
615
    /**
616
     * Get the Server HTTP header
617
     *
618
     * @return string|null A name for the server
619
     */
620
    public function getServer()
621
    {
622
        return (string)  $this->getHeader('Server');
623
    }
624
 
625
    /**
626
     * Get the Set-Cookie HTTP header
627
     *
628
     * @return string|null An HTTP cookie.
629
     */
630
    public function getSetCookie()
631
    {
632
        return (string) $this->getHeader('Set-Cookie');
633
    }
634
 
635
    /**
636
     * Get the Trailer HTTP header
637
     *
638
     * @return string|null The Trailer general field value indicates that the given set of header fields is present in
639
     *                     the trailer of a message encoded with chunked transfer-coding.
640
     */
641
    public function getTrailer()
642
    {
643
        return (string) $this->getHeader('Trailer');
644
    }
645
 
646
    /**
647
     * Get the Transfer-Encoding HTTP header
648
     *
649
     * @return string|null The form of encoding used to safely transfer the entity to the user
650
     */
651
    public function getTransferEncoding()
652
    {
653
        return (string) $this->getHeader('Transfer-Encoding');
654
    }
655
 
656
    /**
657
     * Get the Vary HTTP header
658
     *
659
     * @return string|null Tells downstream proxies how to match future request headers to decide whether the cached
660
     *                     response can be used rather than requesting a fresh one from the origin server.
661
     */
662
    public function getVary()
663
    {
664
        return (string) $this->getHeader('Vary');
665
    }
666
 
667
    /**
668
     * Get the Via HTTP header
669
     *
670
     * @return string|null Informs the client of proxies through which the response was sent.
671
     */
672
    public function getVia()
673
    {
674
        return (string) $this->getHeader('Via');
675
    }
676
 
677
    /**
678
     * Get the Warning HTTP header
679
     *
680
     * @return string|null A general warning about possible problems with the entity body
681
     */
682
    public function getWarning()
683
    {
684
        return (string) $this->getHeader('Warning');
685
    }
686
 
687
    /**
688
     * Get the WWW-Authenticate HTTP header
689
     *
690
     * @return string|null Indicates the authentication scheme that should be used to access the requested entity
691
     */
692
    public function getWwwAuthenticate()
693
    {
694
        return (string) $this->getHeader('WWW-Authenticate');
695
    }
696
 
697
    /**
698
     * Checks if HTTP Status code is a Client Error (4xx)
699
     *
700
     * @return bool
701
     */
702
    public function isClientError()
703
    {
704
        return $this->statusCode >= 400 && $this->statusCode < 500;
705
    }
706
 
707
    /**
708
     * Checks if HTTP Status code is Server OR Client Error (4xx or 5xx)
709
     *
710
     * @return boolean
711
     */
712
    public function isError()
713
    {
714
        return $this->isClientError() || $this->isServerError();
715
    }
716
 
717
    /**
718
     * Checks if HTTP Status code is Information (1xx)
719
     *
720
     * @return bool
721
     */
722
    public function isInformational()
723
    {
724
        return $this->statusCode < 200;
725
    }
726
 
727
    /**
728
     * Checks if HTTP Status code is a Redirect (3xx)
729
     *
730
     * @return bool
731
     */
732
    public function isRedirect()
733
    {
734
        return $this->statusCode >= 300 && $this->statusCode < 400;
735
    }
736
 
737
    /**
738
     * Checks if HTTP Status code is Server Error (5xx)
739
     *
740
     * @return bool
741
     */
742
    public function isServerError()
743
    {
744
        return $this->statusCode >= 500 && $this->statusCode < 600;
745
    }
746
 
747
    /**
748
     * Checks if HTTP Status code is Successful (2xx | 304)
749
     *
750
     * @return bool
751
     */
752
    public function isSuccessful()
753
    {
754
        return ($this->statusCode >= 200 && $this->statusCode < 300) || $this->statusCode == 304;
755
    }
756
 
757
    /**
758
     * Check if the response can be cached based on the response headers
759
     *
760
     * @return bool Returns TRUE if the response can be cached or false if not
761
     */
762
    public function canCache()
763
    {
764
        // Check if the response is cacheable based on the code
765
        if (!in_array((int) $this->getStatusCode(), self::$cacheResponseCodes)) {
766
            return false;
767
        }
768
 
769
        // Make sure a valid body was returned and can be cached
770
        if ((!$this->getBody()->isReadable() || !$this->getBody()->isSeekable())
771
            && ($this->getContentLength() > 0 || $this->getTransferEncoding() == 'chunked')) {
772
            return false;
773
        }
774
 
775
        // Never cache no-store resources (this is a private cache, so private
776
        // can be cached)
777
        if ($this->getHeader('Cache-Control') && $this->getHeader('Cache-Control')->hasDirective('no-store')) {
778
            return false;
779
        }
780
 
781
        return $this->isFresh() || $this->getFreshness() === null || $this->canValidate();
782
    }
783
 
784
    /**
785
     * Gets the number of seconds from the current time in which this response is still considered fresh
786
     *
787
     * @return int|null Returns the number of seconds
788
     */
789
    public function getMaxAge()
790
    {
791
        if ($header = $this->getHeader('Cache-Control')) {
792
            // s-max-age, then max-age, then Expires
793
            if ($age = $header->getDirective('s-maxage')) {
794
                return $age;
795
            }
796
            if ($age = $header->getDirective('max-age')) {
797
                return $age;
798
            }
799
        }
800
 
801
        if ($this->getHeader('Expires')) {
802
            return strtotime($this->getExpires()) - time();
803
        }
804
 
805
        return null;
806
    }
807
 
808
    /**
809
     * Check if the response is considered fresh.
810
     *
811
     * A response is considered fresh when its age is less than or equal to the freshness lifetime (maximum age) of the
812
     * response.
813
     *
814
     * @return bool|null
815
     */
816
    public function isFresh()
817
    {
818
        $fresh = $this->getFreshness();
819
 
820
        return $fresh === null ? null : $fresh >= 0;
821
    }
822
 
823
    /**
824
     * Check if the response can be validated against the origin server using a conditional GET request.
825
     *
826
     * @return bool
827
     */
828
    public function canValidate()
829
    {
830
        return $this->getEtag() || $this->getLastModified();
831
    }
832
 
833
    /**
834
     * Get the freshness of the response by returning the difference of the maximum lifetime of the response and the
835
     * age of the response (max-age - age).
836
     *
837
     * Freshness values less than 0 mean that the response is no longer fresh and is ABS(freshness) seconds expired.
838
     * Freshness values of greater than zero is the number of seconds until the response is no longer fresh. A NULL
839
     * result means that no freshness information is available.
840
     *
841
     * @return int
842
     */
843
    public function getFreshness()
844
    {
845
        $maxAge = $this->getMaxAge();
846
        $age = $this->calculateAge();
847
 
848
        return $maxAge && $age ? ($maxAge - $age) : null;
849
    }
850
 
851
    /**
852
     * Parse the JSON response body and return an array
853
     *
854
     * @return array|string|int|bool|float
855
     * @throws RuntimeException if the response body is not in JSON format
856
     */
857
    public function json()
858
    {
859
        $data = json_decode((string) $this->body, true);
860
        if (JSON_ERROR_NONE !== json_last_error()) {
861
            throw new RuntimeException('Unable to parse response body into JSON: ' . json_last_error());
862
        }
863
 
864
        return $data === null ? array() : $data;
865
    }
866
 
867
    /**
868
     * Parse the XML response body and return a \SimpleXMLElement.
869
     *
870
     * In order to prevent XXE attacks, this method disables loading external
871
     * entities. If you rely on external entities, then you must parse the
872
     * XML response manually by accessing the response body directly.
873
     *
874
     * @return \SimpleXMLElement
875
     * @throws RuntimeException if the response body is not in XML format
876
     * @link http://websec.io/2012/08/27/Preventing-XXE-in-PHP.html
877
     */
878
    public function xml()
879
    {
880
        $errorMessage = null;
881
        $internalErrors = libxml_use_internal_errors(true);
882
        $disableEntities = libxml_disable_entity_loader(true);
883
        libxml_clear_errors();
884
 
885
        try {
886
            $xml = new \SimpleXMLElement((string) $this->body ?: '<root />', LIBXML_NONET);
887
            if ($error = libxml_get_last_error()) {
888
                $errorMessage = $error->message;
889
            }
890
        } catch (\Exception $e) {
891
            $errorMessage = $e->getMessage();
892
        }
893
 
894
        libxml_clear_errors();
895
        libxml_use_internal_errors($internalErrors);
896
        libxml_disable_entity_loader($disableEntities);
897
 
898
        if ($errorMessage) {
899
            throw new RuntimeException('Unable to parse response body into XML: ' . $errorMessage);
900
        }
901
 
902
        return $xml;
903
    }
904
 
905
    /**
906
     * Get the redirect count of this response
907
     *
908
     * @return int
909
     */
910
    public function getRedirectCount()
911
    {
912
        return (int) $this->params->get(RedirectPlugin::REDIRECT_COUNT);
913
    }
914
 
915
    /**
916
     * Set the effective URL that resulted in this response (e.g. the last redirect URL)
917
     *
918
     * @param string $url The effective URL
919
     *
920
     * @return self
921
     */
922
    public function setEffectiveUrl($url)
923
    {
924
        $this->effectiveUrl = $url;
925
 
926
        return $this;
927
    }
928
 
929
    /**
930
     * Get the effective URL that resulted in this response (e.g. the last redirect URL)
931
     *
932
     * @return string
933
     */
934
    public function getEffectiveUrl()
935
    {
936
        return $this->effectiveUrl;
937
    }
938
 
939
    /**
940
     * @deprecated
941
     * @codeCoverageIgnore
942
     */
943
    public function getPreviousResponse()
944
    {
945
        Version::warn(__METHOD__ . ' is deprecated. Use the HistoryPlugin.');
946
        return null;
947
    }
948
 
949
    /**
950
     * @deprecated
951
     * @codeCoverageIgnore
952
     */
953
    public function setRequest($request)
954
    {
955
        Version::warn(__METHOD__ . ' is deprecated');
956
        return $this;
957
    }
958
 
959
    /**
960
     * @deprecated
961
     * @codeCoverageIgnore
962
     */
963
    public function getRequest()
964
    {
965
        Version::warn(__METHOD__ . ' is deprecated');
966
        return null;
967
    }
968
}