Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 liveuser 1
<?php
2
 
3
namespace GuzzleHttp\Psr7;
4
 
5
use Psr\Http\Message\MessageInterface;
6
use Psr\Http\Message\RequestInterface;
7
use Psr\Http\Message\ResponseInterface;
8
 
9
final class Message
10
{
11
    /**
12
     * Returns the string representation of an HTTP message.
13
     *
14
     * @param MessageInterface $message Message to convert to a string.
15
     *
16
     * @return string
17
     */
18
    public static function toString(MessageInterface $message)
19
    {
20
        if ($message instanceof RequestInterface) {
21
            $msg = trim($message->getMethod() . ' '
22
                    . $message->getRequestTarget())
23
                . ' HTTP/' . $message->getProtocolVersion();
24
            if (!$message->hasHeader('host')) {
25
                $msg .= "\r\nHost: " . $message->getUri()->getHost();
26
            }
27
        } elseif ($message instanceof ResponseInterface) {
28
            $msg = 'HTTP/' . $message->getProtocolVersion() . ' '
29
                . $message->getStatusCode() . ' '
30
                . $message->getReasonPhrase();
31
        } else {
32
            throw new \InvalidArgumentException('Unknown message type');
33
        }
34
 
35
        foreach ($message->getHeaders() as $name => $values) {
36
            if (strtolower($name) === 'set-cookie') {
37
                foreach ($values as $value) {
38
                    $msg .= "\r\n{$name}: " . $value;
39
                }
40
            } else {
41
                $msg .= "\r\n{$name}: " . implode(', ', $values);
42
            }
43
        }
44
 
45
        return "{$msg}\r\n\r\n" . $message->getBody();
46
    }
47
 
48
    /**
49
     * Get a short summary of the message body.
50
     *
51
     * Will return `null` if the response is not printable.
52
     *
53
     * @param MessageInterface $message    The message to get the body summary
54
     * @param int              $truncateAt The maximum allowed size of the summary
55
     *
56
     * @return string|null
57
     */
58
    public static function bodySummary(MessageInterface $message, $truncateAt = 120)
59
    {
60
        $body = $message->getBody();
61
 
62
        if (!$body->isSeekable() || !$body->isReadable()) {
63
            return null;
64
        }
65
 
66
        $size = $body->getSize();
67
 
68
        if ($size === 0) {
69
            return null;
70
        }
71
 
72
        $summary = $body->read($truncateAt);
73
        $body->rewind();
74
 
75
        if ($size > $truncateAt) {
76
            $summary .= ' (truncated...)';
77
        }
78
 
79
        // Matches any printable character, including unicode characters:
80
        // letters, marks, numbers, punctuation, spacing, and separators.
81
        if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary)) {
82
            return null;
83
        }
84
 
85
        return $summary;
86
    }
87
 
88
    /**
89
     * Attempts to rewind a message body and throws an exception on failure.
90
     *
91
     * The body of the message will only be rewound if a call to `tell()`
92
     * returns a value other than `0`.
93
     *
94
     * @param MessageInterface $message Message to rewind
95
     *
96
     * @throws \RuntimeException
97
     */
98
    public static function rewindBody(MessageInterface $message)
99
    {
100
        $body = $message->getBody();
101
 
102
        if ($body->tell()) {
103
            $body->rewind();
104
        }
105
    }
106
 
107
    /**
108
     * Parses an HTTP message into an associative array.
109
     *
110
     * The array contains the "start-line" key containing the start line of
111
     * the message, "headers" key containing an associative array of header
112
     * array values, and a "body" key containing the body of the message.
113
     *
114
     * @param string $message HTTP request or response to parse.
115
     *
116
     * @return array
117
     */
118
    public static function parseMessage($message)
119
    {
120
        if (!$message) {
121
            throw new \InvalidArgumentException('Invalid message');
122
        }
123
 
124
        $message = ltrim($message, "\r\n");
125
 
126
        $messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
127
 
128
        if ($messageParts === false || count($messageParts) !== 2) {
129
            throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
130
        }
131
 
132
        list($rawHeaders, $body) = $messageParts;
133
        $rawHeaders .= "\r\n"; // Put back the delimiter we split previously
134
        $headerParts = preg_split("/\r?\n/", $rawHeaders, 2);
135
 
136
        if ($headerParts === false || count($headerParts) !== 2) {
137
            throw new \InvalidArgumentException('Invalid message: Missing status line');
138
        }
139
 
140
        list($startLine, $rawHeaders) = $headerParts;
141
 
142
        if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') {
143
            // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
144
            $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders);
145
        }
146
 
147
        /** @var array[] $headerLines */
148
        $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);
149
 
150
        // If these aren't the same, then one line didn't match and there's an invalid header.
151
        if ($count !== substr_count($rawHeaders, "\n")) {
152
            // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4
153
            if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
154
                throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
155
            }
156
 
157
            throw new \InvalidArgumentException('Invalid header syntax');
158
        }
159
 
160
        $headers = [];
161
 
162
        foreach ($headerLines as $headerLine) {
163
            $headers[$headerLine[1]][] = $headerLine[2];
164
        }
165
 
166
        return [
167
            'start-line' => $startLine,
168
            'headers' => $headers,
169
            'body' => $body,
170
        ];
171
    }
172
 
173
    /**
174
     * Constructs a URI for an HTTP request message.
175
     *
176
     * @param string $path    Path from the start-line
177
     * @param array  $headers Array of headers (each value an array).
178
     *
179
     * @return string
180
     */
181
    public static function parseRequestUri($path, array $headers)
182
    {
183
        $hostKey = array_filter(array_keys($headers), function ($k) {
184
            return strtolower($k) === 'host';
185
        });
186
 
187
        // If no host is found, then a full URI cannot be constructed.
188
        if (!$hostKey) {
189
            return $path;
190
        }
191
 
192
        $host = $headers[reset($hostKey)][0];
193
        $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
194
 
195
        return $scheme . '://' . $host . '/' . ltrim($path, '/');
196
    }
197
 
198
    /**
199
     * Parses a request message string into a request object.
200
     *
201
     * @param string $message Request message string.
202
     *
203
     * @return Request
204
     */
205
    public static function parseRequest($message)
206
    {
207
        $data = self::parseMessage($message);
208
        $matches = [];
209
        if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
210
            throw new \InvalidArgumentException('Invalid request string');
211
        }
212
        $parts = explode(' ', $data['start-line'], 3);
213
        $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';
214
 
215
        $request = new Request(
216
            $parts[0],
217
            $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1],
218
            $data['headers'],
219
            $data['body'],
220
            $version
221
        );
222
 
223
        return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
224
    }
225
 
226
    /**
227
     * Parses a response message string into a response object.
228
     *
229
     * @param string $message Response message string.
230
     *
231
     * @return Response
232
     */
233
    public static function parseResponse($message)
234
    {
235
        $data = self::parseMessage($message);
236
        // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space
237
        // between status-code and reason-phrase is required. But browsers accept
238
        // responses without space and reason as well.
239
        if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
240
            throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']);
241
        }
242
        $parts = explode(' ', $data['start-line'], 3);
243
 
244
        return new Response(
245
            (int) $parts[1],
246
            $data['headers'],
247
            $data['body'],
248
            explode('/', $parts[0])[1],
249
            isset($parts[2]) ? $parts[2] : null
250
        );
251
    }
252
}