|
2 | 2 |
|
3 | 3 | namespace Recca0120\LaravelParallel;
|
4 | 4 |
|
5 |
| -use GuzzleHttp\Psr7\Message; |
6 | 5 | use Illuminate\Http\Response;
|
| 6 | +use InvalidArgumentException; |
7 | 7 | use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
|
8 | 8 |
|
9 | 9 | class ResponseIdentifier
|
10 | 10 | {
|
| 11 | + private const HEADER_REGEX = "(^([^()<>@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; |
| 12 | + private const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; |
| 13 | + |
11 | 14 | /**
|
12 | 15 | * @var string
|
13 | 16 | */
|
@@ -40,9 +43,84 @@ public static function fromSymfonyResponse(SymfonyResponse $response): self
|
40 | 43 |
|
41 | 44 | public static function fromMessage(string $message): self
|
42 | 45 | {
|
43 |
| - $response = Message::parseResponse(PreventEcho::prevent($message)); |
| 46 | + $data = self::parseMessage(PreventEcho::prevent($message)); |
| 47 | + // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space |
| 48 | + // between status-code and reason-phrase is required. But browsers accept |
| 49 | + // responses without space and reason as well. |
| 50 | + if (! preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { |
| 51 | + throw new InvalidArgumentException('Invalid response string: '.$data['start-line']); |
| 52 | + } |
| 53 | + $parts = explode(' ', $data['start-line'], 3); |
| 54 | + |
| 55 | + return new self($data['body'], (int) $parts[1], $data['headers']); |
| 56 | + } |
| 57 | + |
| 58 | + /** |
| 59 | + * Parses an HTTP message into an associative array. |
| 60 | + * |
| 61 | + * The array contains the "start-line" key containing the start line of |
| 62 | + * the message, "headers" key containing an associative array of header |
| 63 | + * array values, and a "body" key containing the body of the message. |
| 64 | + * |
| 65 | + * @link https://github.com/guzzle/psr7/blob/2.4.0/src/Message.php#L114-L167 |
| 66 | + * |
| 67 | + * @license https://github.com/guzzle/psr7/blob/2.4.0/LICENSE |
| 68 | + * |
| 69 | + * @param string $message HTTP request or response to parse. |
| 70 | + */ |
| 71 | + public static function parseMessage(string $message): array |
| 72 | + { |
| 73 | + if (! $message) { |
| 74 | + throw new InvalidArgumentException('Invalid message'); |
| 75 | + } |
| 76 | + |
| 77 | + $message = ltrim($message, "\r\n"); |
| 78 | + |
| 79 | + $messageParts = preg_split("/\r?\n\r?\n/", $message, 2); |
| 80 | + |
| 81 | + if ($messageParts === false || count($messageParts) !== 2) { |
| 82 | + throw new InvalidArgumentException('Invalid message: Missing header delimiter'); |
| 83 | + } |
| 84 | + |
| 85 | + [$rawHeaders, $body] = $messageParts; |
| 86 | + $rawHeaders .= "\r\n"; // Put back the delimiter we split previously |
| 87 | + $headerParts = preg_split("/\r?\n/", $rawHeaders, 2); |
| 88 | + |
| 89 | + if ($headerParts === false || count($headerParts) !== 2) { |
| 90 | + throw new InvalidArgumentException('Invalid message: Missing status line'); |
| 91 | + } |
| 92 | + |
| 93 | + [$startLine, $rawHeaders] = $headerParts; |
| 94 | + |
| 95 | + if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') { |
| 96 | + // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0 |
| 97 | + $rawHeaders = preg_replace(self::HEADER_FOLD_REGEX, ' ', $rawHeaders); |
| 98 | + } |
| 99 | + |
| 100 | + /** @var array[] $headerLines */ |
| 101 | + $count = preg_match_all(self::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER); |
| 102 | + |
| 103 | + // If these aren't the same, then one line didn't match and there's an invalid header. |
| 104 | + if ($count !== substr_count($rawHeaders, "\n")) { |
| 105 | + // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4 |
| 106 | + if (preg_match(self::HEADER_FOLD_REGEX, $rawHeaders)) { |
| 107 | + throw new InvalidArgumentException('Invalid header syntax: Obsolete line folding'); |
| 108 | + } |
| 109 | + |
| 110 | + throw new InvalidArgumentException('Invalid header syntax'); |
| 111 | + } |
| 112 | + |
| 113 | + $headers = []; |
| 114 | + |
| 115 | + foreach ($headerLines as $headerLine) { |
| 116 | + $headers[$headerLine[1]][] = $headerLine[2]; |
| 117 | + } |
44 | 118 |
|
45 |
| - return new self((string) $response->getBody(), $response->getStatusCode(), $response->getHeaders()); |
| 119 | + return [ |
| 120 | + 'start-line' => $startLine, |
| 121 | + 'headers' => $headers, |
| 122 | + 'body' => $body, |
| 123 | + ]; |
46 | 124 | }
|
47 | 125 |
|
48 | 126 | public function toMessage(): string
|
|
0 commit comments