$output = []; foreach ($headers as $name => $values) { foreach ((array) $values as $value) { foreach (self::TABLE as $index => list($header_name)) { if ($name == $header_name) { break; } } if ($name == $header_name) { if (++$index < 0x10) { $output[] = \chr($index); } else { $output[] = "" . \chr($index - 0xf); } } elseif (\strlen($name) < 0x7f) { $output[] = "" . \chr(\strlen($name)) . $name; } else { $output[] = "" . self::encode_dynamic_integer(\strlen($name) - 0x7f) . $name; } if (\strlen($value) < 0x7f) { $output[] = \chr(\strlen($value)) . $value; } else { $output[] = "" . self::encode_dynamic_integer(\strlen($value) - 0x7f) . $value; } } } return implode($output); } } HPack::init();
public function parser(Client $client) : \Generator { // @TODO apply restrictions $maxHeaderSize = $this->options->maxHeaderSize; $maxPendingSize = $this->options->maxPendingSize; $maxBodySize = $this->options->maxBodySize; // $bodyEmitSize = $this->options->ioGranularity; // redundant because data frames (?) assert(!\defined("Aerys\\DEBUG_HTTP2") || (print "INIT\n")); $this->writeFrame($client, pack("nN", self::INITIAL_WINDOW_SIZE, $maxBodySize), self::SETTINGS, self::NOFLAG); $this->writeFrame($client, "þÿÿ", self::WINDOW_UPDATE, self::NOFLAG); // effectively disabling flow control... $headers = []; $table = new HPack(); $initialWindowSize = 65536; $client->window = 65536; $buffer = yield; while (1) { while (\strlen($buffer) < 9) { $buffer .= yield; } $length = unpack("N", "{$buffer}")[1]; // @TODO SETTINGS: MAX_FRAME_SIZE $type = $buffer[3]; $flags = $buffer[4]; $id = unpack("N", substr($buffer, 5, 4))[1]; // the highest bit must be zero... but RFC does not specify what should happen when it is set to 1? if ($id < 0) { $id = ~$id; } assert(!\defined("Aerys\\DEBUG_HTTP2") || (print "Flag: " . bin2hex($flags) . "; Type: " . bin2hex($type) . "; Stream: {$id}; Length: {$length}\n")); $buffer = substr($buffer, 9); switch ($type) { case self::DATA: if ($id === 0) { $error = self::PROTOCOL_ERROR; break 2; } if (!isset($client->bodyPromisors[$id])) { if (isset($headers[$id])) { $error = self::PROTOCOL_ERROR; break 2; } else { $error = self::STREAM_CLOSED; while (\strlen($buffer) < $length) { $buffer .= yield; } $buffer = substr($buffer, $length); goto stream_error; } } if (($flags & self::PADDED) != "") { if ($buffer === "") { $buffer = yield; } $padding = ord($buffer[0]); $buffer = substr($buffer, 1); $length--; if ($padding >= $length) { $error = self::PROTOCOL_ERROR; break 2; } } else { $padding = 0; } while (\strlen($buffer) < $length) { $buffer .= yield; } if (($flags & self::END_STREAM) != "") { $type = HttpDriver::ENTITY_RESULT; } else { $type = HttpDriver::ENTITY_PART; } assert(!\defined("Aerys\\DEBUG_HTTP2") || (print "DATA({$length}): " . substr($buffer, 0, $length - $padding) . "\n")); ($this->emit)([$type, ["id" => $id, "protocol" => "2.0", "body" => substr($buffer, 0, $length - $padding)], null], $client); $buffer = substr($buffer, $length); continue 2; case self::HEADERS: if (($flags & self::PADDED) != "") { if ($buffer == "") { $buffer = yield; } $padding = ord($buffer[0]); $buffer = substr($buffer, 1); $length--; if ($padding >= $length) { $error = self::PROTOCOL_ERROR; break 2; } } else { $padding = 0; } if (($flags & self::PRIORITY_FLAG) != "") { while (\strlen($buffer) < 5) { $buffer .= yield; } /* Not yet needed?! $dependency = unpack("N", $buffer)[1]; if ($dependency < 0) { $dependency = ~$dependency; $exclusive = true; } else { $exclusive = false; } $weight = $buffer[4]; */ $buffer = substr($buffer, 5); $length -= 5; } while (\strlen($buffer) < $length) { $buffer .= yield; } $packed = substr($buffer, 0, $length); $buffer = substr($buffer, $length); $streamEnd = ($flags & self::END_STREAM) != ""; if (($flags & self::END_HEADERS) != "") { goto parse_headers; } else { $headers[$id] = $packed; } continue 2; case self::PRIORITY: if ($length != 5) { $error = self::FRAME_SIZE_ERROR; while (\strlen($buffer) < $length) { $buffer .= yield; } $buffer = substr($buffer, $length); goto stream_error; } if ($id === 0) { $error = self::PROTOCOL_ERROR; break 2; } while (\strlen($buffer) < 5) { $buffer .= yield; } /* @TODO PRIORITY frames not yet handled?! $dependency = unpack("N", $buffer); if ($dependency < 0) { $dependency = ~$dependency; $exclusive = true; } else { $exclusive = false; } if ($dependency == 0) { $error = self::PROTOCOL_ERROR; break 2; } $weight = $buffer[4]; */ $buffer = substr($buffer, 5); assert(!defined("Aerys\\DEBUG_HTTP2") || (print "PRIORITY: - \n")); continue 2; case self::RST_STREAM: if ($length != 4) { $error = self::FRAME_SIZE_ERROR; break 2; } if ($id === 0) { $error = self::PROTOCOL_ERROR; break 2; } while (\strlen($buffer) < 4) { $buffer .= yield; } $error = unpack("N", $buffer)[1]; assert(!defined("Aerys\\DEBUG_HTTP2") || (print "RST_STREAM: {$error}\n")); if (isset($client->bodyPromisors[$id])) { $client->bodyPromisors[$id]->fail(new ClientException()); } unset($headers[$id], $client->streamWindow[$id], $client->streamEnd[$id], $client->streamWindowBuffer[$id], $client->bodyPromisors[$id]); continue 2; case self::SETTINGS: if ($id !== 0) { $error = self::PROTOCOL_ERROR; break 2; } if (($flags & self::ACK) != "") { if ($length) { $error = self::PROTOCOL_ERROR; break 2; } assert(!defined("Aerys\\DEBUG_HTTP2") || (print "SETTINGS: ACK\n")); // got ACK continue 2; } elseif ($length % 6 != 0) { $error = self::FRAME_SIZE_ERROR; break 2; } while ($length > 0) { while (\strlen($buffer) < 6) { $buffer .= yield; } $unpacked = unpack("nsetting/Nvalue", $buffer); // $unpacked["value" >= 0 assert(!defined("Aerys\\DEBUG_HTTP2") || (print "SETTINGS({$unpacked["setting"]}): {$unpacked["value"]}\n")); switch ($unpacked["setting"]) { case self::MAX_HEADER_LIST_SIZE: if ($unpacked["value"] >= 4096) { $error = self::PROTOCOL_ERROR; // @TODO correct error?? break 4; } $table->table_resize($unpacked["value"]); break; case self::INITIAL_WINDOW_SIZE: if ($unpacked["value"] >= 1 << 31) { $error = self::FLOW_CONTROL_ERROR; break 4; } $initialWindowSize = $unpacked["value"]; break; } $buffer = substr($buffer, 6); $length -= 6; } $this->writeFrame($client, "", self::SETTINGS, self::ACK); continue 2; // PUSH_PROMISE sent by client is a PROTOCOL_ERROR (just like undefined frame types) // PUSH_PROMISE sent by client is a PROTOCOL_ERROR (just like undefined frame types) case self::PING: if ($length != 8) { $error = self::FRAME_SIZE_ERROR; break 2; } if ($id !== 0) { $error = self::PROTOCOL_ERROR; break 2; } while (\strlen($buffer) < 8) { $buffer .= yield; } $data = substr($buffer, 0, 8); if ($flags & self::ACK) { // @TODO resolve ping } else { $this->writeFrame($client, $data, self::PING, self::ACK); } $buffer = substr($buffer, 8); continue 2; case self::GOAWAY: if ($id !== 0) { $error = self::PROTOCOL_ERROR; break 2; } $lastId = unpack("N", $buffer)[1]; // the highest bit must be zero... but RFC does not specify what should happen when it is set to 1? if ($lastId < 0) { $lastId = ~$lastId; } $error = unpack("N", substr($buffer, 4, 4))[1]; $buffer = substr($buffer, 8); $length -= 8; while (\strlen($buffer) < $length) { $buffer .= yield; } if ($error !== 0) { // ($this->emit)([HttpDriver::ERROR, ["body" => substr($buffer, 0, $length)], $error], $client); } assert(!defined("Aerys\\DEBUG_HTTP2") || (print "GOAWAY({$error}): " . substr($buffer, 0, $length) . "\n")); return; // @TODO verify whether it needs to be manually closed // @TODO verify whether it needs to be manually closed case self::WINDOW_UPDATE: while (\strlen($buffer) < 4) { $buffer .= yield; } if ($buffer === "") { $error = self::PROTOCOL_ERROR; if ($id) { goto stream_error; } else { break 2; } } $windowSize = unpack("N", $buffer)[1]; if ($id) { if (!isset($client->streamWindow[$id])) { $client->streamWindow[$id] = $initialWindowSize + $windowSize; } else { $client->streamWindow[$id] += $windowSize; } if (isset($client->streamWindowBuffer[$id])) { $this->tryDataSend($client, $id); } } else { $client->window += $windowSize; foreach ($client->streamWindowBuffer as $stream => $data) { $this->tryDataSend($client, $stream); if ($client->window == 0) { break; } } } $buffer = substr($buffer, 4); continue 2; case self::CONTINUATION: if (!isset($headers[$id])) { $error = self::PROTOCOL_ERROR; break 2; } while (\strlen($buffer) < $length) { $buffer .= yield; } $headers[$id] .= substr($buffer, 0, $length); $buffer = substr($buffer, $length); if (($flags & self::END_HEADERS) != "") { $packed = $headers[$id]; unset($headers[$id]); goto parse_headers; } continue 2; default: print "BAD TYPE: " . ord($type) . "\n"; $error = self::PROTOCOL_ERROR; break 2; } parse_headers: $headerList = $table->decode($padding ? substr($packed, 0, -$padding) : $packed); if ($headerList === null) { $error = self::COMPRESSION_ERROR; break; } assert(!defined("Aerys\\DEBUG_HTTP2") || (print "HEADER(" . (\strlen($packed) - $padding) . "): " . implode(" | ", array_map(function ($x) { return implode(": ", $x); }, $headerList)) . "\n")); $headerArray = []; foreach ($headerList as list($name, $value)) { $headerArray[$name][] = $value; } $parseResult = ["id" => $id, "trace" => $headerList, "protocol" => "2.0", "method" => $headerArray[":method"][0], "uri" => !empty($headerArray[":authority"][0]) ? "{$headerArray[":scheme"][0]}://{$headerArray[":authority"][0]}{$headerArray[":path"][0]}" : $headerArray[":path"][0], "headers" => $headerArray, "body" => ""]; if (!isset($client->streamWindow[$id])) { $client->streamWindow[$id] = $initialWindowSize; } $client->streamWindowBuffer[$id] = ""; if ($streamEnd) { ($this->emit)([HttpDriver::RESULT, $parseResult, null], $client); } else { ($this->emit)([HttpDriver::ENTITY_HEADERS, $parseResult, null], $client); } continue; stream_error: $this->writeFrame($client, pack("N", $error), self::RST_STREAM, self::NOFLAG, $id); assert(!defined("Aerys\\DEBUG_HTTP2") || (print "Stream ERROR: {$error}\n")); continue; } $this->writeFrame($client, pack("NN", 0, $error), self::GOAWAY, self::NOFLAG); assert(!defined("Aerys\\DEBUG_HTTP2") || (print "Connection ERROR: {$error}\n")); }