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
55 changes: 54 additions & 1 deletion ext/node/ops/llhttp/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ struct Inner {
header_overflow: bool,
initialized: bool,

/// Bytes consumed up to (but not including) the parse error position
/// from the most recent execute() call. Set to 0 on success, valid
/// only after execute() returns -1.
last_bytes_parsed: u32,
/// llhttp errno from the most recent execute() / finish() call.
/// Used to surface specific error codes (e.g. HPE_INVALID_TRANSFER_ENCODING)
/// to JS instead of the generic HPE_ERROR.
last_errno: i32,

/// The stream being consumed (for parser.consume optimization).
/// When set, the parser reads directly from the stream handle
/// via a ReadInterceptor, bypassing the JS readable stream.
Expand Down Expand Up @@ -137,6 +146,8 @@ impl HTTPParser {
header_nread: 0,
header_overflow: false,
initialized: false,
last_bytes_parsed: 0,
last_errno: 0,
consumed_stream: None,
consume_callbacks: None,
consume_isolate: v8::UnsafeRawIsolatePtr::null(),
Expand Down Expand Up @@ -220,6 +231,9 @@ impl Inner {
self.got_exception = false;
self.pending_pause = false;
self.header_nread = 0;
self.last_bytes_parsed = 0;
self.last_errno = sys::HPE_OK;
self.header_overflow = false;
self.initialized = true;
}

Expand Down Expand Up @@ -819,6 +833,8 @@ unsafe fn consume_read_callback(
}
}
}
inner.last_bytes_parsed = nread_result.max(0) as u32;
inner.last_errno = err as i32;

if inner.pending_pause {
inner.pending_pause = false;
Expand All @@ -842,7 +858,7 @@ unsafe fn consume_read_callback(
let is_error =
inner.got_exception || (inner.parser.upgrade == 0 && err != sys::HPE_OK);
let result_val: v8::Local<v8::Value> = if is_error {
let bytes_parsed = data.len() as i32;
let bytes_parsed = nread_result.max(0);
let parse_err = if inner.header_overflow {
ParseError::HeaderOverflow { bytes_parsed }
} else {
Expand Down Expand Up @@ -974,6 +990,8 @@ impl HTTPParser {
}
}
}
inner.last_bytes_parsed = nread as u32;
inner.last_errno = err as i32;

if inner.pending_pause {
inner.pending_pause = false;
Expand Down Expand Up @@ -1063,6 +1081,41 @@ impl HTTPParser {
self.inner().header_overflow
}

/// Bytes consumed before the parse error. Set by the most recent
/// execute() call; only meaningful when execute() returned -1.
#[fast]
fn get_last_bytes_parsed(&self) -> u32 {
self.inner().last_bytes_parsed
}

/// llhttp errno name (e.g. "HPE_INVALID_TRANSFER_ENCODING") for the
/// most recent execute() / finish() call. Returns "HPE_OK" when no
/// error is recorded.
#[string]
fn get_last_error_code(&self) -> String {
let errno = self.inner().last_errno;
let name_ptr = unsafe { sys::llhttp_errno_name(errno) };
if name_ptr.is_null() {
return String::from("HPE_ERROR");
}
let cstr = unsafe { CStr::from_ptr(name_ptr) };
cstr.to_string_lossy().into_owned()
}

/// Human-readable reason for the most recent parse error
/// (e.g. "Transfer-Encoding can't be present with Content-Length").
/// Returns the empty string if no reason is set.
#[string]
fn get_last_error_reason(&self) -> String {
let inner = self.inner();
let reason_ptr = unsafe { sys::llhttp_get_error_reason(&inner.parser) };
if reason_ptr.is_null() {
return String::new();
}
let cstr = unsafe { CStr::from_ptr(reason_ptr) };
cstr.to_string_lossy().into_owned()
}

/// Get the current buffer being parsed (for error reporting).
#[buffer]
fn get_current_buffer(&self) -> Box<[u8]> {
Expand Down
11 changes: 8 additions & 3 deletions ext/node/polyfills/internal_binding/http_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,15 @@ HTTPParser.prototype.execute = function (
err.reason = "Header overflow";
err.message = "Parse Error: Header overflow";
} else {
err.code = "HPE_ERROR";
err.reason = "Parse Error";
const code = this._native.getLastErrorCode();
const reason = this._native.getLastErrorReason();
err.code = code && code !== "HPE_OK" ? code : "HPE_ERROR";
err.reason = reason || "Parse Error";
if (reason) {
err.message = `Parse Error: ${reason}`;
}
}
err.bytesParsed = data.byteLength;
err.bytesParsed = this._native.getLastBytesParsed();
return err;
}

Expand Down
1 change: 1 addition & 0 deletions tests/node_compat/config.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -1310,6 +1310,7 @@
"parallel/test-http-client-default-headers-exist.js": {},
"parallel/test-http-client-defaults.js": {},
"parallel/test-http-client-encoding.js": {},
"parallel/test-http-client-error-rawbytes.js": {},
"parallel/test-http-client-finished.js": {},
"parallel/test-http-client-get-url.js": {},
"parallel/test-http-client-headers-array.js": {},
Expand Down
Loading