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
3 changes: 1 addition & 2 deletions packages/bun-uws/src/Http2Context.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,7 @@ static inline bool validFieldName(const char *p, unsigned n) {
/* The methods Bun.serve can represent: the HTTP/1 parser's strict set, in
* their RFC 9110 case-sensitive wire form. */
static inline bool isKnownMethod(std::string_view method) {
for (unsigned char c : method) if (!((c >= 'A' && c <= 'Z') || c == '-')) return false;
return Bun__HTTPMethod__from(method.data(), method.size()) != -1;
return methodIdFromWire(method) != HTTP_METHOD_NONE;
}

/* The request fields validation cares about. lshpack reports the HPACK
Expand Down
53 changes: 1 addition & 52 deletions packages/bun-uws/src/HttpContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
#include "MoveOnlyFunction.h"
#include "HttpParser.h"
#include <span>
#include <array>
#include <mutex>


Expand All @@ -41,56 +40,6 @@ extern "C" void Bun__NodeHTTP__onReadParsed(int ssl, struct us_socket_t *s);

namespace uWS {

namespace detail {

template <typename T, typename... Args>
[[nodiscard]] constexpr auto makeArray(T&& el0, Args&&... values) noexcept {
return std::array<std::decay_t<T>, 1 + sizeof...(Args)>{
std::forward<T>(el0), std::forward<Args>(values)...
};
}

static constexpr auto supportedHttpMethods = makeArray<std::string_view>(
"ACL",
"BIND",
"CHECKOUT",
"CONNECT",
"COPY",
"DELETE",
"GET",
"HEAD",
"LINK",
"LOCK",
"M-SEARCH",
"MERGE",
"MKACTIVITY",
"MKADDRESSBOOK",
"MKCALENDAR",
"MKCOL",
"MOVE",
"NOTIFY",
"OPTIONS",
"PATCH",
"POST",
"PROPFIND",
"PROPPATCH",
"PURGE",
"PUT",
"QUERY",
"REBIND",
"REPORT",
"SEARCH",
"SOURCE",
"SUBSCRIBE",
"TRACE",
"UNBIND",
"UNLINK",
"UNLOCK",
"UNSUBSCRIBE"
);

} // namespace detail

template<bool> struct HttpResponse;

/* Real heap-allocated owner of one HTTP server's socket group + router state.
Expand Down Expand Up @@ -1195,7 +1144,7 @@ struct HttpContext {
std::string_view method_sv_buffer;
// When it's NOT node:http, allow the uWS default precedence ordering.
if (method == "*" && !httpContextData->flags.useStrictMethodValidation) {
methods = detail::supportedHttpMethods;
methods = HTTP_METHOD_NAMES;
} else {
method_buffer = std::string(method);
method_sv_buffer = std::string_view(method_buffer);
Expand Down
152 changes: 152 additions & 0 deletions packages/bun-uws/src/HttpMethod.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#ifndef UWS_HTTPMETHOD_H
#define UWS_HTTPMETHOD_H

#include <array>
#include <cstddef>
#include <cstdint>
#include <string_view>

namespace uWS {

/* The request methods Bun can represent. The index of a name is the method's
* id: the discriminant of bun_http_types::Method (src/http_types/Method.rs).
* Both lists are in ascending byte order of the name. */
inline constexpr std::array<std::string_view, 36> HTTP_METHOD_NAMES = {
Comment thread
claude[bot] marked this conversation as resolved.
"ACL",
"BIND",
"CHECKOUT",
"CONNECT",
"COPY",
"DELETE",
"GET",
"HEAD",
"LINK",
"LOCK",
"M-SEARCH",
"MERGE",
"MKACTIVITY",
"MKADDRESSBOOK",
"MKCALENDAR",
"MKCOL",
"MOVE",
"NOTIFY",
"OPTIONS",
"PATCH",
"POST",
"PROPFIND",
"PROPPATCH",
"PURGE",
"PUT",
"QUERY",
"REBIND",
"REPORT",
"SEARCH",
"SOURCE",
"SUBSCRIBE",
"TRACE",
"UNBIND",
"UNLINK",
"UNLOCK",
"UNSUBSCRIBE",
};

inline constexpr uint8_t HTTP_METHOD_COUNT = (uint8_t) HTTP_METHOD_NAMES.size();

/* The id of a token that is none of HTTP_METHOD_NAMES. */
inline constexpr uint8_t HTTP_METHOD_NONE = 0xff;

namespace detail {

template <size_t N>
constexpr bool methodIs(const char *method, const char (&name)[N]) {
return __builtin_memcmp(method, name, N - 1) == 0;
}

} // namespace detail

/* The id of a method as the request line or :method carries it. A method is
* case-sensitive (RFC 9110 9.1), so "get" has no id. One switch on the length,
* then compares of that fixed width, the common methods first. */
constexpr uint8_t methodIdFromWire(std::string_view method) {
using detail::methodIs;
const char *m = method.data();
switch (method.length()) {
case 3:
if (methodIs(m, "GET")) return 6;
if (methodIs(m, "PUT")) return 24;
if (methodIs(m, "ACL")) return 0;
break;
case 4:
if (methodIs(m, "POST")) return 20;
if (methodIs(m, "HEAD")) return 7;
if (methodIs(m, "BIND")) return 1;
if (methodIs(m, "COPY")) return 4;
if (methodIs(m, "LINK")) return 8;
if (methodIs(m, "LOCK")) return 9;
if (methodIs(m, "MOVE")) return 16;
break;
case 5:
if (methodIs(m, "PATCH")) return 19;
if (methodIs(m, "TRACE")) return 31;
if (methodIs(m, "MERGE")) return 11;
if (methodIs(m, "MKCOL")) return 15;
if (methodIs(m, "PURGE")) return 23;
if (methodIs(m, "QUERY")) return 25;
break;
case 6:
if (methodIs(m, "DELETE")) return 5;
if (methodIs(m, "NOTIFY")) return 17;
if (methodIs(m, "REBIND")) return 26;
if (methodIs(m, "REPORT")) return 27;
if (methodIs(m, "SEARCH")) return 28;
if (methodIs(m, "SOURCE")) return 29;
if (methodIs(m, "UNBIND")) return 32;
if (methodIs(m, "UNLINK")) return 33;
if (methodIs(m, "UNLOCK")) return 34;
break;
case 7:
if (methodIs(m, "OPTIONS")) return 18;
if (methodIs(m, "CONNECT")) return 3;
break;
case 8:
if (methodIs(m, "PROPFIND")) return 21;
if (methodIs(m, "CHECKOUT")) return 2;
if (methodIs(m, "M-SEARCH")) return 10;
break;
case 9:
if (methodIs(m, "PROPPATCH")) return 22;
if (methodIs(m, "SUBSCRIBE")) return 30;
break;
case 10:
if (methodIs(m, "MKACTIVITY")) return 12;
if (methodIs(m, "MKCALENDAR")) return 14;
break;
case 11:
if (methodIs(m, "UNSUBSCRIBE")) return 35;
break;
case 13:
if (methodIs(m, "MKADDRESSBOOK")) return 13;
break;
}
return HTTP_METHOD_NONE;
}

static_assert([] {
for (uint8_t id = 0; id < HTTP_METHOD_COUNT; id++) {
if (methodIdFromWire(HTTP_METHOD_NAMES[id]) != id) {
return false;
}
if (id && !(HTTP_METHOD_NAMES[id - 1] < HTTP_METHOD_NAMES[id])) {
return false;
}
}
return methodIdFromWire("") == HTTP_METHOD_NONE
&& methodIdFromWire("get") == HTTP_METHOD_NONE
&& methodIdFromWire("Get") == HTTP_METHOD_NONE
&& methodIdFromWire("GETS") == HTTP_METHOD_NONE
&& methodIdFromWire("BREW") == HTTP_METHOD_NONE;
}(), "methodIdFromWire() returns each name's index in HTTP_METHOD_NAMES and no id for any other token");

} // namespace uWS

#endif // UWS_HTTPMETHOD_H
19 changes: 3 additions & 16 deletions packages/bun-uws/src/HttpParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@
#include "BloomFilter.h"
#include "QueryParser.h"
#include "HttpErrors.h"
#include "HttpMethod.h"

#if defined(_WIN32)
#define strncasecmp _strnicmp
#endif

extern "C" size_t BUN_DEFAULT_MAX_HTTP_HEADER_SIZE;
extern "C" int16_t Bun__HTTPMethod__from(const char *str, size_t len);

namespace uWS
{
Expand Down Expand Up @@ -809,20 +809,6 @@ struct HttpResponseData;
return p;
}

static bool isValidMethod(std::string_view str, bool useStrictMethodValidation) {
if (str.empty()) return false;

if (useStrictMethodValidation) {
return Bun__HTTPMethod__from(str.data(), str.length()) != -1;
}

for (char c : str) {
if (!isValidMethodChar(c))
return false;
}
return true;
}

static inline bool isValidMethodChar(char c) {
return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) || c == '-';
}
Expand Down Expand Up @@ -935,7 +921,8 @@ struct HttpResponseData;
if (isHTTPMethod || isConnect || isProxyStyleURL) [[likely]] {
header.key = {start, (size_t) (data - start)};
data++;
if(!isValidMethod(header.key, useStrictMethodValidation)) {
/* The loop above checked each byte of the method. Strict mode also requires one of HTTP_METHOD_NAMES. */
if (useStrictMethodValidation && methodIdFromWire(header.key) == HTTP_METHOD_NONE) {
return ConsumeRequestLineResult::error(HTTP_HEADER_PARSER_ERROR_INVALID_METHOD);
}
/* Scan for less than 33 (catches post padded CR and fails) */
Expand Down
37 changes: 22 additions & 15 deletions src/http_types/Method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,21 +244,6 @@ impl Optional {
}
}

#[unsafe(no_mangle)]
/// # Safety
/// `str` must point to `len` initialised bytes for the duration of the call.
unsafe extern "C" fn Bun__HTTPMethod__from(str: *const u8, len: usize) -> i16 {
// SAFETY: genuine FFI boundary — C++ caller passes a non-null, byte-aligned
// pointer to `len` initialised bytes. The (ptr,len) pair cannot be a `&[u8]` across
// the C ABI, so `from_raw_parts` is irreducible here; the borrow does not
// outlive this stack frame.
let slice = unsafe { core::slice::from_raw_parts(str, len) };
let Some(method) = Method::find(slice) else {
return -1;
};
method as i16
}

// ═══════════════════════════════════════════════════════════════════════
// HTTPHeaderName — moved from bun_runtime::webcore::FetchHeaders.
//
Expand Down Expand Up @@ -394,4 +379,26 @@ mod tests {
assert_eq!(Method::which(b"BREW"), None);
assert_eq!(Method::which(b"MKADDRESSBOOKS"), None);
}

#[test]
fn discriminants_follow_wire_name_order() {
let all = enumset::EnumSet::<Method>::all();
assert_eq!(
all.len(),
36,
"HTTP_METHOD_NAMES in packages/bun-uws/src/HttpMethod.h lists the same names in the same order"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
);
let mut previous: Option<&'static str> = None;
for (index, m) in all.iter().enumerate() {
assert_eq!(m as usize, index, "{}", m.as_str());
if let Some(previous) = previous {
assert!(
previous.as_bytes() < m.as_str().as_bytes(),
"{previous} < {}",
m.as_str()
);
}
previous = Some(m.as_str());
}
}
}
14 changes: 12 additions & 2 deletions src/jsc/bindings/BunCommonStrings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "ZigGlobalObject.h"
#include <JavaScriptCore/SlotVisitorInlines.h>
#include <JavaScriptCore/VMTrapsInlines.h>
#include <bun-uws/src/HttpMethod.h>

namespace Bun {
using namespace JSC;
Expand Down Expand Up @@ -124,12 +125,21 @@ enum class HTTPMethod : uint8_t {
httpUNLOCK = 34,
httpUNSUBSCRIBE = 35,
};
static_assert(static_cast<size_t>(HTTPMethod::httpUNSUBSCRIBE) + 1 == uWS::HTTP_METHOD_NAMES.size());

// The enumerator of "M-SEARCH" has no hyphen.
static constexpr bool isNameOfMethod(std::string_view enumerator, HTTPMethod method)
{
std::string_view name = uWS::HTTP_METHOD_NAMES[static_cast<size_t>(method)];
return name == enumerator || (name == "M-SEARCH" && enumerator == "MSEARCH");
}

static JSC::JSValue toJS(Zig::GlobalObject* globalObject, HTTPMethod method)
{
auto& commonStrings = Bun::commonStrings(globalObject->vm());
#define FOR_EACH_METHOD(method) \
case HTTPMethod::http##method: \
#define FOR_EACH_METHOD(method) \
case HTTPMethod::http##method: \
static_assert(isNameOfMethod(#method, HTTPMethod::http##method)); \
return commonStrings.http##method##String();

switch (method) {
Expand Down
Loading