Compare commits

..

22 Commits

Author SHA1 Message Date
Sosuke Suzuki
e60329f6a7 fix(napi): validate cell pointer in napi_typeof to prevent segfault (BUN-1PYR)
Use bloom filter + MarkedBlock set to verify that a cell pointer belongs to
a known GC-managed block before dereferencing it. This prevents crashes when
native modules accidentally pass garbage values (e.g. C string pointers) as
napi_value to napi_typeof. Returns napi_invalid_arg instead of segfaulting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 07:18:27 +09:00
Sosuke Suzuki
6d14b90e5e test(napi): add regression test for napi_typeof crash with invalid pointer (BUN-1PYR)
Add a test that passes a raw C string pointer as napi_value to napi_typeof,
reproducing the segfault where the crash address 0x6F20726F736E6554 decoded
to ASCII "Tensor o". The test currently crashes and will pass once the fix
is applied.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 21:27:29 +09:00
Alan Stott
7848648e09 fix: clean up raw TCP socket on TLS upgrade close (#26766)
## Summary

Fixes #12117, #24118, #25948

When a TCP socket is upgraded to TLS via `tls.connect({ socket })`,
`upgradeTLS()` creates **two** `TLSSocket` structs — a TLS wrapper and a
raw TCP wrapper. Both are `markActive()`'d and `ref()`'d. On close, uws
fires `onClose` through the **TLS context only**, so the TLS wrapper is
properly cleaned up, but the raw TCP wrapper's `onClose` never fires.
Its `has_pending_activity` stays `true` forever and its `ref_count` is
never decremented, **leaking one raw `TLSSocket` per upgrade cycle**.

This affects any code using the `tls.connect({ socket })` "starttls"
pattern:
- **MongoDB Node.js driver** — SDAM heartbeat connections cycle TLS
upgrades every ~10s, causing unbounded memory growth in production
- **mysql2** TLS upgrade path
- Any custom starttls implementation

### The fix

Adds a `defer` block in `NewWrappedHandler(true).onClose` that cleans up
the raw TCP socket when the TLS socket closes:

```zig
defer {
    if (!this.tcp.socket.isDetached()) {
        this.tcp.socket.detach();
        this.tcp.has_pending_activity.store(false, .release);
        this.tcp.deref();
    }
}
```

- **`isDetached()` guard** — skips cleanup if the raw socket was already
closed through another code path (e.g., JS-side `handle.close()`)
- **`socket.detach()`** — marks `InternalSocket` as `.detached` so
`isClosed()` returns `true` safely (the underlying `us_socket_t` is
freed when uws closes the TLS context)
- **`has_pending_activity.store(false)`** — allows JSC GC to collect the
raw socket's JS wrapper
- **`deref()`** — balances the `ref()` from `upgradeTLS`; the remaining
ref is the implicit one from JSC (`ref_count.init() == 1`). When GC
later calls `finalize()` → `deref()`, ref_count hits 0 and `deinit()`
runs the full cleanup chain (markInactive, handlers, poll_ref,
socket_context)

`markInactive()` is intentionally **not** called in the defer — it must
run inside `deinit()` to avoid double-freeing the handlers struct.

### Why Node.js doesn't have this bug

Node.js implements TLS upgrades purely in JavaScript/C++ with OpenSSL,
where the TLS wrapper takes ownership of the underlying socket. There is
no separate "raw socket wrapper" that needs independent cleanup.

## Test Results

### Regression test
```
$ bun test test/js/node/tls/node-tls-upgrade-leak.test.ts
 1 pass, 0 fail
```
Creates 20 TCP→TLS upgrade cycles, closes all connections, runs GC,
asserts `TLSSocket` count stays below 10.

### Existing TLS test suite (all passing)
```
node-tls-upgrade.test.ts      1 pass
node-tls-connect.test.ts     24 pass, 1 skip
node-tls-server.test.ts      21 pass
node-tls-cert.test.ts        25 pass, 3 todo
renegotiation.test.ts          6 pass
```

### MongoDB TLS scenario (patched Bun, 4 minutes)
```
Baseline: RSS=282.4 MB | Heap Used=26.4 MB
Check #4:  RSS=166.7 MB | Heap Used=24.2 MB — No TLSSocket growth. RSS DECREASED.
```

## Test plan

- [x] New regression test passes (`node-tls-upgrade-leak.test.ts`)
- [x] All existing TLS tests pass (upgrade, connect, server, cert,
renegotiation)
- [x] MongoDB TLS scenario shows zero `TLSSocket` accumulation
- [x] Node.js control confirms leak is Bun-specific
- [ ] CI passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-16 23:42:57 -08:00
Jarred Sumner
379daff22d Fix test failure (#27073)
### What does this PR do?

### How did you verify your code works?

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-16 22:52:43 -08:00
robobun
5b0db0191e fix(bundler): copy non-JS/CSS files referenced as URL assets in HTML (#27039)
## Summary

- Fix `<link rel="manifest" href="./manifest.json" />` (and similar
non-JS/CSS URL assets) resulting in 404s when using `Bun.build` with
HTML entrypoints
- The HTML scanner correctly identifies these as `ImportKind.url`
imports, but the bundler was assigning the extension-based loader (e.g.
`.json`) which parses the file instead of copying it as a static asset
- Force the `.file` loader for `ImportKind.url` imports when the
resolved loader wouldn't `shouldCopyForBundling()` and isn't JS/CSS/HTML
(which have their own handling)

## Test plan

- [x] Added `html/manifest-json` test: verifies manifest.json is copied
as hashed asset and HTML href is rewritten
- [x] Added `html/xml-asset` test: verifies `.webmanifest` files are
also handled correctly
- [x] All 20 HTML bundler tests pass (`bun bd test
test/bundler/bundler_html.test.ts`)
- [x] New tests fail on system bun (`USE_SYSTEM_BUN=1`) confirming they
validate the fix

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-16 18:06:25 -08:00
robobun
9ef9ac1db1 fix(http): fix setHeaders throwing ERR_HTTP_HEADERS_SENT on new requests (#27050)
## Summary

- Fix `OutgoingMessage.setHeaders()` incorrectly throwing
`ERR_HTTP_HEADERS_SENT` on brand new `ClientRequest` instances
- The guard condition `this[headerStateSymbol] !==
NodeHTTPHeaderState.none` failed when `headerStateSymbol` was
`undefined` (since `ClientRequest` doesn't call the `OutgoingMessage`
constructor), and was also stricter than Node.js which only checks
`this._header`
- Align the check with the working `setHeader()` (singular) method: only
throw when `_header` is set or `headerStateSymbol` equals `sent`

Closes #27049

## Test plan
- [x] New regression test `test/regression/issue/27049.test.ts` covers:
  - `ClientRequest.setHeaders()` with `Headers` object
  - `ClientRequest.setHeaders()` with `Map`
  - `ServerResponse.setHeaders()` before headers are sent
- [x] Test fails with system bun (`USE_SYSTEM_BUN=1`)
- [x] Test passes with debug build (`bun bd test`)
- [x] Existing header-related tests in `node-http.test.ts` still pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-16 18:04:35 -08:00
robobun
f5d98191b7 fix(install): store and verify SHA-512 integrity hash for GitHub tarball dependencies (#27019)
## Summary

- Compute SHA-512 hash of GitHub tarball bytes during extraction and
store in `bun.lock`
- Verify the hash on subsequent installs when re-downloading, rejecting
tampered tarballs
- Automatically upgrade old lockfiles without integrity by computing and
persisting the hash
- Maintain backward compatibility with old lockfile format (no integrity
field)

Fixes GHSA-pfwx-36v6-832x

## Lockfile format change

```
Before: ["pkg@github:user/repo#ref", {}, "resolved-commit"]
After:  ["pkg@github:user/repo#ref", {}, "resolved-commit", "sha512-..."]
```

The integrity field is optional for backward compatibility. Old
lockfiles are automatically upgraded when the tarball is re-downloaded.

## Test plan

- [x] Fresh install stores SHA-512 integrity hash in lockfile
- [x] Re-install with matching hash succeeds
- [x] Re-install with mismatched hash rejects the tarball
- [x] Old lockfile without integrity is auto-upgraded with hash on
re-download
- [x] Cache hits still work without re-downloading
- [x] Existing GitHub dependency tests pass (10/10)
- [x] Existing git resolution snapshot test passes
- [x] Yarn migration snapshot tests pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-16 17:53:32 -08:00
robobun
83bca9bea8 docs: fix plugin API documentation to reflect onStart/onEnd support (#27068)
## Summary
- Fixes the esbuild migration guide (`docs/bundler/esbuild.mdx`) which
incorrectly stated that `onStart`, `onEnd`, `onDispose`, and `resolve`
were all unimplemented. `onStart` and `onEnd` **are** implemented — only
`onDispose` and `resolve` remain unimplemented.
- Adds missing `onEnd()` documentation section to both
`docs/bundler/plugins.mdx` and `docs/runtime/plugins.mdx`, including
type signature, description, and usage examples.
- Adds `onEnd` to the type reference overview and lifecycle hooks list
in both plugin docs.

Fixes #27083

## Test plan
- Documentation-only change — no code changes.
- Verified the `onEnd` implementation exists in
`src/js/builtins/BundlerPlugin.ts` and matches the documented API.
- Verified `onStart` implementation exists and is fully functional.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-16 17:44:09 -08:00
robobun
7794cc866e fix(http): preserve explicit Content-Length header with streaming request body (#27062)
## Summary

- When `http.ClientRequest.write()` was called more than once (streaming
data in chunks), Bun was stripping the explicitly-set `Content-Length`
header and switching to `Transfer-Encoding: chunked`. Node.js preserves
`Content-Length` in all cases when it's explicitly set by the user.
- This caused real-world failures (e.g. Vercel CLI file uploads) where
large binary files streamed via multiple `write()` calls had their
Content-Length stripped, causing server-side "invalid file size" errors.
- The fix preserves the user's explicit `Content-Length` for streaming
request bodies and skips chunked transfer encoding framing when
`Content-Length` is set.

Closes #27061
Closes #26976

## Changes

- **`src/http.zig`**: When a streaming request body has an explicit
`Content-Length` header set by the user, use that instead of adding
`Transfer-Encoding: chunked`. Added
`is_streaming_request_body_with_content_length` flag to track this.
- **`src/bun.js/webcore/fetch/FetchTasklet.zig`**: Skip chunked transfer
encoding framing (`writeRequestData`) and the chunked terminator
(`writeEndRequest`) when the request has an explicit `Content-Length`.
- **`test/regression/issue/27061.test.ts`**: Regression test covering
multiple write patterns (2x write, write+end(data), 3x write) plus
validation that chunked encoding is still used when no `Content-Length`
is set.

## Test plan

- [x] New regression test passes with `bun bd test
test/regression/issue/27061.test.ts`
- [x] Test fails with `USE_SYSTEM_BUN=1` (confirms the bug exists in
current release)
- [x] Existing `test/js/node/http/` tests pass (no regressions)
- [x] Fetch file upload tests pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-16 17:43:23 -08:00
robobun
70b354aa04 fix(bundler): include CSS in all HTML entrypoints sharing a deduplicated CSS chunk (#27040) 2026-02-15 03:36:06 -08:00
Jarred Sumner
9d5a800c3d fix(napi,timers): callback scope (#27026) 2026-02-15 03:33:48 -08:00
Jarred Sumner
77ca318336 Reduce the number of closures in generated bundler code (#27022)
### Problem

The bundler's `__toESM` helper creates a new getter-wrapped proxy object
every time a CJS
module is imported. In a large app, a popular dependency like React can
be imported 600+
times — each creating a fresh object with ~44 getter properties. This
produces ~27K
unnecessary `GetterSetter` objects, ~25K closures, and ~25K
`JSLexicalEnvironment` scope
objects at startup.

Additionally, `__export` and `__exportValue` use `var`-scoped loop
variables captured by
setter closures, meaning all setters incorrectly reference the last
iterated key (a latent
  bug).

### Changes

1. **`__toESM`: add WeakMap cache** — deduplicate repeated wrappings of
the same CJS
module. Two caches (one per `isNodeMode` value) to handle both import
modes correctly.
2. **Replace closures with `.bind()`** — `() => obj[key]` becomes
`__accessProp.bind(obj,
key)`. BoundFunction is cheaper than Function + JSLexicalEnvironment,
and frees the for-in
  `JSPropertyNameEnumerator` from the closure scope.
3. **Fix var-scoping bug in `__export`/`__exportValue`** — setter
closures captured a
shared `var name` and would all modify the last iterated key. `.bind()`
eagerly captures
the correct key per iteration.
4. **`__toCommonJS`: `.map()` → `for..of`** — eliminates throwaway array
allocation.
5. **`__reExport`: single `getOwnPropertyNames` call** — was calling it
twice when
`secondTarget` was provided.

### Impact (measured on a ~23MB single-bundle app with 600+ React
imports)

| Metric | Before | After | Delta |
|--------|--------|-------|-------|
| **Total objects** | 745,985 | 664,001 | **-81,984 (-11%)** |
| **Heap size** | 115 MB | 111 MB | **-4 MB** |
| GetterSetter | 34,625 | 13,428 | -21,197 (-61%) |
| Function | 221,302 | 197,024 | -24,278 (-11%) |
| JSLexicalEnvironment | 70,101 | 44,633 | -25,468 (-36%) |
| Structure | 40,254 | 39,762 | -492 |
2026-02-15 00:36:57 -08:00
Jarred Sumner
337a9f7f2b fix(module): prevent crash when resolving bun:main before entry_po… (#27027)
…int.generate()

`ServerEntryPoint.source` defaults to `undefined`, and accessing its
`.contents` or `.path.text` fields before `generate()` has been called
causes a segfault. This happens when `bun:main` is resolved in contexts
where `entry_point.generate()` is skipped (HTML entry points) or never
called (test runner).

Add a `generated` flag to `ServerEntryPoint` and guard both access
sites:
- `getHardcodedModule()` in ModuleLoader.zig (returns null instead of
crashing)
- `_resolve()` in VirtualMachine.zig (falls through to normal
resolution)

### What does this PR do?

### How did you verify your code works?

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-14 12:11:41 -08:00
robobun
38f41dccdf fix(crypto): fix segfault in X509Certificate.issuerCertificate getter (#27025) 2026-02-14 05:58:41 -08:00
robobun
883e43c371 update(crypto): update root certificates to NSS 3.119; eliminate external HTTPS in tests (#27020) 2026-02-14 02:47:13 -08:00
Dylan Conway
cd17934207 fix(stripANSI): handle SIMD false positives for non-ANSI control chars (#27021)
The SIMD fast path in `findEscapeCharacter` matches bytes in `0x10-0x1F`
and `0x90-0x9F`, but `consumeANSI` only handles actual ANSI escape
introducers (`0x1b`, `0x9b`, `0x9d`, `0x90`, `0x98`, `0x9e`, `0x9f`).
For non-ANSI bytes like `0x16` (SYN), `consumeANSI` returns the same
pointer it received, causing an infinite loop in release builds and an
assertion failure in debug builds.

When a SIMD false positive is detected (`consumeANSI` returns the same
pointer), the byte is preserved in the output and scanning advances past
it.

Fixes #27014
2026-02-14 02:01:25 -08:00
robobun
f6d4ff6779 fix(http): validate statusMessage for CRLF to prevent HTTP response splitting (#26949)
## Summary

- Fixes HTTP response splitting vulnerability where `res.statusMessage`
could contain CRLF characters that were written directly to the socket,
allowing injection of arbitrary HTTP headers and response body
- Adds native-layer validation in `NodeHTTPResponse.zig` `writeHead()`
to reject status messages containing control characters (matching
Node.js's `checkInvalidHeaderChar` behavior)
- The `writeHead(code, msg)` API already validated via JS-side
`checkInvalidHeaderChar`, but direct property assignment
(`res.statusMessage = userInput`) followed by `res.end()` or
`res.write()` bypassed all validation

## Test plan

- [x] Verified vulnerability is reproducible: attacker can inject
`Set-Cookie` headers via `res.statusMessage = "OK\r\nSet-Cookie:
admin=true"`
- [x] Verified fix throws `ERR_INVALID_CHAR` TypeError when CRLF is
present in status message
- [x] Added 4 new tests covering: property assignment + `res.end()`,
property assignment + `res.write()`, explicit `writeHead()` rejection,
and valid status message passthrough
- [x] Tests fail with `USE_SYSTEM_BUN=1` (confirming they detect the
vulnerability) and pass with `bun bd test`
- [x] Existing Node.js compat test
`test-http-status-reason-invalid-chars.js` still passes
- [x] All 14 HTTP security tests pass
- [x] Full `node-http.test.ts` suite passes (77 pass, 1 pre-existing
skip, 1 pre-existing proxy failure)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-14 00:25:07 -08:00
robobun
2c173529fa fix(test): eliminate external HTTPS fetches and renew expired TLS certs (#27013)
## Summary

- **HTMLRewriter test**: Replaced `fetch("https://www.example.com/")`
with a local HTTP content server, eliminating dependency on external
HTTPS and system CA certificates
- **HTTPS agent test**: Replaced `https.request("https://example.com/")`
with a local TLS server using self-signed certs from harness
- **Expired certs**: Regenerated self-signed certificates in
`test/js/bun/http/fixtures/` and `test/regression/fixtures/` (were
expired since Sep 2024, now valid until 2036)

## Root cause

Tests fetching external HTTPS URLs (`https://example.com`,
`https://www.example.com`) fail on CI environments (Alpine Linux,
Windows) that lack system CA certificate bundles, producing
`UNABLE_TO_GET_ISSUER_CERT_LOCALLY` errors. This affected:
- `test/js/workerd/html-rewriter.test.js` (HTMLRewriter: async
replacement using fetch + Bun.serve)
-
`test/js/bun/test/parallel/test-https-should-work-when-sending-request-with-agent-false.ts`

## Test plan

- [x] `bun bd test test/js/workerd/html-rewriter.test.js` - 41 pass, 0
fail
- [x] `bun bd test test/js/workerd/html-rewriter.test.js -t "async
replacement using fetch"` - 1 pass
- [x] `bun bd
test/js/bun/test/parallel/test-https-should-work-when-sending-request-with-agent-false.ts`
- exits 0
- [x] `bun bd test test/js/bun/http/serve-listen.test.ts` - 27 pass, 0
fail (uses renewed certs)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-13 20:45:06 -08:00
Dylan Conway
243fa45bec fix(shell): prevent use-after-free in builtin OutputTask callbacks inside $() (#26987)
## Summary

- Fixes use-after-free (ASAN use-after-poison) when shell builtins
(`ls`, `touch`, `mkdir`, `cp`) run inside command substitution `$(...)`
and encounter errors (e.g., permission denied)
- The `output_waiting` and `output_done` counters in the builtin exec
state went out of sync because `output_waiting` was only incremented for
async IO operations, while `output_done` was always incremented
- In command substitution, stdout is `.pipe` (sync) and stderr is `.fd`
(async), so a single OutputTask completing both writes could satisfy the
done condition while another OutputTask's async stderr write was still
pending in the IOWriter

The fix moves `output_waiting += 1` before the `needsIO()` check in all
four affected builtins so it's always incremented, matching
`output_done`.

## Test plan

- [x] `echo $(ls /tmp/*)` — no ASAN errors (previously crashed with
use-after-poison)
- [x] `echo $(touch /root/a /root/b ...)` — no ASAN errors
- [x] `echo $(mkdir /root/a /root/b ...)` — no ASAN errors
- [x] `ls /nonexistent` — still reports error and exits with code 1
- [x] `echo $(ls /tmp)` — still captures output correctly
- [x] Existing shell test suite: 292 pass, 52 fail (pre-existing), 83
todo — no regressions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:40:49 -08:00
Dylan Conway
c19dcb3181 fix(shell): reject non-finite seq args and handle empty condexpr args (#26993)
## Summary
- **`seq inf` / `seq nan` / `seq -inf` hang**: `std.fmt.parseFloat`
accepts non-finite float values like `inf`, `nan`, `-inf`, but the loop
`while (current <= this._end)` never terminates when `_end` is infinity.
Now rejects non-finite values after parsing.
- **`[[ -d "" ]]` out-of-bounds panic**: Empty string expansion produces
no args in the args list, but `doStat()` unconditionally accesses
`args.items[0]`. Now checks `args.items.len == 0` before calling
`doStat()` and returns exit code 1 (path doesn't exist).

## Test plan
- [x] `seq inf`, `seq nan`, `seq -inf` return exit code 1 with "invalid
argument" instead of hanging
- [x] `[[ -d "" ]]` and `[[ -f "" ]]` return exit code 1 instead of
panicking
- [x] `seq 3` still works normally (produces 1, 2, 3)
- [x] `[[ -d /tmp ]]`, `[[ -f /etc/hostname ]]` still work correctly
- [x] Tests pass with `bun bd test`, seq tests fail with
`USE_SYSTEM_BUN=1`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:01:40 -08:00
Jarred Sumner
c57af9df38 Fix websocket proxy ping crash (#26995)
### What does this PR do?

The `sendPong` fix alone wasn't sufficient. The bug only manifests with
**wss:// through HTTP proxy** (not ws://), because only that path uses
`initWithTunnel` with a detached socket.

**Two bugs were found and fixed:**

1. **`sendPong`/`sendCloseWithBody` socket checks**
(`src/http/websocket_client.zig`): Replaced `socket.isClosed() or
socket.isShutdown()` with `!this.hasTCP()` as originally proposed. Also
guarded `shutdownRead()` against detached sockets.

2. **Spurious 1006 during clean close** (`src/http/websocket_client.zig`
+ `WebSocketProxyTunnel.zig`): When `sendCloseWithBody` calls
`clearData()`, it shuts down the proxy tunnel. The tunnel's `onClose`
callback was calling `ws.fail(ErrorCode.ended)` which dispatched a 1006
abrupt close, overriding the clean 1000 close already in progress. Fixed
by adding `tunnel.clearConnectedWebSocket()` before `tunnel.shutdown()`
so the callback is a no-op.

### How did you verify your code works?

- `USE_SYSTEM_BUN=1`: Fails with `Unexpected close code: 1006`
- `bun bd test`: Passes with clean 1000 close
- Full proxy test suite: 25 pass, 4 skip, 0 fail
- Related fragmented/close tests: all passing

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-13 14:49:40 -08:00
SUZUKI Sosuke
3debd0a2d2 perf(structuredClone): remove unnecessary zero-fill for Int32 and Double array fast paths (#26989)
## What

Replace two-step `Vector<uint8_t>` zero-initialization + `memcpy` with
direct `std::span` copy construction in the `structuredClone` Int32 and
Double array fast paths.

## Why

The previous code allocated a zero-filled buffer and then immediately
overwrote it with `memcpy`. By constructing the `Vector` directly from a
`std::span`, we eliminate the redundant zero-fill and reduce the
operation to a single copy.

### Before
```cpp
Vector<uint8_t> buffer(byteSize, 0);
memcpy(buffer.mutableSpan().data(), data, byteSize);
```

### After
```cpp
Vector<uint8_t> buffer(std::span<const uint8_t> { reinterpret_cast<const uint8_t*>(data), byteSize });
```

## Test

`bun bd test test/js/web/structured-clone-fastpath.test.ts` — 91/92 pass
(1 pre-existing flaky memory test unrelated to this change).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 23:50:54 -08:00
73 changed files with 2009 additions and 1487 deletions

View File

@@ -1,68 +0,0 @@
import { bench, group, run, summary } from "../runner.mjs";
// === TypedArray structuredClone benchmarks ===
// Uint8Array at various sizes
var uint8_64 = new Uint8Array(64);
var uint8_1K = new Uint8Array(1024);
var uint8_64K = new Uint8Array(64 * 1024);
var uint8_1M = new Uint8Array(1024 * 1024);
// Fill with non-zero data to be realistic
for (var i = 0; i < uint8_64.length; i++) uint8_64[i] = i & 0xff;
for (var i = 0; i < uint8_1K.length; i++) uint8_1K[i] = i & 0xff;
for (var i = 0; i < uint8_64K.length; i++) uint8_64K[i] = i & 0xff;
for (var i = 0; i < uint8_1M.length; i++) uint8_1M[i] = i & 0xff;
// Other typed array types (1KB each)
var int8_1K = new Int8Array(1024);
var uint16_1K = new Uint16Array(512); // 1KB
var int32_1K = new Int32Array(256); // 1KB
var float32_1K = new Float32Array(256); // 1KB
var float64_1K = new Float64Array(128); // 1KB
var bigint64_1K = new BigInt64Array(128); // 1KB
for (var i = 0; i < int8_1K.length; i++) int8_1K[i] = (i % 256) - 128;
for (var i = 0; i < uint16_1K.length; i++) uint16_1K[i] = i;
for (var i = 0; i < int32_1K.length; i++) int32_1K[i] = i * 1000;
for (var i = 0; i < float32_1K.length; i++) float32_1K[i] = i * 0.1;
for (var i = 0; i < float64_1K.length; i++) float64_1K[i] = i * 0.1;
for (var i = 0; i < bigint64_1K.length; i++) bigint64_1K[i] = BigInt(i);
// Slice view (byteOffset != 0) — should fall back to slow path
var sliceBuf = new ArrayBuffer(2048);
var uint8_slice = new Uint8Array(sliceBuf, 512, 512);
summary(() => {
group("Uint8Array by size", () => {
bench("Uint8Array 64B", () => structuredClone(uint8_64));
bench("Uint8Array 1KB", () => structuredClone(uint8_1K));
bench("Uint8Array 64KB", () => structuredClone(uint8_64K));
bench("Uint8Array 1MB", () => structuredClone(uint8_1M));
});
});
summary(() => {
group("TypedArray types (1KB each)", () => {
bench("Int8Array", () => structuredClone(int8_1K));
bench("Uint8Array", () => structuredClone(uint8_1K));
bench("Uint16Array", () => structuredClone(uint16_1K));
bench("Int32Array", () => structuredClone(int32_1K));
bench("Float32Array", () => structuredClone(float32_1K));
bench("Float64Array", () => structuredClone(float64_1K));
bench("BigInt64Array", () => structuredClone(bigint64_1K));
});
});
// Pre-create for fair comparison
var uint8_whole = new Uint8Array(512);
for (var i = 0; i < 512; i++) uint8_whole[i] = i & 0xff;
summary(() => {
group("fast path vs slow path (512B)", () => {
bench("Uint8Array whole (fast path)", () => structuredClone(uint8_whole));
bench("Uint8Array slice (slow path)", () => structuredClone(uint8_slice));
});
});
await run();

View File

@@ -59,15 +59,4 @@ var objectsMedium = Array.from({ length: 100 }, (_, i) => ({ id: i, name: `item-
bench("structuredClone([10 objects])", () => structuredClone(objectsSmall));
bench("structuredClone([100 objects])", () => structuredClone(objectsMedium));
// TypedArray fast path targets
var uint8Small = new Uint8Array(64);
var uint8Medium = new Uint8Array(1024);
var uint8Large = new Uint8Array(1024 * 1024);
var float64Medium = new Float64Array(128);
bench("structuredClone(Uint8Array 64B)", () => structuredClone(uint8Small));
bench("structuredClone(Uint8Array 1KB)", () => structuredClone(uint8Medium));
bench("structuredClone(Uint8Array 1MB)", () => structuredClone(uint8Large));
bench("structuredClone(Float64Array 1KB)", () => structuredClone(float64Medium));
await run();

View File

@@ -198,13 +198,16 @@ const myPlugin: BunPlugin = {
};
```
The builder object provides some methods for hooking into parts of the bundling process. Bun implements `onResolve` and `onLoad`; it does not yet implement the esbuild hooks `onStart`, `onEnd`, and `onDispose`, and `resolve` utilities. `initialOptions` is partially implemented, being read-only and only having a subset of esbuild's options; use `config` (same thing but with Bun's `BuildConfig` format) instead.
The builder object provides some methods for hooking into parts of the bundling process. Bun implements `onStart`, `onEnd`, `onResolve`, and `onLoad`. It does not yet implement the esbuild hooks `onDispose` and `resolve`. `initialOptions` is partially implemented, being read-only and only having a subset of esbuild's options; use `config` (same thing but with Bun's `BuildConfig` format) instead.
```ts title="myPlugin.ts" icon="/icons/typescript.svg"
import type { BunPlugin } from "bun";
const myPlugin: BunPlugin = {
name: "my-plugin",
setup(builder) {
builder.onStart(() => {
/* called when the bundle starts */
});
builder.onResolve(
{
/* onResolve.options */
@@ -225,6 +228,9 @@ const myPlugin: BunPlugin = {
};
},
);
builder.onEnd(result => {
/* called when the bundle is complete */
});
},
};
```

View File

@@ -15,6 +15,7 @@ Plugins can register callbacks to be run at various points in the lifecycle of a
- `onResolve()`: Run before a module is resolved
- `onLoad()`: Run before a module is loaded
- `onBeforeParse()`: Run zero-copy native addons in the parser thread before a file is parsed
- `onEnd()`: Run after the bundle is complete
## Reference
@@ -39,6 +40,7 @@ type PluginBuilder = {
exports?: Record<string, any>;
},
) => void;
onEnd(callback: (result: BuildOutput) => void | Promise<void>): void;
config: BuildConfig;
};
@@ -423,3 +425,53 @@ This lifecycle callback is run immediately before a file is parsed by Bun's bund
As input, it receives the file's contents and can optionally return new source code.
<Info>This callback can be called from any thread and so the napi module implementation must be thread-safe.</Info>
### onEnd
```ts
onEnd(callback: (result: BuildOutput) => void | Promise<void>): void;
```
Registers a callback to be run after the bundle is complete. The callback receives the [`BuildOutput`](/docs/bundler#outputs) object containing the build results, including output files and any build messages.
```ts title="index.ts" icon="/icons/typescript.svg"
const result = await Bun.build({
entrypoints: ["./app.ts"],
outdir: "./dist",
plugins: [
{
name: "onEnd example",
setup(build) {
build.onEnd(result => {
console.log(`Build completed with ${result.outputs.length} files`);
for (const log of result.logs) {
console.log(log);
}
});
},
},
],
});
```
The callback can return a `Promise`. The build output promise from `Bun.build()` will not resolve until all `onEnd()` callbacks have completed.
```ts title="index.ts" icon="/icons/typescript.svg"
const result = await Bun.build({
entrypoints: ["./app.ts"],
outdir: "./dist",
plugins: [
{
name: "Upload to S3",
setup(build) {
build.onEnd(async result => {
if (!result.success) return;
for (const output of result.outputs) {
await uploadToS3(output);
}
});
},
},
],
});
```

View File

@@ -23196,557 +23196,6 @@ CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_TRUSTED_DELEGATOR
CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE
#
# Certificate "CommScope Public Trust ECC Root-01"
#
# Issuer: CN=CommScope Public Trust ECC Root-01,O=CommScope,C=US
# Serial Number:43:70:82:77:cf:4d:5d:34:f1:ca:ae:32:2f:37:f7:f4:7f:75:a0:9e
# Subject: CN=CommScope Public Trust ECC Root-01,O=CommScope,C=US
# Not Valid Before: Wed Apr 28 17:35:43 2021
# Not Valid After : Sat Apr 28 17:35:42 2046
# Fingerprint (SHA-256): 11:43:7C:DA:7B:B4:5E:41:36:5F:45:B3:9A:38:98:6B:0D:E0:0D:EF:34:8E:0C:7B:B0:87:36:33:80:0B:C3:8B
# Fingerprint (SHA1): 07:86:C0:D8:DD:8E:C0:80:98:06:98:D0:58:7A:EF:DE:A6:CC:A2:5D
CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE
CKA_TOKEN CK_BBOOL CK_TRUE
CKA_PRIVATE CK_BBOOL CK_FALSE
CKA_MODIFIABLE CK_BBOOL CK_FALSE
CKA_LABEL UTF8 "CommScope Public Trust ECC Root-01"
CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509
CKA_SUBJECT MULTILINE_OCTAL
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
\162\165\163\164\040\105\103\103\040\122\157\157\164\055\060\061
END
CKA_ID UTF8 "0"
CKA_ISSUER MULTILINE_OCTAL
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
\162\165\163\164\040\105\103\103\040\122\157\157\164\055\060\061
END
CKA_SERIAL_NUMBER MULTILINE_OCTAL
\002\024\103\160\202\167\317\115\135\064\361\312\256\062\057\067
\367\364\177\165\240\236
END
CKA_VALUE MULTILINE_OCTAL
\060\202\002\035\060\202\001\243\240\003\002\001\002\002\024\103
\160\202\167\317\115\135\064\361\312\256\062\057\067\367\364\177
\165\240\236\060\012\006\010\052\206\110\316\075\004\003\003\060
\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061\022
\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143\157
\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157\155
\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124\162
\165\163\164\040\105\103\103\040\122\157\157\164\055\060\061\060
\036\027\015\062\061\060\064\062\070\061\067\063\065\064\063\132
\027\015\064\066\060\064\062\070\061\067\063\065\064\062\132\060
\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061\022
\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143\157
\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157\155
\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124\162
\165\163\164\040\105\103\103\040\122\157\157\164\055\060\061\060
\166\060\020\006\007\052\206\110\316\075\002\001\006\005\053\201
\004\000\042\003\142\000\004\113\066\351\256\127\136\250\160\327
\320\217\164\142\167\303\136\172\252\345\266\242\361\170\375\002
\176\127\335\221\171\234\154\271\122\210\124\274\057\004\276\270
\315\366\020\321\051\354\265\320\240\303\360\211\160\031\273\121
\145\305\103\234\303\233\143\235\040\203\076\006\013\246\102\104
\205\021\247\112\072\055\351\326\150\057\110\116\123\053\007\077
\115\275\271\254\167\071\127\243\102\060\100\060\017\006\003\125
\035\023\001\001\377\004\005\060\003\001\001\377\060\016\006\003
\125\035\017\001\001\377\004\004\003\002\001\006\060\035\006\003
\125\035\016\004\026\004\024\216\007\142\300\120\335\306\031\006
\000\106\164\004\367\363\256\175\165\115\060\060\012\006\010\052
\206\110\316\075\004\003\003\003\150\000\060\145\002\061\000\234
\063\337\101\343\043\250\102\066\046\227\065\134\173\353\333\113
\370\252\213\163\125\025\134\254\170\051\017\272\041\330\304\240
\330\321\003\335\155\321\071\075\304\223\140\322\343\162\262\002
\060\174\305\176\210\323\120\365\036\045\350\372\116\165\346\130
\226\244\065\137\033\145\352\141\232\160\043\265\015\243\233\222
\122\157\151\240\214\215\112\320\356\213\016\313\107\216\320\215
\021
END
CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE
CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE
CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE
# Trust for "CommScope Public Trust ECC Root-01"
# Issuer: CN=CommScope Public Trust ECC Root-01,O=CommScope,C=US
# Serial Number:43:70:82:77:cf:4d:5d:34:f1:ca:ae:32:2f:37:f7:f4:7f:75:a0:9e
# Subject: CN=CommScope Public Trust ECC Root-01,O=CommScope,C=US
# Not Valid Before: Wed Apr 28 17:35:43 2021
# Not Valid After : Sat Apr 28 17:35:42 2046
# Fingerprint (SHA-256): 11:43:7C:DA:7B:B4:5E:41:36:5F:45:B3:9A:38:98:6B:0D:E0:0D:EF:34:8E:0C:7B:B0:87:36:33:80:0B:C3:8B
# Fingerprint (SHA1): 07:86:C0:D8:DD:8E:C0:80:98:06:98:D0:58:7A:EF:DE:A6:CC:A2:5D
CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST
CKA_TOKEN CK_BBOOL CK_TRUE
CKA_PRIVATE CK_BBOOL CK_FALSE
CKA_MODIFIABLE CK_BBOOL CK_FALSE
CKA_LABEL UTF8 "CommScope Public Trust ECC Root-01"
CKA_CERT_SHA1_HASH MULTILINE_OCTAL
\007\206\300\330\335\216\300\200\230\006\230\320\130\172\357\336
\246\314\242\135
END
CKA_CERT_MD5_HASH MULTILINE_OCTAL
\072\100\247\374\003\214\234\070\171\057\072\242\154\266\012\026
END
CKA_ISSUER MULTILINE_OCTAL
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
\162\165\163\164\040\105\103\103\040\122\157\157\164\055\060\061
END
CKA_SERIAL_NUMBER MULTILINE_OCTAL
\002\024\103\160\202\167\317\115\135\064\361\312\256\062\057\067
\367\364\177\165\240\236
END
CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR
CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE
#
# Certificate "CommScope Public Trust ECC Root-02"
#
# Issuer: CN=CommScope Public Trust ECC Root-02,O=CommScope,C=US
# Serial Number:28:fd:99:60:41:47:a6:01:3a:ca:14:7b:1f:ef:f9:68:08:83:5d:7d
# Subject: CN=CommScope Public Trust ECC Root-02,O=CommScope,C=US
# Not Valid Before: Wed Apr 28 17:44:54 2021
# Not Valid After : Sat Apr 28 17:44:53 2046
# Fingerprint (SHA-256): 2F:FB:7F:81:3B:BB:B3:C8:9A:B4:E8:16:2D:0F:16:D7:15:09:A8:30:CC:9D:73:C2:62:E5:14:08:75:D1:AD:4A
# Fingerprint (SHA1): 3C:3F:EF:57:0F:FE:65:93:86:9E:A0:FE:B0:F6:ED:8E:D1:13:C7:E5
CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE
CKA_TOKEN CK_BBOOL CK_TRUE
CKA_PRIVATE CK_BBOOL CK_FALSE
CKA_MODIFIABLE CK_BBOOL CK_FALSE
CKA_LABEL UTF8 "CommScope Public Trust ECC Root-02"
CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509
CKA_SUBJECT MULTILINE_OCTAL
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
\162\165\163\164\040\105\103\103\040\122\157\157\164\055\060\062
END
CKA_ID UTF8 "0"
CKA_ISSUER MULTILINE_OCTAL
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
\162\165\163\164\040\105\103\103\040\122\157\157\164\055\060\062
END
CKA_SERIAL_NUMBER MULTILINE_OCTAL
\002\024\050\375\231\140\101\107\246\001\072\312\024\173\037\357
\371\150\010\203\135\175
END
CKA_VALUE MULTILINE_OCTAL
\060\202\002\034\060\202\001\243\240\003\002\001\002\002\024\050
\375\231\140\101\107\246\001\072\312\024\173\037\357\371\150\010
\203\135\175\060\012\006\010\052\206\110\316\075\004\003\003\060
\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061\022
\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143\157
\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157\155
\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124\162
\165\163\164\040\105\103\103\040\122\157\157\164\055\060\062\060
\036\027\015\062\061\060\064\062\070\061\067\064\064\065\064\132
\027\015\064\066\060\064\062\070\061\067\064\064\065\063\132\060
\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061\022
\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143\157
\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157\155
\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124\162
\165\163\164\040\105\103\103\040\122\157\157\164\055\060\062\060
\166\060\020\006\007\052\206\110\316\075\002\001\006\005\053\201
\004\000\042\003\142\000\004\170\060\201\350\143\036\345\353\161
\121\017\367\007\007\312\071\231\174\116\325\017\314\060\060\013
\217\146\223\076\317\275\305\206\275\371\261\267\264\076\264\007
\310\363\226\061\363\355\244\117\370\243\116\215\051\025\130\270
\325\157\177\356\154\042\265\260\257\110\105\012\275\250\111\224
\277\204\103\260\333\204\112\003\043\031\147\152\157\301\156\274
\006\071\067\321\210\042\367\243\102\060\100\060\017\006\003\125
\035\023\001\001\377\004\005\060\003\001\001\377\060\016\006\003
\125\035\017\001\001\377\004\004\003\002\001\006\060\035\006\003
\125\035\016\004\026\004\024\346\030\165\377\357\140\336\204\244
\365\106\307\336\112\125\343\062\066\171\365\060\012\006\010\052
\206\110\316\075\004\003\003\003\147\000\060\144\002\060\046\163
\111\172\266\253\346\111\364\175\122\077\324\101\004\256\200\103
\203\145\165\271\205\200\070\073\326\157\344\223\206\253\217\347
\211\310\177\233\176\153\012\022\125\141\252\021\340\171\002\060
\167\350\061\161\254\074\161\003\326\204\046\036\024\270\363\073
\073\336\355\131\374\153\114\060\177\131\316\105\351\163\140\025
\232\114\360\346\136\045\042\025\155\302\207\131\320\262\216\152
END
CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE
CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE
CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE
# Trust for "CommScope Public Trust ECC Root-02"
# Issuer: CN=CommScope Public Trust ECC Root-02,O=CommScope,C=US
# Serial Number:28:fd:99:60:41:47:a6:01:3a:ca:14:7b:1f:ef:f9:68:08:83:5d:7d
# Subject: CN=CommScope Public Trust ECC Root-02,O=CommScope,C=US
# Not Valid Before: Wed Apr 28 17:44:54 2021
# Not Valid After : Sat Apr 28 17:44:53 2046
# Fingerprint (SHA-256): 2F:FB:7F:81:3B:BB:B3:C8:9A:B4:E8:16:2D:0F:16:D7:15:09:A8:30:CC:9D:73:C2:62:E5:14:08:75:D1:AD:4A
# Fingerprint (SHA1): 3C:3F:EF:57:0F:FE:65:93:86:9E:A0:FE:B0:F6:ED:8E:D1:13:C7:E5
CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST
CKA_TOKEN CK_BBOOL CK_TRUE
CKA_PRIVATE CK_BBOOL CK_FALSE
CKA_MODIFIABLE CK_BBOOL CK_FALSE
CKA_LABEL UTF8 "CommScope Public Trust ECC Root-02"
CKA_CERT_SHA1_HASH MULTILINE_OCTAL
\074\077\357\127\017\376\145\223\206\236\240\376\260\366\355\216
\321\023\307\345
END
CKA_CERT_MD5_HASH MULTILINE_OCTAL
\131\260\104\325\145\115\270\134\125\031\222\002\266\321\224\262
END
CKA_ISSUER MULTILINE_OCTAL
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
\162\165\163\164\040\105\103\103\040\122\157\157\164\055\060\062
END
CKA_SERIAL_NUMBER MULTILINE_OCTAL
\002\024\050\375\231\140\101\107\246\001\072\312\024\173\037\357
\371\150\010\203\135\175
END
CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR
CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE
#
# Certificate "CommScope Public Trust RSA Root-01"
#
# Issuer: CN=CommScope Public Trust RSA Root-01,O=CommScope,C=US
# Serial Number:3e:03:49:81:75:16:74:31:8e:4c:ab:d5:c5:90:29:96:c5:39:10:dd
# Subject: CN=CommScope Public Trust RSA Root-01,O=CommScope,C=US
# Not Valid Before: Wed Apr 28 16:45:54 2021
# Not Valid After : Sat Apr 28 16:45:53 2046
# Fingerprint (SHA-256): 02:BD:F9:6E:2A:45:DD:9B:F1:8F:C7:E1:DB:DF:21:A0:37:9B:A3:C9:C2:61:03:44:CF:D8:D6:06:FE:C1:ED:81
# Fingerprint (SHA1): 6D:0A:5F:F7:B4:23:06:B4:85:B3:B7:97:64:FC:AC:75:F5:33:F2:93
CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE
CKA_TOKEN CK_BBOOL CK_TRUE
CKA_PRIVATE CK_BBOOL CK_FALSE
CKA_MODIFIABLE CK_BBOOL CK_FALSE
CKA_LABEL UTF8 "CommScope Public Trust RSA Root-01"
CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509
CKA_SUBJECT MULTILINE_OCTAL
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
\162\165\163\164\040\122\123\101\040\122\157\157\164\055\060\061
END
CKA_ID UTF8 "0"
CKA_ISSUER MULTILINE_OCTAL
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
\162\165\163\164\040\122\123\101\040\122\157\157\164\055\060\061
END
CKA_SERIAL_NUMBER MULTILINE_OCTAL
\002\024\076\003\111\201\165\026\164\061\216\114\253\325\305\220
\051\226\305\071\020\335
END
CKA_VALUE MULTILINE_OCTAL
\060\202\005\154\060\202\003\124\240\003\002\001\002\002\024\076
\003\111\201\165\026\164\061\216\114\253\325\305\220\051\226\305
\071\020\335\060\015\006\011\052\206\110\206\367\015\001\001\013
\005\000\060\116\061\013\060\011\006\003\125\004\006\023\002\125
\123\061\022\060\020\006\003\125\004\012\014\011\103\157\155\155
\123\143\157\160\145\061\053\060\051\006\003\125\004\003\014\042
\103\157\155\155\123\143\157\160\145\040\120\165\142\154\151\143
\040\124\162\165\163\164\040\122\123\101\040\122\157\157\164\055
\060\061\060\036\027\015\062\061\060\064\062\070\061\066\064\065
\065\064\132\027\015\064\066\060\064\062\070\061\066\064\065\065
\063\132\060\116\061\013\060\011\006\003\125\004\006\023\002\125
\123\061\022\060\020\006\003\125\004\012\014\011\103\157\155\155
\123\143\157\160\145\061\053\060\051\006\003\125\004\003\014\042
\103\157\155\155\123\143\157\160\145\040\120\165\142\154\151\143
\040\124\162\165\163\164\040\122\123\101\040\122\157\157\164\055
\060\061\060\202\002\042\060\015\006\011\052\206\110\206\367\015
\001\001\001\005\000\003\202\002\017\000\060\202\002\012\002\202
\002\001\000\260\110\145\243\015\035\102\343\221\155\235\204\244
\141\226\022\302\355\303\332\043\064\031\166\366\352\375\125\132
\366\125\001\123\017\362\314\214\227\117\271\120\313\263\001\104
\126\226\375\233\050\354\173\164\013\347\102\153\125\316\311\141
\262\350\255\100\074\272\271\101\012\005\117\033\046\205\217\103
\265\100\265\205\321\324\161\334\203\101\363\366\105\307\200\242
\204\120\227\106\316\240\014\304\140\126\004\035\007\133\106\245
\016\262\113\244\016\245\174\356\370\324\142\003\271\223\152\212
\024\270\160\370\056\202\106\070\043\016\164\307\153\101\267\320
\051\243\235\200\260\176\167\223\143\102\373\064\203\073\163\243
\132\041\066\353\107\372\030\027\331\272\146\302\223\244\217\374
\135\244\255\374\120\152\225\254\274\044\063\321\275\210\177\206
\365\365\262\163\052\217\174\257\010\362\032\230\077\251\201\145
\077\301\214\211\305\226\060\232\012\317\364\324\310\064\355\235
\057\274\215\070\206\123\356\227\237\251\262\143\224\027\215\017
\334\146\052\174\122\121\165\313\231\216\350\075\134\277\236\073
\050\215\203\002\017\251\237\162\342\054\053\263\334\146\227\000
\100\320\244\124\216\233\135\173\105\066\046\326\162\103\353\317
\300\352\015\334\316\022\346\175\070\237\005\047\250\227\076\351
\121\306\154\005\050\301\002\017\351\030\155\354\275\234\006\324
\247\111\364\124\005\153\154\060\361\353\003\325\352\075\152\166
\302\313\032\050\111\115\177\144\340\372\053\332\163\203\201\377
\221\003\275\224\273\344\270\216\234\062\143\315\237\273\150\201
\261\204\133\257\066\277\167\356\035\177\367\111\233\122\354\322
\167\132\175\221\235\115\302\071\055\344\272\202\370\157\362\116
\036\017\116\346\077\131\245\043\334\075\207\250\050\130\050\321
\361\033\066\333\117\304\377\341\214\133\162\214\307\046\003\047
\243\071\012\001\252\300\262\061\140\203\042\241\117\022\011\001
\021\257\064\324\317\327\256\142\323\005\007\264\061\165\340\015
\155\127\117\151\207\371\127\251\272\025\366\310\122\155\241\313
\234\037\345\374\170\250\065\232\237\101\024\316\245\264\316\224
\010\034\011\255\126\345\332\266\111\232\112\352\143\030\123\234
\054\056\303\002\003\001\000\001\243\102\060\100\060\017\006\003
\125\035\023\001\001\377\004\005\060\003\001\001\377\060\016\006
\003\125\035\017\001\001\377\004\004\003\002\001\006\060\035\006
\003\125\035\016\004\026\004\024\067\135\246\232\164\062\302\302
\371\307\246\025\020\131\270\344\375\345\270\155\060\015\006\011
\052\206\110\206\367\015\001\001\013\005\000\003\202\002\001\000
\257\247\317\336\377\340\275\102\215\115\345\042\226\337\150\352
\175\115\052\175\320\255\075\026\134\103\347\175\300\206\350\172
\065\143\361\314\201\310\306\013\350\056\122\065\244\246\111\220
\143\121\254\064\254\005\073\127\000\351\323\142\323\331\051\325
\124\276\034\020\221\234\262\155\376\131\375\171\367\352\126\320
\236\150\124\102\217\046\122\342\114\337\057\227\246\057\322\007
\230\250\363\140\135\113\232\130\127\210\357\202\345\372\257\154
\201\113\222\217\100\232\223\106\131\313\137\170\026\261\147\076
\102\013\337\050\331\260\255\230\040\276\103\174\321\136\032\011
\027\044\215\173\135\225\351\253\301\140\253\133\030\144\200\373
\255\340\006\175\035\312\131\270\363\170\051\147\306\126\035\257
\266\265\164\052\166\241\077\373\165\060\237\224\136\073\245\140
\363\313\134\014\342\016\311\140\370\311\037\026\212\046\335\347
\047\177\353\045\246\212\275\270\055\066\020\232\261\130\115\232
\150\117\140\124\345\366\106\023\216\210\254\274\041\102\022\255
\306\112\211\175\233\301\330\055\351\226\003\364\242\164\014\274
\000\035\277\326\067\045\147\264\162\213\257\205\275\352\052\003
\217\314\373\074\104\044\202\342\001\245\013\131\266\064\215\062
\013\022\015\353\047\302\375\101\327\100\074\162\106\051\300\214
\352\272\017\361\006\223\056\367\234\250\364\140\076\243\361\070
\136\216\023\301\263\072\227\207\077\222\312\170\251\034\257\320
\260\033\046\036\276\160\354\172\365\063\230\352\134\377\053\013
\004\116\103\335\143\176\016\247\116\170\003\225\076\324\055\060
\225\021\020\050\056\277\240\002\076\377\136\131\323\005\016\225
\137\123\105\357\153\207\325\110\315\026\246\226\203\341\337\263
\006\363\301\024\333\247\354\034\213\135\220\220\015\162\121\347
\141\371\024\312\257\203\217\277\257\261\012\131\135\334\134\327
\344\226\255\133\140\035\332\256\227\262\071\331\006\365\166\000
\023\370\150\114\041\260\065\304\334\125\262\311\301\101\132\034
\211\300\214\157\164\240\153\063\115\265\001\050\375\255\255\211
\027\073\246\232\204\274\353\214\352\304\161\044\250\272\051\371
\010\262\047\126\065\062\137\352\071\373\061\232\325\031\314\360
END
CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE
CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE
CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE
# Trust for "CommScope Public Trust RSA Root-01"
# Issuer: CN=CommScope Public Trust RSA Root-01,O=CommScope,C=US
# Serial Number:3e:03:49:81:75:16:74:31:8e:4c:ab:d5:c5:90:29:96:c5:39:10:dd
# Subject: CN=CommScope Public Trust RSA Root-01,O=CommScope,C=US
# Not Valid Before: Wed Apr 28 16:45:54 2021
# Not Valid After : Sat Apr 28 16:45:53 2046
# Fingerprint (SHA-256): 02:BD:F9:6E:2A:45:DD:9B:F1:8F:C7:E1:DB:DF:21:A0:37:9B:A3:C9:C2:61:03:44:CF:D8:D6:06:FE:C1:ED:81
# Fingerprint (SHA1): 6D:0A:5F:F7:B4:23:06:B4:85:B3:B7:97:64:FC:AC:75:F5:33:F2:93
CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST
CKA_TOKEN CK_BBOOL CK_TRUE
CKA_PRIVATE CK_BBOOL CK_FALSE
CKA_MODIFIABLE CK_BBOOL CK_FALSE
CKA_LABEL UTF8 "CommScope Public Trust RSA Root-01"
CKA_CERT_SHA1_HASH MULTILINE_OCTAL
\155\012\137\367\264\043\006\264\205\263\267\227\144\374\254\165
\365\063\362\223
END
CKA_CERT_MD5_HASH MULTILINE_OCTAL
\016\264\025\274\207\143\135\135\002\163\324\046\070\150\163\330
END
CKA_ISSUER MULTILINE_OCTAL
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
\162\165\163\164\040\122\123\101\040\122\157\157\164\055\060\061
END
CKA_SERIAL_NUMBER MULTILINE_OCTAL
\002\024\076\003\111\201\165\026\164\061\216\114\253\325\305\220
\051\226\305\071\020\335
END
CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR
CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE
#
# Certificate "CommScope Public Trust RSA Root-02"
#
# Issuer: CN=CommScope Public Trust RSA Root-02,O=CommScope,C=US
# Serial Number:54:16:bf:3b:7e:39:95:71:8d:d1:aa:00:a5:86:0d:2b:8f:7a:05:4e
# Subject: CN=CommScope Public Trust RSA Root-02,O=CommScope,C=US
# Not Valid Before: Wed Apr 28 17:16:43 2021
# Not Valid After : Sat Apr 28 17:16:42 2046
# Fingerprint (SHA-256): FF:E9:43:D7:93:42:4B:4F:7C:44:0C:1C:3D:64:8D:53:63:F3:4B:82:DC:87:AA:7A:9F:11:8F:C5:DE:E1:01:F1
# Fingerprint (SHA1): EA:B0:E2:52:1B:89:93:4C:11:68:F2:D8:9A:AC:22:4C:A3:8A:57:AE
CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE
CKA_TOKEN CK_BBOOL CK_TRUE
CKA_PRIVATE CK_BBOOL CK_FALSE
CKA_MODIFIABLE CK_BBOOL CK_FALSE
CKA_LABEL UTF8 "CommScope Public Trust RSA Root-02"
CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509
CKA_SUBJECT MULTILINE_OCTAL
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
\162\165\163\164\040\122\123\101\040\122\157\157\164\055\060\062
END
CKA_ID UTF8 "0"
CKA_ISSUER MULTILINE_OCTAL
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
\162\165\163\164\040\122\123\101\040\122\157\157\164\055\060\062
END
CKA_SERIAL_NUMBER MULTILINE_OCTAL
\002\024\124\026\277\073\176\071\225\161\215\321\252\000\245\206
\015\053\217\172\005\116
END
CKA_VALUE MULTILINE_OCTAL
\060\202\005\154\060\202\003\124\240\003\002\001\002\002\024\124
\026\277\073\176\071\225\161\215\321\252\000\245\206\015\053\217
\172\005\116\060\015\006\011\052\206\110\206\367\015\001\001\013
\005\000\060\116\061\013\060\011\006\003\125\004\006\023\002\125
\123\061\022\060\020\006\003\125\004\012\014\011\103\157\155\155
\123\143\157\160\145\061\053\060\051\006\003\125\004\003\014\042
\103\157\155\155\123\143\157\160\145\040\120\165\142\154\151\143
\040\124\162\165\163\164\040\122\123\101\040\122\157\157\164\055
\060\062\060\036\027\015\062\061\060\064\062\070\061\067\061\066
\064\063\132\027\015\064\066\060\064\062\070\061\067\061\066\064
\062\132\060\116\061\013\060\011\006\003\125\004\006\023\002\125
\123\061\022\060\020\006\003\125\004\012\014\011\103\157\155\155
\123\143\157\160\145\061\053\060\051\006\003\125\004\003\014\042
\103\157\155\155\123\143\157\160\145\040\120\165\142\154\151\143
\040\124\162\165\163\164\040\122\123\101\040\122\157\157\164\055
\060\062\060\202\002\042\060\015\006\011\052\206\110\206\367\015
\001\001\001\005\000\003\202\002\017\000\060\202\002\012\002\202
\002\001\000\341\372\016\373\150\000\022\310\115\325\254\042\304
\065\001\073\305\124\345\131\166\143\245\177\353\301\304\152\230
\275\062\215\027\200\353\135\272\321\142\075\045\043\031\065\024
\351\177\211\247\033\142\074\326\120\347\064\225\003\062\261\264
\223\042\075\247\342\261\355\346\173\116\056\207\233\015\063\165
\012\336\252\065\347\176\345\066\230\242\256\045\236\225\263\062
\226\244\053\130\036\357\077\376\142\064\110\121\321\264\215\102
\255\140\332\111\152\225\160\335\322\000\342\314\127\143\002\173
\226\335\111\227\133\222\116\225\323\371\313\051\037\030\112\370
\001\052\322\143\011\156\044\351\211\322\345\307\042\114\334\163
\206\107\000\252\015\210\216\256\205\175\112\351\273\063\117\016
\122\160\235\225\343\174\155\226\133\055\075\137\241\203\106\135
\266\343\045\270\174\247\031\200\034\352\145\103\334\221\171\066
\054\164\174\362\147\006\311\211\311\333\277\332\150\277\043\355
\334\153\255\050\203\171\057\354\070\245\015\067\001\147\047\232
\351\063\331\063\137\067\241\305\360\253\075\372\170\260\347\054
\237\366\076\237\140\340\357\110\351\220\105\036\005\121\170\032
\054\022\054\134\050\254\015\242\043\236\064\217\005\346\242\063
\316\021\167\023\324\016\244\036\102\037\206\315\160\376\331\056
\025\075\035\273\270\362\123\127\333\314\306\164\051\234\030\263
\066\165\070\056\017\124\241\370\222\037\211\226\117\273\324\356
\235\351\073\066\102\265\012\073\052\324\144\171\066\020\341\371
\221\003\053\173\040\124\315\015\031\032\310\101\062\064\321\260
\231\341\220\036\001\100\066\265\267\372\251\345\167\165\244\042
\201\135\260\213\344\047\022\017\124\210\306\333\205\164\346\267
\300\327\246\051\372\333\336\363\223\227\047\004\125\057\012\157
\067\305\075\023\257\012\000\251\054\213\034\201\050\327\357\206
\061\251\256\362\156\270\312\152\054\124\107\330\052\210\056\257
\301\007\020\170\254\021\242\057\102\360\067\305\362\270\126\335
\016\142\055\316\055\126\176\125\362\247\104\366\053\062\364\043
\250\107\350\324\052\001\170\317\152\303\067\250\236\145\322\054
\345\372\272\063\301\006\104\366\346\317\245\015\247\146\010\064
\212\054\363\002\003\001\000\001\243\102\060\100\060\017\006\003
\125\035\023\001\001\377\004\005\060\003\001\001\377\060\016\006
\003\125\035\017\001\001\377\004\004\003\002\001\006\060\035\006
\003\125\035\016\004\026\004\024\107\320\347\261\042\377\235\054
\365\331\127\140\263\261\261\160\225\357\141\172\060\015\006\011
\052\206\110\206\367\015\001\001\013\005\000\003\202\002\001\000
\206\151\261\115\057\351\237\117\042\223\150\216\344\041\231\243
\316\105\123\033\163\104\123\000\201\141\315\061\343\010\272\201
\050\050\172\222\271\266\250\310\103\236\307\023\046\115\302\330
\345\125\234\222\135\120\330\302\053\333\376\346\250\227\317\122
\072\044\303\145\144\134\107\061\243\145\065\023\303\223\271\367
\371\121\227\273\244\360\142\207\305\326\006\323\227\203\040\251
\176\273\266\041\302\245\015\204\000\341\362\047\020\203\272\335
\003\201\325\335\150\303\146\020\310\321\166\264\263\157\051\236
\000\371\302\051\365\261\223\031\122\151\032\054\114\240\213\340
\025\232\061\057\323\210\225\131\156\345\304\263\120\310\024\010
\112\233\213\023\203\261\244\162\262\073\166\063\101\334\334\252
\246\007\157\035\044\022\237\310\166\275\057\331\216\364\054\356
\267\322\070\020\044\066\121\057\343\134\135\201\041\247\332\273
\116\377\346\007\250\376\271\015\047\154\273\160\132\125\172\023
\351\361\052\111\151\307\137\207\127\114\103\171\155\072\145\351
\060\134\101\356\353\167\245\163\022\210\350\277\175\256\345\304
\250\037\015\216\034\155\120\002\117\046\030\103\336\217\125\205
\261\013\067\005\140\311\125\071\022\004\241\052\317\161\026\237
\066\121\111\277\160\073\236\147\234\373\173\171\311\071\034\170
\254\167\221\124\232\270\165\012\201\122\227\343\146\141\153\355
\076\070\036\226\141\125\341\221\124\214\355\214\044\037\201\311
\020\232\163\231\053\026\116\162\000\077\124\033\370\215\272\213
\347\024\326\266\105\117\140\354\226\256\303\057\002\116\135\235
\226\111\162\000\262\253\165\134\017\150\133\035\145\302\137\063
\017\036\017\360\073\206\365\260\116\273\234\367\352\045\005\334
\255\242\233\113\027\001\276\102\337\065\041\035\255\253\256\364
\277\256\037\033\323\342\073\374\263\162\163\034\233\050\220\211
\023\075\035\301\000\107\011\226\232\070\033\335\261\317\015\302
\264\104\363\226\225\316\062\072\217\064\234\340\027\307\136\316
\256\015\333\207\070\345\077\133\375\233\031\341\061\101\172\160
\252\043\153\001\341\105\114\315\224\316\073\236\055\347\210\002
\042\364\156\350\310\354\326\074\363\271\262\327\167\172\254\173
END
CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE
CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE
CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE
# Trust for "CommScope Public Trust RSA Root-02"
# Issuer: CN=CommScope Public Trust RSA Root-02,O=CommScope,C=US
# Serial Number:54:16:bf:3b:7e:39:95:71:8d:d1:aa:00:a5:86:0d:2b:8f:7a:05:4e
# Subject: CN=CommScope Public Trust RSA Root-02,O=CommScope,C=US
# Not Valid Before: Wed Apr 28 17:16:43 2021
# Not Valid After : Sat Apr 28 17:16:42 2046
# Fingerprint (SHA-256): FF:E9:43:D7:93:42:4B:4F:7C:44:0C:1C:3D:64:8D:53:63:F3:4B:82:DC:87:AA:7A:9F:11:8F:C5:DE:E1:01:F1
# Fingerprint (SHA1): EA:B0:E2:52:1B:89:93:4C:11:68:F2:D8:9A:AC:22:4C:A3:8A:57:AE
CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST
CKA_TOKEN CK_BBOOL CK_TRUE
CKA_PRIVATE CK_BBOOL CK_FALSE
CKA_MODIFIABLE CK_BBOOL CK_FALSE
CKA_LABEL UTF8 "CommScope Public Trust RSA Root-02"
CKA_CERT_SHA1_HASH MULTILINE_OCTAL
\352\260\342\122\033\211\223\114\021\150\362\330\232\254\042\114
\243\212\127\256
END
CKA_CERT_MD5_HASH MULTILINE_OCTAL
\341\051\371\142\173\166\342\226\155\363\324\327\017\256\037\252
END
CKA_ISSUER MULTILINE_OCTAL
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
\162\165\163\164\040\122\123\101\040\122\157\157\164\055\060\062
END
CKA_SERIAL_NUMBER MULTILINE_OCTAL
\002\024\124\026\277\073\176\071\225\161\215\321\252\000\245\206
\015\053\217\172\005\116
END
CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR
CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE
#
# Certificate "D-Trust SBR Root CA 1 2022"
#

View File

@@ -3182,96 +3182,6 @@ static struct us_cert_string_t root_certs[] = {
"MvHVI5TWWA==\n"
"-----END CERTIFICATE-----",.len=869},
/* CommScope Public Trust ECC Root-01 */
{.str="-----BEGIN CERTIFICATE-----\n"
"MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMwTjELMAkG\n"
"A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1Ymxp\n"
"YyBUcnVzdCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNaFw00NjA0MjgxNzM1NDJaME4x\n"
"CzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQ\n"
"dWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16o\n"
"cNfQj3Rid8NeeqrltqLxeP0CflfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOc\n"
"w5tjnSCDPgYLpkJEhRGnSjot6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMB\n"
"Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggq\n"
"hkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg2NED3W3R\n"
"OT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liWpDVfG2XqYZpwI7UNo5uSUm9poIyNStDuiw7L\n"
"R47QjRE=\n"
"-----END CERTIFICATE-----",.len=792},
/* CommScope Public Trust ECC Root-02 */
{.str="-----BEGIN CERTIFICATE-----\n"
"MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMwTjELMAkG\n"
"A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1Ymxp\n"
"YyBUcnVzdCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRaFw00NjA0MjgxNzQ0NTNaME4x\n"
"CzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQ\n"
"dWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l\n"
"63FRD/cHB8o5mXxO1Q/MMDALj2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/u\n"
"bCK1sK9IRQq9qEmUv4RDsNuESgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMB\n"
"Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggq\n"
"hkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/nich/m35r\n"
"ChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs73u1Z/GtMMH9ZzkXpc2AVmkzw5l4lIhVtwodZ\n"
"0LKOag==\n"
"-----END CERTIFICATE-----",.len=792},
/* CommScope Public Trust RSA Root-01 */
{.str="-----BEGIN CERTIFICATE-----\n"
"MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQELBQAwTjEL\n"
"MAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1\n"
"YmxpYyBUcnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1NTRaFw00NjA0MjgxNjQ1NTNa\n"
"ME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29w\n"
"ZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3QtMDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\n"
"AoICAQCwSGWjDR1C45FtnYSkYZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2b\n"
"KOx7dAvnQmtVzslhsuitQDy6uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBW\n"
"BB0HW0alDrJLpA6lfO741GIDuZNqihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3Oj\n"
"WiE260f6GBfZumbCk6SP/F2krfxQapWsvCQz0b2If4b19bJzKo98rwjyGpg/qYFlP8GMicWW\n"
"MJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/cZip8UlF1y5mO6D1cv547KI2DAg+pn3LiLCuz\n"
"3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTifBSeolz7pUcZsBSjBAg/pGG3svZwG1KdJ\n"
"9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/kQO9lLvkuI6cMmPNn7togbGEW682v3fu\n"
"HX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JOHg9O5j9ZpSPcPYeoKFgo0fEbNttPxP/hjFtyjMcm\n"
"AyejOQoBqsCyMWCDIqFPEgkBEa801M/XrmLTBQe0MXXgDW1XT2mH+VepuhX2yFJtocucH+X8\n"
"eKg1mp9BFM6ltM6UCBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD\n"
"AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm45P3luG0wDQYJ\n"
"KoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6NWPxzIHI\n"
"xgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQnmhUQo8mUuJM3y+X\n"
"pi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+QgvfKNmwrZggvkN80V4aCRck\n"
"jXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2vtrV0KnahP/t1MJ+UXjulYPPLXAziDslg\n"
"+MkfFoom3ecnf+slpoq9uC02EJqxWE2aaE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0\n"
"DLwAHb/WNyVntHKLr4W96ioDj8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/x\n"
"BpMu95yo9GA+o/E4Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054\n"
"A5U+1C0wlREQKC6/oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHn\n"
"YfkUyq+Dj7+vsQpZXdxc1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVocicCMb3Sg\n"
"azNNtQEo/a2tiRc7ppqEvOuM6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw\n"
"-----END CERTIFICATE-----",.len=1935},
/* CommScope Public Trust RSA Root-02 */
{.str="-----BEGIN CERTIFICATE-----\n"
"MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQELBQAwTjEL\n"
"MAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1\n"
"YmxpYyBUcnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2NDNaFw00NjA0MjgxNzE2NDJa\n"
"ME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29w\n"
"ZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3QtMDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\n"
"AoICAQDh+g77aAASyE3VrCLENQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mn\n"
"G2I81lDnNJUDMrG0kyI9p+Kx7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0\n"
"SFHRtI1CrWDaSWqVcN3SAOLMV2MCe5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxz\n"
"hkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2WWy09X6GDRl224yW4fKcZgBzqZUPckXk2LHR88mcG\n"
"yYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rpM9kzXzehxfCrPfp4sOcsn/Y+n2Dg70jpkEUe\n"
"BVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIfhs1w/tkuFT0du7jyU1fbzMZ0KZwYszZ1\n"
"OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5kQMreyBUzQ0ZGshBMjTRsJnhkB4BQDa1\n"
"t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3wNemKfrb3vOTlycEVS8KbzfFPROvCgCpLIscgSjX\n"
"74Yxqa7ybrjKaixUR9gqiC6vwQcQeKwRoi9C8DfF8rhW3Q5iLc4tVn5V8qdE9isy9COoR+jU\n"
"KgF4z2rDN6ieZdIs5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD\n"
"AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7GxcJXvYXowDQYJ\n"
"KoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqBKCh6krm2\n"
"qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3+VGXu6TwYofF1gbT\n"
"l4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbymeAPnCKfWxkxlSaRosTKCL4BWa\n"
"MS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3NyqpgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv\n"
"41xdgSGn2rtO/+YHqP65DSdsu3BaVXoT6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u\n"
"5cSoHw2OHG1QAk8mGEPej1WFsQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FU\n"
"mrh1CoFSl+NmYWvtPjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jau\n"
"wy8CTl2dlklyALKrdVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670\n"
"v64fG9PiO/yzcnMcmyiQiRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17Org3bhzjl\n"
"P1v9mxnhMUF6cKojawHhRUzNlM47ni3niAIi9G7oyOzWPPO5std3eqx7\n"
"-----END CERTIFICATE-----",.len=1935},
/* Telekom Security TLS ECC Root 2020 */
{.str="-----BEGIN CERTIFICATE-----\n"
"MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQswCQYDVQQG\n"

View File

@@ -1140,14 +1140,14 @@ export fn Bun__runVirtualModule(globalObject: *JSGlobalObject, specifier_ptr: *c
fn getHardcodedModule(jsc_vm: *VirtualMachine, specifier: bun.String, hardcoded: HardcodedModule) ?ResolvedSource {
analytics.Features.builtin_modules.insert(hardcoded);
return switch (hardcoded) {
.@"bun:main" => .{
.@"bun:main" => if (jsc_vm.entry_point.generated) .{
.allocator = null,
.source_code = bun.String.cloneUTF8(jsc_vm.entry_point.source.contents),
.specifier = specifier,
.source_url = specifier,
.tag = .esm,
.source_code_needs_deref = true,
},
} else null,
.@"bun:internal-for-testing" => {
if (!Environment.isDebug) {
if (!is_allowed_to_use_internal_testing_apis)

View File

@@ -1616,7 +1616,7 @@ fn _resolve(
if (strings.eqlComptime(std.fs.path.basename(specifier), Runtime.Runtime.Imports.alt_name)) {
ret.path = Runtime.Runtime.Imports.Name;
return;
} else if (strings.eqlComptime(specifier, main_file_name)) {
} else if (strings.eqlComptime(specifier, main_file_name) and jsc_vm.entry_point.generated) {
ret.result = null;
ret.path = jsc_vm.entry_point.source.path.text;
return;

View File

@@ -4,7 +4,7 @@ const TimerObjectInternals = @This();
/// Identifier for this timer that is exposed to JavaScript (by `+timer`)
id: i32 = -1,
interval: u31 = 0,
strong_this: jsc.Strong.Optional = .empty,
this_value: jsc.JSRef = .empty(),
flags: Flags = .{},
/// Used by:
@@ -76,31 +76,41 @@ pub fn runImmediateTask(this: *TimerObjectInternals, vm: *VirtualMachine) bool {
// loop alive other than setImmediates
(!this.flags.is_keeping_event_loop_alive and !vm.isEventLoopAliveExcludingImmediates()))
{
this.setEnableKeepingEventLoopAlive(vm, false);
this.this_value.downgrade();
this.deref();
return false;
}
const timer = this.strong_this.get() orelse {
const timer = this.this_value.tryGet() orelse {
if (Environment.isDebug) {
@panic("TimerObjectInternals.runImmediateTask: this_object is null");
}
this.setEnableKeepingEventLoopAlive(vm, false);
this.deref();
return false;
};
const globalThis = vm.global;
this.strong_this.deinit();
this.this_value.downgrade();
this.eventLoopTimer().state = .FIRED;
this.setEnableKeepingEventLoopAlive(vm, false);
timer.ensureStillAlive();
vm.eventLoop().enter();
const callback = ImmediateObject.js.callbackGetCached(timer).?;
const arguments = ImmediateObject.js.argumentsGetCached(timer).?;
this.ref();
const exception_thrown = this.run(globalThis, timer, callback, arguments, this.asyncID(), vm);
this.deref();
if (this.eventLoopTimer().state == .FIRED) {
this.deref();
}
const exception_thrown = brk: {
this.ref();
defer {
if (this.eventLoopTimer().state == .FIRED) {
this.deref();
}
this.deref();
}
break :brk this.run(globalThis, timer, callback, arguments, this.asyncID(), vm);
};
// --- after this point, the timer is no longer guaranteed to be alive ---
vm.eventLoop().exitMaybeDrainMicrotasks(!exception_thrown) catch return true;
@@ -120,7 +130,13 @@ pub fn fire(this: *TimerObjectInternals, _: *const timespec, vm: *jsc.VirtualMac
this.eventLoopTimer().state = .FIRED;
const globalThis = vm.global;
const this_object = this.strong_this.get().?;
const this_object = this.this_value.tryGet() orelse {
this.setEnableKeepingEventLoopAlive(vm, false);
this.flags.has_cleared_timer = true;
this.this_value.downgrade();
this.deref();
return;
};
const callback: JSValue, const arguments: JSValue, var idle_timeout: JSValue, var repeat: JSValue = switch (kind) {
.setImmediate => .{
@@ -143,7 +159,7 @@ pub fn fire(this: *TimerObjectInternals, _: *const timespec, vm: *jsc.VirtualMac
}
this.setEnableKeepingEventLoopAlive(vm, false);
this.flags.has_cleared_timer = true;
this.strong_this.deinit();
this.this_value.downgrade();
this.deref();
return;
@@ -152,7 +168,7 @@ pub fn fire(this: *TimerObjectInternals, _: *const timespec, vm: *jsc.VirtualMac
var time_before_call: timespec = undefined;
if (kind != .setInterval) {
this.strong_this.clearWithoutDeallocation();
this.this_value.downgrade();
} else {
time_before_call = timespec.msFromNow(.allow_mocked_time, this.interval);
}
@@ -239,7 +255,7 @@ fn convertToInterval(this: *TimerObjectInternals, global: *JSGlobalObject, timer
// https://github.com/nodejs/node/blob/a7cbb904745591c9a9d047a364c2c188e5470047/lib/internal/timers.js#L613
TimeoutObject.js.idleTimeoutSetCached(timer, global, repeat);
this.strong_this.set(global, timer);
this.this_value.setStrong(timer, global);
this.flags.kind = .setInterval;
this.interval = new_interval;
this.reschedule(timer, vm, global);
@@ -297,7 +313,7 @@ pub fn init(
this.reschedule(timer, vm, global);
}
this.strong_this.set(global, timer);
this.this_value.setStrong(timer, global);
}
pub fn doRef(this: *TimerObjectInternals, _: *jsc.JSGlobalObject, this_value: JSValue) JSValue {
@@ -327,7 +343,7 @@ pub fn doRefresh(this: *TimerObjectInternals, globalObject: *jsc.JSGlobalObject,
return this_value;
}
this.strong_this.set(globalObject, this_value);
this.this_value.setStrong(this_value, globalObject);
this.reschedule(this_value, VirtualMachine.get(), globalObject);
return this_value;
@@ -350,12 +366,18 @@ pub fn cancel(this: *TimerObjectInternals, vm: *VirtualMachine) void {
this.setEnableKeepingEventLoopAlive(vm, false);
this.flags.has_cleared_timer = true;
if (this.flags.kind == .setImmediate) return;
if (this.flags.kind == .setImmediate) {
// Release the strong reference so the GC can collect the JS object.
// The immediate task is still in the event loop queue and will be skipped
// by runImmediateTask when it sees has_cleared_timer == true.
this.this_value.downgrade();
return;
}
const was_active = this.eventLoopTimer().state == .ACTIVE;
this.eventLoopTimer().state = .CANCELLED;
this.strong_this.deinit();
this.this_value.downgrade();
if (was_active) {
vm.timer.remove(this.eventLoopTimer());
@@ -442,12 +464,12 @@ pub fn getDestroyed(this: *TimerObjectInternals) bool {
}
pub fn finalize(this: *TimerObjectInternals) void {
this.strong_this.deinit();
this.this_value.finalize();
this.deref();
}
pub fn deinit(this: *TimerObjectInternals) void {
this.strong_this.deinit();
this.this_value.deinit();
const vm = VirtualMachine.get();
const kind = this.flags.kind;

View File

@@ -1707,6 +1707,15 @@ pub fn NewWrappedHandler(comptime tls: bool) type {
pub fn onClose(this: WrappedSocket, socket: Socket, err: c_int, data: ?*anyopaque) bun.JSError!void {
if (comptime tls) {
// Clean up the raw TCP socket from upgradeTLS() — its onClose
// never fires because uws closes through the TLS context only.
defer {
if (!this.tcp.socket.isDetached()) {
this.tcp.socket.detach();
this.tcp.has_pending_activity.store(false, .release);
this.tcp.deref();
}
}
try TLSSocket.onClose(this.tls, socket, err, data);
} else {
try TLSSocket.onClose(this.tcp, socket, err, data);

View File

@@ -468,6 +468,17 @@ pub fn writeHead(this: *NodeHTTPResponse, globalObject: *jsc.JSGlobalObject, cal
return globalObject.ERR(.HTTP_HEADERS_SENT, "Stream already started", .{}).throw();
}
// Validate status message does not contain invalid characters (defense-in-depth
// against HTTP response splitting). Matches Node.js checkInvalidHeaderChar:
// rejects any char not in [\t\x20-\x7e\x80-\xff].
if (status_message_slice.len > 0) {
for (status_message_slice.slice()) |c| {
if (c != '\t' and (c < 0x20 or c == 0x7f)) {
return globalObject.ERR(.INVALID_CHAR, "Invalid character in statusMessage", .{}).throw();
}
}
}
do_it: {
if (status_message_slice.len == 0) {
if (HTTPStatusText.get(@intCast(status_code))) |status_message| {

View File

@@ -560,6 +560,8 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_infoAccess, (JSGlobalObject * g
return JSValue::encode(jsUndefined());
BUF_MEM* bptr = bio;
if (!bptr)
return JSValue::encode(jsUndefined());
return JSValue::encode(undefinedIfEmpty(jsString(vm, String::fromUTF8(std::span(bptr->data, bptr->length)))));
}
@@ -602,20 +604,10 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_issuerCertificate, (JSGlobalObj
return {};
}
auto issuerCert = thisObject->view().getIssuer();
if (!issuerCert)
return JSValue::encode(jsUndefined());
auto bio = issuerCert.get();
BUF_MEM* bptr = nullptr;
BIO_get_mem_ptr(bio, &bptr);
std::span<const uint8_t> span(reinterpret_cast<const uint8_t*>(bptr->data), bptr->length);
auto* zigGlobalObject = defaultGlobalObject(globalObject);
auto* structure = zigGlobalObject->m_JSX509CertificateClassStructure.get(zigGlobalObject);
auto jsIssuerCert = JSX509Certificate::create(vm, structure, globalObject, span);
RETURN_IF_EXCEPTION(scope, {});
return JSValue::encode(jsIssuerCert);
// issuerCertificate is only available when the certificate was obtained from
// a TLS connection with a peer certificate chain. For certificates parsed
// directly from PEM/DER data, it is always undefined (matching Node.js behavior).
return JSValue::encode(jsUndefined());
}
JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_publicKey, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName))

View File

@@ -42,6 +42,9 @@
#include <JavaScriptCore/ExceptionScope.h>
#include <JavaScriptCore/FunctionConstructor.h>
#include <JavaScriptCore/Heap.h>
#include <JavaScriptCore/Integrity.h>
#include <JavaScriptCore/MarkedBlock.h>
#include <JavaScriptCore/PreciseAllocation.h>
#include <JavaScriptCore/Identifier.h>
#include <JavaScriptCore/InitializeThreading.h>
#include <JavaScriptCore/IteratorOperations.h>
@@ -2415,6 +2418,18 @@ extern "C" napi_status napi_typeof(napi_env env, napi_value val,
if (value.isCell()) {
JSCell* cell = value.asCell();
// Validate that the cell pointer is a real GC-managed object.
// Native modules may accidentally pass garbage (e.g. a C string pointer)
// as napi_value, which would crash when we dereference the cell.
// isSanePointer rejects obviously invalid addresses (null-near, non-canonical).
// The bloom filter provides fast rejection of pointers not in any known
// MarkedBlock, using only pointer arithmetic (no dereference).
if (!JSC::Integrity::isSanePointer(cell)
|| (!JSC::PreciseAllocation::isPreciseAllocation(cell)
&& toJS(env)->vm().heap.objectSpace().blocks().filter().ruleOut(
std::bit_cast<uintptr_t>(JSC::MarkedBlock::blockFor(cell))))) [[unlikely]]
return napi_set_last_error(env, napi_invalid_arg);
switch (cell->type()) {
case JSC::JSFunctionType:
case JSC::InternalFunctionType:

View File

@@ -42,6 +42,13 @@ static std::optional<WTF::String> stripANSI(const std::span<const Char> input)
// Append everything before the escape sequence
result.append(std::span { start, escPos });
const auto newPos = ANSI::consumeANSI(escPos, end);
if (newPos == escPos) {
// No ANSI found
result.append(std::span { escPos, escPos + 1 });
start = escPos + 1;
continue;
}
ASSERT(newPos > start);
ASSERT(newPos <= end);
foundANSI = true;

View File

@@ -95,7 +95,6 @@
#include <JavaScriptCore/RegExp.h>
#include <JavaScriptCore/RegExpObject.h>
#include <JavaScriptCore/TypedArrayInlines.h>
#include <JavaScriptCore/TypedArrayType.h>
#include <JavaScriptCore/TypedArrays.h>
#include <JavaScriptCore/WasmModule.h>
#include <JavaScriptCore/YarrFlags.h>
@@ -386,38 +385,6 @@ static unsigned typedArrayElementSize(ArrayBufferViewSubtag tag)
}
}
static ArrayBufferViewSubtag subtagForTypedArrayType(TypedArrayType type)
{
switch (type) {
case TypeInt8:
return Int8ArrayTag;
case TypeUint8:
return Uint8ArrayTag;
case TypeUint8Clamped:
return Uint8ClampedArrayTag;
case TypeInt16:
return Int16ArrayTag;
case TypeUint16:
return Uint16ArrayTag;
case TypeInt32:
return Int32ArrayTag;
case TypeUint32:
return Uint32ArrayTag;
case TypeFloat16:
return Float16ArrayTag;
case TypeFloat32:
return Float32ArrayTag;
case TypeFloat64:
return Float64ArrayTag;
case TypeBigInt64:
return BigInt64ArrayTag;
case TypeBigUint64:
return BigUint64ArrayTag;
default:
return DataViewTag;
}
}
enum class SerializableErrorType : uint8_t {
Error,
EvalError,
@@ -5639,14 +5606,6 @@ SerializedScriptValue::SerializedScriptValue(WTF::FixedVector<DenseArrayElement>
m_memoryCost = computeMemoryCost();
}
SerializedScriptValue::SerializedScriptValue(Vector<uint8_t>&& data, uint8_t subtag)
: m_arrayButterflyData(WTF::move(data))
, m_fastPath(FastPath::TypedArray)
, m_typedArraySubtag(subtag)
{
m_memoryCost = computeMemoryCost();
}
Ref<SerializedScriptValue> SerializedScriptValue::createDenseArrayFastPath(
WTF::FixedVector<DenseArrayElement>&& elements)
{
@@ -5736,7 +5695,6 @@ size_t SerializedScriptValue::computeMemoryCost() const
break;
case FastPath::Int32Array:
case FastPath::DoubleArray:
case FastPath::TypedArray:
cost += m_arrayButterflyData.size();
break;
case FastPath::DenseArray:
@@ -5958,35 +5916,7 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb
object = cell->getObject();
structure = object->structure();
// TypedArray fast path: check before JSArray since TypedArray is not a JSArray
auto jsType = structure->typeInfo().type();
if (isTypedView(jsType)) {
auto* view = jsCast<JSArrayBufferView*>(object);
size_t byteLength = view->byteLength();
if (!view->isDetached()
&& !view->isOutOfBounds()
&& !view->isShared()
&& !view->isResizableOrGrowableShared()
&& view->byteOffset() == 0
&& structure->maxOffset() == invalidOffset
// For WastefulTypedArray (hasArrayBuffer()==true), verify the view
// covers the full ArrayBuffer; partial views (e.g. new Uint8Array(buf, 0, 8)
// over a 16-byte buffer) must fall through to the slow path.
// possiblySharedBuffer() is safe after isDetached()/isShared() checks:
// WastefulTypedArray just returns existingBufferInButterfly() without
// triggering slowDownAndWasteMemory(). Must be evaluated AFTER isDetached()
// to avoid null deref on detached buffers.
&& (!view->hasArrayBuffer()
|| view->byteLength() == view->possiblySharedBuffer()->byteLength())) {
auto taType = typedArrayType(jsType);
auto subtag = subtagForTypedArrayType(taType);
auto* data = static_cast<const uint8_t*>(view->vector());
// Use span constructor: single allocation + memcpy, no zero-fill
Vector<uint8_t> buffer(std::span<const uint8_t> { data, byteLength });
return SerializedScriptValue::createTypedArrayFastPath(WTF::move(buffer), static_cast<uint8_t>(subtag));
}
// Conditions not met → fall through to slow path
} else if (auto* jsArray = jsDynamicCast<JSArray*>(object)) {
if (auto* jsArray = jsDynamicCast<JSArray*>(object)) {
canUseArrayFastPath = true;
array = jsArray;
} else if (isObjectFastPathCandidate(structure)) {
@@ -6024,16 +5954,14 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb
auto* data = array->butterfly()->contiguous().data();
if (!containsHole(data, length)) {
size_t byteSize = sizeof(JSValue) * length;
Vector<uint8_t> buffer(byteSize, 0);
memcpy(buffer.mutableSpan().data(), data, byteSize);
Vector<uint8_t> buffer(std::span<const uint8_t> { reinterpret_cast<const uint8_t*>(data), byteSize });
return SerializedScriptValue::createInt32ArrayFastPath(WTF::move(buffer), length);
}
} else {
auto* data = array->butterfly()->contiguousDouble().data();
if (!containsHole(data, length)) {
size_t byteSize = sizeof(double) * length;
Vector<uint8_t> buffer(byteSize, 0);
memcpy(buffer.mutableSpan().data(), data, byteSize);
Vector<uint8_t> buffer(std::span<const uint8_t> { reinterpret_cast<const uint8_t*>(data), byteSize });
return SerializedScriptValue::createDoubleArrayFastPath(WTF::move(buffer), length);
}
}
@@ -6436,11 +6364,6 @@ Ref<SerializedScriptValue> SerializedScriptValue::createDoubleArrayFastPath(Vect
return adoptRef(*new SerializedScriptValue(WTF::move(data), length, FastPath::DoubleArray));
}
Ref<SerializedScriptValue> SerializedScriptValue::createTypedArrayFastPath(Vector<uint8_t>&& data, uint8_t subtag)
{
return adoptRef(*new SerializedScriptValue(WTF::move(data), subtag));
}
RefPtr<SerializedScriptValue> SerializedScriptValue::create(JSContextRef originContext, JSValueRef apiValue, JSValueRef* exception)
{
JSGlobalObject* lexicalGlobalObject = toJS(originContext);
@@ -6751,71 +6674,6 @@ JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject,
*didFail = false;
return resultArray;
}
case FastPath::TypedArray: {
size_t byteLength = m_arrayButterflyData.size();
auto subtag = static_cast<ArrayBufferViewSubtag>(m_typedArraySubtag);
unsigned elemSize = typedArrayElementSize(subtag);
if (!elemSize) [[unlikely]]
break;
auto arrayBuffer = ArrayBuffer::tryCreate(m_arrayButterflyData.span());
if (!arrayBuffer) [[unlikely]] {
if (didFail)
*didFail = true;
return {};
}
std::optional<size_t> length = byteLength / elemSize;
JSValue typedArrayValue;
switch (subtag) {
case Int8ArrayTag:
typedArrayValue = toJS(&lexicalGlobalObject, globalObject, Int8Array::wrappedAs(arrayBuffer.releaseNonNull(), 0, length).get());
break;
case Uint8ArrayTag:
typedArrayValue = toJS(&lexicalGlobalObject, globalObject, Uint8Array::wrappedAs(arrayBuffer.releaseNonNull(), 0, length).get());
break;
case Uint8ClampedArrayTag:
typedArrayValue = toJS(&lexicalGlobalObject, globalObject, Uint8ClampedArray::wrappedAs(arrayBuffer.releaseNonNull(), 0, length).get());
break;
case Int16ArrayTag:
typedArrayValue = toJS(&lexicalGlobalObject, globalObject, Int16Array::wrappedAs(arrayBuffer.releaseNonNull(), 0, length).get());
break;
case Uint16ArrayTag:
typedArrayValue = toJS(&lexicalGlobalObject, globalObject, Uint16Array::wrappedAs(arrayBuffer.releaseNonNull(), 0, length).get());
break;
case Int32ArrayTag:
typedArrayValue = toJS(&lexicalGlobalObject, globalObject, Int32Array::wrappedAs(arrayBuffer.releaseNonNull(), 0, length).get());
break;
case Uint32ArrayTag:
typedArrayValue = toJS(&lexicalGlobalObject, globalObject, Uint32Array::wrappedAs(arrayBuffer.releaseNonNull(), 0, length).get());
break;
case Float16ArrayTag:
typedArrayValue = toJS(&lexicalGlobalObject, globalObject, Float16Array::wrappedAs(arrayBuffer.releaseNonNull(), 0, length).get());
break;
case Float32ArrayTag:
typedArrayValue = toJS(&lexicalGlobalObject, globalObject, Float32Array::wrappedAs(arrayBuffer.releaseNonNull(), 0, length).get());
break;
case Float64ArrayTag:
typedArrayValue = toJS(&lexicalGlobalObject, globalObject, Float64Array::wrappedAs(arrayBuffer.releaseNonNull(), 0, length).get());
break;
case BigInt64ArrayTag:
typedArrayValue = toJS(&lexicalGlobalObject, globalObject, BigInt64Array::wrappedAs(arrayBuffer.releaseNonNull(), 0, length).get());
break;
case BigUint64ArrayTag:
typedArrayValue = toJS(&lexicalGlobalObject, globalObject, BigUint64Array::wrappedAs(arrayBuffer.releaseNonNull(), 0, length).get());
break;
default:
break;
}
if (typedArrayValue) {
if (didFail)
*didFail = false;
return typedArrayValue;
}
break;
}
case FastPath::None: {
break;
}

View File

@@ -87,7 +87,6 @@ enum class FastPath : uint8_t {
Int32Array,
DoubleArray,
DenseArray,
TypedArray,
};
#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS)
@@ -149,9 +148,6 @@ public:
// Fast path for postMessage with dense arrays containing simple objects
static Ref<SerializedScriptValue> createDenseArrayFastPath(WTF::FixedVector<DenseArrayElement>&& elements);
// Fast path for postMessage with TypedArray (Uint8Array, Float64Array, etc.)
static Ref<SerializedScriptValue> createTypedArrayFastPath(Vector<uint8_t>&& data, uint8_t subtag);
static Ref<SerializedScriptValue> nullValue();
WEBCORE_EXPORT JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr);
@@ -259,8 +255,6 @@ private:
SerializedScriptValue(Vector<uint8_t>&& butterflyData, uint32_t length, FastPath fastPath);
// Constructor for DenseArray fast path
explicit SerializedScriptValue(WTF::FixedVector<DenseArrayElement>&& denseElements);
// Constructor for TypedArray fast path
SerializedScriptValue(Vector<uint8_t>&& data, uint8_t subtag);
size_t computeMemoryCost() const;
@@ -300,9 +294,6 @@ private:
// DenseArray fast path: array of primitives/strings/simple objects
FixedVector<DenseArrayElement> m_denseArrayElements {};
// TypedArray fast path: subtag identifying the TypedArray type (ArrayBufferViewSubtag)
uint8_t m_typedArraySubtag { 0 };
};
template<class Encoder>

View File

@@ -1154,6 +1154,14 @@ pub const FetchTasklet = struct {
}
}
/// Whether the request body should skip chunked transfer encoding framing.
/// True for upgraded connections (e.g. WebSocket) or when the user explicitly
/// set Content-Length without setting Transfer-Encoding.
fn skipChunkedFraming(this: *const FetchTasklet) bool {
return this.upgraded_connection or
(this.request_headers.get("content-length") != null and this.request_headers.get("transfer-encoding") == null);
}
pub fn writeRequestData(this: *FetchTasklet, data: []const u8) ResumableSinkBackpressure {
log("writeRequestData {}", .{data.len});
if (this.signal) |signal| {
@@ -1175,7 +1183,7 @@ pub const FetchTasklet = struct {
// dont have backpressure so we will schedule the data to be written
// if we have backpressure the onWritable will drain the buffer
needs_schedule = stream_buffer.isEmpty();
if (this.upgraded_connection) {
if (this.skipChunkedFraming()) {
bun.handleOom(stream_buffer.write(data));
} else {
//16 is the max size of a hex number size that represents 64 bits + 2 for the \r\n
@@ -1209,15 +1217,14 @@ pub const FetchTasklet = struct {
}
this.abortTask();
} else {
if (!this.upgraded_connection) {
// If is not upgraded we need to send the terminating chunk
if (!this.skipChunkedFraming()) {
// Using chunked transfer encoding, send the terminating chunk
const thread_safe_stream_buffer = this.request_body_streaming_buffer orelse return;
const stream_buffer = thread_safe_stream_buffer.acquire();
defer thread_safe_stream_buffer.release();
bun.handleOom(stream_buffer.write(http.end_of_chunked_http1_1_encoding_response_body));
}
if (this.http) |http_| {
// just tell to write the end of the chunked encoding aka 0\r\n\r\n
http.http_thread.scheduleRequestWrite(http_, .end);
}
}

View File

@@ -68,6 +68,16 @@ pub const Chunk = struct {
}
pub fn getCSSChunkForHTML(this: *const Chunk, chunks: []Chunk) ?*Chunk {
// Look up the CSS chunk via the JS chunk's css_chunks indices.
// This correctly handles deduplicated CSS chunks that are shared
// across multiple HTML entry points (see issue #23668).
if (this.getJSChunkForHTML(chunks)) |js_chunk| {
const css_chunk_indices = js_chunk.content.javascript.css_chunks;
if (css_chunk_indices.len > 0) {
return &chunks[css_chunk_indices[0]];
}
}
// Fallback: match by entry_point_id for cases without a JS chunk.
const entry_point_id = this.entry_point.entry_point_id;
for (chunks) |*other| {
if (other.content == .css) {

View File

@@ -3683,7 +3683,20 @@ pub const BundleV2 = struct {
}
}
const import_record_loader = import_record.loader orelse path.loader(&transpiler.options.loaders) orelse .file;
const import_record_loader = brk: {
const resolved_loader = import_record.loader orelse path.loader(&transpiler.options.loaders) orelse .file;
// When an HTML file references a URL asset (e.g. <link rel="manifest" href="./manifest.json" />),
// the file must be copied to the output directory as-is. If the resolved loader would
// parse/transform the file (e.g. .json, .toml) rather than copy it, force the .file loader
// so that `shouldCopyForBundling()` returns true and the asset is emitted.
// Only do this for HTML sources — CSS url() imports should retain their original behavior.
if (loader == .html and import_record.kind == .url and !resolved_loader.shouldCopyForBundling() and
!resolved_loader.isJavaScriptLike() and !resolved_loader.isCSS() and resolved_loader != .html)
{
break :brk Loader.file;
}
break :brk resolved_loader;
};
import_record.loader = import_record_loader;
const is_html_entrypoint = import_record_loader == .html and target.isServerSide() and this.transpiler.options.dev_server == null;

View File

@@ -150,6 +150,7 @@ pub const ClientEntryPoint = struct {
pub const ServerEntryPoint = struct {
source: logger.Source = undefined,
generated: bool = false,
pub fn generate(
entry: *ServerEntryPoint,
@@ -230,6 +231,7 @@ pub const ServerEntryPoint = struct {
entry.source = logger.Source.initPathString(name, code);
entry.source.path.text = name;
entry.source.path.namespace = "server-entry";
entry.generated = true;
}
};

View File

@@ -22,7 +22,6 @@ pub noinline fn computeChunks(
const entry_source_indices = this.graph.entry_points.items(.source_index);
const css_asts = this.graph.ast.items(.css);
const css_chunking = this.options.css_chunking;
var html_chunks = bun.StringArrayHashMap(Chunk).init(temp_allocator);
const loaders = this.parse_graph.input_files.items(.loader);
const ast_targets = this.graph.ast.items(.target);
@@ -148,10 +147,11 @@ pub noinline fn computeChunks(
if (css_source_indices.len > 0) {
const order = this.findImportedFilesInCSSOrder(temp_allocator, css_source_indices.slice());
const use_content_based_key = css_chunking or has_server_html_imports;
const hash_to_use = if (!use_content_based_key)
bun.hash(try temp_allocator.dupe(u8, entry_bits.bytes(this.graph.entry_points.len)))
else brk: {
// Always use content-based hashing for CSS chunk deduplication.
// This ensures that when multiple JS entry points import the
// same CSS files, they share a single CSS output chunk rather
// than producing duplicates that collide on hash-based naming.
const hash_to_use = brk: {
var hasher = std.hash.Wyhash.init(5);
bun.writeAnyToHasher(&hasher, order.len);
for (order.slice()) |x| x.hash(&hasher);
@@ -322,7 +322,10 @@ pub noinline fn computeChunks(
const remapped_css_indexes = try temp_allocator.alloc(u32, css_chunks.count());
const css_chunk_values = css_chunks.values();
for (sorted_css_keys, js_chunks.count()..) |key, sorted_index| {
// Use sorted_chunks.len as the starting index because HTML chunks
// may be interleaved with JS chunks, so js_chunks.count() would be
// incorrect when HTML entry points are present.
for (sorted_css_keys, sorted_chunks.len..) |key, sorted_index| {
const index = css_chunks.getIndex(key) orelse unreachable;
sorted_chunks.appendAssumeCapacity(css_chunk_values[index]);
remapped_css_indexes[index] = @intCast(sorted_index);

View File

@@ -719,7 +719,21 @@ pub fn buildRequest(this: *HTTPClient, body_len: usize) picohttp.Request {
if (body_len > 0 or this.method.hasRequestBody()) {
if (this.flags.is_streaming_request_body) {
if (add_transfer_encoding and this.flags.upgrade_state == .none) {
if (original_content_length) |content_length| {
if (add_transfer_encoding) {
// User explicitly set Content-Length and did not set Transfer-Encoding;
// preserve Content-Length instead of using chunked encoding.
// This matches Node.js behavior where an explicit Content-Length is always honored.
request_headers_buf[header_count] = .{
.name = content_length_header_name,
.value = content_length,
};
header_count += 1;
}
// If !add_transfer_encoding, the user explicitly set Transfer-Encoding,
// which was already added to request_headers_buf. We respect that and
// do not add Content-Length (they are mutually exclusive per HTTP/1.1).
} else if (add_transfer_encoding and this.flags.upgrade_state == .none) {
request_headers_buf[header_count] = chunked_encoded_header;
header_count += 1;
}

View File

@@ -138,6 +138,10 @@ pub fn NewWebSocketClient(comptime ssl: bool) type {
// Set to null FIRST to prevent re-entrancy (shutdown can trigger callbacks)
if (this.proxy_tunnel) |tunnel| {
this.proxy_tunnel = null;
// Detach the websocket from the tunnel before shutdown so the
// tunnel's onClose callback doesn't dispatch a spurious 1006
// after we've already handled a clean close.
tunnel.clearConnectedWebSocket();
tunnel.shutdown();
tunnel.deref();
}
@@ -910,7 +914,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type {
}
fn sendPong(this: *WebSocket, socket: Socket) bool {
if (socket.isClosed() or socket.isShutdown()) {
if (!this.hasTCP()) {
this.dispatchAbruptClose(ErrorCode.ended);
return false;
}
@@ -942,14 +946,17 @@ pub fn NewWebSocketClient(comptime ssl: bool) type {
body_len: usize,
) void {
log("Sending close with code {d}", .{code});
if (socket.isClosed() or socket.isShutdown()) {
if (!this.hasTCP()) {
this.dispatchAbruptClose(ErrorCode.ended);
this.clearData();
return;
}
// we dont wanna shutdownRead when SSL, because SSL handshake can happen when writting
// For tunnel mode, shutdownRead on the detached socket is a no-op; skip it.
if (comptime !ssl) {
socket.shutdownRead();
if (this.proxy_tunnel == null) {
socket.shutdownRead();
}
}
var final_body_bytes: [128 + 8]u8 = undefined;
var header = @as(WebsocketHeader, @bitCast(@as(u16, 0)));

View File

@@ -253,6 +253,13 @@ pub fn setConnectedWebSocket(this: *WebSocketProxyTunnel, ws: *WebSocketClient)
this.#upgrade_client = .{ .none = {} };
}
/// Clear the connected WebSocket reference. Called before tunnel shutdown during
/// a clean close so the tunnel's onClose callback doesn't dispatch a spurious
/// abrupt close (1006) after the WebSocket has already sent a clean close frame.
pub fn clearConnectedWebSocket(this: *WebSocketProxyTunnel) void {
this.#connected_websocket = null;
}
/// SSLWrapper callback: Called with encrypted data to send to network
fn writeEncrypted(this: *WebSocketProxyTunnel, encrypted_data: []const u8) void {
log("writeEncrypted: {} bytes", .{encrypted_data.len});

View File

@@ -623,6 +623,17 @@ pub const PackageInstaller = struct {
// else => unreachable,
// };
// If a newly computed integrity hash is available (e.g. for a GitHub
// tarball) and the lockfile doesn't already have one, persist it so
// the lockfile gets re-saved with the hash.
if (data.integrity.tag.isSupported()) {
var pkg_metas = this.lockfile.packages.items(.meta);
if (!pkg_metas[package_id].integrity.tag.isSupported()) {
pkg_metas[package_id].integrity = data.integrity;
this.manager.options.enable.force_save_lockfile = true;
}
}
if (this.manager.task_queue.fetchRemove(task_id)) |removed| {
var callbacks = removed.value;
defer callbacks.deinit(this.manager.allocator);

View File

@@ -133,6 +133,12 @@ pub fn processExtractedTarballPackage(
break :package pkg;
};
// Store the tarball integrity hash so the lockfile can pin the
// exact content downloaded from the remote (GitHub) server.
if (data.integrity.tag.isSupported()) {
package.meta.integrity = data.integrity;
}
package = manager.lockfile.appendPackage(package) catch unreachable;
package_id.* = package.meta.id;

View File

@@ -23,7 +23,26 @@ pub inline fn run(this: *const ExtractTarball, log: *logger.Log, bytes: []const
return error.IntegrityCheckFailed;
}
}
return this.extract(log, bytes);
var result = try this.extract(log, bytes);
// Compute and store SHA-512 integrity hash for GitHub tarballs so the
// lockfile can pin the exact tarball content. On subsequent installs the
// hash stored in the lockfile is forwarded via this.integrity and verified
// above, preventing a compromised server from silently swapping the tarball.
if (this.resolution.tag == .github) {
if (this.integrity.tag.isSupported()) {
// Re-installing with an existing lockfile: integrity was already
// verified above, propagate the known value to ExtractData so that
// the lockfile keeps it on re-serialisation.
result.integrity = this.integrity;
} else {
// First install (no integrity in the lockfile yet): compute it.
result.integrity = .{ .tag = .sha512 };
Crypto.SHA512.hash(bytes, result.integrity.value[0..Crypto.SHA512.digest]);
}
}
return result;
}
pub fn buildURL(
@@ -547,6 +566,7 @@ const string = []const u8;
const Npm = @import("./npm.zig");
const std = @import("std");
const Crypto = @import("../sha.zig").Hashers;
const FileSystem = @import("../fs.zig").FileSystem;
const Integrity = @import("./integrity.zig").Integrity;
const Resolution = @import("./resolution.zig").Resolution;

View File

@@ -209,6 +209,7 @@ pub const ExtractData = struct {
path: string = "",
buf: []u8 = "",
} = null,
integrity: Integrity = .{},
};
pub const DependencyInstallContext = struct {
@@ -271,6 +272,7 @@ pub const VersionSlice = external.VersionSlice;
pub const Dependency = @import("./dependency.zig");
pub const Behavior = @import("./dependency.zig").Behavior;
pub const Integrity = @import("./integrity.zig").Integrity;
pub const Lockfile = @import("./lockfile.zig");
pub const PatchedDep = Lockfile.PatchedDep;

View File

@@ -644,9 +644,16 @@ pub const Stringifier = struct {
&path_buf,
);
try writer.print(", {f}]", .{
repo.resolved.fmtJson(buf, .{}),
});
if (pkg_meta.integrity.tag.isSupported()) {
try writer.print(", {f}, \"{f}\"]", .{
repo.resolved.fmtJson(buf, .{}),
pkg_meta.integrity,
});
} else {
try writer.print(", {f}]", .{
repo.resolved.fmtJson(buf, .{}),
});
}
},
else => unreachable,
}
@@ -1885,6 +1892,15 @@ pub fn parseIntoBinaryLockfile(
};
@field(res.value, @tagName(tag)).resolved = try string_buf.append(bun_tag_str);
// Optional integrity hash (added to pin tarball content)
if (i < pkg_info.len) {
const integrity_expr = pkg_info.at(i);
if (integrity_expr.asString(allocator)) |integrity_str| {
pkg.meta.integrity = Integrity.parse(integrity_str);
i += 1;
}
}
},
else => {},
}

View File

@@ -51,6 +51,15 @@ function onError(msg, err, callback) {
process.nextTick(emitErrorNt, msg, err, callback);
}
function isHTTPHeaderStateSentOrAssigned(state) {
return state === NodeHTTPHeaderState.sent || state === NodeHTTPHeaderState.assigned;
}
function throwHeadersSentIfNecessary(self, action) {
if (self._header != null || isHTTPHeaderStateSentOrAssigned(self[headerStateSymbol])) {
throw $ERR_HTTP_HEADERS_SENT(action);
}
}
function write_(msg, chunk, encoding, callback, fromEnd) {
if (typeof callback !== "function") callback = nop;
@@ -252,18 +261,14 @@ const OutgoingMessagePrototype = {
removeHeader(name) {
validateString(name, "name");
if ((this._header !== undefined && this._header !== null) || this[headerStateSymbol] === NodeHTTPHeaderState.sent) {
throw $ERR_HTTP_HEADERS_SENT("remove");
}
throwHeadersSentIfNecessary(this, "remove");
const headers = this[headersSymbol];
if (!headers) return;
headers.delete(name);
},
setHeader(name, value) {
if ((this._header !== undefined && this._header !== null) || this[headerStateSymbol] == NodeHTTPHeaderState.sent) {
throw $ERR_HTTP_HEADERS_SENT("set");
}
throwHeadersSentIfNecessary(this, "set");
validateHeaderName(name);
validateHeaderValue(name, value);
const headers = (this[headersSymbol] ??= new Headers());
@@ -271,9 +276,7 @@ const OutgoingMessagePrototype = {
return this;
},
setHeaders(headers) {
if (this._header || this[headerStateSymbol] !== NodeHTTPHeaderState.none) {
throw $ERR_HTTP_HEADERS_SENT("set");
}
throwHeadersSentIfNecessary(this, "set");
if (!headers || $isArray(headers) || typeof headers.keys !== "function" || typeof headers.get !== "function") {
throw $ERR_INVALID_ARG_TYPE("headers", ["Headers", "Map"], headers);

View File

@@ -766,19 +766,13 @@ pub extern fn napi_type_tag_object(env: napi_env, _: napi_value, _: [*c]const na
pub extern fn napi_check_object_type_tag(env: napi_env, _: napi_value, _: [*c]const napi_type_tag, _: *bool) napi_status;
// do nothing for both of these
pub export fn napi_open_callback_scope(env_: napi_env, _: napi_value, _: *anyopaque, _: *anyopaque) napi_status {
pub export fn napi_open_callback_scope(_: napi_env, _: napi_value, _: *anyopaque, _: *anyopaque) napi_status {
log("napi_open_callback_scope", .{});
const env = env_ orelse {
return envIsNull();
};
return env.ok();
return @intFromEnum(NapiStatus.ok);
}
pub export fn napi_close_callback_scope(env_: napi_env, _: *anyopaque) napi_status {
pub export fn napi_close_callback_scope(_: napi_env, _: *anyopaque) napi_status {
log("napi_close_callback_scope", .{});
const env = env_ orelse {
return envIsNull();
};
return env.ok();
return @intFromEnum(NapiStatus.ok);
}
pub extern fn napi_throw(env: napi_env, @"error": napi_value) napi_status;
pub extern fn napi_throw_error(env: napi_env, code: [*c]const u8, msg: [*c]const u8) napi_status;

View File

@@ -12,24 +12,32 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __hasOwnProp = Object.prototype.hasOwnProperty;
// Shared getter/setter functions: .bind(obj, key) avoids creating a closure
// and JSLexicalEnvironment per property. BoundFunction is much cheaper.
// Must be regular functions (not arrows) so .bind() can set `this`.
function __accessProp(key) {
return this[key];
}
// This is used to implement "export * from" statements. It copies properties
// from the imported module to the current module's ESM export object. If the
// current module is an entry point and the target format is CommonJS, we
// also copy the properties to "module.exports" in addition to our module's
// internal ESM export object.
export var __reExport = (target, mod, secondTarget) => {
for (let key of __getOwnPropNames(mod))
var keys = __getOwnPropNames(mod);
for (let key of keys)
if (!__hasOwnProp.call(target, key) && key !== "default")
__defProp(target, key, {
get: () => mod[key],
get: __accessProp.bind(mod, key),
enumerable: true,
});
if (secondTarget) {
for (let key of __getOwnPropNames(mod))
for (let key of keys)
if (!__hasOwnProp.call(secondTarget, key) && key !== "default")
__defProp(secondTarget, key, {
get: () => mod[key],
get: __accessProp.bind(mod, key),
enumerable: true,
});
@@ -37,11 +45,22 @@ export var __reExport = (target, mod, secondTarget) => {
}
};
/*__PURE__*/
var __toESMCache_node;
/*__PURE__*/
var __toESMCache_esm;
// Converts the module from CommonJS to ESM. When in node mode (i.e. in an
// ".mjs" file, package.json has "type: module", or the "__esModule" export
// in the CommonJS file is falsy or missing), the "default" property is
// overridden to point to the original CommonJS exports object instead.
export var __toESM = (mod, isNodeMode, target) => {
var canCache = mod != null && typeof mod === "object";
if (canCache) {
var cache = isNodeMode ? (__toESMCache_node ??= new WeakMap()) : (__toESMCache_esm ??= new WeakMap());
var cached = cache.get(mod);
if (cached) return cached;
}
target = mod != null ? __create(__getProtoOf(mod)) : {};
const to =
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
@@ -53,34 +72,34 @@ export var __toESM = (mod, isNodeMode, target) => {
for (let key of __getOwnPropNames(mod))
if (!__hasOwnProp.call(to, key))
__defProp(to, key, {
get: () => mod[key],
get: __accessProp.bind(mod, key),
enumerable: true,
});
if (canCache) cache.set(mod, to);
return to;
};
// Converts the module from ESM to CommonJS. This clones the input module
// object with the addition of a non-enumerable "__esModule" property set
// to "true", which overwrites any existing export named "__esModule".
var __moduleCache = /* @__PURE__ */ new WeakMap();
export var __toCommonJS = /* @__PURE__ */ from => {
var entry = __moduleCache.get(from),
export var __toCommonJS = from => {
var entry = (__moduleCache ??= new WeakMap()).get(from),
desc;
if (entry) return entry;
entry = __defProp({}, "__esModule", { value: true });
if ((from && typeof from === "object") || typeof from === "function")
__getOwnPropNames(from).map(
key =>
!__hasOwnProp.call(entry, key) &&
for (var key of __getOwnPropNames(from))
if (!__hasOwnProp.call(entry, key))
__defProp(entry, key, {
get: () => from[key],
get: __accessProp.bind(from, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable,
}),
);
});
__moduleCache.set(from, entry);
return entry;
};
/*__PURE__*/
var __moduleCache;
// When you do know the module is CJS
export var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
@@ -97,6 +116,10 @@ export var __name = (target, name) => {
// ESM export -> CJS export
// except, writable incase something re-exports
var __returnValue = v => v;
function __exportSetter(name, newValue) {
this[name] = __returnValue.bind(null, newValue);
}
export var __export = /* @__PURE__ */ (target, all) => {
for (var name in all)
@@ -104,15 +127,19 @@ export var __export = /* @__PURE__ */ (target, all) => {
get: all[name],
enumerable: true,
configurable: true,
set: newValue => (all[name] = () => newValue),
set: __exportSetter.bind(all, name),
});
};
function __exportValueSetter(name, newValue) {
this[name] = newValue;
}
export var __exportValue = (target, all) => {
for (var name in all) {
__defProp(target, name, {
get: () => all[name],
set: newValue => (all[name] = newValue),
get: __accessProp.bind(all, name),
set: __exportValueSetter.bind(all, name),
enumerable: true,
configurable: true,
});

View File

@@ -266,8 +266,8 @@ pub const ShellCpOutputTask = OutputTask(Cp, .{
const ShellCpOutputTaskVTable = struct {
pub fn writeErr(this: *Cp, childptr: anytype, errbuf: []const u8) ?Yield {
this.state.exec.output_waiting += 1;
if (this.bltn().stderr.needsIO()) |safeguard| {
this.state.exec.output_waiting += 1;
return this.bltn().stderr.enqueue(childptr, errbuf, safeguard);
}
_ = this.bltn().writeNoIO(.stderr, errbuf);
@@ -279,8 +279,8 @@ const ShellCpOutputTaskVTable = struct {
}
pub fn writeOut(this: *Cp, childptr: anytype, output: *OutputSrc) ?Yield {
this.state.exec.output_waiting += 1;
if (this.bltn().stdout.needsIO()) |safeguard| {
this.state.exec.output_waiting += 1;
return this.bltn().stdout.enqueue(childptr, output.slice(), safeguard);
}
_ = this.bltn().writeNoIO(.stdout, output.slice());

View File

@@ -175,8 +175,8 @@ pub const ShellLsOutputTask = OutputTask(Ls, .{
const ShellLsOutputTaskVTable = struct {
pub fn writeErr(this: *Ls, childptr: anytype, errbuf: []const u8) ?Yield {
log("ShellLsOutputTaskVTable.writeErr(0x{x}, {s})", .{ @intFromPtr(this), errbuf });
this.state.exec.output_waiting += 1;
if (this.bltn().stderr.needsIO()) |safeguard| {
this.state.exec.output_waiting += 1;
return this.bltn().stderr.enqueue(childptr, errbuf, safeguard);
}
_ = this.bltn().writeNoIO(.stderr, errbuf);
@@ -190,8 +190,8 @@ const ShellLsOutputTaskVTable = struct {
pub fn writeOut(this: *Ls, childptr: anytype, output: *OutputSrc) ?Yield {
log("ShellLsOutputTaskVTable.writeOut(0x{x}, {s})", .{ @intFromPtr(this), output.slice() });
this.state.exec.output_waiting += 1;
if (this.bltn().stdout.needsIO()) |safeguard| {
this.state.exec.output_waiting += 1;
return this.bltn().stdout.enqueue(childptr, output.slice(), safeguard);
}
log("ShellLsOutputTaskVTable.writeOut(0x{x}, {s}) no IO", .{ @intFromPtr(this), output.slice() });

View File

@@ -129,8 +129,8 @@ pub const ShellMkdirOutputTask = OutputTask(Mkdir, .{
const ShellMkdirOutputTaskVTable = struct {
pub fn writeErr(this: *Mkdir, childptr: anytype, errbuf: []const u8) ?Yield {
this.state.exec.output_waiting += 1;
if (this.bltn().stderr.needsIO()) |safeguard| {
this.state.exec.output_waiting += 1;
return this.bltn().stderr.enqueue(childptr, errbuf, safeguard);
}
_ = this.bltn().writeNoIO(.stderr, errbuf);
@@ -142,8 +142,8 @@ const ShellMkdirOutputTaskVTable = struct {
}
pub fn writeOut(this: *Mkdir, childptr: anytype, output: *OutputSrc) ?Yield {
this.state.exec.output_waiting += 1;
if (this.bltn().stdout.needsIO()) |safeguard| {
this.state.exec.output_waiting += 1;
const slice = output.slice();
log("THE SLICE: {d} {s}", .{ slice.len, slice });
return this.bltn().stdout.enqueue(childptr, slice, safeguard);

View File

@@ -46,12 +46,14 @@ pub fn start(this: *@This()) Yield {
const maybe1 = iter.next().?;
const int1 = std.fmt.parseFloat(f32, bun.sliceTo(maybe1, 0)) catch return this.fail("seq: invalid argument\n");
if (!std.math.isFinite(int1)) return this.fail("seq: invalid argument\n");
this._end = int1;
if (this._start > this._end) this.increment = -1;
const maybe2 = iter.next();
if (maybe2 == null) return this.do();
const int2 = std.fmt.parseFloat(f32, bun.sliceTo(maybe2.?, 0)) catch return this.fail("seq: invalid argument\n");
if (!std.math.isFinite(int2)) return this.fail("seq: invalid argument\n");
this._start = int1;
this._end = int2;
if (this._start < this._end) this.increment = 1;
@@ -60,6 +62,7 @@ pub fn start(this: *@This()) Yield {
const maybe3 = iter.next();
if (maybe3 == null) return this.do();
const int3 = std.fmt.parseFloat(f32, bun.sliceTo(maybe3.?, 0)) catch return this.fail("seq: invalid argument\n");
if (!std.math.isFinite(int3)) return this.fail("seq: invalid argument\n");
this._start = int1;
this.increment = int2;
this._end = int3;

View File

@@ -132,8 +132,8 @@ pub const ShellTouchOutputTask = OutputTask(Touch, .{
const ShellTouchOutputTaskVTable = struct {
pub fn writeErr(this: *Touch, childptr: anytype, errbuf: []const u8) ?Yield {
this.state.exec.output_waiting += 1;
if (this.bltn().stderr.needsIO()) |safeguard| {
this.state.exec.output_waiting += 1;
return this.bltn().stderr.enqueue(childptr, errbuf, safeguard);
}
_ = this.bltn().writeNoIO(.stderr, errbuf);
@@ -145,8 +145,8 @@ const ShellTouchOutputTaskVTable = struct {
}
pub fn writeOut(this: *Touch, childptr: anytype, output: *OutputSrc) ?Yield {
this.state.exec.output_waiting += 1;
if (this.bltn().stdout.needsIO()) |safeguard| {
this.state.exec.output_waiting += 1;
const slice = output.slice();
log("THE SLICE: {d} {s}", .{ slice.len, slice });
return this.bltn().stdout.enqueue(childptr, slice, safeguard);

View File

@@ -168,6 +168,8 @@ fn commandImplStart(this: *CondExpr) Yield {
.@"-d",
.@"-f",
=> {
// Empty string expansion produces no args; the path doesn't exist.
if (this.args.items.len == 0) return this.parent.childDone(this, 1);
this.state = .waiting_stat;
return this.doStat();
},

View File

@@ -2,13 +2,17 @@
exports[`Bun.build Bun.write(BuildArtifact) 1`] = `
"var __defProp = Object.defineProperty;
var __returnValue = (v) => v;
function __exportSetter(name, newValue) {
this[name] = __returnValue.bind(null, newValue);
}
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, {
get: all[name],
enumerable: true,
configurable: true,
set: (newValue) => all[name] = () => newValue
set: __exportSetter.bind(all, name)
});
};
@@ -31,13 +35,17 @@ NS.then(({ fn: fn2 }) => {
exports[`Bun.build outdir + reading out blobs works 1`] = `
"var __defProp = Object.defineProperty;
var __returnValue = (v) => v;
function __exportSetter(name, newValue) {
this[name] = __returnValue.bind(null, newValue);
}
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, {
get: all[name],
enumerable: true,
configurable: true,
set: (newValue) => all[name] = () => newValue
set: __exportSetter.bind(all, name)
});
};
@@ -58,23 +66,27 @@ NS.then(({ fn: fn2 }) => {
"
`;
exports[`Bun.build BuildArtifact properties: hash 1`] = `"d1c7nm6t"`;
exports[`Bun.build BuildArtifact properties: hash 1`] = `"est79qzq"`;
exports[`Bun.build BuildArtifact properties + entry.naming: hash 1`] = `"rm7e36cf"`;
exports[`Bun.build BuildArtifact properties + entry.naming: hash 1`] = `"7gfnt0h6"`;
exports[`Bun.build BuildArtifact properties sourcemap: hash index.js 1`] = `"d1c7nm6t"`;
exports[`Bun.build BuildArtifact properties sourcemap: hash index.js 1`] = `"est79qzq"`;
exports[`Bun.build BuildArtifact properties sourcemap: hash index.js.map 1`] = `"00000000"`;
exports[`Bun.build new Response(BuildArtifact) sets content type: response text 1`] = `
"var __defProp = Object.defineProperty;
var __returnValue = (v) => v;
function __exportSetter(name, newValue) {
this[name] = __returnValue.bind(null, newValue);
}
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, {
get: all[name],
enumerable: true,
configurable: true,
set: (newValue) => all[name] = () => newValue
set: __exportSetter.bind(all, name)
});
};

View File

@@ -416,7 +416,8 @@ describe("bundler", () => {
const db = new Database("test.db");
const query = db.query(\`select "Hello world" as message\`);
if (query.get().message !== "Hello world") throw "fail from sqlite";
const icon = await fetch("https://bun.sh/favicon.ico").then(x=>x.arrayBuffer())
const icon = new Uint8Array(256);
for (let i = 0; i < 256; i++) icon[i] = i;
if(icon.byteLength < 100) throw "fail from icon";
if (typeof getRandomSeed() !== 'number') throw "fail from bun:jsc";
const server = serve({

View File

@@ -1113,7 +1113,7 @@ describe("bundler", () => {
snapshotSourceMap: {
"entry.js.map": {
files: ["../node_modules/react/index.js", "../entry.js"],
mappingsExactMatch: "qYACA,WAAW,IAAQ,EAAE,ICDrB,eACA,QAAQ,IAAI,CAAK",
mappingsExactMatch: "miBACA,WAAW,IAAQ,EAAE,ICDrB,eACA,QAAQ,IAAI,CAAK",
},
},
});

View File

@@ -843,4 +843,131 @@ body {
api.expectFile("out/" + jsFile).toContain("sourceMappingURL");
},
});
// Test that multiple HTML entrypoints sharing the same CSS file both get
// the CSS link tag in production mode (css_chunking deduplication).
// Regression test for https://github.com/oven-sh/bun/issues/23668
itBundled("html/SharedCSSProductionMultipleEntries", {
outdir: "out/",
production: true,
files: {
"/entry1.html": `<!doctype html>
<html>
<head>
<link rel="stylesheet" href="./global.css" />
</head>
<body>
<div id="root"></div>
<script src="./main1.tsx"></script>
</body>
</html>`,
"/entry2.html": `<!doctype html>
<html>
<head>
<link rel="stylesheet" href="./global.css" />
</head>
<body>
<div id="root"></div>
<script src="./main2.tsx"></script>
</body>
</html>`,
"/global.css": `h1 { font-size: 24px; }`,
"/main1.tsx": `console.log("entry1");`,
"/main2.tsx": `console.log("entry2");`,
},
entryPoints: ["/entry1.html", "/entry2.html"],
onAfterBundle(api) {
const entry1Html = api.readFile("out/entry1.html");
const entry2Html = api.readFile("out/entry2.html");
// Both HTML files must contain a CSS link tag
const cssMatch1 = entry1Html.match(/href="(.*\.css)"/);
const cssMatch2 = entry2Html.match(/href="(.*\.css)"/);
expect(cssMatch1).not.toBeNull();
expect(cssMatch2).not.toBeNull();
// Both should reference the same deduplicated CSS chunk
expect(cssMatch1![1]).toBe(cssMatch2![1]);
// The CSS file should contain the shared styles
const cssContent = api.readFile("out/" + cssMatch1![1]);
expect(cssContent).toContain("font-size");
// Both HTML files should also have their respective JS bundles
expect(entry1Html).toMatch(/src=".*\.js"/);
expect(entry2Html).toMatch(/src=".*\.js"/);
},
});
// Test manifest.json is copied as an asset and link href is rewritten
itBundled("html/manifest-json", {
outdir: "out/",
files: {
"/index.html": `
<!DOCTYPE html>
<html>
<head>
<link rel="manifest" href="./manifest.json" />
</head>
<body>
<h1>App</h1>
<script src="./app.js"></script>
</body>
</html>`,
"/manifest.json": JSON.stringify({
name: "My App",
short_name: "App",
start_url: "/",
display: "standalone",
background_color: "#ffffff",
theme_color: "#000000",
}),
"/app.js": "console.log('hello')",
},
entryPoints: ["/index.html"],
onAfterBundle(api) {
const htmlContent = api.readFile("out/index.html");
// The original manifest.json reference should be rewritten to a hashed filename
expect(htmlContent).not.toContain('manifest.json"');
expect(htmlContent).toMatch(/href="(?:\.\/|\/)?manifest-[a-zA-Z0-9]+\.json"/);
// Extract the hashed manifest filename and verify its content
const manifestMatch = htmlContent.match(/href="(?:\.\/|\/)?(manifest-[a-zA-Z0-9]+\.json)"/);
expect(manifestMatch).not.toBeNull();
const manifestContent = api.readFile("out/" + manifestMatch![1]);
expect(manifestContent).toContain('"name"');
expect(manifestContent).toContain('"My App"');
},
});
// Test that other non-JS/CSS file types referenced via URL imports are copied as assets
itBundled("html/xml-asset", {
outdir: "out/",
files: {
"/index.html": `
<!DOCTYPE html>
<html>
<head>
<link rel="manifest" href="./site.webmanifest" />
</head>
<body>
<h1>App</h1>
</body>
</html>`,
"/site.webmanifest": JSON.stringify({
name: "My App",
icons: [{ src: "/icon.png", sizes: "192x192" }],
}),
},
entryPoints: ["/index.html"],
onAfterBundle(api) {
const htmlContent = api.readFile("out/index.html");
// The webmanifest reference should be rewritten to a hashed filename
expect(htmlContent).not.toContain("site.webmanifest");
expect(htmlContent).toMatch(/href=".*\.webmanifest"/);
},
});
});

View File

@@ -57,17 +57,17 @@ describe("bundler", () => {
"../entry.tsx",
],
mappings: [
["react.development.js:524:'getContextName'", "1:5412:Y1"],
["react.development.js:524:'getContextName'", "1:5567:Y1"],
["react.development.js:2495:'actScopeDepth'", "23:4082:GJ++"],
["react.development.js:696:''Component'", '1:7474:\'Component "%s"'],
["entry.tsx:6:'\"Content-Type\"'", '100:18809:"Content-Type"'],
["entry.tsx:11:'<html>'", "100:19063:void"],
["entry.tsx:23:'await'", "100:19163:await"],
["react.development.js:696:''Component'", '1:7629:\'Component "%s"'],
["entry.tsx:6:'\"Content-Type\"'", '100:18808:"Content-Type"'],
["entry.tsx:11:'<html>'", "100:19062:void"],
["entry.tsx:23:'await'", "100:19161:await"],
],
},
},
expectExactFilesize: {
"out/entry.js": 221720,
"out/entry.js": 221895,
},
run: {
stdout: "<!DOCTYPE html><html><body><h1>Hello World</h1><p>This is an example.</p></body></html>",

View File

@@ -76,13 +76,17 @@ describe("bundler", () => {
expect(bundled).toMatchInlineSnapshot(`
"var __defProp = Object.defineProperty;
var __returnValue = (v) => v;
function __exportSetter(name, newValue) {
this[name] = __returnValue.bind(null, newValue);
}
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, {
get: all[name],
enumerable: true,
configurable: true,
set: (newValue) => all[name] = () => newValue
set: __exportSetter.bind(all, name)
});
};
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -160,7 +164,7 @@ describe("bundler", () => {
var { AsyncEntryPoint: AsyncEntryPoint2 } = await Promise.resolve().then(() => exports_AsyncEntryPoint);
AsyncEntryPoint2();
//# debugId=5E85CC0956C6307964756E2164756E21
//# debugId=42062903F19477CF64756E2164756E21
//# sourceMappingURL=out.js.map
"
`);
@@ -337,13 +341,17 @@ describe("bundler", () => {
expect(bundled).toMatchInlineSnapshot(`
"var __defProp = Object.defineProperty;
var __returnValue = (v) => v;
function __exportSetter(name, newValue) {
this[name] = __returnValue.bind(null, newValue);
}
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, {
get: all[name],
enumerable: true,
configurable: true,
set: (newValue) => all[name] = () => newValue
set: __exportSetter.bind(all, name)
});
};
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -402,7 +410,7 @@ describe("bundler", () => {
var { AsyncEntryPoint: AsyncEntryPoint2 } = await Promise.resolve().then(() => exports_AsyncEntryPoint);
AsyncEntryPoint2();
//# debugId=C92CBF0103732ECC64756E2164756E21
//# debugId=BF876FBF618133C264756E2164756E21
//# sourceMappingURL=out.js.map
"
`);

View File

@@ -2150,10 +2150,7 @@ c {
toplevel-tilde.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
`, */
});
// TODO: Bun's bundler doesn't support multiple entry points generating CSS outputs
// with identical content hashes to the same output path. This test exposes that
// limitation. Skip until the bundler can deduplicate or handle this case.
itBundled.skip("css/MetafileCSSBundleTwoToOne", {
itBundled("css/MetafileCSSBundleTwoToOne", {
files: {
"/foo/entry.js": /* js */ `
import '../common.css'

View File

@@ -103,11 +103,11 @@ console.log(favicon);
"files": [
{
"input": "client.html",
"path": "./client-s249t5qg.js",
"path": "./client-b5m4ng86.js",
"loader": "js",
"isEntry": true,
"headers": {
"etag": "fxoJ6L-0X3o",
"etag": "Ax71YVYyZQc",
"content-type": "text/javascript;charset=utf-8"
}
},

View File

@@ -0,0 +1,255 @@
import { file } from "bun";
import { describe, expect, test } from "bun:test";
import { rm } from "fs/promises";
import { bunEnv, bunExe, tempDir } from "harness";
import { join } from "path";
// Each test uses its own BUN_INSTALL_CACHE_DIR inside the temp dir for full
// isolation. This avoids interfering with the global cache or other tests.
function envWithCache(dir: string) {
return { ...bunEnv, BUN_INSTALL_CACHE_DIR: join(String(dir), ".bun-cache") };
}
describe.concurrent("GitHub tarball integrity", () => {
test("should store integrity hash in lockfile for GitHub dependencies", async () => {
using dir = tempDir("github-integrity", {
"package.json": JSON.stringify({
name: "test-github-integrity",
dependencies: {
"is-number": "jonschlinkert/is-number#98e8ff1",
},
}),
});
const env = envWithCache(dir);
await using proc = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: String(dir),
env,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toContain("Saved lockfile");
expect(exitCode).toBe(0);
const lockfileContent = await file(join(String(dir), "bun.lock")).text();
// The lockfile should contain a sha512 integrity hash for the GitHub dependency
expect(lockfileContent).toContain("sha512-");
// The resolved commit hash should be present
expect(lockfileContent).toContain("jonschlinkert-is-number-98e8ff1");
// Verify the format: the integrity appears after the resolved commit hash
expect(lockfileContent).toMatch(/"jonschlinkert-is-number-98e8ff1",\s*"sha512-/);
});
test("should verify integrity passes on re-install with matching hash", async () => {
using dir = tempDir("github-integrity-match", {
"package.json": JSON.stringify({
name: "test-github-integrity-match",
dependencies: {
"is-number": "jonschlinkert/is-number#98e8ff1",
},
}),
});
const env = envWithCache(dir);
// First install to generate lockfile with correct integrity
await using proc1 = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: String(dir),
env,
stdout: "pipe",
stderr: "pipe",
});
const [stdout1, stderr1, exitCode1] = await Promise.all([proc1.stdout.text(), proc1.stderr.text(), proc1.exited]);
expect(stderr1).not.toContain("error:");
expect(exitCode1).toBe(0);
// Read the generated lockfile and extract the integrity hash adjacent to
// the GitHub resolved entry to avoid accidentally matching an npm hash.
const lockfileContent = await file(join(String(dir), "bun.lock")).text();
const integrityMatch = lockfileContent.match(/"jonschlinkert-is-number-98e8ff1",\s*"(sha512-[A-Za-z0-9+/]+=*)"/);
expect(integrityMatch).not.toBeNull();
const integrityHash = integrityMatch![1];
// Clear cache and node_modules, then re-install with the same lockfile
await rm(join(String(dir), ".bun-cache"), { recursive: true, force: true });
await rm(join(String(dir), "node_modules"), { recursive: true, force: true });
await using proc2 = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: String(dir),
env,
stdout: "pipe",
stderr: "pipe",
});
const [stdout2, stderr2, exitCode2] = await Promise.all([proc2.stdout.text(), proc2.stderr.text(), proc2.exited]);
// Should succeed because the integrity matches
expect(stderr2).not.toContain("Integrity check failed");
expect(exitCode2).toBe(0);
// Lockfile should still contain the same integrity hash
const lockfileContent2 = await file(join(String(dir), "bun.lock")).text();
expect(lockfileContent2).toContain(integrityHash);
});
test("should reject GitHub tarball when integrity check fails", async () => {
using dir = tempDir("github-integrity-reject", {
"package.json": JSON.stringify({
name: "test-github-integrity-reject",
dependencies: {
"is-number": "jonschlinkert/is-number#98e8ff1",
},
}),
// Pre-create a lockfile with an invalid integrity hash (valid base64, 64 zero bytes)
"bun.lock": JSON.stringify({
lockfileVersion: 1,
configVersion: 1,
workspaces: {
"": {
name: "test-github-integrity-reject",
dependencies: {
"is-number": "jonschlinkert/is-number#98e8ff1",
},
},
},
packages: {
"is-number": [
"is-number@github:jonschlinkert/is-number#98e8ff1",
{},
"jonschlinkert-is-number-98e8ff1",
"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
],
},
}),
});
// Fresh per-test cache ensures the tarball must be downloaded from the network
const env = envWithCache(dir);
await using proc = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: String(dir),
env,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toContain("Integrity check failed");
expect(exitCode).not.toBe(0);
});
test("should update lockfile with integrity when old format has none", async () => {
using dir = tempDir("github-integrity-upgrade", {
"package.json": JSON.stringify({
name: "test-github-integrity-upgrade",
dependencies: {
"is-number": "jonschlinkert/is-number#98e8ff1",
},
}),
// Pre-create a lockfile in the old format (no integrity hash)
"bun.lock": JSON.stringify({
lockfileVersion: 1,
configVersion: 1,
workspaces: {
"": {
name: "test-github-integrity-upgrade",
dependencies: {
"is-number": "jonschlinkert/is-number#98e8ff1",
},
},
},
packages: {
"is-number": ["is-number@github:jonschlinkert/is-number#98e8ff1", {}, "jonschlinkert-is-number-98e8ff1"],
},
}),
});
// Fresh per-test cache ensures the tarball must be downloaded
const env = envWithCache(dir);
await using proc = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: String(dir),
env,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Should succeed without errors
expect(stderr).not.toContain("Integrity check failed");
expect(stderr).not.toContain("error:");
// The lockfile should be re-saved with the new integrity hash
expect(stderr).toContain("Saved lockfile");
expect(exitCode).toBe(0);
// Verify the lockfile now contains the integrity hash
const lockfileContent = await file(join(String(dir), "bun.lock")).text();
expect(lockfileContent).toContain("sha512-");
expect(lockfileContent).toMatch(/"jonschlinkert-is-number-98e8ff1",\s*"sha512-/);
});
test("should accept GitHub dependency from cache without re-downloading", async () => {
// Use a shared cache dir for both installs so the second is a true cache hit
using dir = tempDir("github-integrity-cached", {
"package.json": JSON.stringify({
name: "test-github-integrity-cached",
dependencies: {
"is-number": "jonschlinkert/is-number#98e8ff1",
},
}),
});
const env = envWithCache(dir);
// First install warms the per-test cache
await using proc1 = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: String(dir),
env,
stdout: "pipe",
stderr: "pipe",
});
const [stdout1, stderr1, exitCode1] = await Promise.all([proc1.stdout.text(), proc1.stderr.text(), proc1.exited]);
expect(stderr1).not.toContain("error:");
expect(exitCode1).toBe(0);
// Remove node_modules but keep the cache
await rm(join(String(dir), "node_modules"), { recursive: true, force: true });
// Strip the integrity from the lockfile to simulate an old-format lockfile
// that should still work when the cache already has the package
const lockfileContent = await file(join(String(dir), "bun.lock")).text();
const stripped = lockfileContent.replace(/,\s*"sha512-[^"]*"/, "");
await Bun.write(join(String(dir), "bun.lock"), stripped);
// Second install should hit the cache and succeed without re-downloading
await using proc2 = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: String(dir),
env,
stdout: "pipe",
stderr: "pipe",
});
const [stdout2, stderr2, exitCode2] = await Promise.all([proc2.stdout.text(), proc2.stderr.text(), proc2.exited]);
// Should succeed without integrity errors (package served from cache)
expect(stderr2).not.toContain("Integrity check failed");
expect(stderr2).not.toContain("error:");
expect(exitCode2).toBe(0);
});
});

View File

@@ -634,3 +634,42 @@ test.concurrent("bun serve files with correct Content-Type headers", async () =>
// The process will be automatically cleaned up by 'await using'
}
});
test("importing bun:main from HTML entry preload does not crash", async () => {
const dir = tempDirWithFiles("html-entry-bun-main", {
"index.html": /*html*/ `
<!DOCTYPE html>
<html>
<head><title>Test</title></head>
<body><h1>Hello</h1></body>
</html>
`,
"preload.mjs": /*js*/ `
try {
await import("bun:main");
} catch {}
// Signal that preload ran successfully without crashing
console.log("PRELOAD_OK");
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "--preload", "./preload.mjs", "index.html", "--port=0"],
env: bunEnv,
cwd: dir,
stdout: "pipe",
stderr: "pipe",
});
const decoder = new TextDecoder();
let text = "";
for await (const chunk of proc.stdout) {
text += decoder.decode(chunk, { stream: true });
if (text.includes("http://")) break;
}
expect(text).toContain("PRELOAD_OK");
proc.kill();
await proc.exited;
});

View File

@@ -1,28 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCIzOJskt6VkEJY
XKSJv/Gdil3XYkjk3NVc/+m+kzqnkTRbPtT9w+IGWgmJhuf9DJPLCwHFAEFarVwV
x16Q0PbU4ajXaLRHEYGhrH10oTMjQnJ24xVm26mxRXPQa5vaLpWJqNyIdNLIQLe+
UXUOzSGGsFTRMAjvYrkzjBe4ZUnaZV+aFY/ug0jfzeA1dJjzKZs6+yTJRbsuWUEb
8MsDmT4v+kBZDKdaDn7AFDWRVqx/38BnqsRzkM0CxpnyT2kRzw5zQajIE13gdTJo
1EHvYSUkkxrY5m30Rl9BuBBZBjhMzOHq0fYVVooHO+sf4XHPgvFTTxJum85u7J1J
oEUjrLKtAgMBAAECggEACInVNhaiqu4infZGVMy0rXMV8VwSlapM7O2SLtFsr0nK
XUmaLK6dvGzBPKK9dxdiYCFzPlMKQTkhzsAvYFWSmm3tRmikG+11TFyCRhXLpc8/
ark4vD9Io6ZkmKUmyKLwtXNjNGcqQtJ7RXc7Ga3nAkueN6JKZHqieZusXVeBGQ70
YH1LKyVNBeJggbj+g9rqaksPyNJQ8EWiNTJkTRQPazZ0o1VX/fzDFyr/a5npFtHl
4BHfafv9o1Xyr70Kie8CYYRJNViOCN+ylFs7Gd3XRaAkSkgMT/7DzrHdEM2zrrHK
yNg2gyDVX9UeEJG2X5UtU0o9BVW7WBshz/2hqIUHoQKBgQC8zsRFvC7u/rGr5vRR
mhZZG+Wvg03/xBSuIgOrzm+Qie6mAzOdVmfSL/pNV9EFitXt1yd2ROo31AbS7Evy
Bm/QVKr2mBlmLgov3B7O/e6ABteooOL7769qV/v+yo8VdEg0biHmsfGIIXDe3Lwl
OT0XwF9r/SeZLbw1zfkSsUVG/QKBgQC5fANM3Dc9LEek+6PHv5+eC1cKkyioEjUl
/y1VUD00aABI1TUcdLF3BtFN2t/S6HW0hrP3KwbcUfqC25k+GDLh1nM6ZK/gI3Yn
IGtCHxtE3S6jKhE9QcK/H+PzGVKWge9SezeYRP0GHJYDrTVTA8Kt9HgoZPPeReJl
+Ss9c8ThcQKBgECX6HQHFnNzNSufXtSQB7dCoQizvjqTRZPxVRoxDOABIGExVTYt
umUhPtu5AGyJ+/hblEeU+iBRbGg6qRzK8PPwE3E7xey8MYYAI5YjL7YjISKysBUL
AhM6uJ6Jg/wOBSnSx8xZ8kzlS+0izUda1rjKeprCSArSp8IsjlrDxPStAoGAEcPr
+P+altRX5Fhpvmb/Hb8OTif8G+TqjEIdkG9H/W38oP0ywg/3M2RGxcMx7txu8aR5
NjI7zPxZFxF7YvQkY3cLwEsGgVxEI8k6HLIoBXd90Qjlb82NnoqqZY1GWL4HMwo0
L/Rjm6M/Rwje852Hluu0WoIYzXA6F/Q+jPs6nzECgYAxx4IbDiGXuenkwSF1SUyj
NwJXhx4HDh7U6EO/FiPZE5BHE3BoTrFu3o1lzverNk7G3m+j+m1IguEAalHlukYl
rip9iUISlKYqbYZdLBoLwHAfHhszdrjqn8/v6oqbB5yR3HXjPFUWJo0WJ2pqJp56
ZshgmQQ/5Khoj6x0/dMPSg==
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCt7iqkEIco372h
v19q0zjaYbm6gzxEnR45UjpQYqgztq4QHicD80mqIkCBCYknFxhwxhNn+Y3g5RWQ
dReplpQbkneqRVp+qixMvu2FmOA4zRRoqObP7FyF1Yusvmroe0Y9SP2xTTmA9Zo7
3paywPUIuZ9eKGwIiFTtj1yQ1FdghLhzZgxcf3LHEHRkGnxgxxNITFxh4nd6fGIj
NqM5fQAY8z35lMXdeWjrhtaqgFYB+Z20YY0X7LJx39vYao0wqW8sZjX88TqHI1zX
WLpUk6UK9RqaNza5xc80wV+9/zjhr3dc1FRjBxI1DS/ufo33dUfvilxv9/LtWwUn
KfKLns9LAgMBAAECggEAAacPHM2G7GBIm/9rCr6tvihNgD8M685zOOZAqGYn9CqY
cYHC4gtF/L2U6CBj2pNAoCwo3LXUkD+6r7MYKXAgqQg3HTCM4rwFbhD1rU8FVHfh
OL0QwwZ2ut95DVdjoxTAlEN9ZcdSFc//llMJ1cF8lxoVvKFc4cv3uCI2mcaJk858
iABfJLl3yfdv1xtpAuOfXf66sXbAmn5NQfN0qTEg2iOdgb4BUee5Wb35MakDQb6+
/s7/bWB+ublZzYt12ChIh1jkBBHaGyQ8mFnPj99ZAJdFjAzi6ydoJ0a2rCVY7Ugs
bkhnzDUtAaHKxo9JXaqIwbUaVFkX8dDhbg82dJrWUQKBgQDb7hNR0bJFW845N19M
74p2PM+0dIiVzwxAg4E2dXDVe39awO/tw8Vu1o1+NPFhWAzGcidP7pAHmPEgRTVO
7LA2P3CDXpkAEx5E0QW6QWZGqHfSa3+P1AvetvAV+OxtlDphcNeLApY16TUVOKZg
SZlxW2e0dZylbHewgLBTIV9wUQKBgQDKdML+JD18WfenPeowsw8HzKdaw01iGiV1
fvTjEXu6YxPPynWFMuj5gjBQodXM2vv0EsQBAPKYfe0nzRFL2kNuYs7TLoaNxqkp
DNfJ2Ww5OSg7Mp76XgppeKKlsXLyUMYHHrDh6MRi5jvWtiHRpaNmV3cHMRs22c+B
cqKP5Zma2wKBgCPNnS2Lsrbh3C+qWQRgVq0q9zFMa1PgEgGKpwVjlwvaAACZOjX9
0e1aVkx+d/E98U55FPdJQf9Koa58NdJ0a7dZGor4YnYFpr7TPFh2/xxvnpoN0AVt
IsWOCIW7MVohcGOeiChkMmnyXibnQwaX1LgEhlx1bRvtDYsZWBsgarYRAoGAARvo
oYnDSHYZtDHToZapg2pslEOzndD02ZLrdn73BYtbZWz/fc5MlmlPKHHqgOfGL40W
w8akjY9LCEfIS3kTm3wxE9kSZZ5r+MyYNgPZ4upcPQ7G7iortm4xveSd85PbsdhK
McKbqMsIEuIGh2Z34ayi+0galQ9WYqglGdKxJ7cCgYEAuSPBHa+en0xaraZNRvMk
OfV9Su/wrpR3TXSeo0E1mZHLwq1JwulpfO1SjxTH5uOJtG0tusl122wfm0KjrXUO
vG5/It+X4u1Nv9oWj+z1+EV4fQrQ/Coqcc1r+5w1yzfURkKlHh74jbK5Yy/KfXrE
eqbbJD40tKhY8ho15D3iCSo=
-----END PRIVATE KEY-----

View File

@@ -1,23 +1,24 @@
-----BEGIN CERTIFICATE-----
MIID5jCCAs6gAwIBAgIUN7coIsdMcLo9amZfkwogu0YkeLEwDQYJKoZIhvcNAQEL
MIIEDDCCAvSgAwIBAgIUbddWE2woW5e96uC4S2fd2M0AsFAwDQYJKoZIhvcNAQEL
BQAwfjELMAkGA1UEBhMCU0UxDjAMBgNVBAgMBVN0YXRlMREwDwYDVQQHDAhMb2Nh
dGlvbjEaMBgGA1UECgwRT3JnYW5pemF0aW9uIE5hbWUxHDAaBgNVBAsME09yZ2Fu
aXphdGlvbmFsIFVuaXQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMzA5MjExNDE2
MjNaFw0yNDA5MjAxNDE2MjNaMH4xCzAJBgNVBAYTAlNFMQ4wDAYDVQQIDAVTdGF0
aXphdGlvbmFsIFVuaXQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yNjAyMTMyMzEx
MjlaFw0zNjAyMTEyMzExMjlaMH4xCzAJBgNVBAYTAlNFMQ4wDAYDVQQIDAVTdGF0
ZTERMA8GA1UEBwwITG9jYXRpb24xGjAYBgNVBAoMEU9yZ2FuaXphdGlvbiBOYW1l
MRwwGgYDVQQLDBNPcmdhbml6YXRpb25hbCBVbml0MRIwEAYDVQQDDAlsb2NhbGhv
c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCIzOJskt6VkEJYXKSJ
v/Gdil3XYkjk3NVc/+m+kzqnkTRbPtT9w+IGWgmJhuf9DJPLCwHFAEFarVwVx16Q
0PbU4ajXaLRHEYGhrH10oTMjQnJ24xVm26mxRXPQa5vaLpWJqNyIdNLIQLe+UXUO
zSGGsFTRMAjvYrkzjBe4ZUnaZV+aFY/ug0jfzeA1dJjzKZs6+yTJRbsuWUEb8MsD
mT4v+kBZDKdaDn7AFDWRVqx/38BnqsRzkM0CxpnyT2kRzw5zQajIE13gdTJo1EHv
YSUkkxrY5m30Rl9BuBBZBjhMzOHq0fYVVooHO+sf4XHPgvFTTxJum85u7J1JoEUj
rLKtAgMBAAGjXDBaMA4GA1UdDwEB/wQEAwIDiDATBgNVHSUEDDAKBggrBgEFBQcD
ATAUBgNVHREEDTALgglsb2NhbGhvc3QwHQYDVR0OBBYEFNzx4Rfs9m8XR5ML0WsI
sorKmB4PMA0GCSqGSIb3DQEBCwUAA4IBAQB87iQy8R0fiOky9WTcyzVeMaavS3MX
iTe1BRn1OCyDq+UiwwoNz7zdzZJFEmRtFBwPNFOe4HzLu6E+7yLFR552eYRHlqIi
/fiLb5JiZfPtokUHeqwELWBsoXtU8vKxViPiLZ09jkWOPZWo7b/xXd6QYykBfV91
usUXLzyTD2orMagpqNksLDGS3p3ggHEJBZtRZA8R7kPEw98xZHznOQpr26iv8kYz
ZWdLFoFdwgFBSfxePKax5rfo+FbwdrcTX0MhbORyiu2XsBAghf8s2vKDkHg2UQE8
haonxFYMFaASfaZ/5vWKYDTCJkJ67m/BtkpRafFEO+ad1i1S61OjfxH4
c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt7iqkEIco372hv19q
0zjaYbm6gzxEnR45UjpQYqgztq4QHicD80mqIkCBCYknFxhwxhNn+Y3g5RWQdRep
lpQbkneqRVp+qixMvu2FmOA4zRRoqObP7FyF1Yusvmroe0Y9SP2xTTmA9Zo73pay
wPUIuZ9eKGwIiFTtj1yQ1FdghLhzZgxcf3LHEHRkGnxgxxNITFxh4nd6fGIjNqM5
fQAY8z35lMXdeWjrhtaqgFYB+Z20YY0X7LJx39vYao0wqW8sZjX88TqHI1zXWLpU
k6UK9RqaNza5xc80wV+9/zjhr3dc1FRjBxI1DS/ufo33dUfvilxv9/LtWwUnKfKL
ns9LAgMBAAGjgYEwfzAdBgNVHQ4EFgQUQCpSY7ODhdyD6pdZHvfHoWRXWsIwHwYD
VR0jBBgwFoAUQCpSY7ODhdyD6pdZHvfHoWRXWsIwDwYDVR0TAQH/BAUwAwEB/zAs
BgNVHREEJTAjgglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJ
KoZIhvcNAQELBQADggEBAGKTIzGQsOqfD0+x15F2cu7FKjIo1ua0OiILAhPqGX65
kGcetjC/dJip2bGnw1NjG9WxEJNZ4YcsGrwh9egfnXXmfHNL0wzx/LTo2oysbXsN
nEj+cmzw3Lwjn/ywJc+AC221/xrmDfm3m/hMzLqncnj23ZAHqkXTSp5UtSMs+UDQ
my0AJOvsDGPVKHQsAX3JDjKHaoVJn4YqpHcIGmpjrNcQSvwUocDHPcC0ywco6SgF
Ylzy2bwWWdPd9Cz9JkAMb95nWc7Rwf/nxAqCjJFzKEisvrx7VZ+QSVI0nqJzt8V1
pbtWYH5gMFVstU3ghWdSLbAk4XufGYrIWAlA5mqjQ4o=
-----END CERTIFICATE-----

View File

@@ -221,8 +221,14 @@ describe.concurrent(() => {
["''", "''"],
['""', '""'],
])("test proxy env, http_proxy=%s https_proxy=%s", async (http_proxy, https_proxy) => {
using localServer = Bun.serve({
port: 0,
fetch() {
return new Response("OK");
},
});
const { exited, stderr: stream } = Bun.spawn({
cmd: [bunExe(), "-e", 'await fetch("https://example.com")'],
cmd: [bunExe(), "-e", `await fetch("${localServer.url}")`],
env: {
...bunEnv,
http_proxy: http_proxy,

View File

@@ -0,0 +1,48 @@
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
// Regression test for use-after-poison in builtin OutputTask callbacks
// inside command substitution $().
//
// The bug: output_waiting was only incremented for async writes but
// output_done was always incremented, so when stdout is sync (.pipe
// in cmdsub) the counter check `output_done >= output_waiting` fires
// prematurely, calling done() and freeing the builtin while IOWriter
// callbacks are still pending.
//
// Repro requires many ls tasks with errors — listing many entries
// alongside non-existent paths reliably triggers the ASAN
// use-after-poison.
describe("builtins in command substitution with errors should not crash", () => {
test("ls with errors in command substitution", async () => {
// Create a temp directory with many files to produce output,
// and include non-existent paths to produce errors.
const files: Record<string, string> = {};
for (let i = 0; i < 50; i++) {
files[`file${i}.txt`] = `content${i}`;
}
using dir = tempDir("shell-cmdsub", files);
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
import { $ } from "bun";
$.throws(false);
await $\`echo $(ls $TEST_DIR/* /nonexistent_path_1 /nonexistent_path_2)\`;
console.log("done");
`,
],
env: { ...bunEnv, TEST_DIR: String(dir) },
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("done");
expect(exitCode).toBe(0);
});
});

View File

@@ -0,0 +1,85 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
test("seq inf does not hang", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`import { $ } from "bun"; $.throws(false); const r = await $\`seq inf\`; process.exit(r.exitCode)`,
],
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toContain("invalid argument");
expect(exitCode).toBe(1);
}, 10_000);
test("seq nan does not hang", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`import { $ } from "bun"; $.throws(false); const r = await $\`seq nan\`; process.exit(r.exitCode)`,
],
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toContain("invalid argument");
expect(exitCode).toBe(1);
}, 10_000);
test("seq -inf does not hang", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`import { $ } from "bun"; $.throws(false); const r = await $\`seq -- -inf\`; process.exit(r.exitCode)`,
],
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toContain("invalid argument");
expect(exitCode).toBe(1);
}, 10_000);
test('[[ -d "" ]] does not crash', async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`import { $ } from "bun"; $.throws(false); const r = await $\`[[ -d "" ]]\`; process.exit(r.exitCode)`,
],
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(exitCode).toBe(1);
}, 10_000);
test('[[ -f "" ]] does not crash', async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`import { $ } from "bun"; $.throws(false); const r = await $\`[[ -f "" ]]\`; process.exit(r.exitCode)`,
],
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(exitCode).toBe(1);
}, 10_000);

View File

@@ -1,7 +1,7 @@
import { spawnSync } from "bun";
import { constants, Database, SQLiteError } from "bun:sqlite";
import { describe, expect, it } from "bun:test";
import { existsSync, readdirSync, realpathSync, writeFileSync } from "fs";
import { readdirSync, realpathSync } from "fs";
import { bunEnv, bunExe, isMacOS, isMacOSVersionAtLeast, isWindows, tempDirWithFiles } from "harness";
import { tmpdir } from "os";
import path from "path";
@@ -846,13 +846,15 @@ it("db.transaction()", () => {
// this bug was fixed by ensuring FinalObject has no more than 64 properties
it("inlineCapacity #987", async () => {
const path = tmpbase + "bun-987.db";
if (!existsSync(path)) {
const arrayBuffer = await (await fetch("https://github.com/oven-sh/bun/files/9265429/logs.log")).arrayBuffer();
writeFileSync(path, arrayBuffer);
}
const db = new Database(path);
const db = new Database(":memory:");
// Create schema matching the original regression test (media + logs tables)
db.exec(`
CREATE TABLE media (id INTEGER PRIMARY KEY, mid TEXT, name TEXT, url TEXT, duration INTEGER);
CREATE TABLE logs (mid INTEGER, duration INTEGER, start INTEGER, did TEXT, vid TEXT);
INSERT INTO media VALUES (1, 'm1', 'Test Media', 'http://test', 120);
INSERT INTO logs VALUES (1, 60, 1654100000, 'd1', 'v1');
INSERT INTO logs VALUES (1, 45, 1654200000, 'd2', 'v2');
`);
const query = `SELECT
media.mid,

View File

@@ -1,7 +1,20 @@
import { tls } from "harness";
import https from "node:https";
using server = Bun.serve({
port: 0,
tls,
fetch() {
return new Response("OK");
},
});
const { promise, resolve, reject } = Promise.withResolvers();
const client = https.request("https://example.com/", { agent: false });
const client = https.request(`https://localhost:${server.port}/`, {
agent: false,
ca: tls.cert,
rejectUnauthorized: true,
});
client.on("error", reject);
client.on("close", resolve);
client.end();

View File

@@ -212,10 +212,16 @@ describe("async context passes through", () => {
expect(s.getStore()).toBe(undefined);
});
test("fetch", async () => {
using server = Bun.serve({
port: 0,
fetch() {
return new Response("OK");
},
});
const s = new AsyncLocalStorage<string>();
await s.run("value", async () => {
expect(s.getStore()).toBe("value");
const response = await fetch("https://bun.sh") //
const response = await fetch(server.url) //
.then(r => {
expect(s.getStore()).toBe("value");
return true;

View File

@@ -5,7 +5,7 @@
*
* A handful of older tests do not run in Node in this file. These tests should be updated to run in Node, or deleted.
*/
import { bunEnv, bunExe, exampleSite, randomPort } from "harness";
import { bunEnv, bunExe, exampleSite, randomPort, tls as tlsCert } from "harness";
import { createTest } from "node-harness";
import { EventEmitter, once } from "node:events";
import nodefs, { unlinkSync } from "node:fs";
@@ -1081,9 +1081,19 @@ describe("node:http", () => {
});
test("should not decompress gzip, issue#4397", async () => {
using server = Bun.serve({
port: 0,
tls: tlsCert,
fetch() {
const body = Bun.gzipSync(Buffer.from("<html>Hello</html>"));
return new Response(body, {
headers: { "content-encoding": "gzip" },
});
},
});
const { promise, resolve } = Promise.withResolvers();
https
.request("https://bun.sh/", { headers: { "accept-encoding": "gzip" } }, res => {
.request(server.url, { ca: tlsCert.cert, headers: { "accept-encoding": "gzip" } }, res => {
res.on("data", function cb(chunk) {
resolve(chunk);
res.off("data", cb);
@@ -1632,6 +1642,75 @@ describe("HTTP Server Security Tests - Advanced", () => {
});
});
describe("Response Splitting Protection", () => {
test("rejects CRLF in statusMessage set via property assignment followed by res.end()", async () => {
const { promise: errorPromise, resolve: resolveError } = Promise.withResolvers<Error>();
server.on("request", (req, res) => {
res.statusCode = 200;
res.statusMessage = "OK\r\nSet-Cookie: admin=true";
try {
res.end("body");
} catch (e: any) {
resolveError(e);
res.statusMessage = "OK";
res.end("safe");
}
});
const response = (await sendRequest("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")) as string;
const err = await errorPromise;
expect((err as any).code).toBe("ERR_INVALID_CHAR");
// The injected Set-Cookie header must NOT appear in the response
expect(response).not.toInclude("Set-Cookie: admin=true");
});
test("rejects CRLF in statusMessage set via property assignment followed by res.write()", async () => {
const { promise: errorPromise, resolve: resolveError } = Promise.withResolvers<Error>();
server.on("request", (req, res) => {
res.statusCode = 200;
res.statusMessage = "OK\r\nX-Injected: evil";
try {
res.write("chunk");
} catch (e: any) {
resolveError(e);
res.statusMessage = "OK";
res.end("safe");
}
});
const response = (await sendRequest("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")) as string;
const err = await errorPromise;
expect((err as any).code).toBe("ERR_INVALID_CHAR");
expect(response).not.toInclude("X-Injected: evil");
});
test("rejects CRLF in statusMessage passed to writeHead()", async () => {
server.on("request", (req, res) => {
expect(() => {
res.writeHead(200, "OK\r\nX-Injected: evil");
}).toThrow(/Invalid character in statusMessage/);
res.writeHead(200, "OK");
res.end("safe");
});
const response = (await sendRequest("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")) as string;
expect(response).not.toInclude("X-Injected");
expect(response).toInclude("safe");
});
test("allows valid statusMessage without control characters", async () => {
server.on("request", (req, res) => {
res.statusCode = 200;
res.statusMessage = "Everything Is Fine";
res.end("ok");
});
const response = (await sendRequest("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")) as string;
expect(response).toInclude("200 Everything Is Fine");
expect(response).toInclude("ok");
});
});
test("Server should not crash in clientError is emitted when calling destroy", async () => {
await using server = http.createServer(async (req, res) => {
res.end("Hello World");

View File

@@ -821,60 +821,74 @@ for (const nodeExecutable of [nodeExe(), bunExe()]) {
expect(typeof settings.maxHeaderListSize).toBe("number");
expect(typeof settings.maxHeaderSize).toBe("number");
};
const { promise, resolve, reject } = Promise.withResolvers();
const client = http2.connect("https://www.example.com");
client.on("error", reject);
expect(client.connecting).toBeTrue();
expect(client.alpnProtocol).toBeUndefined();
expect(client.encrypted).toBeTrue();
expect(client.closed).toBeFalse();
expect(client.destroyed).toBeFalse();
expect(client.originSet.length).toBe(1);
expect(client.pendingSettingsAck).toBeTrue();
assertSettings(client.localSettings);
expect(client.remoteSettings).toBeNull();
const headers = { ":path": "/" };
const req = client.request(headers);
expect(req.closed).toBeFalse();
expect(req.destroyed).toBeFalse();
// we always asign a stream id to the request
expect(req.pending).toBeFalse();
expect(typeof req.id).toBe("number");
expect(req.session).toBeDefined();
expect(req.sentHeaders).toEqual({
":authority": "www.example.com",
":method": "GET",
":path": "/",
":scheme": "https",
const h2Server = http2.createSecureServer({ ...TLS_CERT, allowHTTP1: false });
h2Server.on("stream", (stream, headers) => {
stream.respond({ ":status": 200 });
stream.end("OK");
});
expect(req.sentTrailers).toBeUndefined();
expect(req.sentInfoHeaders.length).toBe(0);
expect(req.scheme).toBe("https");
let response_headers = null;
req.on("response", (headers, flags) => {
response_headers = headers;
});
req.resume();
req.on("end", () => {
resolve();
});
await promise;
expect(response_headers[":status"]).toBe(200);
const settings = client.remoteSettings;
const localSettings = client.localSettings;
assertSettings(settings);
assertSettings(localSettings);
expect(settings).toEqual(client.remoteSettings);
expect(localSettings).toEqual(client.localSettings);
client.destroy();
expect(client.connecting).toBeFalse();
expect(client.alpnProtocol).toBe("h2");
expect(client.pendingSettingsAck).toBeFalse();
expect(client.destroyed).toBeTrue();
expect(client.closed).toBeTrue();
expect(req.closed).toBeTrue();
expect(req.destroyed).toBeTrue();
expect(req.rstCode).toBe(http2.constants.NGHTTP2_NO_ERROR);
const { promise: listenPromise, resolve: listenResolve } = Promise.withResolvers();
h2Server.listen(0, () => listenResolve());
await listenPromise;
const serverAddress = h2Server.address();
const serverUrl = `https://localhost:${serverAddress.port}`;
try {
const { promise, resolve, reject } = Promise.withResolvers();
const client = http2.connect(serverUrl, TLS_OPTIONS);
client.on("error", reject);
expect(client.connecting).toBeTrue();
expect(client.alpnProtocol).toBeUndefined();
expect(client.encrypted).toBeTrue();
expect(client.closed).toBeFalse();
expect(client.destroyed).toBeFalse();
expect(client.originSet.length).toBe(1);
expect(client.pendingSettingsAck).toBeTrue();
assertSettings(client.localSettings);
expect(client.remoteSettings).toBeNull();
const headers = { ":path": "/" };
const req = client.request(headers);
expect(req.closed).toBeFalse();
expect(req.destroyed).toBeFalse();
// we always asign a stream id to the request
expect(req.pending).toBeFalse();
expect(typeof req.id).toBe("number");
expect(req.session).toBeDefined();
expect(req.sentHeaders).toEqual({
":authority": `localhost:${serverAddress.port}`,
":method": "GET",
":path": "/",
":scheme": "https",
});
expect(req.sentTrailers).toBeUndefined();
expect(req.sentInfoHeaders.length).toBe(0);
expect(req.scheme).toBe("https");
let response_headers = null;
req.on("response", (headers, flags) => {
response_headers = headers;
});
req.resume();
req.on("end", () => {
resolve();
});
await promise;
expect(response_headers[":status"]).toBe(200);
const settings = client.remoteSettings;
const localSettings = client.localSettings;
assertSettings(settings);
assertSettings(localSettings);
expect(settings).toEqual(client.remoteSettings);
expect(localSettings).toEqual(client.localSettings);
client.destroy();
expect(client.connecting).toBeFalse();
expect(client.alpnProtocol).toBe("h2");
expect(client.pendingSettingsAck).toBeFalse();
expect(client.destroyed).toBeTrue();
expect(client.closed).toBeTrue();
expect(req.closed).toBeTrue();
expect(req.destroyed).toBeTrue();
expect(req.rstCode).toBe(http2.constants.NGHTTP2_NO_ERROR);
} finally {
h2Server.close();
}
});
it("ping events should work", async () => {
await using server = await nodeEchoServer(paddingStrategy);

View File

@@ -577,19 +577,27 @@ describe("fetch", () => {
});
it.concurrent('redirect: "follow"', async () => {
using target = Bun.serve({
port: 0,
tls,
fetch() {
return new Response("redirected!");
},
});
using server = Bun.serve({
port: 0,
fetch(req) {
return new Response(null, {
status: 302,
headers: {
Location: "https://example.com",
Location: target.url.href,
},
});
},
});
const response = await fetch(`http://${server.hostname}:${server.port}`, {
redirect: "follow",
tls: { ca: tls.cert },
});
expect(response.status).toBe(200);
expect(response.headers.get("location")).toBe(null);
@@ -734,8 +742,15 @@ it.concurrent("simultaneous HTTPS fetch", async () => {
});
it.concurrent("website with tlsextname", async () => {
// irony
await fetch("https://bun.sh", { method: "HEAD" });
using server = Bun.serve({
port: 0,
tls,
fetch() {
return new Response("OK");
},
});
const resp = await fetch(server.url, { method: "HEAD", tls: { ca: tls.cert } });
expect(resp.status).toBe(200);
});
function testBlobInterface(blobbyConstructor: { (..._: any[]): any }, hasBlobFn?: boolean) {

View File

@@ -30,94 +30,110 @@ async function createServer(cert: TLSOptions, callback: (port: number) => Promis
describe.concurrent("fetch-tls", () => {
it("can handle multiple requests with non native checkServerIdentity", async () => {
async function request() {
let called = false;
const result = await fetch("https://www.example.com", {
keepalive: false,
tls: {
checkServerIdentity(hostname: string, cert: tls.PeerCertificate) {
called = true;
return tls.checkServerIdentity(hostname, cert);
},
},
}).then((res: Response) => res.blob());
expect(result?.size).toBeGreaterThan(0);
expect(called).toBe(true);
}
const promises = [];
for (let i = 0; i < 5; i++) {
promises.push(request());
}
await Promise.all(promises);
});
it("fetch with valid tls should not throw", async () => {
const promises = [`https://example.com`, `https://www.example.com`].map(async url => {
const result = await fetch(url, { keepalive: false }).then((res: Response) => res.blob());
expect(result?.size).toBeGreaterThan(0);
});
await Promise.all(promises);
});
it("fetch with valid tls and non-native checkServerIdentity should work", async () => {
for (const isBusy of [true, false]) {
let count = 0;
const promises = [`https://example.com`, `https://www.example.com`].map(async url => {
await fetch(url, {
await createServer(CERT_LOCALHOST_IP, async port => {
async function request() {
let called = false;
const result = await fetch(`https://localhost:${port}`, {
keepalive: false,
tls: {
ca: validTls.cert,
checkServerIdentity(hostname: string, cert: tls.PeerCertificate) {
count++;
expect(url).toContain(hostname);
called = true;
return tls.checkServerIdentity(hostname, cert);
},
},
}).then((res: Response) => res.blob());
});
if (isBusy) {
const start = performance.now();
while (performance.now() - start < 500) {}
expect(result?.size).toBeGreaterThan(0);
expect(called).toBe(true);
}
const promises = [];
for (let i = 0; i < 5; i++) {
promises.push(request());
}
await Promise.all(promises);
expect(count).toBe(2);
}
});
});
it("fetch with valid tls should not throw", async () => {
await createServer(CERT_LOCALHOST_IP, async port => {
const urls = [`https://localhost:${port}`, `https://127.0.0.1:${port}`];
const promises = urls.map(async url => {
const result = await fetch(url, { keepalive: false, tls: { ca: validTls.cert } }).then((res: Response) =>
res.blob(),
);
expect(result?.size).toBeGreaterThan(0);
});
await Promise.all(promises);
});
});
it("fetch with valid tls and non-native checkServerIdentity should work", async () => {
let count = 0;
const promises = [`https://example.com`, `https://www.example.com`].map(async url => {
await fetch(url, {
keepalive: false,
tls: {
checkServerIdentity(hostname: string, cert: tls.PeerCertificate) {
count++;
expect(url).toContain(hostname);
throw new Error("CustomError");
},
},
});
await createServer(CERT_LOCALHOST_IP, async port => {
for (const isBusy of [true, false]) {
let count = 0;
const urls = [`https://localhost:${port}`, `https://127.0.0.1:${port}`];
const promises = urls.map(async url => {
await fetch(url, {
keepalive: false,
tls: {
ca: validTls.cert,
checkServerIdentity(hostname: string, cert: tls.PeerCertificate) {
count++;
return tls.checkServerIdentity(hostname, cert);
},
},
}).then((res: Response) => res.blob());
});
if (isBusy) {
const start = performance.now();
while (performance.now() - start < 500) {}
}
await Promise.all(promises);
expect(count).toBe(2);
}
});
});
it("fetch with valid tls and non-native checkServerIdentity that throws should reject", async () => {
await createServer(CERT_LOCALHOST_IP, async port => {
let count = 0;
const urls = [`https://localhost:${port}`, `https://127.0.0.1:${port}`];
const promises = urls.map(async url => {
await fetch(url, {
keepalive: false,
tls: {
ca: validTls.cert,
checkServerIdentity(hostname: string, cert: tls.PeerCertificate) {
count++;
throw new Error("CustomError");
},
},
});
});
const start = performance.now();
while (performance.now() - start < 1000) {}
expect((await Promise.allSettled(promises)).every(p => p.status === "rejected")).toBe(true);
expect(count).toBe(2);
});
const start = performance.now();
while (performance.now() - start < 1000) {}
expect((await Promise.allSettled(promises)).every(p => p.status === "rejected")).toBe(true);
expect(count).toBe(2);
});
it("fetch with rejectUnauthorized: false should not call checkServerIdentity", async () => {
let count = 0;
await createServer(CERT_LOCALHOST_IP, async port => {
let count = 0;
await fetch("https://example.com", {
keepalive: false,
tls: {
rejectUnauthorized: false,
checkServerIdentity(hostname: string, cert: tls.PeerCertificate) {
count++;
return tls.checkServerIdentity(hostname, cert);
await fetch(`https://localhost:${port}`, {
keepalive: false,
tls: {
rejectUnauthorized: false,
checkServerIdentity(hostname: string, cert: tls.PeerCertificate) {
count++;
return tls.checkServerIdentity(hostname, cert);
},
},
},
}).then((res: Response) => res.blob());
expect(count).toBe(0);
}).then((res: Response) => res.blob());
expect(count).toBe(0);
});
});
it("fetch with self-sign tls should throw", async () => {
@@ -152,20 +168,23 @@ describe.concurrent("fetch-tls", () => {
});
it("fetch with checkServerIdentity failing should throw", async () => {
try {
await fetch(`https://example.com`, {
keepalive: false,
tls: {
checkServerIdentity() {
return new Error("CustomError");
await createServer(CERT_LOCALHOST_IP, async port => {
try {
await fetch(`https://localhost:${port}`, {
keepalive: false,
tls: {
ca: validTls.cert,
checkServerIdentity() {
return new Error("CustomError");
},
},
},
}).then((res: Response) => res.blob());
}).then((res: Response) => res.blob());
expect.unreachable();
} catch (e: any) {
expect(e.message).toBe("CustomError");
}
expect.unreachable();
} catch (e: any) {
expect(e.message).toBe("CustomError");
}
});
});
it("fetch with self-sign certificate tls + rejectUnauthorized: false should not throw", async () => {

View File

@@ -883,253 +883,4 @@ describe("Structured Clone Fast Path", () => {
port1.close();
port2.close();
});
// === TypedArray fast path tests ===
const typedArrayCtors = [
{ name: "Uint8Array", ctor: Uint8Array, values: [0, 1, 127, 255] },
{ name: "Int8Array", ctor: Int8Array, values: [-128, -1, 0, 1, 127] },
{ name: "Uint8ClampedArray", ctor: Uint8ClampedArray, values: [0, 1, 127, 255] },
{ name: "Uint16Array", ctor: Uint16Array, values: [0, 1, 256, 65535] },
{ name: "Int16Array", ctor: Int16Array, values: [-32768, -1, 0, 1, 32767] },
{ name: "Uint32Array", ctor: Uint32Array, values: [0, 1, 65536, 4294967295] },
{ name: "Int32Array", ctor: Int32Array, values: [-2147483648, -1, 0, 1, 2147483647] },
{ name: "Float32Array", ctor: Float32Array, values: [0, 1.5, -1.5, 3.4028234663852886e38] },
{ name: "Float64Array", ctor: Float64Array, values: [0, 1.5, -1.5, Number.MAX_VALUE, Number.MIN_VALUE] },
] as const;
for (const { name, ctor, values } of typedArrayCtors) {
test(`structuredClone(${name}) basic values`, () => {
const input = new ctor(values as any);
const cloned = structuredClone(input);
expect(cloned).toBeInstanceOf(ctor);
expect(cloned).toEqual(input);
expect(cloned.buffer).not.toBe(input.buffer);
});
}
test("structuredClone(BigInt64Array) basic values", () => {
const input = new BigInt64Array([-9223372036854775808n, -1n, 0n, 1n, 9223372036854775807n]);
const cloned = structuredClone(input);
expect(cloned).toBeInstanceOf(BigInt64Array);
expect(cloned).toEqual(input);
expect(cloned.buffer).not.toBe(input.buffer);
});
test("structuredClone(BigUint64Array) basic values", () => {
const input = new BigUint64Array([0n, 1n, 18446744073709551615n]);
const cloned = structuredClone(input);
expect(cloned).toBeInstanceOf(BigUint64Array);
expect(cloned).toEqual(input);
expect(cloned.buffer).not.toBe(input.buffer);
});
test("structuredClone(Float16Array) basic values", () => {
const input = new Float16Array([0, 1.5, -1.5, 65504]);
const cloned = structuredClone(input);
expect(cloned).toBeInstanceOf(Float16Array);
expect(cloned).toEqual(input);
expect(cloned.buffer).not.toBe(input.buffer);
});
test("structuredClone empty TypedArray", () => {
const input = new Uint8Array(0);
const cloned = structuredClone(input);
expect(cloned).toBeInstanceOf(Uint8Array);
expect(cloned.length).toBe(0);
expect(cloned.byteLength).toBe(0);
});
test("structuredClone large TypedArray (1MB)", () => {
const input = new Uint8Array(1024 * 1024);
for (let i = 0; i < input.length; i++) input[i] = i & 0xff;
const cloned = structuredClone(input);
expect(cloned).toBeInstanceOf(Uint8Array);
expect(cloned.length).toBe(input.length);
expect(cloned).toEqual(input);
expect(cloned.buffer).not.toBe(input.buffer);
});
test("structuredClone Float64Array with special values", () => {
const input = new Float64Array([NaN, Infinity, -Infinity, -0, 0]);
const cloned = structuredClone(input);
expect(cloned).toBeInstanceOf(Float64Array);
expect(cloned[0]).toBeNaN();
expect(cloned[1]).toBe(Infinity);
expect(cloned[2]).toBe(-Infinity);
expect(Object.is(cloned[3], -0)).toBe(true);
expect(cloned[4]).toBe(0);
});
test("structuredClone Float32Array with special values", () => {
const input = new Float32Array([NaN, Infinity, -Infinity, -0]);
const cloned = structuredClone(input);
expect(cloned).toBeInstanceOf(Float32Array);
expect(cloned[0]).toBeNaN();
expect(cloned[1]).toBe(Infinity);
expect(cloned[2]).toBe(-Infinity);
expect(Object.is(cloned[3], -0)).toBe(true);
});
test("structuredClone TypedArray creates independent copy", () => {
const input = new Uint8Array([1, 2, 3, 4, 5]);
const cloned = structuredClone(input);
cloned[0] = 255;
expect(input[0]).toBe(1);
input[1] = 200;
expect(cloned[1]).toBe(2);
});
test("structuredClone DataView falls back to slow path but works correctly", () => {
const buf = new ArrayBuffer(8);
const view = new DataView(buf);
view.setFloat64(0, 3.14);
const cloned = structuredClone(view);
expect(cloned).toBeInstanceOf(DataView);
expect(cloned.getFloat64(0)).toBe(3.14);
expect(cloned.buffer).not.toBe(buf);
});
test("structuredClone TypedArray slice view falls back to slow path", () => {
const buf = new ArrayBuffer(16);
new Uint8Array(buf).set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
const sliceView = new Uint8Array(buf, 4, 4);
const cloned = structuredClone(sliceView);
expect(cloned).toBeInstanceOf(Uint8Array);
expect(cloned).toEqual(new Uint8Array([4, 5, 6, 7]));
// structuredClone clones the full backing ArrayBuffer, preserving byteOffset
expect(cloned.byteOffset).toBe(4);
expect(cloned.buffer.byteLength).toBe(16);
});
test("structuredClone TypedArray with named properties falls back to slow path", () => {
const input = new Uint8Array([1, 2, 3]) as any;
input.customProp = "hello";
// Named properties on TypedArray are not cloneable via structuredClone,
// the slow path handles this correctly (ignores them)
const cloned = structuredClone(input);
expect(cloned).toBeInstanceOf(Uint8Array);
expect(cloned).toEqual(new Uint8Array([1, 2, 3]));
});
test("structuredClone detached TypedArray throws DataCloneError", () => {
const buf = new ArrayBuffer(8);
const input = new Uint8Array(buf);
// Detach the buffer by transferring it
structuredClone(buf, { transfer: [buf] });
expect(() => structuredClone(input)).toThrow();
});
test("postMessage TypedArray via MessageChannel", async () => {
const { port1, port2 } = new MessageChannel();
const input = new Uint8Array([10, 20, 30, 40, 50]);
const { promise, resolve } = Promise.withResolvers();
port2.onmessage = (e: MessageEvent) => resolve(e.data);
port1.postMessage(input);
const result = await promise;
expect(result).toBeInstanceOf(Uint8Array);
expect(result).toEqual(input);
port1.close();
port2.close();
});
test("postMessage Float64Array via MessageChannel", async () => {
const { port1, port2 } = new MessageChannel();
const input = new Float64Array([1.1, 2.2, 3.3, NaN, Infinity]);
const { promise, resolve } = Promise.withResolvers();
port2.onmessage = (e: MessageEvent) => resolve(e.data);
port1.postMessage(input);
const result = await promise;
expect(result).toBeInstanceOf(Float64Array);
expect(result[0]).toBe(1.1);
expect(result[1]).toBe(2.2);
expect(result[2]).toBe(3.3);
expect(result[3]).toBeNaN();
expect(result[4]).toBe(Infinity);
port1.close();
port2.close();
});
test("postMessage BigInt64Array via MessageChannel", async () => {
const { port1, port2 } = new MessageChannel();
const input = new BigInt64Array([0n, -1n, 9223372036854775807n]);
const { promise, resolve } = Promise.withResolvers();
port2.onmessage = (e: MessageEvent) => resolve(e.data);
port1.postMessage(input);
const result = await promise;
expect(result).toBeInstanceOf(BigInt64Array);
expect(result).toEqual(input);
port1.close();
port2.close();
});
test("structuredClone TypedArray backed by SharedArrayBuffer falls back to slow path", () => {
const sab = new SharedArrayBuffer(16);
const view = new Uint8Array(sab);
view.set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const cloned = structuredClone(view);
expect(cloned).toBeInstanceOf(Uint8Array);
expect(cloned).toEqual(view);
// The cloned view should NOT share memory with the original
expect(cloned.buffer).not.toBe(sab);
// Verify independence: modifying original doesn't affect clone
view[0] = 255;
expect(cloned[0]).toBe(1);
});
test("structuredClone Int32Array backed by SharedArrayBuffer preserves values", () => {
const sab = new SharedArrayBuffer(16);
const view = new Int32Array(sab);
view.set([100, 200, 300, 400]);
const cloned = structuredClone(view);
expect(cloned).toBeInstanceOf(Int32Array);
expect(cloned).toEqual(new Int32Array([100, 200, 300, 400]));
expect(cloned.buffer).not.toBe(sab);
});
test("postMessage TypedArray backed by SharedArrayBuffer via MessageChannel", async () => {
const { port1, port2 } = new MessageChannel();
const sab = new SharedArrayBuffer(8);
const input = new Uint8Array(sab);
input.set([10, 20, 30, 40, 50, 60, 70, 80]);
const { promise, resolve } = Promise.withResolvers();
port2.onmessage = (e: MessageEvent) => resolve(e.data);
port1.postMessage(input);
const result = await promise;
expect(result).toBeInstanceOf(Uint8Array);
expect(result).toEqual(input);
port1.close();
port2.close();
});
test("structuredClone partial-buffer TypedArray with byteOffset==0 preserves full buffer", () => {
// new Uint8Array(buf, 0, 8) over a 16-byte buffer: byteOffset is 0 but
// the view only covers the first half. The slow path clones the entire
// backing ArrayBuffer and preserves byteOffset/byteLength.
const buf = new ArrayBuffer(16);
const full = new Uint8Array(buf);
full.set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
const partial = new Uint8Array(buf, 0, 8);
const cloned = structuredClone(partial);
expect(cloned).toBeInstanceOf(Uint8Array);
expect(cloned).toEqual(new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]));
expect(cloned.byteOffset).toBe(0);
expect(cloned.byteLength).toBe(8);
// The cloned buffer must preserve the full backing ArrayBuffer size
expect(cloned.buffer.byteLength).toBe(16);
});
test("structuredClone partial-buffer Int32Array with byteOffset==0 preserves full buffer", () => {
const buf = new ArrayBuffer(32);
const partial = new Int32Array(buf, 0, 4); // 16 bytes out of 32
partial.set([100, 200, 300, 400]);
const cloned = structuredClone(partial);
expect(cloned).toBeInstanceOf(Int32Array);
expect(cloned).toEqual(new Int32Array([100, 200, 300, 400]));
expect(cloned.byteOffset).toBe(0);
expect(cloned.byteLength).toBe(16);
expect(cloned.buffer.byteLength).toBe(32);
});
});

View File

@@ -398,6 +398,71 @@ describe("WebSocket wss:// through HTTP proxy (TLS tunnel)", () => {
expect(messages).toContain("hello via tls tunnel");
gc();
});
test("server-initiated ping survives through TLS tunnel proxy", async () => {
// Regression test: sendPong checked socket.isClosed() on the detached tcp
// field instead of using hasTCP(). For wss:// through HTTP proxy, the
// WebSocket uses initWithTunnel which sets tcp = detached (all I/O goes
// through proxy_tunnel). Detached sockets return true for isClosed(), so
// sendPong would immediately dispatch a 1006 close instead of sending the
// pong through the tunnel.
using pingServer = Bun.serve({
port: 0,
tls: {
key: tlsCerts.key,
cert: tlsCerts.cert,
},
fetch(req, server) {
if (server.upgrade(req)) return;
return new Response("Expected WebSocket", { status: 400 });
},
websocket: {
message(ws, message) {
if (String(message) === "ready") {
// Send a ping after the client confirms it's connected.
// On the buggy code path, this triggers sendPong on the detached
// socket → dispatchAbruptClose → 1006.
ws.ping();
// Follow up with a text message. If the client receives this,
// the connection survived the ping/pong exchange.
ws.send("after-ping");
}
},
},
});
const { promise, resolve, reject } = Promise.withResolvers<void>();
const ws = new WebSocket(`wss://127.0.0.1:${pingServer.port}`, {
proxy: `http://127.0.0.1:${proxyPort}`,
tls: { rejectUnauthorized: false },
});
ws.onopen = () => {
ws.send("ready");
};
ws.onmessage = event => {
if (String(event.data) === "after-ping") {
ws.close(1000);
}
};
ws.onclose = event => {
if (event.code === 1000) {
resolve();
} else {
reject(new Error(`Unexpected close code: ${event.code}`));
}
};
ws.onerror = event => {
reject(event);
};
await promise;
gc();
});
});
describe("WebSocket through HTTPS proxy (TLS proxy)", () => {

View File

@@ -109,13 +109,22 @@ describe("HTMLRewriter", () => {
await gcTick();
let content;
{
using contentServer = Bun.serve({
port: 0,
fetch(req) {
return new Response("<h1>Hello from content server</h1>", {
headers: { "Content-Type": "text/html" },
});
},
});
using server = Bun.serve({
port: 0,
fetch(req) {
return new HTMLRewriter()
.on("div", {
async element(element) {
content = await fetch("https://www.example.com/").then(res => res.text());
content = await fetch(`http://localhost:${contentServer.port}/`).then(res => res.text());
element.setInnerContent(content, { html: true });
},
})

View File

@@ -2119,6 +2119,35 @@ static napi_value test_napi_create_tsfn_async_context_frame(const Napi::Callback
return env.Undefined();
}
// Test for BUN-1PYR: napi_typeof should not crash when given an invalid
// napi_value that is actually a raw C string pointer. This simulates the
// scenario where a native module passes garbage data (e.g., a string pointer
// like "Tensor ...") as a napi_value to napi_typeof.
static napi_value test_napi_typeof_invalid_pointer(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
// Simulate the exact crash scenario: a C string pointer reinterpreted as napi_value.
// The crash address 0x6F20726F736E6554 decoded to ASCII is "Tensor o",
// meaning a string pointer was being used as a JSValue.
// Use aligned_alloc to ensure 16-byte alignment (bit 3 = 0), so the pointer
// goes through the MarkedBlock validation path (not the PreciseAllocation path).
char *fake_string = static_cast<char *>(aligned_alloc(16, 64));
memcpy(fake_string, "Tensor operation test string", 29);
napi_value bad_value = reinterpret_cast<napi_value>(fake_string);
napi_valuetype type;
napi_status status = napi_typeof(env, bad_value, &type);
if (status != napi_ok) {
printf("PASS: napi_typeof returned error status %d for invalid pointer\n", status);
} else {
printf("PASS: napi_typeof did not crash for invalid pointer (returned type %d)\n", type);
}
free(fake_string);
return ok(env);
}
void register_standalone_tests(Napi::Env env, Napi::Object exports) {
REGISTER_FUNCTION(env, exports, test_issue_7685);
REGISTER_FUNCTION(env, exports, test_issue_11949);
@@ -2157,6 +2186,7 @@ void register_standalone_tests(Napi::Env env, Napi::Object exports) {
REGISTER_FUNCTION(env, exports, test_issue_25933);
REGISTER_FUNCTION(env, exports, test_napi_make_callback_async_context_frame);
REGISTER_FUNCTION(env, exports, test_napi_create_tsfn_async_context_frame);
REGISTER_FUNCTION(env, exports, test_napi_typeof_invalid_pointer);
}
} // namespace napitests

View File

@@ -822,6 +822,24 @@ describe("cleanup hooks", () => {
expect(output).toContain("PASS: napi_create_threadsafe_function accepted AsyncContextFrame");
});
it("should not crash when given an invalid pointer as napi_value", async () => {
// Regression test for BUN-1PYR: napi_typeof segfaults when a native
// module passes a raw C string pointer as napi_value. The crash address
// 0x6F20726F736E6554 decoded to "Tensor o", indicating string data was
// being dereferenced as a JSValue.
const { BUN_INSPECT_CONNECT_TO: _, ...rest } = bunEnv;
await using exec = spawn({
cmd: [bunExe(), "--expose-gc", join(__dirname, "napi-app/main.js"), "test_napi_typeof_invalid_pointer", "[]"],
env: rest,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, exitCode] = await Promise.all([new Response(exec.stdout).text(), exec.exited]);
// napi_typeof should return an error status instead of crashing
expect(stdout).toContain("PASS");
expect(exitCode).toBe(0);
});
it("should return napi_object for boxed primitives (String, Number, Boolean)", async () => {
// Regression test for https://github.com/oven-sh/bun/issues/25351
// napi_typeof was incorrectly returning napi_string for String objects (new String("hello"))

View File

@@ -1,28 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCIzOJskt6VkEJY
XKSJv/Gdil3XYkjk3NVc/+m+kzqnkTRbPtT9w+IGWgmJhuf9DJPLCwHFAEFarVwV
x16Q0PbU4ajXaLRHEYGhrH10oTMjQnJ24xVm26mxRXPQa5vaLpWJqNyIdNLIQLe+
UXUOzSGGsFTRMAjvYrkzjBe4ZUnaZV+aFY/ug0jfzeA1dJjzKZs6+yTJRbsuWUEb
8MsDmT4v+kBZDKdaDn7AFDWRVqx/38BnqsRzkM0CxpnyT2kRzw5zQajIE13gdTJo
1EHvYSUkkxrY5m30Rl9BuBBZBjhMzOHq0fYVVooHO+sf4XHPgvFTTxJum85u7J1J
oEUjrLKtAgMBAAECggEACInVNhaiqu4infZGVMy0rXMV8VwSlapM7O2SLtFsr0nK
XUmaLK6dvGzBPKK9dxdiYCFzPlMKQTkhzsAvYFWSmm3tRmikG+11TFyCRhXLpc8/
ark4vD9Io6ZkmKUmyKLwtXNjNGcqQtJ7RXc7Ga3nAkueN6JKZHqieZusXVeBGQ70
YH1LKyVNBeJggbj+g9rqaksPyNJQ8EWiNTJkTRQPazZ0o1VX/fzDFyr/a5npFtHl
4BHfafv9o1Xyr70Kie8CYYRJNViOCN+ylFs7Gd3XRaAkSkgMT/7DzrHdEM2zrrHK
yNg2gyDVX9UeEJG2X5UtU0o9BVW7WBshz/2hqIUHoQKBgQC8zsRFvC7u/rGr5vRR
mhZZG+Wvg03/xBSuIgOrzm+Qie6mAzOdVmfSL/pNV9EFitXt1yd2ROo31AbS7Evy
Bm/QVKr2mBlmLgov3B7O/e6ABteooOL7769qV/v+yo8VdEg0biHmsfGIIXDe3Lwl
OT0XwF9r/SeZLbw1zfkSsUVG/QKBgQC5fANM3Dc9LEek+6PHv5+eC1cKkyioEjUl
/y1VUD00aABI1TUcdLF3BtFN2t/S6HW0hrP3KwbcUfqC25k+GDLh1nM6ZK/gI3Yn
IGtCHxtE3S6jKhE9QcK/H+PzGVKWge9SezeYRP0GHJYDrTVTA8Kt9HgoZPPeReJl
+Ss9c8ThcQKBgECX6HQHFnNzNSufXtSQB7dCoQizvjqTRZPxVRoxDOABIGExVTYt
umUhPtu5AGyJ+/hblEeU+iBRbGg6qRzK8PPwE3E7xey8MYYAI5YjL7YjISKysBUL
AhM6uJ6Jg/wOBSnSx8xZ8kzlS+0izUda1rjKeprCSArSp8IsjlrDxPStAoGAEcPr
+P+altRX5Fhpvmb/Hb8OTif8G+TqjEIdkG9H/W38oP0ywg/3M2RGxcMx7txu8aR5
NjI7zPxZFxF7YvQkY3cLwEsGgVxEI8k6HLIoBXd90Qjlb82NnoqqZY1GWL4HMwo0
L/Rjm6M/Rwje852Hluu0WoIYzXA6F/Q+jPs6nzECgYAxx4IbDiGXuenkwSF1SUyj
NwJXhx4HDh7U6EO/FiPZE5BHE3BoTrFu3o1lzverNk7G3m+j+m1IguEAalHlukYl
rip9iUISlKYqbYZdLBoLwHAfHhszdrjqn8/v6oqbB5yR3HXjPFUWJo0WJ2pqJp56
ZshgmQQ/5Khoj6x0/dMPSg==
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCt7iqkEIco372h
v19q0zjaYbm6gzxEnR45UjpQYqgztq4QHicD80mqIkCBCYknFxhwxhNn+Y3g5RWQ
dReplpQbkneqRVp+qixMvu2FmOA4zRRoqObP7FyF1Yusvmroe0Y9SP2xTTmA9Zo7
3paywPUIuZ9eKGwIiFTtj1yQ1FdghLhzZgxcf3LHEHRkGnxgxxNITFxh4nd6fGIj
NqM5fQAY8z35lMXdeWjrhtaqgFYB+Z20YY0X7LJx39vYao0wqW8sZjX88TqHI1zX
WLpUk6UK9RqaNza5xc80wV+9/zjhr3dc1FRjBxI1DS/ufo33dUfvilxv9/LtWwUn
KfKLns9LAgMBAAECggEAAacPHM2G7GBIm/9rCr6tvihNgD8M685zOOZAqGYn9CqY
cYHC4gtF/L2U6CBj2pNAoCwo3LXUkD+6r7MYKXAgqQg3HTCM4rwFbhD1rU8FVHfh
OL0QwwZ2ut95DVdjoxTAlEN9ZcdSFc//llMJ1cF8lxoVvKFc4cv3uCI2mcaJk858
iABfJLl3yfdv1xtpAuOfXf66sXbAmn5NQfN0qTEg2iOdgb4BUee5Wb35MakDQb6+
/s7/bWB+ublZzYt12ChIh1jkBBHaGyQ8mFnPj99ZAJdFjAzi6ydoJ0a2rCVY7Ugs
bkhnzDUtAaHKxo9JXaqIwbUaVFkX8dDhbg82dJrWUQKBgQDb7hNR0bJFW845N19M
74p2PM+0dIiVzwxAg4E2dXDVe39awO/tw8Vu1o1+NPFhWAzGcidP7pAHmPEgRTVO
7LA2P3CDXpkAEx5E0QW6QWZGqHfSa3+P1AvetvAV+OxtlDphcNeLApY16TUVOKZg
SZlxW2e0dZylbHewgLBTIV9wUQKBgQDKdML+JD18WfenPeowsw8HzKdaw01iGiV1
fvTjEXu6YxPPynWFMuj5gjBQodXM2vv0EsQBAPKYfe0nzRFL2kNuYs7TLoaNxqkp
DNfJ2Ww5OSg7Mp76XgppeKKlsXLyUMYHHrDh6MRi5jvWtiHRpaNmV3cHMRs22c+B
cqKP5Zma2wKBgCPNnS2Lsrbh3C+qWQRgVq0q9zFMa1PgEgGKpwVjlwvaAACZOjX9
0e1aVkx+d/E98U55FPdJQf9Koa58NdJ0a7dZGor4YnYFpr7TPFh2/xxvnpoN0AVt
IsWOCIW7MVohcGOeiChkMmnyXibnQwaX1LgEhlx1bRvtDYsZWBsgarYRAoGAARvo
oYnDSHYZtDHToZapg2pslEOzndD02ZLrdn73BYtbZWz/fc5MlmlPKHHqgOfGL40W
w8akjY9LCEfIS3kTm3wxE9kSZZ5r+MyYNgPZ4upcPQ7G7iortm4xveSd85PbsdhK
McKbqMsIEuIGh2Z34ayi+0galQ9WYqglGdKxJ7cCgYEAuSPBHa+en0xaraZNRvMk
OfV9Su/wrpR3TXSeo0E1mZHLwq1JwulpfO1SjxTH5uOJtG0tusl122wfm0KjrXUO
vG5/It+X4u1Nv9oWj+z1+EV4fQrQ/Coqcc1r+5w1yzfURkKlHh74jbK5Yy/KfXrE
eqbbJD40tKhY8ho15D3iCSo=
-----END PRIVATE KEY-----

View File

@@ -1,23 +1,24 @@
-----BEGIN CERTIFICATE-----
MIID5jCCAs6gAwIBAgIUN7coIsdMcLo9amZfkwogu0YkeLEwDQYJKoZIhvcNAQEL
MIIEDDCCAvSgAwIBAgIUbddWE2woW5e96uC4S2fd2M0AsFAwDQYJKoZIhvcNAQEL
BQAwfjELMAkGA1UEBhMCU0UxDjAMBgNVBAgMBVN0YXRlMREwDwYDVQQHDAhMb2Nh
dGlvbjEaMBgGA1UECgwRT3JnYW5pemF0aW9uIE5hbWUxHDAaBgNVBAsME09yZ2Fu
aXphdGlvbmFsIFVuaXQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMzA5MjExNDE2
MjNaFw0yNDA5MjAxNDE2MjNaMH4xCzAJBgNVBAYTAlNFMQ4wDAYDVQQIDAVTdGF0
aXphdGlvbmFsIFVuaXQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yNjAyMTMyMzEx
MjlaFw0zNjAyMTEyMzExMjlaMH4xCzAJBgNVBAYTAlNFMQ4wDAYDVQQIDAVTdGF0
ZTERMA8GA1UEBwwITG9jYXRpb24xGjAYBgNVBAoMEU9yZ2FuaXphdGlvbiBOYW1l
MRwwGgYDVQQLDBNPcmdhbml6YXRpb25hbCBVbml0MRIwEAYDVQQDDAlsb2NhbGhv
c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCIzOJskt6VkEJYXKSJ
v/Gdil3XYkjk3NVc/+m+kzqnkTRbPtT9w+IGWgmJhuf9DJPLCwHFAEFarVwVx16Q
0PbU4ajXaLRHEYGhrH10oTMjQnJ24xVm26mxRXPQa5vaLpWJqNyIdNLIQLe+UXUO
zSGGsFTRMAjvYrkzjBe4ZUnaZV+aFY/ug0jfzeA1dJjzKZs6+yTJRbsuWUEb8MsD
mT4v+kBZDKdaDn7AFDWRVqx/38BnqsRzkM0CxpnyT2kRzw5zQajIE13gdTJo1EHv
YSUkkxrY5m30Rl9BuBBZBjhMzOHq0fYVVooHO+sf4XHPgvFTTxJum85u7J1JoEUj
rLKtAgMBAAGjXDBaMA4GA1UdDwEB/wQEAwIDiDATBgNVHSUEDDAKBggrBgEFBQcD
ATAUBgNVHREEDTALgglsb2NhbGhvc3QwHQYDVR0OBBYEFNzx4Rfs9m8XR5ML0WsI
sorKmB4PMA0GCSqGSIb3DQEBCwUAA4IBAQB87iQy8R0fiOky9WTcyzVeMaavS3MX
iTe1BRn1OCyDq+UiwwoNz7zdzZJFEmRtFBwPNFOe4HzLu6E+7yLFR552eYRHlqIi
/fiLb5JiZfPtokUHeqwELWBsoXtU8vKxViPiLZ09jkWOPZWo7b/xXd6QYykBfV91
usUXLzyTD2orMagpqNksLDGS3p3ggHEJBZtRZA8R7kPEw98xZHznOQpr26iv8kYz
ZWdLFoFdwgFBSfxePKax5rfo+FbwdrcTX0MhbORyiu2XsBAghf8s2vKDkHg2UQE8
haonxFYMFaASfaZ/5vWKYDTCJkJ67m/BtkpRafFEO+ad1i1S61OjfxH4
c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt7iqkEIco372hv19q
0zjaYbm6gzxEnR45UjpQYqgztq4QHicD80mqIkCBCYknFxhwxhNn+Y3g5RWQdRep
lpQbkneqRVp+qixMvu2FmOA4zRRoqObP7FyF1Yusvmroe0Y9SP2xTTmA9Zo73pay
wPUIuZ9eKGwIiFTtj1yQ1FdghLhzZgxcf3LHEHRkGnxgxxNITFxh4nd6fGIjNqM5
fQAY8z35lMXdeWjrhtaqgFYB+Z20YY0X7LJx39vYao0wqW8sZjX88TqHI1zXWLpU
k6UK9RqaNza5xc80wV+9/zjhr3dc1FRjBxI1DS/ufo33dUfvilxv9/LtWwUnKfKL
ns9LAgMBAAGjgYEwfzAdBgNVHQ4EFgQUQCpSY7ODhdyD6pdZHvfHoWRXWsIwHwYD
VR0jBBgwFoAUQCpSY7ODhdyD6pdZHvfHoWRXWsIwDwYDVR0TAQH/BAUwAwEB/zAs
BgNVHREEJTAjgglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJ
KoZIhvcNAQELBQADggEBAGKTIzGQsOqfD0+x15F2cu7FKjIo1ua0OiILAhPqGX65
kGcetjC/dJip2bGnw1NjG9WxEJNZ4YcsGrwh9egfnXXmfHNL0wzx/LTo2oysbXsN
nEj+cmzw3Lwjn/ywJc+AC221/xrmDfm3m/hMzLqncnj23ZAHqkXTSp5UtSMs+UDQ
my0AJOvsDGPVKHQsAX3JDjKHaoVJn4YqpHcIGmpjrNcQSvwUocDHPcC0ywco6SgF
Ylzy2bwWWdPd9Cz9JkAMb95nWc7Rwf/nxAqCjJFzKEisvrx7VZ+QSVI0nqJzt8V1
pbtWYH5gMFVstU3ghWdSLbAk4XufGYrIWAlA5mqjQ4o=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,63 @@
// Regression test for TLS upgrade raw socket leak (#12117, #24118, #25948)
// When a TCP socket is upgraded to TLS via tls.connect({ socket }),
// both a TLS wrapper and a raw TCP wrapper are created in Zig.
// Previously, the raw socket's has_pending_activity was never set to
// false on close, causing it (and all its retained objects) to leak.
import { describe, expect, it } from "bun:test";
import { tls as COMMON_CERT, expectMaxObjectTypeCount } from "harness";
import { once } from "node:events";
import net from "node:net";
import tls from "node:tls";
describe("TLS upgrade", () => {
it("should not leak TLSSocket objects after close", async () => {
// Create a TLS server that echoes data and closes
const server = tls.createServer(
{
key: COMMON_CERT.key,
cert: COMMON_CERT.cert,
},
socket => {
socket.end("hello");
},
);
await once(server.listen(0, "127.0.0.1"), "listening");
const port = (server.address() as net.AddressInfo).port;
// Simulate the MongoDB driver pattern: create a plain TCP socket,
// then upgrade it to TLS via tls.connect({ socket }).
// Do this multiple times to accumulate leaked objects.
const iterations = 50;
try {
for (let i = 0; i < iterations; i++) {
const tcpSocket = net.createConnection({ host: "127.0.0.1", port });
await once(tcpSocket, "connect");
const tlsSocket = tls.connect({
socket: tcpSocket,
ca: COMMON_CERT.cert,
rejectUnauthorized: false,
});
await once(tlsSocket, "secureConnect");
// Read any data and destroy the TLS socket (simulates SDAM close)
tlsSocket.on("data", () => {});
tlsSocket.destroy();
await once(tlsSocket, "close");
}
} finally {
server.close();
await once(server, "close");
}
// After all connections are closed and GC runs, the TLSSocket count
// should be low. Before the fix, each iteration would leak 1 raw
// TLSSocket (the TCP wrapper from upgradeTLS), accumulating over time.
// Allow some slack for prototypes/structures (typically 2-3 baseline).
await expectMaxObjectTypeCount(expect, "TLSSocket", 10, 1000);
});
});

View File

@@ -0,0 +1,8 @@
import { expect, test } from "bun:test";
// https://github.com/oven-sh/bun/issues/27014
test("Bun.stripANSI does not hang on non-ANSI control characters", () => {
const s = "\u0016zo\u00BAd\u0019\u00E8\u00E0\u0013?\u00C1+\u0014d\u00D3\u00E9";
const result = Bun.stripANSI(s);
expect(result).toBe(s);
});

View File

@@ -0,0 +1,31 @@
import { expect, test } from "bun:test";
import { X509Certificate } from "crypto";
import { readFileSync } from "fs";
import { join } from "path";
const certPem = readFileSync(join(import.meta.dir, "../../js/node/test/fixtures/keys/agent1-cert.pem"));
test("issuerCertificate should return undefined for directly-parsed certificates without crashing", () => {
const cert = new X509Certificate(certPem);
// issuerCertificate is only populated for certificates obtained from TLS
// connections with a peer certificate chain. For directly parsed certs,
// it should be undefined (matching Node.js behavior).
expect(cert.issuerCertificate).toBeUndefined();
});
test("X509Certificate properties should not crash on valid certificates", () => {
const cert = new X509Certificate(certPem);
// These should all work without segfaulting
expect(cert.subject).toBeDefined();
expect(cert.issuer).toBeDefined();
expect(cert.validFrom).toBeDefined();
expect(cert.validTo).toBeDefined();
expect(cert.fingerprint).toBeDefined();
expect(cert.fingerprint256).toBeDefined();
expect(cert.fingerprint512).toBeDefined();
expect(cert.serialNumber).toBeDefined();
expect(cert.raw).toBeInstanceOf(Uint8Array);
expect(cert.ca).toBe(false);
});

View File

@@ -0,0 +1,89 @@
import { expect, test } from "bun:test";
import http from "node:http";
test("ClientRequest.setHeaders should not throw ERR_HTTP_HEADERS_SENT on new request", async () => {
await using server = Bun.serve({
port: 0,
fetch(req) {
return new Response(req.headers.get("x-test") ?? "missing");
},
});
const { resolve, reject, promise } = Promise.withResolvers<string>();
const req = http.request(`http://localhost:${server.port}/test`, { method: "GET" }, res => {
let data = "";
res.on("data", (chunk: Buffer) => {
data += chunk.toString();
});
res.on("end", () => resolve(data));
});
req.on("error", reject);
// This should not throw - headers haven't been sent yet
req.setHeaders(new Headers({ "x-test": "value" }));
req.end();
const body = await promise;
expect(body).toBe("value");
});
test("ClientRequest.setHeaders works with Map", async () => {
await using server = Bun.serve({
port: 0,
fetch(req) {
return new Response(req.headers.get("x-map-test") ?? "missing");
},
});
const { resolve, reject, promise } = Promise.withResolvers<string>();
const req = http.request(`http://localhost:${server.port}/test`, { method: "GET" }, res => {
let data = "";
res.on("data", (chunk: Buffer) => {
data += chunk.toString();
});
res.on("end", () => resolve(data));
});
req.on("error", reject);
req.setHeaders(new Map([["x-map-test", "map-value"]]));
req.end();
const body = await promise;
expect(body).toBe("map-value");
});
test("ServerResponse.setHeaders should not throw before headers are sent", async () => {
const { resolve, reject, promise } = Promise.withResolvers<string>();
const server = http.createServer((req, res) => {
// This should not throw - headers haven't been sent yet
res.setHeaders(new Headers({ "x-custom": "server-value" }));
res.writeHead(200);
res.end("ok");
});
try {
server.listen(0, () => {
const port = (server.address() as any).port;
try {
const req = http.request(`http://localhost:${port}/test`, res => {
resolve(res.headers["x-custom"] as string);
});
req.on("error", reject);
req.end();
} catch (e) {
reject(e);
}
});
expect(await promise).toBe("server-value");
} finally {
server.close();
}
});

View File

@@ -0,0 +1,336 @@
import { describe, expect, test } from "bun:test";
import http from "node:http";
// Regression test for https://github.com/oven-sh/bun/issues/27061
// When http.ClientRequest.write() is called more than once (streaming data in chunks),
// Bun was stripping the explicitly-set Content-Length header and switching to
// Transfer-Encoding: chunked. Node.js preserves Content-Length in all cases.
describe("node:http ClientRequest preserves explicit Content-Length", () => {
test("with multiple req.write() calls", async () => {
const { promise, resolve, reject } = Promise.withResolvers<{
contentLength: string | undefined;
transferEncoding: string | undefined;
bodyLength: number;
}>();
const server = http.createServer((req, res) => {
const chunks: Buffer[] = [];
req.on("data", (chunk: Buffer) => chunks.push(chunk));
req.on("end", () => {
resolve({
contentLength: req.headers["content-length"],
transferEncoding: req.headers["transfer-encoding"],
bodyLength: Buffer.concat(chunks).length,
});
res.writeHead(200);
res.end("ok");
});
});
await new Promise<void>(res => server.listen(0, "127.0.0.1", res));
const port = (server.address() as any).port;
try {
const chunk1 = Buffer.alloc(100, "a");
const chunk2 = Buffer.alloc(100, "b");
const totalLength = chunk1.length + chunk2.length;
const req = http.request({
hostname: "127.0.0.1",
port,
method: "POST",
headers: {
"Content-Length": totalLength.toString(),
},
});
await new Promise<void>((res, rej) => {
req.on("error", rej);
req.on("response", () => res());
req.write(chunk1);
req.write(chunk2);
req.end();
});
const result = await promise;
expect(result.contentLength).toBe("200");
expect(result.transferEncoding).toBeUndefined();
expect(result.bodyLength).toBe(200);
} finally {
server.close();
}
});
test("with req.write() + req.end(data)", async () => {
const { promise, resolve, reject } = Promise.withResolvers<{
contentLength: string | undefined;
transferEncoding: string | undefined;
bodyLength: number;
}>();
const server = http.createServer((req, res) => {
const chunks: Buffer[] = [];
req.on("data", (chunk: Buffer) => chunks.push(chunk));
req.on("end", () => {
resolve({
contentLength: req.headers["content-length"],
transferEncoding: req.headers["transfer-encoding"],
bodyLength: Buffer.concat(chunks).length,
});
res.writeHead(200);
res.end("ok");
});
});
await new Promise<void>(res => server.listen(0, "127.0.0.1", res));
const port = (server.address() as any).port;
try {
const chunk1 = Buffer.alloc(100, "a");
const chunk2 = Buffer.alloc(100, "b");
const totalLength = chunk1.length + chunk2.length;
const req = http.request({
hostname: "127.0.0.1",
port,
method: "POST",
headers: {
"Content-Length": totalLength.toString(),
},
});
await new Promise<void>((res, rej) => {
req.on("error", rej);
req.on("response", () => res());
req.write(chunk1);
req.end(chunk2);
});
const result = await promise;
expect(result.contentLength).toBe("200");
expect(result.transferEncoding).toBeUndefined();
expect(result.bodyLength).toBe(200);
} finally {
server.close();
}
});
test("with three req.write() calls", async () => {
const { promise, resolve, reject } = Promise.withResolvers<{
contentLength: string | undefined;
transferEncoding: string | undefined;
bodyLength: number;
}>();
const server = http.createServer((req, res) => {
const chunks: Buffer[] = [];
req.on("data", (chunk: Buffer) => chunks.push(chunk));
req.on("end", () => {
resolve({
contentLength: req.headers["content-length"],
transferEncoding: req.headers["transfer-encoding"],
bodyLength: Buffer.concat(chunks).length,
});
res.writeHead(200);
res.end("ok");
});
});
await new Promise<void>(res => server.listen(0, "127.0.0.1", res));
const port = (server.address() as any).port;
try {
const chunk1 = Buffer.alloc(100, "a");
const chunk2 = Buffer.alloc(100, "b");
const chunk3 = Buffer.alloc(100, "c");
const totalLength = chunk1.length + chunk2.length + chunk3.length;
const req = http.request({
hostname: "127.0.0.1",
port,
method: "POST",
headers: {
"Content-Length": totalLength.toString(),
},
});
await new Promise<void>((res, rej) => {
req.on("error", rej);
req.on("response", () => res());
req.write(chunk1);
req.write(chunk2);
req.write(chunk3);
req.end();
});
const result = await promise;
expect(result.contentLength).toBe("300");
expect(result.transferEncoding).toBeUndefined();
expect(result.bodyLength).toBe(300);
} finally {
server.close();
}
});
test("single req.write() still works", async () => {
const { promise, resolve, reject } = Promise.withResolvers<{
contentLength: string | undefined;
transferEncoding: string | undefined;
bodyLength: number;
}>();
const server = http.createServer((req, res) => {
const chunks: Buffer[] = [];
req.on("data", (chunk: Buffer) => chunks.push(chunk));
req.on("end", () => {
resolve({
contentLength: req.headers["content-length"],
transferEncoding: req.headers["transfer-encoding"],
bodyLength: Buffer.concat(chunks).length,
});
res.writeHead(200);
res.end("ok");
});
});
await new Promise<void>(res => server.listen(0, "127.0.0.1", res));
const port = (server.address() as any).port;
try {
const data = Buffer.alloc(200, "x");
const req = http.request({
hostname: "127.0.0.1",
port,
method: "POST",
headers: {
"Content-Length": data.length.toString(),
},
});
await new Promise<void>((res, rej) => {
req.on("error", rej);
req.on("response", () => res());
req.write(data);
req.end();
});
const result = await promise;
expect(result.contentLength).toBe("200");
expect(result.transferEncoding).toBeUndefined();
expect(result.bodyLength).toBe(200);
} finally {
server.close();
}
});
test("without explicit Content-Length still uses chunked encoding", async () => {
const { promise, resolve, reject } = Promise.withResolvers<{
contentLength: string | undefined;
transferEncoding: string | undefined;
bodyLength: number;
}>();
const server = http.createServer((req, res) => {
const chunks: Buffer[] = [];
req.on("data", (chunk: Buffer) => chunks.push(chunk));
req.on("end", () => {
resolve({
contentLength: req.headers["content-length"],
transferEncoding: req.headers["transfer-encoding"],
bodyLength: Buffer.concat(chunks).length,
});
res.writeHead(200);
res.end("ok");
});
});
await new Promise<void>(res => server.listen(0, "127.0.0.1", res));
const port = (server.address() as any).port;
try {
const chunk1 = Buffer.alloc(100, "a");
const chunk2 = Buffer.alloc(100, "b");
const req = http.request({
hostname: "127.0.0.1",
port,
method: "POST",
// No Content-Length header
});
await new Promise<void>((res, rej) => {
req.on("error", rej);
req.on("response", () => res());
req.write(chunk1);
req.write(chunk2);
req.end();
});
const result = await promise;
// Without explicit Content-Length, chunked encoding should be used
expect(result.transferEncoding).toBe("chunked");
expect(result.bodyLength).toBe(200);
} finally {
server.close();
}
});
test("explicit Transfer-Encoding takes precedence over Content-Length", async () => {
const { promise, resolve } = Promise.withResolvers<{
contentLength: string | undefined;
transferEncoding: string | undefined;
bodyLength: number;
}>();
const server = http.createServer((req, res) => {
const chunks: Buffer[] = [];
req.on("data", (chunk: Buffer) => chunks.push(chunk));
req.on("end", () => {
resolve({
contentLength: req.headers["content-length"],
transferEncoding: req.headers["transfer-encoding"],
bodyLength: Buffer.concat(chunks).length,
});
res.writeHead(200);
res.end("ok");
});
});
await new Promise<void>(res => server.listen(0, "127.0.0.1", res));
const port = (server.address() as any).port;
try {
const chunk1 = Buffer.alloc(100, "a");
const chunk2 = Buffer.alloc(100, "b");
const req = http.request({
hostname: "127.0.0.1",
port,
method: "POST",
headers: {
"Content-Length": "200",
"Transfer-Encoding": "chunked",
},
});
await new Promise<void>((res, rej) => {
req.on("error", rej);
req.on("response", () => res());
req.write(chunk1);
req.write(chunk2);
req.end();
});
const result = await promise;
// When user explicitly sets Transfer-Encoding, it should be used
// and Content-Length should not be added
expect(result.transferEncoding).toBe("chunked");
expect(result.contentLength).toBeUndefined();
expect(result.bodyLength).toBe(200);
} finally {
server.close();
}
});
});

View File

@@ -92,13 +92,17 @@ test("cyclic imports with async dependencies should generate async wrappers", as
expect(bundled).toMatchInlineSnapshot(`
"var __defProp = Object.defineProperty;
var __returnValue = (v) => v;
function __exportSetter(name, newValue) {
this[name] = __returnValue.bind(null, newValue);
}
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, {
get: all[name],
enumerable: true,
configurable: true,
set: (newValue) => all[name] = () => newValue
set: __exportSetter.bind(all, name)
});
};
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -176,7 +180,7 @@ test("cyclic imports with async dependencies should generate async wrappers", as
var { AsyncEntryPoint: AsyncEntryPoint2 } = await Promise.resolve().then(() => exports_AsyncEntryPoint);
AsyncEntryPoint2();
//# debugId=986E7BD819E590FD64756E2164756E21
//# debugId=2020261114B67BB564756E2164756E21
//# sourceMappingURL=entryBuild.js.map
"
`);