mirror of
https://github.com/oven-sh/bun
synced 2026-02-14 21:01:52 +00:00
Add support for maxSendHeaderBlockLength in http2 client (#19642)
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'
|
||||
}));
|
||||
}));
|
||||
Reference in New Issue
Block a user