mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-05-20 16:15:47 +08:00
@@ -17,6 +17,7 @@
|
||||
"password": "Password",
|
||||
"proxySetting": "Proxy Setting",
|
||||
"systemProxy": "Set as System Proxy",
|
||||
"enabledHTTP2": "Enable HTTP2",
|
||||
"serverNotStart": "Proxy server not started",
|
||||
"download": "Download",
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"password": "密码",
|
||||
"proxySetting": "代理设置",
|
||||
"systemProxy": "设置为系统代理",
|
||||
"enabledHTTP2": "启用HTTP2",
|
||||
"serverNotStart": "未开启抓包",
|
||||
"download": "下载",
|
||||
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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<T extends HttpMessage> implements Codec<T, T> {
|
||||
|
||||
@override
|
||||
DecoderResult<T> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<T extends HttpMessage> implements Codec<T, T> {
|
||||
static const maxFrameSize = 16384;
|
||||
|
||||
static final List<int> connectionPrefacePRI = "PRI * HTTP/2.0".codeUnits;
|
||||
HPACKDecoder decoder = HPACKDecoder();
|
||||
HPACKEncoder encoder = HPACKEncoder();
|
||||
|
||||
T createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map<String, String> headers);
|
||||
HPackDecoder decoder = HPackDecoder();
|
||||
|
||||
HPackEncoder encoder = HPackEncoder();
|
||||
|
||||
T createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map<String, List<String>> headers);
|
||||
|
||||
T? getMessage(ChannelContext channelContext, FrameHeader frameHeader);
|
||||
|
||||
@@ -53,6 +56,8 @@ abstract class Http2Codec<T extends HttpMessage> implements Codec<T, T> {
|
||||
while (byteBuf.isReadable()) {
|
||||
DecoderResult<T> result = DecoderResult<T>(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<T extends HttpMessage> implements Codec<T, T> {
|
||||
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<T extends HttpMessage> implements Codec<T, T> {
|
||||
return DecoderResult<T>(isDone: false);
|
||||
}
|
||||
|
||||
DecoderResult<T> parseHttp2Packet(ChannelContext channelContext, FrameHeader frameHeader, ByteBuf framePayload) {
|
||||
DecoderResult<T> parseHttp2Packet(ChannelContext channelContext, FrameHeader frameHeader, List<int> framePayload) {
|
||||
var result = DecoderResult<T>();
|
||||
// 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<String, String> headers = _parseHeaders(channelContext, framePayload.readBytes(frameHeader.length));
|
||||
headers.forEach((key, value) => message.headers.add(key, value));
|
||||
Map<String, List<String>> 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<T extends HttpMessage> implements Codec<T, T> {
|
||||
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<T extends HttpMessage> implements Codec<T, T> {
|
||||
@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<T extends HttpMessage> implements Codec<T, T> {
|
||||
}
|
||||
|
||||
_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<T extends HttpMessage> implements Codec<T, T> {
|
||||
|
||||
void _writeFrame(BytesBuilder bytesBuilder, FrameType type, int flag, int streamId, List<int> 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<T extends HttpMessage> implements Codec<T, T> {
|
||||
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<T extends HttpMessage> implements Codec<T, T> {
|
||||
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<T extends HttpMessage> implements Codec<T, T> {
|
||||
var blockFragment = payload.readBytes(headerBlockLength);
|
||||
|
||||
//读取头部信息
|
||||
Map<String, String> headers = _parseHeaders(channelContext, blockFragment);
|
||||
Map<String, List<String>> 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<String, String> _parseHeaders(ChannelContext channelContext, List<int> payload) {
|
||||
Map<String, List<String>> _parseHeaders(ChannelContext channelContext, List<int> payload) {
|
||||
if (channelContext.setting != null) {
|
||||
decoder.updateTableSize(channelContext.setting!.headTableSize);
|
||||
decoder.updateMaxReceivingHeaderTableSize(channelContext.setting!.headTableSize);
|
||||
}
|
||||
|
||||
// Decode the headers
|
||||
List<Header> headers = decoder.decode(payload);
|
||||
|
||||
// Convert the headers to a map
|
||||
Map<String, String> headerMap = {};
|
||||
Map<String, List<String>> 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<T extends HttpMessage> implements Codec<T, T> {
|
||||
|
||||
class Http2RequestDecoder extends Http2Codec<HttpRequest> {
|
||||
@override
|
||||
HttpRequest createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map<String, String> 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<String, List<String>> 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<HttpRequest> {
|
||||
List<Header> encodeHeaders(HttpRequest message) {
|
||||
var headers = <Header>[];
|
||||
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<HttpRequest> {
|
||||
|
||||
class Http2ResponseDecoder extends Http2Codec<HttpResponse> {
|
||||
@override
|
||||
HttpResponse createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map<String, String> headers) {
|
||||
var httpResponse = HttpResponse(HttpStatus.valueOf(int.parse(headers[':status']!)),
|
||||
protocolVersion: headers[":version"] ?? 'HTTP/2');
|
||||
HttpResponse createMessage(
|
||||
ChannelContext channelContext, FrameHeader frameHeader, Map<String, List<String>> 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<HttpResponse> {
|
||||
@override
|
||||
List<Header> encodeHeaders(HttpResponse message) {
|
||||
var headers = <Header>[];
|
||||
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) {
|
||||
@@ -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<Header> decode(List<int> bytes) {
|
||||
var headers = <Header>[];
|
||||
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<int> encodeList(List<Header> headers) {
|
||||
var bytesBuilder = BytesBuilder();
|
||||
|
||||
for (var header in headers) {
|
||||
_encodeHeader(bytesBuilder, header);
|
||||
}
|
||||
|
||||
return bytesBuilder.takeBytes();
|
||||
}
|
||||
|
||||
List<int> 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<Header> _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<Header> _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;
|
||||
}
|
||||
348
lib/network/http/h2/hpack/hpack.dart
Normal file
348
lib/network/http/h2/hpack/hpack.dart
Normal file
@@ -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<int> name;
|
||||
final List<int> 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<Header> decode(List<int> 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<int> 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<int> 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 = <Header>[];
|
||||
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<int> encode(List<Header> 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<int> 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<Header?> _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<Header> _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;
|
||||
}
|
||||
182
lib/network/http/h2/hpack/huffman.dart
Normal file
182
lib/network/http/h2/hpack/huffman.dart
Normal file
@@ -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<int> decode(List<int> bytes) => _decoder.decode(bytes);
|
||||
|
||||
List<int> encode(List<int> 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<int> decode(List<int> 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<EncodedHuffmanValue> _codewords;
|
||||
|
||||
HuffmanEncoder(this._codewords);
|
||||
|
||||
/// Encodes [bytes] using a list of codewords.
|
||||
List<int> encode(List<int> 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<EncodedHuffmanValue> 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;
|
||||
}
|
||||
@@ -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<int> decode(List<int> 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<EncodedHuffmanValue> _codewords;
|
||||
|
||||
HuffmanEncoder() : _codewords = _huffmanTable;
|
||||
|
||||
//http2协议规范 huffman编码
|
||||
List<int> encode(List<int> 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<EncodedHuffmanValue> 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<EncodedHuffmanValue> _huffmanTable = [
|
||||
/// This list of byte encodings via huffman encoding was generated from the
|
||||
/// HPACK specification.
|
||||
const List<EncodedHuffmanValue> _codeWords = <EncodedHuffmanValue>[
|
||||
EncodedHuffmanValue(0x1ff8, 13),
|
||||
EncodedHuffmanValue(0x7fffd8, 23),
|
||||
EncodedHuffmanValue(0xfffffe2, 28),
|
||||
@@ -408,10 +275,3 @@ final List<EncodedHuffmanValue> _huffmanTable = [
|
||||
EncodedHuffmanValue(0x3ffffee, 26),
|
||||
EncodedHuffmanValue(0x3fffffff, 30),
|
||||
];
|
||||
|
||||
class EncodedHuffmanValue {
|
||||
final int encodedBytes;
|
||||
final int numBits;
|
||||
|
||||
EncodedHuffmanValue(this.encodedBytes, this.numBits);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
53
lib/network/util/byte_utils.dart
Normal file
53
lib/network/util/byte_utils.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
List<int> viewOrSublist(List<int> 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<int> bytes, int offset) {
|
||||
var high = readInt32(bytes, offset);
|
||||
var low = readInt32(bytes, offset + 4);
|
||||
return high << 32 | low;
|
||||
}
|
||||
|
||||
int readInt32(List<int> bytes, int offset) {
|
||||
return (bytes[offset] << 24) |
|
||||
(bytes[offset + 1] << 16) |
|
||||
(bytes[offset + 2] << 8) |
|
||||
bytes[offset + 3];
|
||||
}
|
||||
|
||||
int readInt24(List<int> bytes, int offset) {
|
||||
return (bytes[offset] << 16) | (bytes[offset + 1] << 8) | bytes[offset + 2];
|
||||
}
|
||||
|
||||
int readInt16(List<int> bytes, int offset) {
|
||||
return (bytes[offset] << 8) | bytes[offset + 1];
|
||||
}
|
||||
|
||||
void setInt64(List<int> bytes, int offset, int value) {
|
||||
setInt32(bytes, offset, value >> 32);
|
||||
setInt32(bytes, offset + 4, value & 0xffffffff);
|
||||
}
|
||||
|
||||
void setInt32(List<int> 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<int> bytes, int offset, int value) {
|
||||
bytes[offset] = (value >> 16) & 0xff;
|
||||
bytes[offset + 1] = (value >> 8) & 0xff;
|
||||
bytes[offset + 2] = value & 0xff;
|
||||
}
|
||||
|
||||
void setInt16(List<int> bytes, int offset, int value) {
|
||||
bytes[offset] = (value >> 8) & 0xff;
|
||||
bytes[offset + 1] = value & 0xff;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -73,6 +73,15 @@ class _PreferenceState extends State<Preference> {
|
||||
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),
|
||||
|
||||
Reference in New Issue
Block a user