Add support for maxSendHeaderBlockLength in http2 client (#19642)

This commit is contained in:
pfg
2025-05-14 18:56:55 -07:00
committed by GitHub
parent cd8d037c79
commit 06fa1ed598
3 changed files with 92 additions and 14 deletions

View File

@@ -32,12 +32,12 @@ pub fn getHTTP2CommonString(globalObject: *JSC.JSGlobalObject, hpack_index: u32)
if (value.isEmptyOrUndefinedOrNull()) return null;
return value;
}
const MAX_WINDOW_SIZE = 2147483647;
const MAX_HEADER_TABLE_SIZE = 4294967295;
const MAX_STREAM_ID = 2147483647;
const WINDOW_INCREMENT_SIZE = 65536;
const MAX_HPACK_HEADER_SIZE = 65536;
const MAX_FRAME_SIZE = 16777215;
const MAX_WINDOW_SIZE = std.math.maxInt(i32);
const MAX_HEADER_TABLE_SIZE = std.math.maxInt(u32);
const MAX_STREAM_ID = std.math.maxInt(i32);
const WINDOW_INCREMENT_SIZE = std.math.maxInt(u16);
const MAX_HPACK_HEADER_SIZE = std.math.maxInt(u16);
const MAX_FRAME_SIZE = std.math.maxInt(u24);
const PaddingStrategy = enum {
none,
@@ -235,14 +235,6 @@ const FullSettingsPayload = packed struct(u288) {
return (writer.write(std.mem.asBytes(&swap)[0..FullSettingsPayload.byteSize]) catch 0) != 0;
}
};
const ValidPseudoHeaders = bun.ComptimeStringMap(void, .{
.{":status"},
.{":method"},
.{":authority"},
.{":scheme"},
.{":path"},
.{":protocol"},
});
const ValidResponsePseudoHeaders = bun.ComptimeStringMap(void, .{
.{":status"},
@@ -534,6 +526,7 @@ const Handlers = struct {
onAborted: JSC.JSValue = .zero,
onAltSvc: JSC.JSValue = .zero,
onOrigin: JSC.JSValue = .zero,
onFrameError: JSC.JSValue = .zero, // Added for frameError events
binary_type: BinaryType = .Buffer,
vm: *JSC.VirtualMachine,
@@ -592,6 +585,7 @@ const Handlers = struct {
.{ "onWrite", "write" },
.{ "onAltSvc", "altsvc" },
.{ "onOrigin", "origin" },
.{ "onFrameError", "frameError" },
};
inline for (pairs) |pair| {
@@ -648,6 +642,7 @@ const Handlers = struct {
this.onEnd = .zero;
this.onGoAway = .zero;
this.onAborted = .zero;
this.onFrameError = .zero;
this.strong_ctx.deinit();
}
};
@@ -701,6 +696,7 @@ pub const H2FrameParser = struct {
queuedDataSize: u64 = 0, // this is in bytes
maxOutstandingPings: u64 = 10,
outStandingPings: u64 = 0,
maxSendHeaderBlockLength: u32 = 0,
lastStreamID: u32 = 0,
isServer: bool = false,
prefaceReceivedLen: u8 = 0,
@@ -3940,6 +3936,23 @@ pub const H2FrameParser = struct {
}
log("request encoded_size {}", .{encoded_size});
// Check if headers block exceeds maxSendHeaderBlockLength
if (this.maxSendHeaderBlockLength != 0 and encoded_size > this.maxSendHeaderBlockLength) {
stream.state = .CLOSED;
stream.rstCode = @intFromEnum(ErrorCode.REFUSED_STREAM);
this.dispatchWith2Extra(
.onFrameError,
stream.getIdentifier(),
JSC.JSValue.jsNumber(@intFromEnum(FrameType.HTTP_FRAME_HEADERS)),
JSC.JSValue.jsNumber(@intFromEnum(ErrorCode.FRAME_SIZE_ERROR)),
);
this.dispatchWithExtra(.onStreamError, stream.getIdentifier(), JSC.JSValue.jsNumber(stream.rstCode));
return JSC.JSValue.jsNumber(stream_id);
}
const padding = stream.getPadding(encoded_size, buffer.len - 1);
const payload_size = encoded_size + (if (padding != 0) padding + 1 else 0);
if (padding != 0) {
@@ -4204,6 +4217,11 @@ pub const H2FrameParser = struct {
this.maxOutstandingSettings = @max(1, @as(u32, @truncate(max_outstanding_settings.to(u64))));
}
}
if (try settings_js.get(globalObject, "maxSendHeaderBlockLength")) |max_send_header_block_length| {
if (max_send_header_block_length.isNumber()) {
this.maxSendHeaderBlockLength = @bitCast(max_send_header_block_length.toInt32());
}
}
}
}
var is_server = false;

View File

@@ -2479,6 +2479,11 @@ class ServerHttp2Session extends Http2Session {
const stream = new ServerHttp2Stream(stream_id, self, null);
self.#parser?.setStreamContext(stream_id, stream);
},
frameError(self: ServerHttp2Session, stream: ServerHttp2Stream, frameType: number, errorCode: number) {
if (!self || typeof stream !== "object") return;
// Emit the frameError event with the frame type and error code
process.nextTick(emitFrameErrorEventNT, stream, frameType, errorCode);
},
aborted(self: ServerHttp2Session, stream: ServerHttp2Stream, error: any, old_state: number) {
if (!self || typeof stream !== "object") return;
stream.rstCode = constants.NGHTTP2_CANCEL;
@@ -2950,6 +2955,11 @@ class ClientHttp2Session extends Http2Session {
self.#parser?.setStreamContext(stream_id, stream);
}
},
frameError(self: ClientHttp2Session, stream: ClientHttp2Stream, frameType: number, errorCode: number) {
if (!self || typeof stream !== "object") return;
// Emit the frameError event with the frame type and error code
process.nextTick(emitFrameErrorEventNT, stream, frameType, errorCode);
},
aborted(self: ClientHttp2Session, stream: ClientHttp2Stream, error: any, old_state: number) {
if (!self || typeof stream !== "object") return;
stream.rstCode = constants.NGHTTP2_CANCEL;
@@ -3699,6 +3709,9 @@ Http2Server.prototype[EventEmitter.captureRejectionSymbol] = function (err, even
function onErrorSecureServerSession(err, socket) {
if (!this.emit("clientError", err, socket)) socket.destroy(err);
}
function emitFrameErrorEventNT(stream, frameType, errorCode) {
stream.emit("frameError", frameType, errorCode);
}
class Http2SecureServer extends tls.Server {
timeout = 0;
constructor(options, onRequestHandler) {

View File

@@ -0,0 +1,47 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
// We use the lower-level API here
server.on('stream', common.mustNotCall());
server.listen(0, common.mustCall(() => {
// Setting the maxSendHeaderBlockLength, then attempting to send a
// headers block that is too big should cause a 'frameError' to
// be emitted, and will cause the stream to be shutdown.
const options = {
maxSendHeaderBlockLength: 10
};
const client = h2.connect(`http://localhost:${server.address().port}`,
options);
client.on('error', () => {});
const req = client.request();
req.on('response', common.mustNotCall());
req.resume();
req.on('close', common.mustCall(() => {
client.close();
server.close();
}));
req.on('frameError', common.mustCall((type, code) => {
assert.strictEqual(code, h2.constants.NGHTTP2_FRAME_SIZE_ERROR);
}));
// NGHTTP2 will automatically send the NGHTTP2_REFUSED_STREAM with
// the GOAWAY frame.
req.on('error', common.expectsError({
code: 'ERR_HTTP2_STREAM_ERROR',
name: 'Error',
message: 'Stream closed with error code NGHTTP2_REFUSED_STREAM'
}));
}));