1<?php
2
3namespace GuzzleHttp\Exception;
4
5use GuzzleHttp\BodySummarizer;
6use GuzzleHttp\BodySummarizerInterface;
7use Psr\Http\Client\RequestExceptionInterface;
8use Psr\Http\Message\RequestInterface;
9use Psr\Http\Message\ResponseInterface;
10use Psr\Http\Message\UriInterface;
11
12/**
13 * HTTP Request exception
14 */
15class RequestException extends TransferException implements RequestExceptionInterface
16{
17    /**
18     * @var RequestInterface
19     */
20    private $request;
21
22    /**
23     * @var ResponseInterface|null
24     */
25    private $response;
26
27    /**
28     * @var array
29     */
30    private $handlerContext;
31
32    public function __construct(
33        string $message,
34        RequestInterface $request,
35        ResponseInterface $response = null,
36        \Throwable $previous = null,
37        array $handlerContext = []
38    ) {
39        // Set the code of the exception if the response is set and not future.
40        $code = $response ? $response->getStatusCode() : 0;
41        parent::__construct($message, $code, $previous);
42        $this->request = $request;
43        $this->response = $response;
44        $this->handlerContext = $handlerContext;
45    }
46
47    /**
48     * Wrap non-RequestExceptions with a RequestException
49     */
50    public static function wrapException(RequestInterface $request, \Throwable $e): RequestException
51    {
52        return $e instanceof RequestException ? $e : new RequestException($e->getMessage(), $request, null, $e);
53    }
54
55    /**
56     * Factory method to create a new exception with a normalized error message
57     *
58     * @param RequestInterface             $request        Request sent
59     * @param ResponseInterface            $response       Response received
60     * @param \Throwable|null              $previous       Previous exception
61     * @param array                        $handlerContext Optional handler context
62     * @param BodySummarizerInterface|null $bodySummarizer Optional body summarizer
63     */
64    public static function create(
65        RequestInterface $request,
66        ResponseInterface $response = null,
67        \Throwable $previous = null,
68        array $handlerContext = [],
69        BodySummarizerInterface $bodySummarizer = null
70    ): self {
71        if (!$response) {
72            return new self(
73                'Error completing request',
74                $request,
75                null,
76                $previous,
77                $handlerContext
78            );
79        }
80
81        $level = (int) \floor($response->getStatusCode() / 100);
82        if ($level === 4) {
83            $label = 'Client error';
84            $className = ClientException::class;
85        } elseif ($level === 5) {
86            $label = 'Server error';
87            $className = ServerException::class;
88        } else {
89            $label = 'Unsuccessful request';
90            $className = __CLASS__;
91        }
92
93        $uri = $request->getUri();
94        $uri = static::obfuscateUri($uri);
95
96        // Client Error: `GET /` resulted in a `404 Not Found` response:
97        // <html> ... (truncated)
98        $message = \sprintf(
99            '%s: `%s %s` resulted in a `%s %s` response',
100            $label,
101            $request->getMethod(),
102            $uri->__toString(),
103            $response->getStatusCode(),
104            $response->getReasonPhrase()
105        );
106
107        $summary = ($bodySummarizer ?? new BodySummarizer())->summarize($response);
108
109        if ($summary !== null) {
110            $message .= ":\n{$summary}\n";
111        }
112
113        return new $className($message, $request, $response, $previous, $handlerContext);
114    }
115
116    /**
117     * Obfuscates URI if there is a username and a password present
118     */
119    private static function obfuscateUri(UriInterface $uri): UriInterface
120    {
121        $userInfo = $uri->getUserInfo();
122
123        if (false !== ($pos = \strpos($userInfo, ':'))) {
124            return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***');
125        }
126
127        return $uri;
128    }
129
130    /**
131     * Get the request that caused the exception
132     */
133    public function getRequest(): RequestInterface
134    {
135        return $this->request;
136    }
137
138    /**
139     * Get the associated response
140     */
141    public function getResponse(): ?ResponseInterface
142    {
143        return $this->response;
144    }
145
146    /**
147     * Check if a response was received
148     */
149    public function hasResponse(): bool
150    {
151        return $this->response !== null;
152    }
153
154    /**
155     * Get contextual information about the error from the underlying handler.
156     *
157     * The contents of this array will vary depending on which handler you are
158     * using. It may also be just an empty array. Relying on this data will
159     * couple you to a specific handler, but can give more debug information
160     * when needed.
161     */
162    public function getHandlerContext(): array
163    {
164        return $this->handlerContext;
165    }
166}
167