| 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.