Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion docs/cpp.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ A struct representing a parser. It has the following fields:
- `skip_body` (`bool`): If the parser should skip the body.
- `debug` (`bool`): If debug tracing is enabled for this parser. It only affects tracing in debug-enabled builds.
- `max_start_line_length` (`uintptr_t`): Maximum allowed request/status line length. By default is `8192`.
- `max_header_length` (`uintptr_t`): Maximum allowed header length. By default is `8192`.
- `max_header_length` (`uintptr_t`): Maximum allowed individual header or trailer line length. By default is `8192`.
- `max_header_bytes` (`uintptr_t`): Maximum aggregate initial-header field bytes per message. By default is `65536`; `0` means unlimited.
- `max_header_count` (`uintptr_t`): Maximum initial-header field count per message. By default is `100`; `0` means unlimited.
- `max_trailer_bytes` (`uintptr_t`): Maximum aggregate trailer field bytes per message. By default is `65536`; `0` means unlimited.
- `max_trailer_count` (`uintptr_t`): Maximum trailer field count per message. By default is `100`; `0` means unlimited.
- `max_body_payload` (`uint64_t`): Maximum body payload bytes consumed in a single `milo_parse()` call. `0` means unlimited and is the default.
- `context` (`void*`): The context of this parser. Use is reserved to the developer.
- `state` (`uint8_t`): The current parser state.
Expand All @@ -130,6 +134,8 @@ A struct representing a parser. It has the following fields:
- `has_connection_upgrade` (`bool`): If the current message has a `Connection: upgrade` token.
- `has_upgrade` (`bool`): If the current message has an `Upgrade` header.
- `has_trailers` (`bool`): If the current message has a `Trailer` header.
- `header_bytes` / `header_count` (`uintptr_t`): Committed initial-header field bytes and count for the current message.
- `trailer_bytes` / `trailer_count` (`uintptr_t`): Committed trailer field bytes and count for the current message.
- `active_callbacks` (`uint64_t`): Active callback bitmask. Set to one or more `CALLBACK_ACTIVE_*` values.
- `active_events` (`uint64_t`): Active event bitmask. Set to one or more `EVENT_ACTIVE_*` values.
- `callbacks` (`ParserCallbacks`): The callbacks for the current parser.
Expand All @@ -151,6 +157,10 @@ All the fields **MUST** be considered readonly, with the following exceptions:
- `debug`
- `max_start_line_length`
- `max_header_length`
- `max_header_bytes`
- `max_header_count`
- `max_trailer_bytes`
- `max_trailer_count`
- `max_body_payload`
- `context`
- `active_callbacks`
Expand All @@ -167,6 +177,12 @@ The event buffer is terminated by `EVENT_END`. Do not rely on the internal buffe

If an active event would exceed the internal event buffer, parsing stops before consuming the data that would have produced the event. This is not a parser error and does not pause the parser. Call `milo_parse()` again after draining the event buffer.

## Header and Trailer Budgets

Initial headers and trailers have independent aggregate byte and field-count limits. Defaults are 65,536 bytes and 100 fields for each section; set an individual limit to `0` to disable it. Native callers may write the public configuration fields or use `milo_set_max_header_bytes()`, `milo_set_max_header_count()`, `milo_set_max_trailer_bytes()`, and `milo_set_max_trailer_count()`.

The byte total includes every committed field line, including its terminating CRLF, but excludes the final empty CRLF. Complete field lines are checked against the budget before field syntax and semantics are processed, so an over-budget malformed field reports the budget error. Totals commit only after validation and event delivery; fragmented input and a retry after event-buffer suspension do not count a side-effect-free field more than once. Exceeding a limit fails parsing with `ERROR_UNEXPECTED_CHARACTER`.

## Body Payload Limit

`max_body_payload` limits how many body payload bytes a single `milo_parse()` invocation can consume. The default value is `0`, which means unlimited.
Expand Down
26 changes: 24 additions & 2 deletions docs/js.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ Read the event buffer pointer from `parser + ParserFields.EVENTS`, then drain re

If an active event would exceed the internal event buffer, parsing stops before consuming the data that would have produced the event. This is not a parser error and does not pause the parser. Call `parse()` again after draining the event buffer.

### Header and Trailer Budgets

`setMaxHeaderBytes`, `setMaxHeaderCount`, `setMaxTrailerBytes`, and `setMaxTrailerCount` configure independent aggregate initial-header and trailer limits. Defaults are 65,536 bytes and 100 fields for each section; set an individual limit to `0` to disable it.

The byte total includes every committed field line, including its terminating CRLF, but excludes the final empty CRLF. Complete field lines are checked against the budget before field syntax and semantics are processed, so an over-budget malformed field reports the budget error. Totals commit only after validation and event delivery; fragmented input and a retry after event-buffer suspension do not count a side-effect-free field more than once. Exceeding a limit fails parsing with `ERROR_UNEXPECTED_CHARACTER`.

### Body Payload Limit

`setMaxBodyPayload(parser, value)` limits how many body payload bytes a single `parse()` invocation can consume. The default value is `0`, which means unlimited.
Expand Down Expand Up @@ -385,10 +391,18 @@ Default is `8192`.

#### `getMaxHeaderLength(parser)`

Returns the parser maximum allowed header length.
Returns the parser maximum allowed individual header or trailer line length.

Default is `8192`.

#### `getMaxHeaderBytes(parser)` / `getMaxHeaderCount(parser)`

Return the aggregate initial-header byte or field-count limit. Defaults are `65536` and `100`.

#### `getMaxTrailerBytes(parser)` / `getMaxTrailerCount(parser)`

Return the aggregate trailer byte or field-count limit. Defaults are `65536` and `100`.

#### `getMaxBodyPayload(parser)`

Returns the maximum body payload bytes consumed by a single `parse()` invocation.
Expand Down Expand Up @@ -499,10 +513,18 @@ Defaults to `8192` in a new parser.

#### `setMaxHeaderLength(parser, value)`

Sets the parser maximum allowed header length.
Sets the parser maximum allowed individual header or trailer line length.

Defaults to `8192` in a new parser.

#### `setMaxHeaderBytes(parser, value)` / `setMaxHeaderCount(parser, value)`

Set the aggregate initial-header byte or field-count limit. Use `0` for unlimited.

#### `setMaxTrailerBytes(parser, value)` / `setMaxTrailerCount(parser, value)`

Set the aggregate trailer byte or field-count limit. Use `0` for unlimited.

#### `setMaxBodyPayload(parser, value)`

Sets the maximum body payload bytes consumed by a single `parse()` invocation. Use `0` for unlimited.
Expand Down
18 changes: 17 additions & 1 deletion docs/rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,11 @@ A struct representing a parser. It has the following fields:
- `skip_body` (`bool`): If the parser should skip the body.
- `debug` (`bool`): If debug tracing is enabled for this parser. It only affects tracing in debug-enabled builds.
- `max_start_line_length` (`usize`): Maximum allowed request/status line length. By default is `8192`.
- `max_header_length` (`usize`): Maximum allowed header length. By default is `8192`.
- `max_header_length` (`usize`): Maximum allowed individual header or trailer line length. By default is `8192`.
- `max_header_bytes` (`usize`): Maximum aggregate initial-header field bytes per message. By default is `65536`; `0` means unlimited.
- `max_header_count` (`usize`): Maximum initial-header field count per message. By default is `100`; `0` means unlimited.
- `max_trailer_bytes` (`usize`): Maximum aggregate trailer field bytes per message. By default is `65536`; `0` means unlimited.
- `max_trailer_count` (`usize`): Maximum trailer field count per message. By default is `100`; `0` means unlimited.
- `max_body_payload` (`u64`): Maximum body payload bytes consumed in a single `parse()` call. `0` means unlimited and is the default.
- `context` (`*mut c_void`): The context of this parser. Use is reserved to the developer.
- `state` (`u8`): The current parser state.
Expand All @@ -131,6 +135,8 @@ A struct representing a parser. It has the following fields:
- `has_connection_upgrade` (`bool`): If the current message has a `Connection: upgrade` token.
- `has_upgrade` (`bool`): If the current message has an `Upgrade` header.
- `has_trailers` (`bool`): If the current message has a `Trailer` header.
- `header_bytes` / `header_count` (`usize`): Committed initial-header field bytes and count for the current message.
- `trailer_bytes` / `trailer_count` (`usize`): Committed trailer field bytes and count for the current message.
- `active_callbacks` (`u64`): Active callback bitmask. Set to one or more `CALLBACK_ACTIVE_*` flags.
- `active_events` (`u64`): Active event bitmask. Set to one or more `EVENT_ACTIVE_*` flags.
- `callbacks` (`ParserCallbacks`): The callbacks for the current parser.
Expand All @@ -152,6 +158,10 @@ All the fields **MUST** be considered readonly, with the following exceptions:
- `debug`
- `max_start_line_length`
- `max_header_length`
- `max_header_bytes`
- `max_header_count`
- `max_trailer_bytes`
- `max_trailer_count`
- `max_body_payload`
- `context`
- `active_callbacks`
Expand All @@ -168,6 +178,12 @@ The event buffer is terminated by `EVENT_END`. Do not rely on the internal buffe

If an active event would exceed the internal event buffer, parsing stops before consuming the data that would have produced the event. This is not a parser error and does not pause the parser. Call `parse()` again after draining the event buffer.

## Header and Trailer Budgets

Initial headers and trailers have independent aggregate byte and field-count limits. Defaults are 65,536 bytes and 100 fields for each section; set an individual limit to `0` to disable it.

The byte total includes every committed field line, including its terminating CRLF, but excludes the final empty CRLF. Complete field lines are checked against the budget before field syntax and semantics are processed, so an over-budget malformed field reports the budget error. Totals commit only after validation and event delivery; fragmented input and a retry after event-buffer suspension do not count a side-effect-free field more than once. Exceeding a limit fails parsing with `ERROR_UNEXPECTED_CHARACTER`.

## Body Payload Limit

`max_body_payload` limits how many body payload bytes a single `parse()` invocation can consume. The default value is `0`, which means unlimited.
Expand Down
16 changes: 16 additions & 0 deletions macros/src/parser_fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ type WasmUsize = u32;
struct ParserStub {
max_start_line_length: WasmUsize,
max_header_length: WasmUsize,
max_header_bytes: WasmUsize,
max_header_count: WasmUsize,
max_trailer_bytes: WasmUsize,
max_trailer_count: WasmUsize,
max_body_payload: u64,
autodetect: bool,
is_request: bool,
Expand Down Expand Up @@ -39,6 +43,10 @@ struct ParserStub {
has_connection_upgrade: bool,
has_upgrade: bool,
has_trailers: bool,
header_bytes: WasmUsize,
header_count: WasmUsize,
trailer_bytes: WasmUsize,
trailer_count: WasmUsize,
active_callbacks: u64,
active_events: u64,
ptr: WasmPointer,
Expand All @@ -52,6 +60,10 @@ struct ParserStub {
pub const FIELDS: &[(&str, usize)] = &[
("MAX_START_LINE_LENGTH", offset_of!(ParserStub, max_start_line_length)),
("MAX_HEADER_LENGTH", offset_of!(ParserStub, max_header_length)),
("MAX_HEADER_BYTES", offset_of!(ParserStub, max_header_bytes)),
("MAX_HEADER_COUNT", offset_of!(ParserStub, max_header_count)),
("MAX_TRAILER_BYTES", offset_of!(ParserStub, max_trailer_bytes)),
("MAX_TRAILER_COUNT", offset_of!(ParserStub, max_trailer_count)),
("MAX_BODY_PAYLOAD", offset_of!(ParserStub, max_body_payload)),
("AUTODETECT", offset_of!(ParserStub, autodetect)),
("IS_REQUEST", offset_of!(ParserStub, is_request)),
Expand Down Expand Up @@ -85,6 +97,10 @@ pub const FIELDS: &[(&str, usize)] = &[
("HAS_CONNECTION_UPGRADE", offset_of!(ParserStub, has_connection_upgrade)),
("HAS_UPGRADE", offset_of!(ParserStub, has_upgrade)),
("HAS_TRAILERS", offset_of!(ParserStub, has_trailers)),
("HEADER_BYTES", offset_of!(ParserStub, header_bytes)),
("HEADER_COUNT", offset_of!(ParserStub, header_count)),
("TRAILER_BYTES", offset_of!(ParserStub, trailer_bytes)),
("TRAILER_COUNT", offset_of!(ParserStub, trailer_count)),
("ACTIVE_CALLBACKS", offset_of!(ParserStub, active_callbacks)),
("ACTIVE_EVENTS", offset_of!(ParserStub, active_events)),
("PTR", offset_of!(ParserStub, ptr)),
Expand Down
27 changes: 27 additions & 0 deletions parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ pub struct Parser {
pub context: *mut c_void,
pub max_start_line_length: usize,
pub max_header_length: usize,
pub max_header_bytes: usize,
pub max_header_count: usize,
pub max_trailer_bytes: usize,
pub max_trailer_count: usize,
pub max_body_payload: u64,
pub autodetect: bool,
pub is_request: bool,
Expand Down Expand Up @@ -53,6 +57,10 @@ pub struct Parser {
pub has_connection_upgrade: bool,
pub has_upgrade: bool,
pub has_trailers: bool,
pub header_bytes: usize,
pub header_count: usize,
pub trailer_bytes: usize,
pub trailer_count: usize,

// Callback handling
pub active_callbacks: u64,
Expand Down Expand Up @@ -101,6 +109,10 @@ impl Parser {
context: ptr::null_mut(),
max_start_line_length: 8192,
max_header_length: 8192,
max_header_bytes: 65536,
max_header_count: 100,
max_trailer_bytes: 65536,
max_trailer_count: 100,
max_body_payload: 0,
autodetect: true,
is_request: false,
Expand Down Expand Up @@ -130,6 +142,10 @@ impl Parser {
has_connection_upgrade: false,
has_upgrade: false,
has_trailers: false,
header_bytes: 0,
header_count: 0,
trailer_bytes: 0,
trailer_count: 0,
// Callbacks handling
active_callbacks: 0,
active_events: 0,
Expand Down Expand Up @@ -158,6 +174,13 @@ impl Parser {
/// * suspend_after_headers
/// * manage_unconsumed
/// * continue_without_data
/// * max_start_line_length
/// * max_header_length
/// * max_header_bytes
/// * max_header_count
/// * max_trailer_bytes
/// * max_trailer_count
/// * max_body_payload
/// * debug
/// * context
pub fn reset(&mut self, keep_parsed: bool) {
Expand Down Expand Up @@ -209,6 +232,10 @@ impl Parser {
self.has_connection_upgrade = false;
self.has_upgrade = false;
self.has_trailers = false;
self.header_bytes = 0;
self.header_count = 0;
self.trailer_bytes = 0;
self.trailer_count = 0;
self.content_length = 0;
self.chunk_size = 0;
self.remaining_content_length = 0;
Expand Down
38 changes: 38 additions & 0 deletions parser/src/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,38 @@ pub extern "C" fn milo_set_active_events(parser: *mut Parser, value: u64) {
}
}

/// Sets the maximum aggregate initial-header bytes per message.
#[unsafe(no_mangle)]
pub extern "C" fn milo_set_max_header_bytes(parser: *mut Parser, value: usize) {
unsafe {
(*parser).max_header_bytes = value;
}
}

/// Sets the maximum initial-header field count per message.
#[unsafe(no_mangle)]
pub extern "C" fn milo_set_max_header_count(parser: *mut Parser, value: usize) {
unsafe {
(*parser).max_header_count = value;
}
}

/// Sets the maximum aggregate trailer bytes per message.
#[unsafe(no_mangle)]
pub extern "C" fn milo_set_max_trailer_bytes(parser: *mut Parser, value: usize) {
unsafe {
(*parser).max_trailer_bytes = value;
}
}

/// Sets the maximum trailer field count per message.
#[unsafe(no_mangle)]
pub extern "C" fn milo_set_max_trailer_count(parser: *mut Parser, value: usize) {
unsafe {
(*parser).max_trailer_count = value;
}
}

/// Sets the maximum body payload consumed by a single parse invocation.
#[unsafe(no_mangle)]
pub extern "C" fn milo_set_max_body_payload(parser: *mut Parser, value: u64) {
Expand All @@ -114,6 +146,12 @@ pub extern "C" fn milo_set_suspend_after_headers(parser: *mut Parser, value: boo
/// * autodetect
/// * is_request
/// * suspend_after_headers
/// * max_start_line_length
/// * max_header_length
/// * max_header_bytes
/// * max_header_count
/// * max_trailer_bytes
/// * max_trailer_count
/// * max_body_payload
/// * manage_unconsumed
/// * continue_without_data
Expand Down
31 changes: 31 additions & 0 deletions parser/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,19 @@ impl Parser {
}
}

let next_header_count = match self.header_count.checked_add(1) {
Some(count) if self.max_header_count == 0 || count <= self.max_header_count => count,
_ => {
fail!(UNEXPECTED_CHARACTER, "Too many header fields");
}
};
let next_header_bytes = match self.header_bytes.checked_add(cr + 2) {
Some(bytes) if self.max_header_bytes == 0 || bytes <= self.max_header_bytes => bytes,
_ => {
fail!(UNEXPECTED_CHARACTER, "Header section too large");
}
};

// RFC 9112 section.4
// RFC 9110 section 5.5 and 5.6
let header_name_start = 0;
Expand Down Expand Up @@ -796,6 +809,8 @@ impl Parser {
);
}

self.header_count = next_header_count;
self.header_bytes = next_header_bytes;
advance!(cr + 2);
}
HeaderLineScanResult::Invalid(invalid) => {
Expand Down Expand Up @@ -1324,6 +1339,19 @@ impl Parser {
next!();
}

let next_trailer_count = match self.trailer_count.checked_add(1) {
Some(count) if self.max_trailer_count == 0 || count <= self.max_trailer_count => count,
_ => {
fail!(UNEXPECTED_CHARACTER, "Too many trailer fields");
}
};
let next_trailer_bytes = match self.trailer_bytes.checked_add(cr + 2) {
Some(bytes) if self.max_trailer_bytes == 0 || bytes <= self.max_trailer_bytes => bytes,
_ => {
fail!(UNEXPECTED_CHARACTER, "Trailer section too large");
}
};

let trailer_name_start = 0;
let trailer_name_end = match find_char(data, trailer_name_start, cr, b':') {
Some(index) if index > trailer_name_start => index,
Expand Down Expand Up @@ -1358,6 +1386,9 @@ impl Parser {
trailer_value_end - trailer_value_start
);
}

self.trailer_count = next_trailer_count;
self.trailer_bytes = next_trailer_bytes;
advance!(cr + 2);
}
HeaderLineScanResult::Invalid(invalid) => {
Expand Down
Loading
Loading