1<?php
2namespace GuzzleHttp\Exception;
3
4use GuzzleHttp\Promise\PromiseInterface;
5use Psr\Http\Message\RequestInterface;
6use Psr\Http\Message\ResponseInterface;
7use Psr\Http\Message\UriInterface;
8
9/**
10 * HTTP Request exception
11 */
12class RequestException extends TransferException
13{
14    /** @var RequestInterface */
15    private $request;
16
17    /** @var ResponseInterface|null */
18    private $response;
19
20    /** @var array */
21    private $handlerContext;
22
23    public function __construct(
24        $message,
25        RequestInterface $request,
26        ResponseInterface $response = null,
27        \Exception $previous = null,
28        array $handlerContext = []
29    ) {
30        // Set the code of the exception if the response is set and not future.
31        $code = $response && !($response instanceof PromiseInterface)
32            ? $response->getStatusCode()
33            : 0;
34        parent::__construct($message, $code, $previous);
35        $this->request = $request;
36        $this->response = $response;
37        $this->handlerContext = $handlerContext;
38    }
39
40    /**
41     * Wrap non-RequestExceptions with a RequestException
42     *
43     * @param RequestInterface $request
44     * @param \Exception       $e
45     *
46     * @return RequestException
47     */
48    public static function wrapException(RequestInterface $request, \Exception $e)
49    {
50        return $e instanceof RequestException
51            ? $e
52            : 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
59     * @param ResponseInterface $response Response received
60     * @param \Exception        $previous Previous exception
61     * @param array             $ctx      Optional handler context.
62     *
63     * @return self
64     */
65    public static function create(
66        RequestInterface $request,
67        ResponseInterface $response = null,
68        \Exception $previous = null,
69        array $ctx = []
70    ) {
71        if (!$response) {
72            return new self(
73                'Error completing request',
74                $request,
75                null,
76                $previous,
77                $ctx
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,
103            $response->getStatusCode(),
104            $response->getReasonPhrase()
105        );
106
107        $summary = static::getResponseBodySummary($response);
108
109        if ($summary !== null) {
110            $message .= ":\n{$summary}\n";
111        }
112
113        return new $className($message, $request, $response, $previous, $ctx);
114    }
115
116    /**
117     * Get a short summary of the response
118     *
119     * Will return `null` if the response is not printable.
120     *
121     * @param ResponseInterface $response
122     *
123     * @return string|null
124     */
125    public static function getResponseBodySummary(ResponseInterface $response)
126    {
127        return \GuzzleHttp\Psr7\get_message_body_summary($response);
128    }
129
130    /**
131     * Obfuscates URI if there is a username and a password present
132     *
133     * @param UriInterface $uri
134     *
135     * @return UriInterface
136     */
137    private static function obfuscateUri(UriInterface $uri)
138    {
139        $userInfo = $uri->getUserInfo();
140
141        if (false !== ($pos = strpos($userInfo, ':'))) {
142            return $uri->withUserInfo(substr($userInfo, 0, $pos), '***');
143        }
144
145        return $uri;
146    }
147
148    /**
149     * Get the request that caused the exception
150     *
151     * @return RequestInterface
152     */
153    public function getRequest()
154    {
155        return $this->request;
156    }
157
158    /**
159     * Get the associated response
160     *
161     * @return ResponseInterface|null
162     */
163    public function getResponse()
164    {
165        return $this->response;
166    }
167
168    /**
169     * Check if a response was received
170     *
171     * @return bool
172     */
173    public function hasResponse()
174    {
175        return $this->response !== null;
176    }
177
178    /**
179     * Get contextual information about the error from the underlying handler.
180     *
181     * The contents of this array will vary depending on which handler you are
182     * using. It may also be just an empty array. Relying on this data will
183     * couple you to a specific handler, but can give more debug information
184     * when needed.
185     *
186     * @return array
187     */
188    public function getHandlerContext()
189    {
190        return $this->handlerContext;
191    }
192}
193