Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
27 changes: 24 additions & 3 deletions packages/bun-types/sql.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,17 +323,38 @@ declare module "bun" {
max_lifetime?: number | undefined;

/**
* Whether to use TLS/SSL for the connection
* Whether to use TLS/SSL for the connection. A string selects the
* SSL mode (`"disable"`, `"allow"`, `"prefer"`, `"require"`, `"verify-ca"`, `"verify-full"`).
* @default false
*/
tls?: Bun.BunFile | TLSOptions | boolean | undefined;
tls?:
| Bun.BunFile
| TLSOptions
| boolean
| "disable"
| "allow"
| "prefer"
| "require"
| "verify-ca"
| "verify-full"
| undefined;

/**
* Whether to use TLS/SSL for the connection (alias for tls)
* @deprecated Prefer {@link tls}
* @default false
*/
ssl?: Bun.BunFile | TLSOptions | boolean | undefined;
ssl?:
| Bun.BunFile
| TLSOptions
| boolean
| "disable"
| "allow"
| "prefer"
| "require"
| "verify-ca"
| "verify-full"
| undefined;

/**
* Unix domain socket path for connection
Expand Down
16 changes: 8 additions & 8 deletions packages/bun-usockets/src/crypto/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1676,10 +1676,7 @@ static long us_internal_verify_peer_certificate(const SSL *ssl, long def) {
err = SSL_get_verify_result(ssl);
} else {
const SSL_CIPHER *curr_cipher = SSL_get_current_cipher(ssl);
const SSL_SESSION *sess = SSL_get_session(ssl);
if ((curr_cipher && SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk) ||
(sess && SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION &&
SSL_session_reused(ssl))) {
if (curr_cipher && SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk) {
return X509_V_OK;
}
}
Expand Down Expand Up @@ -2742,15 +2739,18 @@ static void us_ssl_apply_selected_ctx(SSL *ssl, SSL_CTX *ctx) {
/* Whether the SNI-selected context of this connection demands closing on a
* client-certificate verification error (requestCert && rejectUnauthorized
* of the per-serverName entry). */
int us_socket_server_name_reject_unauthorized(struct us_socket_t *s) {
if (!s->ssl || us_ctx_sni_policy_ex_idx < 0) return 0;
SSL_CTX *ctx = SSL_get_SSL_CTX(s_ssl(s));
if (!ctx) return 0;
int us_ssl_ctx_reject_unauthorized(SSL_CTX *ctx) {
if (!ctx || us_ctx_sni_policy_ex_idx < 0) return 0;
uintptr_t packed = (uintptr_t)SSL_CTX_get_ex_data(ctx, us_ctx_sni_policy_ex_idx);
return (packed & US_SNI_POLICY_REQUEST_CERT) &&
(packed & US_SNI_POLICY_REJECT_UNAUTHORIZED);
}

int us_socket_server_name_reject_unauthorized(struct us_socket_t *s) {
if (!s->ssl) return 0;
return us_ssl_ctx_reject_unauthorized(SSL_get_SSL_CTX(s_ssl(s)));
}

/* Extracts the host_name from the ClientHello's server_name extension.
* Returns the length written to `out` (NUL-terminated), or 0 if absent /
* malformed. BoringSSL does document SSL_get_servername as usable inside
Expand Down
1 change: 1 addition & 0 deletions packages/bun-usockets/src/libusockets.h
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ void us_ssl_ctx_set_sni_policy(struct ssl_ctx_st *ctx, int request_cert,
/* 1 iff the SNI-selected context for this connection demands closing on a
* client-certificate verification error. */
int us_socket_server_name_reject_unauthorized(us_socket_r s);
int us_ssl_ctx_reject_unauthorized(struct ssl_ctx_st *ctx);
/* Socket-level SNI resolver, for a server-side socket adopted into TLS with no
* listen socket behind it. Same contract as the listener resolver: an owned
* SSL_CTX ref or NULL; *abort_handshake 1 = drop silently, 2 = suspend. */
Expand Down
51 changes: 45 additions & 6 deletions packages/bun-usockets/src/quic.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ extern SSL_CTX *us_ssl_ctx_build_raw(
struct us_bun_socket_context_options_t options,
enum create_bun_socket_error_t *err);
extern X509_STORE *us_get_default_ca_store(void);
extern struct us_bun_verify_error_t us_ssl_socket_verify_error_from_ssl(SSL *ssl);

#define US_QUIC_READ_BUF (16 * 1024)

Expand All @@ -40,6 +41,7 @@ struct us_quic_hset {
struct lsxpack_header scratch;
struct us_quic_header_t *headers;
unsigned int count, hcap;
int is_server;
};

struct us_quic_sni {
Expand Down Expand Up @@ -423,8 +425,32 @@ static int us_quic_alpn_select(SSL *ssl, const unsigned char **out, unsigned cha
/* ───── header-set interface ───── */

static void *us_quic_hsi_create(void *hsi_ctx, lsquic_stream_t *s, int is_push) {
(void) hsi_ctx; (void) s; (void) is_push;
return us_calloc(1, sizeof(struct us_quic_hset));
(void) s; (void) is_push;
struct us_quic_hset *h = (struct us_quic_hset *) us_calloc(1, sizeof(struct us_quic_hset));
if (h) h->is_server = !((us_quic_socket_context_t *) hsi_ctx)->is_client;
return h;
}

static int us_quic_field_is_malformed(const struct lsxpack_header *hdr) {
const unsigned char *name = (const unsigned char *) lsxpack_header_get_name(hdr);
const unsigned char *val = (const unsigned char *) lsxpack_header_get_value(hdr);
if (hdr->name_len == 0) return 1;
if (name[0] == ':' && hdr->name_len == 1) return 1;
for (unsigned int i = name[0] == ':'; i < hdr->name_len; i++) {
unsigned char c = name[i];
if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) continue;
switch (c) {
case '!': case '#': case '$': case '%': case '&': case '\'': case '*':
case '+': case '-': case '.': case '^': case '_': case '`': case '|': case '~':
continue;
}
return 1;
}
for (unsigned int i = 0; i < hdr->val_len; i++) {
unsigned char c = val[i];
if (c == 0 || c == '\r' || c == '\n') return 1;
}
return 0;
}

static struct lsxpack_header *us_quic_hsi_prepare(void *hset_p, struct lsxpack_header *hdr, size_t space) {
Expand Down Expand Up @@ -455,6 +481,7 @@ static struct lsxpack_header *us_quic_hsi_prepare(void *hset_p, struct lsxpack_h
static int us_quic_hsi_process(void *hset_p, struct lsxpack_header *hdr) {
struct us_quic_hset *h = (struct us_quic_hset *) hset_p;
if (hdr == NULL) return 0; /* end of headers */
if (h->is_server && us_quic_field_is_malformed(hdr)) return 1;
if (h->count == h->hcap) {
unsigned int ncap = h->hcap ? h->hcap * 2 : 16;
struct us_quic_header_t *nh = (struct us_quic_header_t *)
Expand Down Expand Up @@ -496,9 +523,18 @@ static void us_quic_hsi_discard(void *hset_p) {

/* ───── stream interface ───── */

static int us_quic_server_peer_verified(us_quic_socket_context_t *ctx, lsquic_conn_t *conn) {
SSL *ssl = lsquic_conn_get_ssl(conn);
int enforce = us_ssl_ctx_reject_unauthorized(ctx->ssl_ctx) ||
(ssl && us_ssl_ctx_reject_unauthorized(SSL_get_SSL_CTX(ssl)));
if (!enforce) return 1;
if (!ssl) return 0;
return us_ssl_socket_verify_error_from_ssl(ssl).error == 0;
}

static lsquic_conn_ctx_t *us_quic_on_new_conn(void *if_ctx, lsquic_conn_t *conn) {
us_quic_socket_context_t *ctx = (us_quic_socket_context_t *) if_ctx;
if (ctx->closing) {
if (ctx->closing || (!ctx->is_client && !us_quic_server_peer_verified(ctx, conn))) {
lsquic_conn_close(conn);
return NULL;
}
Expand Down Expand Up @@ -683,11 +719,12 @@ void us_quic_global_init(void) {
#endif
}

static void us_quic_prepare_ssl_ctx(SSL_CTX *ssl) {
static void us_quic_prepare_ssl_ctx(SSL_CTX *ssl, const struct us_bun_socket_context_options_t *options) {
SSL_CTX_set_min_proto_version(ssl, TLS1_3_VERSION);
SSL_CTX_set_max_proto_version(ssl, TLS1_3_VERSION);
SSL_CTX_set_alpn_select_cb(ssl, us_quic_alpn_select, NULL);
SSL_CTX_set_early_data_enabled(ssl, 0);
us_ssl_ctx_set_sni_policy(ssl, options->request_cert, options->reject_unauthorized);
}

us_quic_socket_context_t *us_create_quic_socket_context(
Expand All @@ -697,7 +734,7 @@ us_quic_socket_context_t *us_create_quic_socket_context(
enum create_bun_socket_error_t ssl_err = 0;
SSL_CTX *ssl = us_ssl_ctx_build_raw(options, &ssl_err);
if (!ssl) return NULL;
us_quic_prepare_ssl_ctx(ssl);
us_quic_prepare_ssl_ctx(ssl, &options);

us_quic_socket_context_t *ctx = (us_quic_socket_context_t *)
us_calloc(1, sizeof(us_quic_socket_context_t) + ext_size);
Expand Down Expand Up @@ -757,7 +794,9 @@ int us_quic_socket_context_add_server_name(us_quic_socket_context_t *ctx,
enum create_bun_socket_error_t ssl_err = 0;
SSL_CTX *ssl = us_ssl_ctx_build_raw(options, &ssl_err);
if (!ssl) return -1;
us_quic_prepare_ssl_ctx(ssl);
us_quic_prepare_ssl_ctx(ssl, &options);
SSL_CTX_set_verify(ssl, SSL_CTX_get_verify_mode(ssl) | SSL_CTX_get_verify_mode(ctx->ssl_ctx),
SSL_CTX_get_verify_callback(ssl));
if (ctx->sni_count == ctx->sni_cap) {
unsigned ncap = ctx->sni_cap ? ctx->sni_cap * 2 : 4;
struct us_quic_sni *n = (struct us_quic_sni *) us_realloc(ctx->sni, ncap * sizeof(*n));
Expand Down
18 changes: 13 additions & 5 deletions src/ast/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3209,21 +3209,29 @@ pub fn initialize_store_or_reset() {

/// RAII guard that pins the thread-local `disable_reset` flag on both AST
/// `Store`s for its scope.
#[must_use = "disable_reset is cleared on drop; bind to a named local"]
pub struct DisableStoreReset(());
#[must_use = "disable_reset is restored on drop; bind to a named local"]
pub struct DisableStoreReset {
prev_expr: bool,
prev_stmt: bool,
}
impl DisableStoreReset {
#[inline]
pub fn new() -> Self {
let prev_expr = expr::data::Store::disable_reset();
let prev_stmt = stmt::data::Store::disable_reset();
expr::data::Store::set_disable_reset(true);
stmt::data::Store::set_disable_reset(true);
Self(())
Self {
prev_expr,
prev_stmt,
}
}
}
impl Drop for DisableStoreReset {
#[inline]
fn drop(&mut self) {
expr::data::Store::set_disable_reset(false);
stmt::data::Store::set_disable_reset(false);
expr::data::Store::set_disable_reset(self.prev_expr);
stmt::data::Store::set_disable_reset(self.prev_stmt);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/bun_core/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1707,12 +1707,12 @@ impl Keywords {

pub(crate) struct RedactedKeywords;
impl RedactedKeywords {
// 5 entries — a `matches!` chain is plenty at this size (the big keyword
// 6 entries — a `matches!` chain is plenty at this size (the big keyword
// table in `Keywords::get` is where the length-dispatched map pays off).
pub(crate) fn has(s: &[u8]) -> bool {
matches!(
s,
b"_auth" | b"_authToken" | b"token" | b"_password" | b"email"
b"_auth" | b"_authToken" | b"token" | b"_password" | b"password" | b"email"
)
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/bun_core/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2047,6 +2047,9 @@ pub(crate) mod strings_impl {
if let Some(r) = starts_with_redacted_item(str, b"_password") {
return Some(r);
}
if let Some(r) = starts_with_redacted_item(str, b"password") {
return Some(r);
}
if let Some(r) = starts_with_redacted_item(str, b"token") {
return Some(r);
}
Expand Down
10 changes: 2 additions & 8 deletions src/bun_core/string/immutable/unicode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -984,14 +984,8 @@ pub fn copy_utf16_into_utf8_impl<const ALLOW_TRUNCATED_UTF8_SEQUENCE: bool>(
written: 0,
};
}
// `trim::utf16` strips a single trailing lone high surrogate so simdutf's
// length estimate never sees invalid input. If that empties the input, it
// was exactly one unpaired surrogate: 3 bytes of U+FFFD, not nothing.
let trimmed = simdutf::trim::utf16(utf16);
let out_len = if trimmed.is_empty() {
3
} else if buf.len() <= (trimmed.len() * 3 + 2) {
simdutf::length::utf8::from::utf16::le(trimmed)
let out_len = if buf.len() <= utf16.len().saturating_mul(3) {
simdutf::length::utf8::from::utf16::le(utf16)
} else {
buf.len()
};
Expand Down
2 changes: 1 addition & 1 deletion src/http/AsyncHTTP.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ fn make_client<'a>(
custom_ssl_ctx: None,
result_callback: noop_callback(),
if_modified_since: b"",
request_content_len_buf: [0u8; b"-4294967295".len()],
request_content_len_buf: [0u8; b"18446744073709551615".len()],
http_proxy,
proxy_settings: None,
proxy_headers,
Expand Down
38 changes: 25 additions & 13 deletions src/http/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,7 @@ pub struct HTTPClient<'a> {
/// Some HTTP servers (such as npm) report Last-Modified times but ignore If-Modified-Since.
/// This is a workaround for that.
pub if_modified_since: &'a [u8],
pub(crate) request_content_len_buf: [u8; b"-4294967295".len()],
pub(crate) request_content_len_buf: [u8; b"18446744073709551615".len()],

pub(crate) http_proxy: Option<URL<'a>>,
/// Captured proxy env (http_proxy / https_proxy / no_proxy) so redirects
Expand Down Expand Up @@ -1435,9 +1435,27 @@ pub(crate) fn print_request(
Protocol::Http2 => "HTTP/2",
Protocol::Http3 => "HTTP/3",
};
bun_core::pretty_errorln!("> {} {} {}", ver, BStr::new(request.method), BStr::new(url));
bun_core::pretty_errorln!(
"> {} {} {}",
ver,
BStr::new(request.method),
bun_core::fmt::redacted_npm_url(url),
);
for header in request.headers {
bun_core::pretty_errorln!("> {}", header);
let name = header.name();
if strings::eql_case_insensitive_ascii(name, b"authorization", true)
|| strings::eql_case_insensitive_ascii(name, b"proxy-authorization", true)
{
Comment thread
claude[bot] marked this conversation as resolved.
let value = header.value();
let scheme_len = strings::index_of_char_usize(value, b' ').map_or(0, |i| i + 1);
bun_core::pretty_errorln!(
"> <r><cyan>{}<r><d>: <r>{}<d>[redacted]<r>",
BStr::new(name),
BStr::new(&value[..scheme_len]),
);
} else {
bun_core::pretty_errorln!("> {}", header);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Output::flush();
}
Expand Down Expand Up @@ -2527,16 +2545,10 @@ impl<'a> HTTPClient<'a> {
header_count += 1;
}
} else {
// 11-byte buf vs 64-bit usize: must fall back to "0" on
// overflow, NOT panic.
let value: &[u8] = match bun_core::fmt::buf_print(
&mut self.request_content_len_buf,
format_args!("{body_len}"),
) {
// SAFETY: borrows `self.request_content_len_buf` which lives for `self`.
Ok(s) => unsafe { bun_ptr::detach_lifetime(s) },
Err(_) => b"0",
};
let value: &[u8] =
bun_core::fmt::int_as_bytes(&mut self.request_content_len_buf, body_len);
// SAFETY: borrows `self.request_content_len_buf` which lives for `self`.
let value: &[u8] = unsafe { bun_ptr::detach_lifetime(value) };
request_headers_buf[header_count] =
picohttp::Header::new(CONTENT_LENGTH_HEADER_NAME, value);
header_count += 1;
Expand Down
Loading