Пример #1
0
        $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();
Пример #2
0
 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"));
 }