forked from reactphp/http
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRequestHeaderParser.php
More file actions
185 lines (153 loc) · 6.53 KB
/
RequestHeaderParser.php
File metadata and controls
185 lines (153 loc) · 6.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
<?php
namespace React\Http;
use Evenement\EventEmitter;
use Exception;
use RingCentral\Psr7 as g7;
/**
* @event headers
* @event error
*
* @internal
*/
class RequestHeaderParser extends EventEmitter
{
private $buffer = '';
private $maxSize = 4096;
private $uri;
public function __construct($localSocketUri = '')
{
$this->uri = $localSocketUri;
}
public function feed($data)
{
$this->buffer .= $data;
$endOfHeader = strpos($this->buffer, "\r\n\r\n");
if (false !== $endOfHeader) {
$currentHeaderSize = $endOfHeader;
} else {
$currentHeaderSize = strlen($this->buffer);
}
if ($currentHeaderSize > $this->maxSize) {
$this->emit('error', array(new \OverflowException("Maximum header size of {$this->maxSize} exceeded.", 431), $this));
$this->removeAllListeners();
return;
}
if (false !== $endOfHeader) {
try {
$this->parseAndEmitRequest();
} catch (Exception $exception) {
$this->emit('error', array($exception));
}
$this->removeAllListeners();
}
}
private function parseAndEmitRequest()
{
list($request, $bodyBuffer) = $this->parseRequest($this->buffer);
$this->emit('headers', array($request, $bodyBuffer));
}
private function parseRequest($data)
{
list($headers, $bodyBuffer) = explode("\r\n\r\n", $data, 2);
// parser does not support asterisk-form and authority-form
// remember original target and temporarily replace and re-apply below
$originalTarget = null;
if (strpos($headers, 'OPTIONS * ') === 0) {
$originalTarget = '*';
$headers = 'OPTIONS / ' . substr($headers, 10);
} elseif (strpos($headers, 'CONNECT ') === 0) {
$parts = explode(' ', $headers, 3);
$uri = parse_url('tcp://' . $parts[1]);
// check this is a valid authority-form request-target (host:port)
if (isset($uri['scheme'], $uri['host'], $uri['port']) && count($uri) === 3) {
$originalTarget = $parts[1];
$parts[1] = '/';
$headers = implode(' ', $parts);
}
}
// parse request headers into obj implementing RequestInterface
$request = g7\parse_request($headers);
// create new obj implementing ServerRequestInterface by preserving all
// previous properties and restoring original request-target
$target = $request->getRequestTarget();
$request = new ServerRequest(
$request->getMethod(),
$request->getUri(),
$request->getHeaders(),
$request->getBody(),
$request->getProtocolVersion()
);
$request = $request->withRequestTarget($target);
// re-apply actual request target from above
if ($originalTarget !== null) {
$uri = $request->getUri()->withPath('');
// re-apply host and port from request-target if given
$parts = parse_url('tcp://' . $originalTarget);
if (isset($parts['host'], $parts['port'])) {
$uri = $uri->withHost($parts['host'])->withPort($parts['port']);
}
$request = $request->withUri(
$uri,
true
)->withRequestTarget($originalTarget);
}
// only support HTTP/1.1 and HTTP/1.0 requests
if ($request->getProtocolVersion() !== '1.1' && $request->getProtocolVersion() !== '1.0') {
throw new \InvalidArgumentException('Received request with invalid protocol version', 505);
}
// ensure absolute-form request-target contains a valid URI
if (strpos($request->getRequestTarget(), '://') !== false) {
$parts = parse_url($request->getRequestTarget());
// make sure value contains valid host component (IP or hostname), but no fragment
if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'http' || isset($parts['fragment'])) {
throw new \InvalidArgumentException('Invalid absolute-form request-target');
}
}
// Optional Host header value MUST be valid (host and optional port)
if ($request->hasHeader('Host')) {
$parts = parse_url('http://' . $request->getHeaderLine('Host'));
// make sure value contains valid host component (IP or hostname)
if (!$parts || !isset($parts['scheme'], $parts['host'])) {
$parts = false;
}
// make sure value does not contain any other URI component
unset($parts['scheme'], $parts['host'], $parts['port']);
if ($parts === false || $parts) {
throw new \InvalidArgumentException('Invalid Host header value');
}
}
// set URI components from socket address if not already filled via Host header
if ($request->getUri()->getHost() === '') {
$parts = parse_url($this->uri);
$request = $request->withUri(
$request->getUri()->withScheme('http')->withHost($parts['host'])->withPort($parts['port']),
true
);
}
// Do not assume this is HTTPS when this happens to be port 443
// detecting HTTPS is left up to the socket layer (TLS detection)
if ($request->getUri()->getScheme() === 'https') {
$request = $request->withUri(
$request->getUri()->withScheme('http')->withPort(443),
true
);
}
// Update request URI to "https" scheme if the connection is encrypted
$parts = parse_url($this->uri);
if (isset($parts['scheme']) && $parts['scheme'] === 'https') {
// The request URI may omit default ports here, so try to parse port
// from Host header field (if possible)
$port = $request->getUri()->getPort();
if ($port === null) {
$port = parse_url('tcp://' . $request->getHeaderLine('Host'), PHP_URL_PORT); // @codeCoverageIgnore
}
$request = $request->withUri(
$request->getUri()->withScheme('https')->withPort($port),
true
);
}
// always sanitize Host header because it contains critical routing information
$request = $request->withUri($request->getUri()->withUserInfo('u')->withUserInfo(''));
return array($request, $bodyBuffer);
}
}