diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d945128..856146e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -17,6 +17,7 @@ "password": "Password", "proxySetting": "Proxy Setting", "systemProxy": "Set as System Proxy", + "enabledHTTP2": "Enable HTTP2", "serverNotStart": "Proxy server not started", "download": "Download", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 1754de8..6cbac32 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -17,6 +17,7 @@ "password": "密码", "proxySetting": "代理设置", "systemProxy": "设置为系统代理", + "enabledHTTP2": "启用HTTP2", "serverNotStart": "未开启抓包", "download": "下载", diff --git a/lib/network/bin/configuration.dart b/lib/network/bin/configuration.dart index 7326a84..6e0ed2f 100644 --- a/lib/network/bin/configuration.dart +++ b/lib/network/bin/configuration.dart @@ -55,7 +55,7 @@ class Configuration { //远程连接 不持久化保存 String? remoteHost; - bool enabledHttp2 = false; // + bool enabledHttp2 = false; // 是否启用http2 //历史记录缓存时间 int historyCacheTime = 0; @@ -88,6 +88,8 @@ class Configuration { startup = config['startup'] ?? Platforms.isDesktop(); enableSystemProxy = config['enableSystemProxy'] ?? (config['enableDesktop'] ?? true); enableSocks5 = config['enableSocks5'] ?? true; + enabledHttp2 = config['enabledHttp2'] ?? false; + proxyPassDomains = config['proxyPassDomains'] ?? SystemProxy.proxyPassDomains; historyCacheTime = config['historyCacheTime'] ?? 0; if (config['externalProxy'] != null) { @@ -147,6 +149,7 @@ class Configuration { 'appWhitelistEnabled': appWhitelistEnabled, 'appBlacklist': appBlacklist, 'historyCacheTime': historyCacheTime, + 'enabledHttp2': enabledHttp2, 'whitelist': HostFilter.whitelist.toJson(), 'blacklist': HostFilter.blacklist.toJson(), }; diff --git a/lib/network/channel/channel.dart b/lib/network/channel/channel.dart index 5598a41..efca2a9 100644 --- a/lib/network/channel/channel.dart +++ b/lib/network/channel/channel.dart @@ -115,6 +115,10 @@ class Channel { await Future.delayed(const Duration(milliseconds: 100)); } + if (isWriting) { + logger.w("[$id] write busy"); + } + isWriting = true; try { if (!isClosed) { diff --git a/lib/network/channel/network.dart b/lib/network/channel/network.dart index 538effd..232c90c 100644 --- a/lib/network/channel/network.dart +++ b/lib/network/channel/network.dart @@ -180,7 +180,7 @@ class Server extends Network { var secureSocket = await SecureSocket.secureServer(channel.socket, certificate, bufferedData: data); channel.serverSecureSocket(secureSocket, channelContext); } catch (error, trace) { - logger.e('$hostAndPort ssl error', error: error); + logger.e('[${channel.id}] $hostAndPort ssl error', error: error, stackTrace: trace); try { channelContext.processInfo ??= await ProcessInfoUtils.getProcessByPort(channel.remoteSocketAddress, hostAndPort?.domain ?? 'unknown'); diff --git a/lib/network/http/codec.dart b/lib/network/http/codec.dart index b63d9b0..a754e83 100644 --- a/lib/network/http/codec.dart +++ b/lib/network/http/codec.dart @@ -21,7 +21,7 @@ import 'package:proxypin/network/channel/channel_context.dart'; import 'package:proxypin/network/channel/host_port.dart'; import 'package:proxypin/network/http/body_reader.dart'; import 'package:proxypin/network/http/constants.dart'; -import 'package:proxypin/network/http/h2/codec.dart'; +import 'package:proxypin/network/http/h2/h2_codec.dart'; import 'package:proxypin/network/http/http_parser.dart'; import 'package:proxypin/network/util/byte_buf.dart'; import 'package:proxypin/network/util/compress.dart'; @@ -94,7 +94,8 @@ abstract class HttpCodec implements Codec { @override DecoderResult decode(ChannelContext channelContext, ByteBuf data) { - if (channelContext.serverChannel?.selectedProtocol == HttpConstants.h2) { + var protocol = channelContext.serverChannel?.selectedProtocol; + if (protocol == HttpConstants.h2 || protocol == HttpConstants.h2_14) { return getH2Codec().decode(channelContext, data); } diff --git a/lib/network/http/constants.dart b/lib/network/http/constants.dart index d8d98b4..5a3559b 100644 --- a/lib/network/http/constants.dart +++ b/lib/network/http/constants.dart @@ -1,6 +1,7 @@ class HttpConstants { //h2协议 static const String h2 = 'h2'; + static const String h2_14 = 'h2-14'; /// Line feed character /n static const int lf = 10; diff --git a/lib/network/http/h2/codec.dart b/lib/network/http/h2/h2_codec.dart similarity index 69% rename from lib/network/http/h2/codec.dart rename to lib/network/http/h2/h2_codec.dart index 110badc..cb17391 100644 --- a/lib/network/http/h2/codec.dart +++ b/lib/network/http/h2/h2_codec.dart @@ -19,22 +19,25 @@ import 'dart:typed_data'; import 'package:proxypin/network/channel/channel_context.dart'; import 'package:proxypin/network/http/codec.dart'; -import 'package:proxypin/network/http/h2/hpack.dart'; import 'package:proxypin/network/http/h2/setting.dart'; import 'package:proxypin/network/http/http.dart'; import 'package:proxypin/network/util/byte_buf.dart'; +import 'package:proxypin/network/util/logger.dart'; import 'frame.dart'; +import 'hpack/hpack.dart'; /// http编解码 abstract class Http2Codec implements Codec { static const maxFrameSize = 16384; static final List connectionPrefacePRI = "PRI * HTTP/2.0".codeUnits; - HPACKDecoder decoder = HPACKDecoder(); - HPACKEncoder encoder = HPACKEncoder(); - T createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map headers); + HPackDecoder decoder = HPackDecoder(); + + HPackEncoder encoder = HPackEncoder(); + + T createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map> headers); T? getMessage(ChannelContext channelContext, FrameHeader frameHeader); @@ -53,6 +56,8 @@ abstract class Http2Codec implements Codec { while (byteBuf.isReadable()) { DecoderResult result = DecoderResult(isDone: false); FrameHeader? frameHeader = FrameReader._readFrameHeader(byteBuf); + // logger.d("${frameHeader?.streamIdentifier} frame ${frameHeader?.length} ${byteBuf.readableBytes()}"); + if (frameHeader == null) { return result; } @@ -63,7 +68,7 @@ abstract class Http2Codec implements Codec { return result; } - result = parseHttp2Packet(channelContext, frameHeader, ByteBuf(framePayload)); + result = parseHttp2Packet(channelContext, frameHeader, framePayload); if (result.isDone) { return result; } @@ -72,27 +77,29 @@ abstract class Http2Codec implements Codec { return DecoderResult(isDone: false); } - DecoderResult parseHttp2Packet(ChannelContext channelContext, FrameHeader frameHeader, ByteBuf framePayload) { + DecoderResult parseHttp2Packet(ChannelContext channelContext, FrameHeader frameHeader, List framePayload) { var result = DecoderResult(); - // logger.d("streamId: ${frameHeader.streamIdentifier} ${frameHeader.type} endHeaders: ${frameHeader.hasEndHeadersFlag} " - // "endStream: ${frameHeader.hasEndStreamFlag}"); + // logger.d( + // "${this is Http2RequestDecoder ? 'request' : 'response'} streamId: ${frameHeader.streamIdentifier} ${frameHeader.type} endHeaders: ${frameHeader.hasEndHeadersFlag} " + // "endStream: ${frameHeader.hasEndStreamFlag} ${frameHeader.length}"); //根据帧类型进行处理 switch (frameHeader.type) { case FrameType.headers: //处理HEADERS帧 - _handleHeadersFrame(channelContext, frameHeader, framePayload); + _handleHeadersFrame(channelContext, frameHeader, ByteBuf(framePayload)); result.isDone = frameHeader.hasEndStreamFlag && frameHeader.hasEndHeadersFlag; break; case FrameType.continuation: //处理CONTINUATION帧 var message = getMessage(channelContext, frameHeader); if (message == null) { - result.forward = List.from(frameHeader.encode())..addAll(framePayload.readAvailableBytes()); + logger.e("CONTINUATION frame but no message found"); + result.forward = List.from(frameHeader.encode())..addAll(framePayload); return result; } - Map headers = _parseHeaders(channelContext, framePayload.readBytes(frameHeader.length)); - headers.forEach((key, value) => message.headers.add(key, value)); + Map> headers = _parseHeaders(channelContext, framePayload); + headers.forEach((key, values) => message.headers.addValues(key, values)); if (frameHeader.hasEndHeadersFlag && channelContext.getStreamRequest(frameHeader.streamIdentifier)?.method == HttpMethod.head) { @@ -101,16 +108,16 @@ abstract class Http2Codec implements Codec { break; case FrameType.data: //处理DATA帧 - _handleDataFrame(channelContext, frameHeader, framePayload); + _handleDataFrame(channelContext, frameHeader, ByteBuf(framePayload)); result.isDone = frameHeader.hasEndStreamFlag; break; case FrameType.settings: - SettingHandler.handleSettingsFrame(channelContext, frameHeader, framePayload); - result.forward = List.from(frameHeader.encode())..addAll(framePayload.bytes); + SettingHandler.handleSettingsFrame(channelContext, frameHeader, ByteBuf(framePayload)); + result.forward = List.from(frameHeader.encode())..addAll(framePayload); return result; default: //其他帧类型 原文转发 - result.forward = List.from(frameHeader.encode())..addAll(framePayload.bytes); + result.forward = List.from(frameHeader.encode())..addAll(framePayload); return result; } @@ -132,34 +139,38 @@ abstract class Http2Codec implements Codec { @override Uint8List encode(T data) { var bytesBuilder = BytesBuilder(); + // data.headers.contentLength = data.body?.length ?? 0; + var emptyBody = data.body == null || data.body!.isEmpty; //headers var headers = encodeHeaders(data); - BytesBuilder headerBlock = BytesBuilder(); + // BytesBuilder headerBlock = BytesBuilder(); bool firstFrame = true; - for (var header in headers) { - var encode = encoder.encode(header); - //防止出现桢分片导致header分裂 - if (headerBlock.length + encode.length < maxFrameSize) { - headerBlock.add(encode); - continue; - } - FrameType frameType = firstFrame ? FrameType.headers : FrameType.continuation; - int flags = frameType == FrameType.headers && data.body == null ? FrameHeader.flagsEndStream : 0; - firstFrame = false; + var headerBlock = encoder.encode(headers); - _writeFrame(bytesBuilder, frameType, flags, data.streamId!, headerBlock.takeBytes()); - headerBlock.add(encode); - } + // for (var header in headers) { + // //防止出现桢分片导致header分裂 + // if (headerBlock.length + encode.length < maxFrameSize) { + // headerBlock.add(encode); + // continue; + // } + // + // FrameType frameType = firstFrame ? FrameType.headers : FrameType.continuation; + // int flags = frameType == FrameType.headers && emptyBody ? FrameHeader.flagsEndStream : 0; + // firstFrame = false; + // + // _writeFrame(bytesBuilder, frameType, flags, data.streamId!, headerBlock.takeBytes()); + // headerBlock.add(encode); + // } FrameType frameType = firstFrame ? FrameType.headers : FrameType.continuation; - int flags = frameType == FrameType.headers && data.body == null ? FrameHeader.flagsEndStream : 0; + int flags = frameType == FrameType.headers && emptyBody ? FrameHeader.flagsEndStream : 0; flags |= FrameHeader.flagsEndHeaders; - _writeFrame(bytesBuilder, frameType, flags, data.streamId!, headerBlock.takeBytes()); + _writeFrame(bytesBuilder, frameType, flags, data.streamId!, headerBlock); //body - if (data.body != null) { + if (data.body?.isNotEmpty == true) { var payload = data.body!; while (payload.length > maxFrameSize) { var chunkSize = min(maxFrameSize, payload.length); @@ -169,6 +180,9 @@ abstract class Http2Codec implements Codec { } _writeFrame(bytesBuilder, FrameType.data, FrameHeader.flagsEndStream, data.streamId!, payload); + } else if (frameType != FrameType.headers && emptyBody) { + //如果没有body,发送一个空的DATA帧 + _writeFrame(bytesBuilder, FrameType.data, FrameHeader.flagsEndStream, data.streamId!, []); } return bytesBuilder.takeBytes(); @@ -176,8 +190,8 @@ abstract class Http2Codec implements Codec { void _writeFrame(BytesBuilder bytesBuilder, FrameType type, int flag, int streamId, List payload) { FrameHeader frameHeader = FrameHeader(payload.length, type, flag, streamId); - // logger.d("_writeFrame streamId: ${frameHeader.streamIdentifier} ${frameHeader.type} endHeaders: ${frameHeader - // .hasEndHeadersFlag} endStream: ${frameHeader.hasEndStreamFlag}"); + // logger.d( + // "${this is Http2RequestDecoder ? 'request' : 'response'} _writeFrame streamId: ${frameHeader.streamIdentifier} ${frameHeader.type} flags:${frameHeader.flags} endHeaders: ${frameHeader.hasEndHeadersFlag} endStream: ${frameHeader.hasEndStreamFlag} ${payload.length}"); bytesBuilder.add(frameHeader.encode()); bytesBuilder.add(payload); } @@ -201,8 +215,10 @@ abstract class Http2Codec implements Codec { if (frameHeader.hasPaddedFlag) { padLength = payload.readByte(); } - frameHeader.length; - int dataLength = payload.readableBytes() - padLength; + + payload.skipBytes(padLength); + //读取数据 + int dataLength = payload.readableBytes(); var data = payload.readBytes(dataLength); var message = getMessage(channelContext, frameHeader)!; if (message.body == null) { @@ -234,7 +250,8 @@ abstract class Http2Codec implements Codec { weight = payload.readByte(); // weight } - var headerBlockLength = payload.length - payload.readerIndex - padLength; + payload.skipBytes(padLength); + var headerBlockLength = payload.length - payload.readerIndex; if (headerBlockLength < 0) { throw Exception("headerBlockLength < 0"); } @@ -242,31 +259,34 @@ abstract class Http2Codec implements Codec { var blockFragment = payload.readBytes(headerBlockLength); //读取头部信息 - Map headers = _parseHeaders(channelContext, blockFragment); + Map> headers = _parseHeaders(channelContext, blockFragment); T message = createMessage(channelContext, frameHeader, headers); - headers.forEach((key, value) { + headers.forEach((key, values) { if (!key.startsWith(":")) { - message.headers.add(key, value); + message.headers.addValues(key, values); } }); return HeadersFrame(frameHeader, padLength, exclusiveDependency, streamDependency, weight, blockFragment); } - Map _parseHeaders(ChannelContext channelContext, List payload) { + Map> _parseHeaders(ChannelContext channelContext, List payload) { if (channelContext.setting != null) { - decoder.updateTableSize(channelContext.setting!.headTableSize); + decoder.updateMaxReceivingHeaderTableSize(channelContext.setting!.headTableSize); } // Decode the headers List
headers = decoder.decode(payload); // Convert the headers to a map - Map headerMap = {}; + Map> headerMap = {}; for (Header header in headers) { - headerMap[header.name] = header.value; + final name = header.nameString; + final value = header.valueString; + headerMap[name] ??= []; + headerMap[name]!.add(value); } return headerMap; @@ -275,9 +295,10 @@ abstract class Http2Codec implements Codec { class Http2RequestDecoder extends Http2Codec { @override - HttpRequest createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map headers) { - HttpMethod httpMethod = HttpMethod.valueOf(headers[":method"]!); - var httpRequest = HttpRequest(httpMethod, headers[":path"]!, protocolVersion: headers[":version"] ?? "HTTP/2"); + HttpRequest createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map> headers) { + HttpMethod httpMethod = HttpMethod.valueOf(headers[":method"]!.first); + var httpRequest = + HttpRequest(httpMethod, headers[":path"]!.first, protocolVersion: headers[":version"]?.firstOrNull ?? "HTTP/2"); var old = channelContext.putStreamRequest(frameHeader.streamIdentifier, httpRequest); assert(old == null, "old request is not null"); return httpRequest; @@ -292,14 +313,14 @@ class Http2RequestDecoder extends Http2Codec { List
encodeHeaders(HttpRequest message) { var headers =
[]; var uri = message.requestUri!; - headers.add(Header(":method", message.method.name)); - headers.add(Header(":scheme", uri.scheme)); - headers.add(Header(":authority", uri.host)); - headers.add(Header(":path", message.uri)); + headers.add(Header.ascii(":method", message.method.name)); + headers.add(Header.ascii(":scheme", uri.scheme)); + headers.add(Header.ascii(":authority", uri.host)); + headers.add(Header.ascii(":path", message.uri)); message.headers.forEach((key, values) { for (var value in values) { - headers.add(Header(key, value)); + headers.add(Header.ascii(key, value)); } }); return headers; @@ -308,9 +329,10 @@ class Http2RequestDecoder extends Http2Codec { class Http2ResponseDecoder extends Http2Codec { @override - HttpResponse createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map headers) { - var httpResponse = HttpResponse(HttpStatus.valueOf(int.parse(headers[':status']!)), - protocolVersion: headers[":version"] ?? 'HTTP/2'); + HttpResponse createMessage( + ChannelContext channelContext, FrameHeader frameHeader, Map> headers) { + var httpResponse = HttpResponse(HttpStatus.valueOf(int.parse(headers[':status']!.first)), + protocolVersion: headers[":version"]?.firstOrNull ?? 'HTTP/2'); httpResponse.requestId = channelContext.getStreamRequest(frameHeader.streamIdentifier)!.requestId; channelContext.putStreamResponse(frameHeader.streamIdentifier, httpResponse); return httpResponse; @@ -324,10 +346,10 @@ class Http2ResponseDecoder extends Http2Codec { @override List
encodeHeaders(HttpResponse message) { var headers =
[]; - headers.add(Header(":status", message.status.code.toString())); + headers.add(Header.ascii(":status", message.status.code.toString())); message.headers.forEach((key, values) { for (var value in values) { - headers.add(Header(key, value)); + headers.add(Header.ascii(key, value)); } }); return headers; @@ -342,7 +364,9 @@ class FrameReader { return null; } - return data.readBytes(length); + var readBytes = data.readBytes(length); + data.clearRead(); + return readBytes; } static FrameHeader? _readFrameHeader(ByteBuf data) { diff --git a/lib/network/http/h2/hpack.dart b/lib/network/http/h2/hpack.dart deleted file mode 100644 index 8786b10..0000000 --- a/lib/network/http/h2/hpack.dart +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:proxypin/network/http/h2/huffman.dart'; -import 'package:proxypin/network/util/byte_buf.dart'; - -class HPACKDecoder { - // static const int _maxHeaderTableSize = 12288; - static HuffmanDecoder huffmanDecoder = HuffmanDecoder(); - final IndexTable _indexTable = IndexTable(); - - updateTableSize(int size) { - _indexTable._maxSize = size; - } - - List
decode(List bytes) { - var headers =
[]; - var payload = ByteBuf(bytes); - while (payload.isReadable()) { - var header = _decodeHeader(payload); - if (header == null) continue; - headers.add(header); - } - return headers; - } - - Header? _decodeHeader(ByteBuf framePayload) { - if (!framePayload.isReadable()) { - return null; - } - - int firstByte = framePayload.get(framePayload.readerIndex); - if (firstByte & 0x80 == 0x80) { - // Indexed Header Field - int headerIndex = _decodeInteger(framePayload, 7); - return _indexTable[headerIndex]; - } else if (firstByte & 0x40 == 0x40) { - // Literal Header Field with Incremental Indexing - int headerIndex = _decodeInteger(framePayload, 6); - Header header = _readHeaderField(framePayload, headerIndex); - _indexTable.add(header); - return header; - } else if (firstByte & 0x20 == 0x20) { - // Dynamic Table Size Update - int maxSize = _decodeInteger(framePayload, 5); - _indexTable._maxSize = maxSize; - } else if (firstByte & 0x10 == 0x10) { - // Literal Header Field never Indexed - int headerIndex = _decodeInteger(framePayload, 4); - Header header = _readHeaderField(framePayload, headerIndex, neverIndexed: true); - return header; - } else { - // Literal Header Field without Indexing - int headerIndex = _decodeInteger(framePayload, 4); - Header header = _readHeaderField(framePayload, headerIndex); - return header; - } - return null; - } - - Header _readHeaderField(ByteBuf data, int index, {bool neverIndexed = false}) { - if (index > 0) { - String name = _indexTable[index].name; - String value = _decodeString(data, neverIndexed: neverIndexed); - return Header(name, value); - } - - String name = _decodeString(data); - String value = _decodeString(data, neverIndexed: neverIndexed); - return Header(name, value); - } - - String _decodeString(ByteBuf data, {bool neverIndexed = false}) { - int firstByte = data.get(data.readerIndex); - bool huffmanEncoded = (firstByte & 0x80) == 0x80; - int length = _decodeInteger(data, 7); - - Uint8List stringBytes = data.readBytes(length); - if (huffmanEncoded) { - // If the string is Huffman encoded, decode it using a Huffman decoder. - return ascii.decode(huffmanDecoder.decode(stringBytes)); - } else { - // If the string is not Huffman encoded, simply create a new string from the bytes. - return ascii.decode(stringBytes); - } - } - - int _decodeInteger(ByteBuf data, int prefixLength) { - int prefixMask = (1 << prefixLength) - 1; - int value = data.read() & prefixMask; - - if (value < prefixMask) { - return value; - } - - int shift = 0; - int b; - do { - b = data.read(); - value += (b & 127) << shift; - shift += 7; - } while ((b & 128) == 128); - - return value; - } -} - -class HPACKEncoder { - final IndexTable _indexTable = IndexTable(); - static HuffmanEncoder huffmanEncoder = HuffmanEncoder(); - - List encodeList(List
headers) { - var bytesBuilder = BytesBuilder(); - - for (var header in headers) { - _encodeHeader(bytesBuilder, header); - } - - return bytesBuilder.takeBytes(); - } - - List encode(Header header) { - var bytesBuilder = BytesBuilder(); - _encodeHeader(bytesBuilder, header); - return bytesBuilder.takeBytes(); - } - - void _encodeHeader(BytesBuilder bytesBuilder, Header header) { - var name = header.name; - var value = header.value; - var index = _getIndex(name, value); - if (index != null) { - _encodeInteger(bytesBuilder, 7, 0x80, index); - return; - } - - var nameIndex = _getIndex(name); - if (nameIndex != null) { - _encodeInteger(bytesBuilder, 4, 0, nameIndex); - } else { - bytesBuilder.addByte(0); - _encodeString(bytesBuilder, name); - } - _encodeString(bytesBuilder, value); - } - - int? _getIndex(String name, [String? value]) { - for (var i = 1; i < _indexTable.length; i++) { - var header = _indexTable[i]; - if (header.name == name && (value == null || header.value == value)) { - return i; - } - } - return null; - } - - _encodeString(BytesBuilder bytesBuilder, String value) { - var encoded = ascii.encode(value); - - var huffmanEncoded = huffmanEncoder.encode(encoded); - if (huffmanEncoded.length < encoded.length) { - _encodeInteger(bytesBuilder, 7, 0x80, huffmanEncoded.length); - bytesBuilder.add(huffmanEncoded); - } else { - _encodeInteger(bytesBuilder, 7, 0x00, encoded.length); - bytesBuilder.add(encoded); - } - } - - void _encodeInteger(BytesBuilder bytesBuilder, int prefixBits, int mask, int value) { - assert(prefixBits <= 8); - - if (value < (1 << prefixBits) - 1) { - bytesBuilder.addByte(value | mask); - } else { - bytesBuilder.addByte(((1 << prefixBits) - 1) | mask); - value -= (1 << prefixBits) - 1; - while (value >= 128) { - bytesBuilder.addByte(value % 128 + 128); - value ~/= 128; - } - bytesBuilder.addByte(value); - } - } -} - -class Header { - final String name; - String value; - - Header(String name, this.value) : name = name.toLowerCase(); - - @override - String toString() { - return '{name: $name, value: $value}'; - } -} - -class IndexTable { - static final List
_staticTable = [ - Header('', ''), - Header(':authority', ''), - Header(':method', 'GET'), - Header(':method', 'POST'), - Header(':path', '/'), - Header(':path', '/index.html'), - Header(':scheme', 'http'), - Header(':scheme', 'https'), - Header(':status', '200'), - Header(':status', '204'), - Header(':status', '206'), - Header(':status', '304'), - Header(':status', '400'), - Header(':status', '404'), - Header(':status', '500'), - Header('accept-charset', ''), - Header('accept-encoding', 'gzip, deflate, br'), - Header('accept-language', ''), - Header('accept-ranges', ''), - Header('accept', ''), - Header('access-control-allow-origin', ''), - Header('age', ''), - Header('allow', ''), - Header('authorization', ''), - Header('cache-control', ''), - Header('content-disposition', ''), - Header('content-encoding', ''), - Header('content-language', ''), - Header('content-length', ''), - Header('content-location', ''), - Header('content-range', ''), - Header('content-type', ''), - Header('cookie', ''), - Header('date', ''), - Header('etag', ''), - Header('expect', ''), - Header('expires', ''), - Header('from', ''), - Header('host', ''), - Header('if-match', ''), - Header('if-modified-since', ''), - Header('if-none-match', ''), - Header('if-range', ''), - Header('if-unmodified-since', ''), - Header('last-modified', ''), - Header('link', ''), - Header('location', ''), - Header('max-forwards', ''), - Header('proxy-authenticate', ''), - Header('proxy-authorization', ''), - Header('range', ''), - Header('referer', ''), - Header('refresh', ''), - Header('retry-after', ''), - Header('server', ''), - Header('set-cookie', ''), - Header('strict-transport-security', ''), - Header('transfer-encoding', ''), - Header('user-agent', ''), - Header('vary', ''), - Header('via', ''), - Header('www-authenticate', ''), - ]; - - //动态表 - final List
_dynamicTable = []; - - int _maxSize = 4096; - - Header operator [](int index) { - if (index < _staticTable.length) return _staticTable[index]; - if (index < _staticTable.length + _dynamicTable.length) { - return _dynamicTable[index - _staticTable.length]; - } - throw RangeError('Invalid index: $index'); - } - - add(Header header) { - _dynamicTable.add(header); - _maxSize -= sizeOf(header); - while (_maxSize < 0) { - _maxSize += sizeOf(_dynamicTable.removeAt(0)); - } - } - - int get length => _staticTable.length + _dynamicTable.length; - - int sizeOf(Header header) => header.name.length + header.value.length + 32; -} diff --git a/lib/network/http/h2/hpack/hpack.dart b/lib/network/http/h2/hpack/hpack.dart new file mode 100644 index 0000000..bd700ea --- /dev/null +++ b/lib/network/http/h2/hpack/hpack.dart @@ -0,0 +1,348 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// HPACK specification. See here for more information: +/// https://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10 + +import 'dart:convert' show ascii; +import 'dart:typed_data'; + +import '../../../util/byte_utils.dart'; +import 'huffman.dart'; +import 'huffman_table.dart'; + +/// Exception raised due to encoding/decoding errors. +class HPackDecodingException implements Exception { + final String _message; + + HPackDecodingException(this._message); + + @override + String toString() => 'HPackDecodingException: $_message'; +} + +/// A HPACK encoding/decoding context. +/// +/// This is a statefull class, so encoding/decoding changes internal state. +class HPackContext { + final HPackEncoder encoder = HPackEncoder(); + final HPackDecoder decoder = HPackDecoder(); + + HPackContext({ + int maxSendingHeaderTableSize = 4096, + int maxReceivingHeaderTableSize = 4096, + }) { + encoder.updateMaxSendingHeaderTableSize(maxSendingHeaderTableSize); + decoder.updateMaxReceivingHeaderTableSize(maxReceivingHeaderTableSize); + } +} + +/// A HTTP/2 header. +class Header { + final List name; + final List value; + final bool neverIndexed; + + Header(this.name, this.value, {this.neverIndexed = false}); + + String get nameString => ascii.decode(name); + + String get valueString => ascii.decode(value); + + factory Header.ascii(String name, String value) { + // Specs: `However, header field names MUST be converted to lowercase prior + // to their encoding in HTTP/2. A request or response containing uppercase + // header field names MUST be treated as malformed (Section 8.1.2.6).` + return Header(ascii.encode(name.toLowerCase()), ascii.encode(value)); + } + +} + +/// A stateful HPACK decoder. +class HPackDecoder { + int _maxHeaderTableSize = 4096; + + final IndexTable _table = IndexTable(); + + void updateMaxReceivingHeaderTableSize(int newMaximumSize) { + _maxHeaderTableSize = newMaximumSize; + } + + List
decode(List data) { + var offset = 0; + + int readInteger(int prefixBits) { + assert(prefixBits <= 8 && prefixBits > 0); + + var byte = data[offset++] & ((1 << prefixBits) - 1); + + int integer; + if (byte == ((1 << prefixBits) - 1)) { + // Length encodeded. + integer = 0; + var shift = 0; + while (true) { + var done = (data[offset] & 0x80) != 0x80; + integer += (data[offset++] & 0x7f) << shift; + shift += 7; + if (done) break; + } + integer += (1 << prefixBits) - 1; + } else { + // In place length. + integer = byte; + } + + return integer; + } + + List readStringLiteral() { + var isHuffmanEncoding = (data[offset] & 0x80) != 0; + var length = readInteger(7); + + var sublist = viewOrSublist(data, offset, length); + offset += length; + if (isHuffmanEncoding) { + return http2HuffmanCodec.decode(sublist); + } else { + return sublist; + } + } + + Header readHeaderFieldInternal(int index, {bool neverIndexed = false}) { + List name, value; + if (index > 0) { + name = _table.lookup(index).name; + value = readStringLiteral(); + } else { + name = readStringLiteral(); + value = readStringLiteral(); + } + return Header(name, value, neverIndexed: neverIndexed); + } + + try { + var headers =
[]; + while (offset < data.length) { + var byte = data[offset]; + var isIndexedField = (byte & 0x80) != 0; + var isIncrementalIndexing = (byte & 0xc0) == 0x40; + + var isWithoutIndexing = (byte & 0xf0) == 0; + var isNeverIndexing = (byte & 0xf0) == 0x10; + var isDynamicTableSizeUpdate = (byte & 0xe0) == 0x20; + + if (isIndexedField) { + var index = readInteger(7); + var field = _table.lookup(index); + headers.add(field); + } else if (isIncrementalIndexing) { + var field = readHeaderFieldInternal(readInteger(6)); + _table.addHeaderField(field); + headers.add(field); + } else if (isWithoutIndexing) { + headers.add(readHeaderFieldInternal(readInteger(4))); + } else if (isNeverIndexing) { + headers.add( + readHeaderFieldInternal(readInteger(4), neverIndexed: true), + ); + } else if (isDynamicTableSizeUpdate) { + var newMaxSize = readInteger(5); + _table.updateMaxSize(newMaxSize); + } else { + throw HPackDecodingException('Invalid encoding of headers.'); + } + } + return headers; + // ignore: avoid_catching_errors + } on RangeError catch (e) { + throw HPackDecodingException('$e'); + } on HuffmanDecodingException catch (e) { + throw HPackDecodingException('$e'); + } + } +} + +/// A stateful HPACK encoder. +// Currently we encode all headers: +// - without huffman encoding +// - without using the dynamic table +class HPackEncoder { + void updateMaxSendingHeaderTableSize(int newMaximumSize) { + // Once we start encoding via dynamic table we need to let the other + // side know the maximum table size we're using. + } + + List encode(List
headers) { + var bytesBuilder = BytesBuilder(); + var currentByte = 0; + + void writeInteger(int prefixBits, int value) { + assert(prefixBits <= 8); + + if (value < (1 << prefixBits) - 1) { + currentByte |= value; + bytesBuilder.addByte(currentByte); + } else { + // Length encodeded. + currentByte |= (1 << prefixBits) - 1; + value -= (1 << prefixBits) - 1; + bytesBuilder.addByte(currentByte); + var done = false; + while (!done) { + currentByte = value & 0x7f; + value = value >> 7; + done = value == 0; + if (!done) currentByte |= 0x80; + bytesBuilder.addByte(currentByte); + } + } + currentByte = 0; + } + + void writeStringLiteral(List bytes) { + // Support huffman encoding. + currentByte = 0; // 1 would be huffman encoding + writeInteger(7, bytes.length); + bytesBuilder.add(bytes); + } + + void writeLiteralHeaderWithoutIndexing(Header header) { + bytesBuilder.addByte(0); + writeStringLiteral(header.name); + writeStringLiteral(header.value); + } + + for (var header in headers) { + writeLiteralHeaderWithoutIndexing(header); + } + + return bytesBuilder.takeBytes(); + } +} + +class IndexTable { + static final List _staticTable = [ + null, + Header(ascii.encode(':authority'), const []), + Header(ascii.encode(':method'), ascii.encode('GET')), + Header(ascii.encode(':method'), ascii.encode('POST')), + Header(ascii.encode(':path'), ascii.encode('/')), + Header(ascii.encode(':path'), ascii.encode('/index.html')), + Header(ascii.encode(':scheme'), ascii.encode('http')), + Header(ascii.encode(':scheme'), ascii.encode('https')), + Header(ascii.encode(':status'), ascii.encode('200')), + Header(ascii.encode(':status'), ascii.encode('204')), + Header(ascii.encode(':status'), ascii.encode('206')), + Header(ascii.encode(':status'), ascii.encode('304')), + Header(ascii.encode(':status'), ascii.encode('400')), + Header(ascii.encode(':status'), ascii.encode('404')), + Header(ascii.encode(':status'), ascii.encode('500')), + Header(ascii.encode('accept-charset'), const []), + Header(ascii.encode('accept-encoding'), ascii.encode('gzip, deflate')), + Header(ascii.encode('accept-language'), const []), + Header(ascii.encode('accept-ranges'), const []), + Header(ascii.encode('accept'), const []), + Header(ascii.encode('access-control-allow-origin'), const []), + Header(ascii.encode('age'), const []), + Header(ascii.encode('allow'), const []), + Header(ascii.encode('authorization'), const []), + Header(ascii.encode('cache-control'), const []), + Header(ascii.encode('content-disposition'), const []), + Header(ascii.encode('content-encoding'), const []), + Header(ascii.encode('content-language'), const []), + Header(ascii.encode('content-length'), const []), + Header(ascii.encode('content-location'), const []), + Header(ascii.encode('content-range'), const []), + Header(ascii.encode('content-type'), const []), + Header(ascii.encode('cookie'), const []), + Header(ascii.encode('date'), const []), + Header(ascii.encode('etag'), const []), + Header(ascii.encode('expect'), const []), + Header(ascii.encode('expires'), const []), + Header(ascii.encode('from'), const []), + Header(ascii.encode('host'), const []), + Header(ascii.encode('if-match'), const []), + Header(ascii.encode('if-modified-since'), const []), + Header(ascii.encode('if-none-match'), const []), + Header(ascii.encode('if-range'), const []), + Header(ascii.encode('if-unmodified-since'), const []), + Header(ascii.encode('last-modified'), const []), + Header(ascii.encode('link'), const []), + Header(ascii.encode('location'), const []), + Header(ascii.encode('max-forwards'), const []), + Header(ascii.encode('proxy-authenticate'), const []), + Header(ascii.encode('proxy-authorization'), const []), + Header(ascii.encode('range'), const []), + Header(ascii.encode('referer'), const []), + Header(ascii.encode('refresh'), const []), + Header(ascii.encode('retry-after'), const []), + Header(ascii.encode('server'), const []), + Header(ascii.encode('set-cookie'), const []), + Header(ascii.encode('strict-transport-security'), const []), + Header(ascii.encode('transfer-encoding'), const []), + Header(ascii.encode('user-agent'), const []), + Header(ascii.encode('vary'), const []), + Header(ascii.encode('via'), const []), + Header(ascii.encode('www-authenticate'), const []), + ]; + + final List
_dynamicTable = []; + + /// The maximum size the dynamic table can grow to before entries need to be + /// evicted. + int _maximumSize = 4096; + + /// The current size of the dynamic table. + int _currentSize = 0; + + IndexTable(); + + /// Updates the maximum size which the dynamic table can grow to. + void updateMaxSize(int newMaxDynTableSize) { + _maximumSize = newMaxDynTableSize; + _reduce(); + } + + /// Lookup an item by index. + Header lookup(int index) { + if (index <= 0) { + throw HPackDecodingException( + 'Invalid index (was: $index) for table lookup.', + ); + } + if (index < _staticTable.length) { + return _staticTable[index]!; + } + index -= _staticTable.length; + if (index < _dynamicTable.length) { + return _dynamicTable[index]; + } + throw HPackDecodingException( + 'Invalid index (was: $index) for table lookup.', + ); + } + + /// Adds a new header field to the dynamic table - and evicts entries as + /// necessary. + void addHeaderField(Header header) { + _dynamicTable.insert(0, header); + _currentSize += _sizeOf(header); + _reduce(); + } + + /// Removes as many entries as required to be within the limit of + /// [_maximumSize]. + void _reduce() { + while (_currentSize > _maximumSize) { + var h = _dynamicTable.removeLast(); + _currentSize -= _sizeOf(h); + } + } + + /// Returns the "size" a [header] has. + /// + /// This is specified to be the number of octets of name/value plus 32. + int _sizeOf(Header header) => header.name.length + header.value.length + 32; +} diff --git a/lib/network/http/h2/hpack/huffman.dart b/lib/network/http/h2/hpack/huffman.dart new file mode 100644 index 0000000..0f5324d --- /dev/null +++ b/lib/network/http/h2/hpack/huffman.dart @@ -0,0 +1,182 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:typed_data'; + +import 'huffman_table.dart'; + +class HuffmanDecodingException implements Exception { + final String _message; + + HuffmanDecodingException(this._message); + + @override + String toString() => 'HuffmanDecodingException: $_message'; +} + +/// A codec used for encoding/decoding using a huffman codec. +class HuffmanCodec { + final HuffmanEncoder _encoder; + final HuffmanDecoder _decoder; + + HuffmanCodec(this._encoder, this._decoder); + + List decode(List bytes) => _decoder.decode(bytes); + + List encode(List bytes) => _encoder.encode(bytes); +} + +/// A huffman decoder based on a [HuffmanTreeNode]. +class HuffmanDecoder { + final HuffmanTreeNode _root; + + HuffmanDecoder(this._root); + + /// Decodes [bytes] using a huffman tree. + List decode(List bytes) { + var buffer = BytesBuilder(); + + var currentByteOffset = 0; + var node = _root; + var currentDepth = 0; + while (currentByteOffset < bytes.length) { + var byte = bytes[currentByteOffset]; + for (var currentBit = 7; currentBit >= 0; currentBit--) { + var right = (byte >> currentBit) & 1 == 1; + if (right) { + node = node.right!; + } else { + node = node.left!; + } + currentDepth++; + if (node.value != null) { + if (node.value == EOS_BYTE) { + throw HuffmanDecodingException( + 'More than 7 bit padding is not allowed. Found entire EOS ' + 'encoding', + ); + } + buffer.addByte(node.value!); + node = _root; + currentDepth = 0; + } + } + currentByteOffset++; + } + + if (node != _root) { + if (currentDepth > 7) { + throw HuffmanDecodingException( + 'Incomplete encoding of a byte or more than 7 bit padding.', + ); + } + + while (node.right != null) { + node = node.right!; + } + + if (node.value != 256) { + throw HuffmanDecodingException('Incomplete encoding of a byte.'); + } + } + + return buffer.takeBytes(); + } +} + +/// A huffman encoder based on a list of codewords. +class HuffmanEncoder { + final List _codewords; + + HuffmanEncoder(this._codewords); + + /// Encodes [bytes] using a list of codewords. + List encode(List bytes) { + var buffer = BytesBuilder(); + + var currentByte = 0; + var currentBitOffset = 7; + + void writeValue(int value, int numBits) { + var i = numBits - 1; + while (i >= 0) { + if (currentBitOffset == 7 && i >= 7) { + assert(currentByte == 0); + + buffer.addByte((value >> (i - 7)) & 0xff); + currentBitOffset = 7; + currentByte = 0; + i -= 8; + } else { + currentByte |= ((value >> i) & 1) << currentBitOffset; + + currentBitOffset--; + if (currentBitOffset == -1) { + buffer.addByte(currentByte); + currentBitOffset = 7; + currentByte = 0; + } + i--; + } + } + } + + for (var i = 0; i < bytes.length; i++) { + var byte = bytes[i]; + var value = _codewords[byte]; + writeValue(value.encodedBytes, value.numBits); + } + + if (currentBitOffset < 7) { + writeValue(0xff, 1 + currentBitOffset); + } + + return buffer.takeBytes(); + } +} + +/// Specifies the encoding of a specific value using huffman encoding. +class EncodedHuffmanValue { + /// An integer representation of the encoded bit-string. + final int encodedBytes; + + /// The number of bits in [encodedBytes]. + final int numBits; + + const EncodedHuffmanValue(this.encodedBytes, this.numBits); +} + +/// A node in the huffman tree. +class HuffmanTreeNode { + HuffmanTreeNode? left; + HuffmanTreeNode? right; + int? value; +} + +/// Generates a huffman decoding tree. +HuffmanTreeNode generateHuffmanTree(List valueEncodings) { + var root = HuffmanTreeNode(); + + for (var byteOffset = 0; byteOffset < valueEncodings.length; byteOffset++) { + var entry = valueEncodings[byteOffset]; + + var current = root; + for (var bitNr = 0; bitNr < entry.numBits; bitNr++) { + var right = + ((entry.encodedBytes >> (entry.numBits - bitNr - 1)) & 1) == 1; + + if (right) { + current.right ??= HuffmanTreeNode(); + current = current.right!; + } else { + current.left ??= HuffmanTreeNode(); + current = current.left!; + } + } + + current.value = byteOffset; + } + + return root; +} diff --git a/lib/network/http/h2/huffman.dart b/lib/network/http/h2/hpack/huffman_table.dart similarity index 70% rename from lib/network/http/h2/huffman.dart rename to lib/network/http/h2/hpack/huffman_table.dart index e1581fc..a61151b 100644 --- a/lib/network/http/h2/huffman.dart +++ b/lib/network/http/h2/hpack/huffman_table.dart @@ -1,155 +1,22 @@ -import 'dart:typed_data'; +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. -main() { - var list = [241, 227, 194, 254, 231, 52, 246, 174, 67, 211]; - print(HuffmanDecoder().decode(list)); -} -class HuffmanDecoder { - static const int eosByte = 256; //end of string +import 'huffman.dart'; - final HuffmanTreeNode _root; +/// The huffman codec for encoding/decoding HTTP/2 header blocks. +final HuffmanCodec http2HuffmanCodec = HuffmanCodec( + HuffmanEncoder(_codeWords), + HuffmanDecoder(generateHuffmanTree(_codeWords)), +); - HuffmanDecoder() : _root = generateHuffmanTree(_huffmanTable); +/// This is the integer representing the End-of-String symbol +/// (it is not representable by a byte). +const int EOS_BYTE = 256; - //http2协议规范 huffman解码 - List decode(List bytes) { - var buffer = BytesBuilder(); - - var currentByteOffset = 0; - var node = _root; - var currentDepth = 0; - while (currentByteOffset < bytes.length) { - var byte = bytes[currentByteOffset]; - for (var currentBit = 7; currentBit >= 0; currentBit--) { - var right = (byte >> currentBit) & 1 == 1; - if (right) { - node = node.right!; - } else { - node = node.left!; - } - currentDepth++; - if (node.value != null) { - if (node.value == eosByte) { - throw Exception('More than 7 bit padding is not allowed. Found entire EOS ' - 'encoding'); - } - buffer.addByte(node.value!); - node = _root; - currentDepth = 0; - } - } - currentByteOffset++; - } - - if (node != _root) { - if (currentDepth > 7) { - throw Exception('Incomplete encoding of a byte or more than 7 bit padding.'); - } - - while (node.right != null) { - node = node.right!; - } - - if (node.value != 256) { - throw Exception('Incomplete encoding of a byte.'); - } - } - - return buffer.takeBytes(); - } -} - -class HuffmanEncoder { - static const int eosByte = 256; //end of string - - final List _codewords; - - HuffmanEncoder() : _codewords = _huffmanTable; - - //http2协议规范 huffman编码 - List encode(List bytes) { - var buffer = BytesBuilder(); - - var currentByte = 0; - var currentBitOffset = 7; - - void writeValue(int value, int numBits) { - var i = numBits - 1; - while (i >= 0) { - if (currentBitOffset == 7 && i >= 7) { - assert(currentByte == 0); - - buffer.addByte((value >> (i - 7)) & 0xff); - currentBitOffset = 7; - currentByte = 0; - i -= 8; - } else { - currentByte |= ((value >> i) & 1) << currentBitOffset; - - currentBitOffset--; - if (currentBitOffset == -1) { - buffer.addByte(currentByte); - currentBitOffset = 7; - currentByte = 0; - } - i--; - } - } - } - - for (var i = 0; i < bytes.length; i++) { - var byte = bytes[i]; - var value = _codewords[byte]; - writeValue(value.encodedBytes, value.numBits); - } - - if (currentBitOffset < 7) { - writeValue(0xff, 1 + currentBitOffset); - } - - return buffer.takeBytes(); - } - -} -///生成h2 huffman解码tree -HuffmanTreeNode generateHuffmanTree(List valueEncodings) { - var root = HuffmanTreeNode(); - - for (var byteOffset = 0; byteOffset < valueEncodings.length; byteOffset++) { - var entry = valueEncodings[byteOffset]; - - var current = root; - for (var bitNr = 0; bitNr < entry.numBits; bitNr++) { - var right = ((entry.encodedBytes >> (entry.numBits - bitNr - 1)) & 1) == 1; - - if (right) { - current.right ??= HuffmanTreeNode(); - current = current.right!; - } else { - current.left ??= HuffmanTreeNode(); - current = current.left!; - } - } - - current.value = byteOffset; - } - - return root; -} - -class HuffmanTreeNode { - int? value; - - HuffmanTreeNode? left; - HuffmanTreeNode? right; - - bool isLeaf() { - return left == null && right == null; - } -} - -//HPACK规范 huffman编码的字节编码列表。 -final List _huffmanTable = [ +/// This list of byte encodings via huffman encoding was generated from the +/// HPACK specification. +const List _codeWords = [ EncodedHuffmanValue(0x1ff8, 13), EncodedHuffmanValue(0x7fffd8, 23), EncodedHuffmanValue(0xfffffe2, 28), @@ -408,10 +275,3 @@ final List _huffmanTable = [ EncodedHuffmanValue(0x3ffffee, 26), EncodedHuffmanValue(0x3fffffff, 30), ]; - -class EncodedHuffmanValue { - final int encodedBytes; - final int numBits; - - EncodedHuffmanValue(this.encodedBytes, this.numBits); -} diff --git a/lib/network/http/h2/setting.dart b/lib/network/http/h2/setting.dart index 2ac33c2..c8c8f26 100644 --- a/lib/network/http/h2/setting.dart +++ b/lib/network/http/h2/setting.dart @@ -63,7 +63,7 @@ class SettingHandler { // Handle the setting based on its identifier switch (identifier) { case 1: // SETTINGS_HEADER_TABLE_SIZE - setting.maxFrameSize = value; + setting.headTableSize = value; break; case 2: // SETTINGS_ENABLE_PUSH setting.enablePush = value == 1; diff --git a/lib/network/util/byte_buf.dart b/lib/network/util/byte_buf.dart index f44df52..c38d5d7 100644 --- a/lib/network/util/byte_buf.dart +++ b/lib/network/util/byte_buf.dart @@ -45,10 +45,16 @@ class ByteBuf { void clear() { readerIndex = 0; writerIndex = 0; + _buffer = Uint8List(0); } ///释放已读的空间 void clearRead() { + if (readerIndex == writerIndex) { + clear(); + return; + } + if (readerIndex > 0) { _buffer = Uint8List.sublistView(_buffer, readerIndex, writerIndex); writerIndex -= readerIndex; diff --git a/lib/network/util/byte_utils.dart b/lib/network/util/byte_utils.dart new file mode 100644 index 0000000..befa22c --- /dev/null +++ b/lib/network/util/byte_utils.dart @@ -0,0 +1,53 @@ +import 'dart:typed_data'; + +List viewOrSublist(List data, int offset, int length) { + if (data is Uint8List) { + return Uint8List.view(data.buffer, data.offsetInBytes + offset, length); + } else { + return data.sublist(offset, offset + length); + } +} + +int readInt64(List bytes, int offset) { + var high = readInt32(bytes, offset); + var low = readInt32(bytes, offset + 4); + return high << 32 | low; +} + +int readInt32(List bytes, int offset) { + return (bytes[offset] << 24) | + (bytes[offset + 1] << 16) | + (bytes[offset + 2] << 8) | + bytes[offset + 3]; +} + +int readInt24(List bytes, int offset) { + return (bytes[offset] << 16) | (bytes[offset + 1] << 8) | bytes[offset + 2]; +} + +int readInt16(List bytes, int offset) { + return (bytes[offset] << 8) | bytes[offset + 1]; +} + +void setInt64(List bytes, int offset, int value) { + setInt32(bytes, offset, value >> 32); + setInt32(bytes, offset + 4, value & 0xffffffff); +} + +void setInt32(List bytes, int offset, int value) { + bytes[offset] = (value >> 24) & 0xff; + bytes[offset + 1] = (value >> 16) & 0xff; + bytes[offset + 2] = (value >> 8) & 0xff; + bytes[offset + 3] = value & 0xff; +} + +void setInt24(List bytes, int offset, int value) { + bytes[offset] = (value >> 16) & 0xff; + bytes[offset + 1] = (value >> 8) & 0xff; + bytes[offset + 2] = value & 0xff; +} + +void setInt16(List bytes, int offset, int value) { + bytes[offset] = (value >> 8) & 0xff; + bytes[offset + 1] = value & 0xff; +} diff --git a/lib/network/util/proxy_helper.dart b/lib/network/util/proxy_helper.dart index 1ee8548..912e715 100644 --- a/lib/network/util/proxy_helper.dart +++ b/lib/network/util/proxy_helper.dart @@ -113,10 +113,13 @@ class ProxyHelper { request.uri = hostAndPort.domain; } - request.response = HttpResponse(status) - ..headers.contentType = 'text/plain' - ..headers.contentLength = message.codeUnits.length - ..body = message.codeUnits; + if (request.response == null || request.method == HttpMethod.connect) { + request.response = HttpResponse(status) + ..headers.contentType = 'text/plain' + ..headers.contentLength = message.codeUnits.length + ..body = message.codeUnits; + } + request.response?.request = request; channelContext.host = hostAndPort; diff --git a/lib/ui/desktop/toolbar/setting/setting.dart b/lib/ui/desktop/toolbar/setting/setting.dart index 8fbc7da..1be3809 100644 --- a/lib/ui/desktop/toolbar/setting/setting.dart +++ b/lib/ui/desktop/toolbar/setting/setting.dart @@ -184,6 +184,7 @@ class _ProxyMenuState extends State<_ProxyMenu> { const Divider(thickness: 0.3, height: 8), setSystemProxy(), const Divider(thickness: 0.3, height: 8), + Row(children: [ Expanded( child: Padding( @@ -199,6 +200,22 @@ class _ProxyMenuState extends State<_ProxyMenu> { SizedBox(width: 10) ]), const Divider(thickness: 0.3, height: 8), + + Row(children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 15), + child: Text(localizations.enabledHTTP2, style: const TextStyle(fontSize: 14)))), + SwitchWidget( + value: configuration.enabledHttp2, + scale: 0.75, + onChanged: (val) { + configuration.enabledHttp2 = val; + changed = true; + }), + SizedBox(width: 10) + ]), + const Divider(thickness: 0.3, height: 8), const SizedBox(height: 3), Padding( padding: const EdgeInsets.only(left: 15), diff --git a/lib/ui/mobile/setting/preference.dart b/lib/ui/mobile/setting/preference.dart index fbd13a5..b82a0c1 100644 --- a/lib/ui/mobile/setting/preference.dart +++ b/lib/ui/mobile/setting/preference.dart @@ -73,6 +73,15 @@ class _PreferenceState extends State { configuration.enableSocks5 = value; proxyServer.configuration.flushConfig(); })), + ListTile( + title: Text(localizations.enabledHTTP2), + trailing: SwitchWidget( + value: configuration.enabledHttp2, + scale: 0.8, + onChanged: (value) { + configuration.enabledHttp2 = value; + proxyServer.configuration.flushConfig(); + })), ListTile( title: Text(localizations.externalProxy), trailing: const Icon(Icons.keyboard_arrow_right), diff --git a/pubspec.yaml b/pubspec.yaml index 1654a9a..aff17e5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,7 +29,7 @@ dependencies: permission_handler: ^12.0.0+1 flutter_toastr: ^1.0.3 share_plus: ^10.1.4 - flutter_js: ^0.8.3 + flutter_js: 0.8.2 flutter_code_editor: git: url: https://github.com/wanghongenpin/flutter-code-editor.git