Conditions | 41 |
Paths | > 20000 |
Total Lines | 160 |
Code Lines | 80 |
Lines | 0 |
Ratio | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
1 | <?php |
||
120 | public function parseRequest($headers, $remoteSocketUri, $localSocketUri) |
||
121 | { |
||
122 | // additional, stricter safe-guard for request line |
||
123 | // because request parser doesn't properly cope with invalid ones |
||
124 | $start = array(); |
||
125 | if (!\preg_match('#^(?<method>[^ ]+) (?<target>[^ ]+) HTTP/(?<version>\d\.\d)#m', $headers, $start)) { |
||
126 | throw new \InvalidArgumentException('Unable to parse invalid request-line'); |
||
127 | } |
||
128 | |||
129 | // only support HTTP/1.1 and HTTP/1.0 requests |
||
130 | if ($start['version'] !== '1.1' && $start['version'] !== '1.0') { |
||
131 | throw new \InvalidArgumentException('Received request with invalid protocol version', Response::STATUS_VERSION_NOT_SUPPORTED); |
||
132 | } |
||
133 | |||
134 | // match all request header fields into array, thanks to @kelunik for checking the HTTP specs and coming up with this regex |
||
135 | $matches = array(); |
||
136 | $n = \preg_match_all('/^([^()<>@,;:\\\"\/\[\]?={}\x01-\x20\x7F]++):[\x20\x09]*+((?:[\x20\x09]*+[\x21-\x7E\x80-\xFF]++)*+)[\x20\x09]*+[\r]?+\n/m', $headers, $matches, \PREG_SET_ORDER); |
||
137 | |||
138 | // check number of valid header fields matches number of lines + request line |
||
139 | if (\substr_count($headers, "\n") !== $n + 1) { |
||
140 | throw new \InvalidArgumentException('Unable to parse invalid request header fields'); |
||
141 | } |
||
142 | |||
143 | // format all header fields into associative array |
||
144 | $host = null; |
||
145 | $fields = array(); |
||
146 | foreach ($matches as $match) { |
||
147 | $fields[$match[1]][] = $match[2]; |
||
148 | |||
149 | // match `Host` request header |
||
150 | if ($host === null && \strtolower($match[1]) === 'host') { |
||
151 | $host = $match[2]; |
||
152 | } |
||
153 | } |
||
154 | |||
155 | // create new obj implementing ServerRequestInterface by preserving all |
||
156 | // previous properties and restoring original request-target |
||
157 | $serverParams = array( |
||
158 | 'REQUEST_TIME' => \time(), |
||
159 | 'REQUEST_TIME_FLOAT' => \microtime(true) |
||
160 | ); |
||
161 | |||
162 | // scheme is `http` unless TLS is used |
||
163 | $localParts = $localSocketUri === null ? array() : \parse_url($localSocketUri); |
||
164 | if (isset($localParts['scheme']) && $localParts['scheme'] === 'tls') { |
||
165 | $scheme = 'https://'; |
||
166 | $serverParams['HTTPS'] = 'on'; |
||
167 | } else { |
||
168 | $scheme = 'http://'; |
||
169 | } |
||
170 | |||
171 | // default host if unset comes from local socket address or defaults to localhost |
||
172 | $hasHost = $host !== null; |
||
173 | if ($host === null) { |
||
174 | $host = isset($localParts['host'], $localParts['port']) ? $localParts['host'] . ':' . $localParts['port'] : '127.0.0.1'; |
||
175 | } |
||
176 | |||
177 | if ($start['method'] === 'OPTIONS' && $start['target'] === '*') { |
||
178 | // support asterisk-form for `OPTIONS *` request line only |
||
179 | $uri = $scheme . $host; |
||
180 | } elseif ($start['method'] === 'CONNECT') { |
||
181 | $parts = \parse_url('tcp://' . $start['target']); |
||
182 | |||
183 | // check this is a valid authority-form request-target (host:port) |
||
184 | if (!isset($parts['scheme'], $parts['host'], $parts['port']) || \count($parts) !== 3) { |
||
185 | throw new \InvalidArgumentException('CONNECT method MUST use authority-form request target'); |
||
186 | } |
||
187 | $uri = $scheme . $start['target']; |
||
188 | } else { |
||
189 | // support absolute-form or origin-form for proxy requests |
||
190 | if ($start['target'][0] === '/') { |
||
191 | $uri = $scheme . $host . $start['target']; |
||
192 | } else { |
||
193 | // ensure absolute-form request-target contains a valid URI |
||
194 | $parts = \parse_url($start['target']); |
||
195 | |||
196 | // make sure value contains valid host component (IP or hostname), but no fragment |
||
197 | if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'http' || isset($parts['fragment'])) { |
||
198 | throw new \InvalidArgumentException('Invalid absolute-form request-target'); |
||
199 | } |
||
200 | |||
201 | $uri = $start['target']; |
||
202 | } |
||
203 | } |
||
204 | |||
205 | // apply REMOTE_ADDR and REMOTE_PORT if source address is known |
||
206 | // address should always be known, unless this is over Unix domain sockets (UDS) |
||
207 | if ($remoteSocketUri !== null) { |
||
208 | $remoteAddress = \parse_url($remoteSocketUri); |
||
209 | $serverParams['REMOTE_ADDR'] = $remoteAddress['host']; |
||
210 | $serverParams['REMOTE_PORT'] = $remoteAddress['port']; |
||
211 | } |
||
212 | |||
213 | // apply SERVER_ADDR and SERVER_PORT if server address is known |
||
214 | // address should always be known, even for Unix domain sockets (UDS) |
||
215 | // but skip UDS as it doesn't have a concept of host/port. |
||
216 | if ($localSocketUri !== null && isset($localParts['host'], $localParts['port'])) { |
||
217 | $serverParams['SERVER_ADDR'] = $localParts['host']; |
||
218 | $serverParams['SERVER_PORT'] = $localParts['port']; |
||
219 | } |
||
220 | |||
221 | $request = new ServerRequest( |
||
222 | $start['method'], |
||
223 | $uri, |
||
224 | $fields, |
||
225 | '', |
||
226 | $start['version'], |
||
227 | $serverParams |
||
228 | ); |
||
229 | |||
230 | // only assign request target if it is not in origin-form (happy path for most normal requests) |
||
231 | if ($start['target'][0] !== '/') { |
||
232 | $request = $request->withRequestTarget($start['target']); |
||
233 | } |
||
234 | |||
235 | if ($hasHost) { |
||
236 | // Optional Host request header value MUST be valid (host and optional port) |
||
237 | $parts = \parse_url('http://' . $request->getHeaderLine('Host')); |
||
238 | |||
239 | // make sure value contains valid host component (IP or hostname) |
||
240 | if (!$parts || !isset($parts['scheme'], $parts['host'])) { |
||
|
|||
241 | $parts = false; |
||
242 | } |
||
243 | |||
244 | // make sure value does not contain any other URI component |
||
245 | if (\is_array($parts)) { |
||
246 | unset($parts['scheme'], $parts['host'], $parts['port']); |
||
247 | } |
||
248 | if ($parts === false || $parts) { |
||
249 | throw new \InvalidArgumentException('Invalid Host header value'); |
||
250 | } |
||
251 | } elseif (!$hasHost && $start['version'] === '1.1' && $start['method'] !== 'CONNECT') { |
||
252 | // require Host request header for HTTP/1.1 (except for CONNECT method) |
||
253 | throw new \InvalidArgumentException('Missing required Host request header'); |
||
254 | } elseif (!$hasHost) { |
||
255 | // remove default Host request header for HTTP/1.0 when not explicitly given |
||
256 | $request = $request->withoutHeader('Host'); |
||
257 | } |
||
258 | |||
259 | // ensure message boundaries are valid according to Content-Length and Transfer-Encoding request headers |
||
260 | if ($request->hasHeader('Transfer-Encoding')) { |
||
261 | if (\strtolower($request->getHeaderLine('Transfer-Encoding')) !== 'chunked') { |
||
262 | throw new \InvalidArgumentException('Only chunked-encoding is allowed for Transfer-Encoding', Response::STATUS_NOT_IMPLEMENTED); |
||
263 | } |
||
264 | |||
265 | // Transfer-Encoding: chunked and Content-Length header MUST NOT be used at the same time |
||
266 | // as per https://tools.ietf.org/html/rfc7230#section-3.3.3 |
||
267 | if ($request->hasHeader('Content-Length')) { |
||
268 | throw new \InvalidArgumentException('Using both `Transfer-Encoding: chunked` and `Content-Length` is not allowed', Response::STATUS_BAD_REQUEST); |
||
269 | } |
||
270 | } elseif ($request->hasHeader('Content-Length')) { |
||
271 | $string = $request->getHeaderLine('Content-Length'); |
||
272 | |||
273 | if ((string)(int)$string !== $string) { |
||
274 | // Content-Length value is not an integer or not a single integer |
||
275 | throw new \InvalidArgumentException('The value of `Content-Length` is not valid', Response::STATUS_BAD_REQUEST); |
||
276 | } |
||
277 | } |
||
278 | |||
279 | return $request; |
||
280 | } |
||
282 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.