friendship ended with social-app. php is my new best friend
1<?php 2 3namespace React\Http\Io; 4 5use Evenement\EventEmitter; 6use Psr\Http\Message\ServerRequestInterface; 7use React\Http\Message\Response; 8use React\Http\Message\ServerRequest; 9use React\Socket\ConnectionInterface; 10use Exception; 11 12/** 13 * [Internal] Parses an incoming request header from an input stream 14 * 15 * This is used internally to parse the request header from the connection and 16 * then process the remaining connection as the request body. 17 * 18 * @event headers 19 * @event error 20 * 21 * @internal 22 */ 23class RequestHeaderParser extends EventEmitter 24{ 25 private $maxSize = 8192; 26 27 /** @var Clock */ 28 private $clock; 29 30 /** @var array<string|int,array<string,string>> */ 31 private $connectionParams = array(); 32 33 public function __construct(Clock $clock) 34 { 35 $this->clock = $clock; 36 } 37 38 public function handle(ConnectionInterface $conn) 39 { 40 $buffer = ''; 41 $maxSize = $this->maxSize; 42 $that = $this; 43 $conn->on('data', $fn = function ($data) use (&$buffer, &$fn, $conn, $maxSize, $that) { 44 // append chunk of data to buffer and look for end of request headers 45 $buffer .= $data; 46 $endOfHeader = \strpos($buffer, "\r\n\r\n"); 47 48 // reject request if buffer size is exceeded 49 if ($endOfHeader > $maxSize || ($endOfHeader === false && isset($buffer[$maxSize]))) { 50 $conn->removeListener('data', $fn); 51 $fn = null; 52 53 $that->emit('error', array( 54 new \OverflowException("Maximum header size of {$maxSize} exceeded.", Response::STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE), 55 $conn 56 )); 57 return; 58 } 59 60 // ignore incomplete requests 61 if ($endOfHeader === false) { 62 return; 63 } 64 65 // request headers received => try to parse request 66 $conn->removeListener('data', $fn); 67 $fn = null; 68 69 try { 70 $request = $that->parseRequest( 71 (string)\substr($buffer, 0, $endOfHeader + 2), 72 $conn 73 ); 74 } catch (Exception $exception) { 75 $buffer = ''; 76 $that->emit('error', array( 77 $exception, 78 $conn 79 )); 80 return; 81 } 82 83 $contentLength = 0; 84 if ($request->hasHeader('Transfer-Encoding')) { 85 $contentLength = null; 86 } elseif ($request->hasHeader('Content-Length')) { 87 $contentLength = (int)$request->getHeaderLine('Content-Length'); 88 } 89 90 if ($contentLength === 0) { 91 // happy path: request body is known to be empty 92 $stream = new EmptyBodyStream(); 93 $request = $request->withBody($stream); 94 } else { 95 // otherwise body is present => delimit using Content-Length or ChunkedDecoder 96 $stream = new CloseProtectionStream($conn); 97 if ($contentLength !== null) { 98 $stream = new LengthLimitedStream($stream, $contentLength); 99 } else { 100 $stream = new ChunkedDecoder($stream); 101 } 102 103 $request = $request->withBody(new HttpBodyStream($stream, $contentLength)); 104 } 105 106 $bodyBuffer = isset($buffer[$endOfHeader + 4]) ? \substr($buffer, $endOfHeader + 4) : ''; 107 $buffer = ''; 108 $that->emit('headers', array($request, $conn)); 109 110 if ($bodyBuffer !== '') { 111 $conn->emit('data', array($bodyBuffer)); 112 } 113 114 // happy path: request body is known to be empty => immediately end stream 115 if ($contentLength === 0) { 116 $stream->emit('end'); 117 $stream->close(); 118 } 119 }); 120 } 121 122 /** 123 * @param string $headers buffer string containing request headers only 124 * @param ConnectionInterface $connection 125 * @return ServerRequestInterface 126 * @throws \InvalidArgumentException 127 * @internal 128 */ 129 public function parseRequest($headers, ConnectionInterface $connection) 130 { 131 // reuse same connection params for all server params for this connection 132 $cid = \PHP_VERSION_ID < 70200 ? \spl_object_hash($connection) : \spl_object_id($connection); 133 if (isset($this->connectionParams[$cid])) { 134 $serverParams = $this->connectionParams[$cid]; 135 } else { 136 // assign new server params for new connection 137 $serverParams = array(); 138 139 // scheme is `http` unless TLS is used 140 $localSocketUri = $connection->getLocalAddress(); 141 $localParts = $localSocketUri === null ? array() : \parse_url($localSocketUri); 142 if (isset($localParts['scheme']) && $localParts['scheme'] === 'tls') { 143 $serverParams['HTTPS'] = 'on'; 144 } 145 146 // apply SERVER_ADDR and SERVER_PORT if server address is known 147 // address should always be known, even for Unix domain sockets (UDS) 148 // but skip UDS as it doesn't have a concept of host/port. 149 if ($localSocketUri !== null && isset($localParts['host'], $localParts['port'])) { 150 $serverParams['SERVER_ADDR'] = $localParts['host']; 151 $serverParams['SERVER_PORT'] = $localParts['port']; 152 } 153 154 // apply REMOTE_ADDR and REMOTE_PORT if source address is known 155 // address should always be known, unless this is over Unix domain sockets (UDS) 156 $remoteSocketUri = $connection->getRemoteAddress(); 157 if ($remoteSocketUri !== null) { 158 $remoteAddress = \parse_url($remoteSocketUri); 159 $serverParams['REMOTE_ADDR'] = $remoteAddress['host']; 160 $serverParams['REMOTE_PORT'] = $remoteAddress['port']; 161 } 162 163 // remember server params for all requests from this connection, reset on connection close 164 $this->connectionParams[$cid] = $serverParams; 165 $params =& $this->connectionParams; 166 $connection->on('close', function () use (&$params, $cid) { 167 assert(\is_array($params)); 168 unset($params[$cid]); 169 }); 170 } 171 172 // create new obj implementing ServerRequestInterface by preserving all 173 // previous properties and restoring original request-target 174 $serverParams['REQUEST_TIME'] = (int) ($now = $this->clock->now()); 175 $serverParams['REQUEST_TIME_FLOAT'] = $now; 176 177 return ServerRequest::parseMessage($headers, $serverParams); 178 } 179}