Compare commits

..

28 Commits

Author SHA1 Message Date
Michael H
81960146ec Merge branch 'main' into claude/canary-test-modified-workflow 2025-11-29 16:03:45 +11:00
RiskyMH
a096619f4a remove test fails [no ci] 2025-11-29 16:02:37 +11:00
RiskyMH
a84466baf4 use slim for others 2025-11-29 15:40:49 +11:00
RiskyMH
aad00c9097 slim 2025-11-29 15:24:54 +11:00
Meghan Denny
56da7c4fd9 zig: address a VM todo (#23678) 2025-11-28 19:38:26 -08:00
Claude Bot
a5d7f36310 fix: normalize Windows backslashes in summary parsing
Windows XML has backslash paths (test\foo) instead of forward slashes.
Normalize to forward slashes so Windows results appear in summary.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 03:21:39 +00:00
Claude Bot
7f9b7b1a1a fix: use macos-15-intel for x64 runner
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 03:16:29 +00:00
Claude Bot
b4d5aa76f8 fix: use macos-13 instead of macos-15-large (free runner)
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 03:15:52 +00:00
Claude Bot
a122164334 refactor: use direct GitHub URLs for all canary downloads
Use direct GitHub release URLs for all platforms to avoid
any issues with bun.sh/download redirect.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 03:14:14 +00:00
Claude Bot
6c17c90f8d fix: use direct GitHub URL for darwin-x64 canary download
bun.sh/download returns old version for darwin/x64 canary.
Use direct GitHub release URL instead.

Also removed txt fallback parsing since it's no longer needed.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 03:12:18 +00:00
Claude Bot
dc5d302c52 fix: use no-cache for setup-bun to ensure fresh canary, fallback to txt parsing
- Add no-cache: true to setup-bun to avoid using stale cached bun versions
  (macos-15-large had old 1.1.22 cached which didn't support --reporter-outfile)
- Add fallback to parse .txt files when .xml files are missing (for older bun)
- Parse pass/fail counts from test output when XML is unavailable

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 03:05:31 +00:00
autofix-ci[bot]
98fcf2ade3 [autofix.ci] apply automated fixes 2025-11-29 02:59:38 +00:00
Claude Bot
7d578ed7fa Add workflow to test modified files against Bun canary
This workflow runs modified test files against Bun canary on PRs to verify
tests fail without the PR's changes. Runs on linux/macos/windows (x64/arm64).

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 02:52:46 +00:00
Meghan Denny
5bdb8ec0cb all: update to debian 13 (#24055) [publish images] 2025-11-28 15:01:40 -08:00
Meghan Denny
4cf9b794c9 ci: update buildkite agent to v3.114.0 (#25127) [publish images] 2025-11-28 15:01:20 -08:00
Meghan Denny
998ec54da9 test: fix spacing in sql.test.ts (#24691) 2025-11-28 14:40:58 -08:00
Jarred Sumner
0305f3d4d2 feat(url): implement URLPattern API (#25168)
## Summary

Implements the [URLPattern Web
API](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) based
on WebKit's implementation. URLPattern provides declarative pattern
matching for URLs, similar to how regular expressions work for strings.

### Features

- **Constructor**: Create patterns from strings or `URLPatternInit`
dictionaries
- **`test()`**: Check if a URL matches the pattern (returns boolean)
- **`exec()`**: Extract matched groups from a URL (returns
`URLPatternResult` or null)
- **Pattern properties**: `protocol`, `username`, `password`,
`hostname`, `port`, `pathname`, `search`, `hash`
- **`hasRegExpGroups`**: Detect if the pattern uses custom regular
expressions

### Example Usage

```js
// Match URLs with a user ID parameter
const pattern = new URLPattern({ pathname: '/users/:id' });

pattern.test('https://example.com/users/123'); // true
pattern.test('https://example.com/posts/456'); // false

const result = pattern.exec('https://example.com/users/123');
console.log(result.pathname.groups.id); // "123"

// Wildcard matching
const filesPattern = new URLPattern({ pathname: '/files/*' });
const match = filesPattern.exec('https://example.com/files/image.png');
console.log(match.pathname.groups[0]); // "image.png"
```

## Implementation Notes

- Adapted from WebKit's URLPattern implementation
- Modified JS bindings to work with Bun's infrastructure (simpler
`convertDictionary` patterns, WTF::Variant handling)
- Added IsoSubspaces for proper GC integration

## Test Plan

- [x] 408 tests from Web Platform Tests pass
- [x] Tests fail with system Bun (URLPattern not defined), pass with
debug build
- [x] Manual testing of basic functionality

Fixes https://github.com/oven-sh/bun/issues/2286

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-11-28 00:04:30 -08:00
Henrique Fonseca
1006a4fac2 Fix incorrect file name in React component test example 🌟 (#25167)
# What does this PR do?

Nyaa~ This PR fixes a small mistake in the documentation where the code
block for a React component test example was using the wrong filename!
(;ω;)💦

It was previously labeled as `matchers.d.ts`, but it should be something
like `myComponent.test.tsx` to properly reflect a test file for a React
component using `@testing-library/react`. 🧁

This makes the example clearer and more accurate for developers using
Bun to test their React components~! 💻🌸💕

# How did you verify your code works?

It's just docs, one single line 🥺

Pwease review and merge it when you can, senpai~~! UwU 🌈🫧
2025-11-27 23:15:34 -08:00
Michael H
c7f7d9bb82 run fmt (#25148)
prettier released a new update which seems to have changed a few
logistics
2025-11-28 17:51:45 +11:00
robobun
37bce389a0 docs: document inlining process.env.* values in static HTML bundling (#25084)
## Summary

- Add documentation for the `env` option that inlines `process.env.*`
values in frontend code when bundling HTML files
- Document runtime configuration via `bunfig.toml` `[serve.static]`
section for `bun ./index.html`
- Document production build configuration via CLI (`--env=PUBLIC_*`) and
`Bun.build` API (`env: "PUBLIC_*"`)
- Explain prefix filtering to avoid exposing sensitive environment
variables

## Test plan

- [x] Verify documentation renders correctly in local preview
- [x] Cross-reference with existing `env` documentation in
bundler/index.mdx

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

---------

Co-authored-by: Michael H <git@riskymh.dev>
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>
2025-11-28 17:51:11 +11:00
robobun
bab583497c fix(cli): correct --dry-run help text for bun publish (#25137)
## Summary
- Fix `bun publish --help` showing incorrect `--dry-run` description
("Don't install anything" → "Perform a dry run without making changes")
- The `--dry-run` flag is in a shared params array used by multiple
commands, so the new generic message works for all of them

Fixes #24806

## Test plan
- [x] Verify `bun publish --help` shows "Perform a dry run without
making changes" for --dry-run
- [x] Regression test added that validates the correct help text is
shown
- [x] Test passes with debug build, fails with system bun (validating it
tests the right thing)

🤖 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>
2025-11-27 18:12:07 -08:00
robobun
a83fceafc7 fix(http2): return server from setTimeout for method chaining (#25138)
## Summary
- Make `Http2Server.setTimeout()` and `Http2SecureServer.setTimeout()`
return `this` to enable method chaining
- Matches Node.js behavior where `server.setTimeout(1000).listen()`
works

Fixes #24924

## Test plan
- [x] Test that `Http2Server.setTimeout()` returns server instance
- [x] Test that `Http2SecureServer.setTimeout()` returns server instance
- [x] Test method chaining works (e.g.,
`server.setTimeout(1000).close()`)
- [x] Tests pass with debug build, fail with system bun

🤖 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>
2025-11-27 16:46:15 -08:00
robobun
ef8eef3df8 fix(http): stricter validation in chunked encoding parser (#25159)
## Summary
- Adds stricter validation for chunk boundaries in the HTTP chunked
transfer encoding parser
- Ensures conformance with RFC 9112 requirements for chunk formatting
- Adds additional test coverage for chunked encoding edge cases

## Test plan
- Added new tests in `test/js/bun/http/request-smuggling.test.ts`
- All existing HTTP tests pass
- `bun bd test test/js/bun/http/request-smuggling.test.ts` passes

🤖 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>
2025-11-27 16:29:35 -08:00
robobun
69b571da41 Delete claude.yml workflow (#25157) 2025-11-27 12:26:50 -08:00
robobun
908ab9ce30 feat(fetch): add proxy object format with headers support (#25090)
## Summary

- Extends `fetch()` proxy option to accept an object format: `proxy: {
url: string, headers?: Headers }`
- Allows sending custom headers to the proxy server (useful for proxy
authentication, custom routing headers, etc.)
- Headers are sent in CONNECT requests (for HTTPS targets) and direct
proxy requests (for HTTP targets)
- User-provided `Proxy-Authorization` header overrides auto-generated
credentials from URL

## Usage

```typescript
// Old format (still works)
fetch(url, { proxy: "http://proxy.example.com:8080" });

// New object format with headers
fetch(url, {
  proxy: {
    url: "http://proxy.example.com:8080",
    headers: {
      "Proxy-Authorization": "Bearer token",
      "X-Custom-Proxy-Header": "value"
    }
  }
});
```

## Test plan

- [x] Test proxy object with url string works same as string proxy
- [x] Test proxy object with headers sends headers to proxy (HTTP
target)
- [x] Test proxy object with headers sends headers in CONNECT request
(HTTPS target)
- [x] Test proxy object with Headers instance
- [x] Test proxy object with empty headers
- [x] Test proxy object with undefined headers
- [x] Test user-provided Proxy-Authorization overrides URL credentials
- [x] All existing proxy tests pass (25 total)

🤖 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>
2025-11-26 15:11:45 -08:00
robobun
43c46b1f77 fix(FormData): throw error instead of assertion failure on very large input (#25006)
## Summary

- Fix crash in `FormData.from()` when called with very large ArrayBuffer
input
- Add length check in C++ `toString` function against both Bun's
synthetic limit and WebKit's `String::MaxLength`
- For UTF-8 tagged strings, use simdutf to calculate actual UTF-16
length only when byte length exceeds the limit

## Root Cause

When `FormData.from()` was called with a very large ArrayBuffer (e.g.,
`new Uint32Array(913148244)` = ~3.6GB), the code would crash with:

```
ASSERTION FAILED: data.size() <= MaxLength
vendor/WebKit/Source/WTF/wtf/text/StringImpl.h(886)
```

The `toString()` function in `helpers.h` was only checking against
`Bun__stringSyntheticAllocationLimit` (which defaults to ~4GB), but not
against WebKit's `String::MaxLength` (INT32_MAX, ~2GB). When the input
exceeded `String::MaxLength`, `createWithoutCopying()` would fail with
an assertion.

## Changes

1. **helpers.h**: Added `|| str.len > WTF::String::MaxLength` checks to
all three code paths in `toString()`:
- UTF-8 tagged pointer path (with simdutf length calculation only when
needed)
   - External pointer path
   - Non-copying creation path

2. **url.zig**: Reverted the incorrect Zig-side check (UTF-8 byte length
!= UTF-16 character length)

## Test plan

- [x] Added test that verifies FormData.from with oversized input
doesn't crash
- [x] Verified original crash case now returns empty FormData instead of
crashing:
  ```js
  const v3 = new Uint32Array(913148244);
  FormData.from(v3); // No longer crashes
  ```

🤖 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>
2025-11-26 13:46:08 -08:00
robobun
a0c5f3dc69 fix(mmap): use coerceToInt64 for offset/size to prevent assertion failure (#25101)
## Summary

- Fix assertion failure in `Bun.mmap` when `offset` or `size` options
are non-numeric values
- Add validation to reject negative `offset`/`size` with clear error
messages

Minimal reproduction: `Bun.mmap("", { offset: null });`

## Root Cause

`Bun.mmap` was calling `toInt64()` directly on the `offset` and `size`
options without validating they are numbers first. `toInt64()` has an
assertion that the value must be a number or BigInt, which fails when
non-numeric values like `null` or functions are passed.

## Test plan

- [x] Added tests for negative offset/size rejection
- [x] Added tests for non-number inputs (null, undefined)
- [x] `bun bd test test/js/bun/util/mmap.test.js` passes

Closes ENG-22413

🤖 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>
2025-11-26 13:37:41 -08:00
robobun
5965ff18ea fix(test): fix assertion failure in expect.extend with non-JSFunction callables (#25099)
## Summary

- Fix debug assertion failure in `JSWrappingFunction` when
`expect.extend()` is called with objects containing non-`JSFunction`
callables
- The crash occurred because `jsCast<JSFunction*>` was used, which
asserts the value inherits from `JSFunction`, but callable class
constructors (like `Expect`) inherit from `InternalFunction` instead

## Changes

- Change `JSWrappingFunction` to store `JSObject*` instead of
`JSFunction*`
- Use `jsDynamicCast` instead of `jsCast` in `getWrappedFunction`
- Use `getObject()` instead of `jsCast` in `create()`

## Reproduction

```js
const jest = Bun.jest();
jest.expect.extend(jest);
```

Before fix (debug build):
```
ASSERTION FAILED: !from || from->JSCell::inherits(std::remove_pointer<To>::type::info())
JSCast.h(40) : To JSC::jsCast(From *) [To = JSC::JSFunction *, From = JSC::JSCell]
```

After fix: Properly throws `TypeError: expect.extend: 'jest' is not a
valid matcher`

## Test plan

- [x] Added regression test
`test/regression/issue/fuzzer-ENG-22942.test.ts`
- [x] Existing `expect-extend.test.js` tests pass (27 tests)
- [x] Build succeeds

Fixes ENG-22942

🤖 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>
2025-11-26 13:34:02 -08:00
100 changed files with 9117 additions and 565 deletions

View File

@@ -125,10 +125,10 @@ const testPlatforms = [
{ os: "darwin", arch: "aarch64", release: "13", tier: "previous" },
{ os: "darwin", arch: "x64", release: "14", tier: "latest" },
{ os: "darwin", arch: "x64", release: "13", tier: "previous" },
{ os: "linux", arch: "aarch64", distro: "debian", release: "12", tier: "latest" },
{ os: "linux", arch: "x64", distro: "debian", release: "12", tier: "latest" },
{ os: "linux", arch: "x64", baseline: true, distro: "debian", release: "12", tier: "latest" },
{ os: "linux", arch: "x64", profile: "asan", distro: "debian", release: "12", tier: "latest" },
{ os: "linux", arch: "aarch64", distro: "debian", release: "13", tier: "latest" },
{ os: "linux", arch: "x64", distro: "debian", release: "13", tier: "latest" },
{ os: "linux", arch: "x64", baseline: true, distro: "debian", release: "13", tier: "latest" },
{ os: "linux", arch: "x64", profile: "asan", distro: "debian", release: "13", tier: "latest" },
{ os: "linux", arch: "aarch64", distro: "ubuntu", release: "25.04", tier: "latest" },
{ os: "linux", arch: "aarch64", distro: "ubuntu", release: "24.04", tier: "latest" },
{ os: "linux", arch: "x64", distro: "ubuntu", release: "25.04", tier: "latest" },

View File

@@ -6,7 +6,7 @@ on:
jobs:
auto-assign:
runs-on: ubuntu-latest
runs-on: ubuntu-slim
if: github.event.label.name == 'types'
permissions:
issues: write

View File

@@ -7,7 +7,7 @@ on:
jobs:
auto-label:
if: github.event.pull_request.user.login == 'robobun' || contains(github.event.pull_request.body, '🤖 Generated with')
runs-on: ubuntu-latest
runs-on: ubuntu-slim
permissions:
contents: read
pull-requests: write

View File

@@ -0,0 +1,190 @@
name: Test Modified Files (Canary)
on:
pull_request:
paths:
- "test/**/*"
jobs:
test-canary:
name: Test (${{ matrix.os }}-${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
continue-on-error: true
strategy:
fail-fast: false
matrix:
include:
- os: linux
arch: x64
runner: ubuntu-latest
bun-download-url: https://github.com/oven-sh/bun/releases/download/canary/bun-linux-x64.zip
- os: linux
arch: arm64
runner: ubuntu-24.04-arm
bun-download-url: https://github.com/oven-sh/bun/releases/download/canary/bun-linux-aarch64.zip
- os: macos
arch: x64
runner: macos-15-intel
bun-download-url: https://github.com/oven-sh/bun/releases/download/canary/bun-darwin-x64.zip
- os: macos
arch: arm64
runner: macos-15
bun-download-url: https://github.com/oven-sh/bun/releases/download/canary/bun-darwin-aarch64.zip
- os: windows
arch: x64
runner: windows-latest
bun-download-url: https://github.com/oven-sh/bun/releases/download/canary/bun-windows-x64.zip
steps:
- uses: actions/checkout@v4
- name: Setup Bun (Canary)
uses: oven-sh/setup-bun@v2
with:
bun-download-url: ${{ matrix.bun-download-url }}
- name: Show Bun version
run: bun --version
- name: Install dependencies
run: |
bun install --frozen-lockfile
cd test && bun install --frozen-lockfile
- name: Get modified test files
id: get-tests
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
files=$(gh pr view ${{ github.event.pull_request.number }} --json files -q '.files[].path' | grep -E '^test/.*\.(test|spec)\.(ts|tsx|js|mjs|cjs)$' | tr '\n' ' ')
echo "files=$files" >> $GITHUB_OUTPUT
echo "Modified test files: $files"
- name: Run tests
if: steps.get-tests.outputs.files != ''
continue-on-error: true
shell: bash
run: |
set +e
export GITHUB_ACTIONS=""
export CI=""
mkdir -p test-results
TEST_FILES="${{ steps.get-tests.outputs.files }}"
echo "Running tests on: $TEST_FILES"
for file in $TEST_FILES; do
echo "Testing: $file"
sanitized=$(echo "$file" | tr '/' '_')
# Capture both stdout and JUnit XML
bun test --reporter=junit --reporter-outfile="test-results/${sanitized}.xml" "$file" 2>&1 | tee "test-results/${sanitized}.txt" || true
done
- name: Create empty results if none
if: always()
shell: bash
run: |
mkdir -p test-results
if [ -z "$(ls -A test-results 2>/dev/null)" ]; then
echo '<testsuites></testsuites>' > test-results/empty.xml
echo 'No tests ran' > test-results/empty.txt
fi
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.os }}-${{ matrix.arch }}
path: test-results/
retention-days: 7
summary:
name: Test Summary
needs: test-canary
if: always()
runs-on: ubuntu-slim
steps:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: canary
no-cache: true
- name: Download test results
uses: actions/download-artifact@v4
with:
pattern: test-results-*
merge-multiple: false
- name: Generate Summary
run: |
bun add fast-xml-parser
{
echo "## Canary Bun Test Summary"
echo ""
echo "This workflow tests modified test files against **Bun Canary** to verify they fail without the PR's changes."
echo ""
echo "> **Note:** Test failures are expected - this workflow verifies that tests fail on stable/canary Bun without the PR's changes."
echo ""
} >> $GITHUB_STEP_SUMMARY
bun -e '
import { XMLParser } from "fast-xml-parser";
import { readFileSync, readdirSync, existsSync } from "fs";
const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "" });
const dirs = readdirSync(".").filter(d => d.startsWith("test-results-"));
const byFile = {};
for (const dir of dirs) {
const platform = dir.replace("test-results-", "");
if (!existsSync(dir)) continue;
const xmlFiles = readdirSync(dir).filter(f => f.endsWith(".xml"));
for (const xmlFile of xmlFiles) {
const baseName = xmlFile.replace(".xml", "");
const xmlPath = `${dir}/${xmlFile}`;
const txtPath = `${dir}/${baseName}.txt`;
const content = readFileSync(xmlPath, "utf-8");
const parsed = parser.parse(content);
const testsuites = parsed.testsuites;
if (!testsuites) continue;
const suites = Array.isArray(testsuites.testsuite) ? testsuites.testsuite : testsuites.testsuite ? [testsuites.testsuite] : [];
for (const suite of suites) {
// Normalize Windows backslashes to forward slashes
const file = (suite.file || suite.name)?.replace(/\\/g, "/");
if (!file?.startsWith("test/")) continue;
const passed = parseInt(suite.tests || 0) - parseInt(suite.failures || 0) - parseInt(suite.skipped || 0);
const output = existsSync(txtPath) ? readFileSync(txtPath, "utf-8") : "No output captured";
if (!byFile[file]) byFile[file] = [];
byFile[file].push({ platform, passed, total: parseInt(suite.tests || 0), output });
}
}
}
const lines = [];
for (const [file, platforms] of Object.entries(byFile)) {
lines.push(`### \`${file}\`\n`);
for (const p of platforms) {
const status = `${p.passed}/${p.total}`;
lines.push(`<details>`);
lines.push(`<summary><code>${p.platform}</code> (${status})</summary>\n`);
lines.push("```");
lines.push(p.output.trim());
lines.push("```");
lines.push(`\n</details>\n`);
}
}
console.log(lines.join("\n"));
' >> $GITHUB_STEP_SUMMARY

View File

@@ -1,66 +0,0 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
github.repository == 'oven-sh/bun' &&
(
(github.event_name == 'issue_comment' && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR')) ||
(github.event_name == 'pull_request_review_comment' && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR')) ||
(github.event_name == 'pull_request_review' && (github.event.review.author_association == 'MEMBER' || github.event.review.author_association == 'OWNER' || github.event.review.author_association == 'COLLABORATOR')) ||
(github.event_name == 'issues' && (github.event.issue.author_association == 'MEMBER' || github.event.issue.author_association == 'OWNER' || github.event.issue.author_association == 'COLLABORATOR'))
) &&
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: claude
env:
IS_SANDBOX: 1
container:
image: localhost:5000/claude-bun:latest
options: --privileged --user 1000:1000
permissions:
contents: read
id-token: write
steps:
- name: Checkout repository
working-directory: /workspace/bun
run: |
git config --global user.email "claude-bot@bun.sh" && \
git config --global user.name "Claude Bot" && \
git config --global url."git@github.com:".insteadOf "https://github.com/" && \
git config --global url."git@github.com:".insteadOf "http://github.com/" && \
git config --global --add safe.directory /workspace/bun && \
git config --global push.default current && \
git config --global pull.rebase true && \
git config --global init.defaultBranch main && \
git config --global core.editor "vim" && \
git config --global color.ui auto && \
git config --global fetch.prune true && \
git config --global diff.colorMoved zebra && \
git config --global merge.conflictStyle diff3 && \
git config --global rerere.enabled true && \
git config --global core.autocrlf input
git fetch origin ${{ github.event.pull_request.head.sha }}
git checkout ${{ github.event.pull_request.head.ref }}
git reset --hard origin/${{ github.event.pull_request.head.ref }}
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
timeout_minutes: "180"
claude_args: |
--dangerously-skip-permissions
--system-prompt "You are working on the Bun codebase"
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

View File

@@ -14,7 +14,7 @@ env:
jobs:
bump:
name: "Bump version"
runs-on: ubuntu-latest
runs-on: ubuntu-slim
permissions:
pull-requests: write
contents: write

View File

@@ -7,7 +7,7 @@ on:
jobs:
check-update:
runs-on: ubuntu-latest
runs-on: ubuntu-slim
permissions:
contents: write
pull-requests: write

View File

@@ -7,7 +7,7 @@ on:
jobs:
check-update:
runs-on: ubuntu-latest
runs-on: ubuntu-slim
permissions:
contents: write
pull-requests: write

View File

@@ -7,7 +7,7 @@ on:
jobs:
check-update:
runs-on: ubuntu-latest
runs-on: ubuntu-slim
permissions:
contents: write
pull-requests: write

View File

@@ -7,7 +7,7 @@ on:
jobs:
check-update:
runs-on: ubuntu-latest
runs-on: ubuntu-slim
permissions:
contents: write
pull-requests: write

View File

@@ -7,7 +7,7 @@ on:
jobs:
check-update:
runs-on: ubuntu-latest
runs-on: ubuntu-slim
permissions:
contents: write
pull-requests: write

View File

@@ -7,7 +7,7 @@ on:
jobs:
check-update:
runs-on: ubuntu-latest
runs-on: ubuntu-slim
permissions:
contents: write
pull-requests: write

View File

@@ -7,7 +7,7 @@ on:
jobs:
check-update:
runs-on: ubuntu-latest
runs-on: ubuntu-slim
permissions:
contents: write
pull-requests: write

View File

@@ -7,7 +7,7 @@ on:
jobs:
check-and-update:
runs-on: ubuntu-latest
runs-on: ubuntu-slim
permissions:
contents: write
pull-requests: write

View File

@@ -7,7 +7,7 @@ on:
jobs:
check-update:
runs-on: ubuntu-latest
runs-on: ubuntu-slim
permissions:
contents: write
pull-requests: write

View File

@@ -7,7 +7,7 @@ on:
jobs:
check-update:
runs-on: ubuntu-latest
runs-on: ubuntu-slim
permissions:
contents: write
pull-requests: write

View File

@@ -7,7 +7,7 @@ on:
jobs:
check-update:
runs-on: ubuntu-latest
runs-on: ubuntu-slim
permissions:
contents: write
pull-requests: write

View File

@@ -10,7 +10,7 @@ on:
jobs:
publish:
name: "Publish to VS Code Marketplace"
runs-on: ubuntu-latest
runs-on: ubuntu-slim
steps:
- name: Checkout
uses: actions/checkout@v4

16
.vscode/settings.json vendored
View File

@@ -27,18 +27,22 @@
"git.ignoreLimitWarning": true,
// Zig
"zig.initialSetupDone": true,
"zig.buildOption": "build",
// "zig.initialSetupDone": true,
// "zig.buildOption": "build",
"zig.zls.zigLibPath": "${workspaceFolder}/vendor/zig/lib",
"zig.buildArgs": ["-Dgenerated-code=./build/debug/codegen", "--watch", "-fincremental"],
"zig.zls.buildOnSaveStep": "check",
"zig.buildOnSaveArgs": [
"-Dgenerated-code=./build/debug/codegen",
"--watch",
"-fincremental"
],
// "zig.zls.buildOnSaveStep": "check",
// "zig.zls.enableBuildOnSave": true,
// "zig.buildOnSave": true,
"zig.buildFilePath": "${workspaceFolder}/build.zig",
// "zig.buildFilePath": "${workspaceFolder}/build.zig",
"zig.path": "${workspaceFolder}/vendor/zig/zig.exe",
"zig.zls.path": "${workspaceFolder}/vendor/zig/zls.exe",
"zig.formattingProvider": "zls",
"zig.zls.enableInlayHints": false,
// "zig.zls.enableInlayHints": false,
"[zig]": {
"editor.tabSize": 4,
"editor.useTabStops": false,

View File

@@ -0,0 +1,48 @@
import { bench, group, run } from "../runner.mjs";
const patterns = [
{ name: "string pattern", input: "https://(sub.)?example(.com/)foo" },
{ name: "hostname IDN", input: { hostname: "xn--caf-dma.com" } },
{
name: "pathname + search + hash + baseURL",
input: {
pathname: "/foo",
search: "bar",
hash: "baz",
baseURL: "https://example.com:8080",
},
},
{ name: "pathname with regex", input: { pathname: "/([[a-z]--a])" } },
{ name: "named groups", input: { pathname: "/users/:id/posts/:postId" } },
{ name: "wildcard", input: { pathname: "/files/*" } },
];
const testURL = "https://sub.example.com/foo";
group("URLPattern parse (constructor)", () => {
for (const { name, input } of patterns) {
bench(name, () => {
return new URLPattern(input);
});
}
});
group("URLPattern.test()", () => {
for (const { name, input } of patterns) {
const pattern = new URLPattern(input);
bench(name, () => {
return pattern.test(testURL);
});
}
});
group("URLPattern.exec()", () => {
for (const { name, input } of patterns) {
const pattern = new URLPattern(input);
bench(name, () => {
return pattern.exec(testURL);
});
}
});
await run();

View File

@@ -1,4 +1,4 @@
FROM debian:bookworm-slim AS build
FROM debian:trixie-slim AS build
# https://github.com/oven-sh/bun/releases
ARG BUN_VERSION=latest
@@ -55,7 +55,7 @@ RUN apt-get update -qq \
&& which bun \
&& bun --version
FROM debian:bookworm-slim
FROM debian:trixie-slim
# Disable the runtime transpiler cache by default inside Docker containers.
# On ephemeral containers, the cache is not useful

View File

@@ -1,4 +1,4 @@
FROM debian:bookworm-slim AS build
FROM debian:trixie-slim AS build
# https://github.com/oven-sh/bun/releases
ARG BUN_VERSION=latest
@@ -56,7 +56,7 @@ RUN apt-get update -qq \
&& rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \
&& chmod +x /usr/local/bin/bun
FROM debian:bookworm
FROM debian:trixie
COPY docker-entrypoint.sh /usr/local/bin
COPY --from=build /usr/local/bin/bun /usr/local/bin/bun

View File

@@ -1,4 +1,4 @@
FROM debian:bookworm-slim AS build
FROM debian:trixie-slim AS build
# https://github.com/oven-sh/bun/releases
ARG BUN_VERSION=latest
@@ -55,7 +55,7 @@ RUN apt-get update -qq \
&& which bun \
&& bun --version
FROM gcr.io/distroless/base-nossl-debian11
FROM gcr.io/distroless/base-nossl-debian13
# Disable the runtime transpiler cache by default inside Docker containers.
# On ephemeral containers, the cache is not useful

View File

@@ -492,6 +492,28 @@ Bun will lazily resolve and load each plugin and use them to bundle your routes.
the CLI.
</Note>
## Inline Environment Variables
Bun can replace `process.env.*` references in your frontend JavaScript and TypeScript with their actual values at build time. Configure the `env` option in your `bunfig.toml`:
```toml title="bunfig.toml" icon="settings"
[serve.static]
env = "PUBLIC_*" # only inline env vars starting with PUBLIC_ (recommended)
# env = "inline" # inline all environment variables
# env = "disable" # disable env var replacement (default)
```
<Note>
This only works with literal `process.env.FOO` references, not `import.meta.env` or indirect access like `const env =
process.env; env.FOO`.
If an environment variable is not set, you may see runtime errors like `ReferenceError: process
is not defined` in the browser.
</Note>
See the [HTML & static sites documentation](/bundler/html-static#inline-environment-variables) for more details on build-time configuration and examples.
## How It Works
Bun uses `HTMLRewriter` to scan for `<script>` and `<link>` tags in HTML files, uses them as entrypoints for Bun's bundler, generates an optimized bundle for the JavaScript/TypeScript/TSX/JSX and CSS files, and serves the result.

View File

@@ -262,6 +262,93 @@ Then, reference TailwindCSS in your HTML via `<link>` tag, `@import` in CSS, or
<Info>Only one of those are necessary, not all three.</Info>
## Inline environment variables
Bun can replace `process.env.*` references in your JavaScript and TypeScript with their actual values at build time. This is useful for injecting configuration like API URLs or feature flags into your frontend code.
### Dev server (runtime)
To inline environment variables when using `bun ./index.html`, configure the `env` option in your `bunfig.toml`:
```toml title="bunfig.toml" icon="settings"
[serve.static]
env = "PUBLIC_*" # only inline env vars starting with PUBLIC_ (recommended)
# env = "inline" # inline all environment variables
# env = "disable" # disable env var replacement (default)
```
<Note>
This only works with literal `process.env.FOO` references, not `import.meta.env` or indirect access like `const env =
process.env; env.FOO`.
If an environment variable is not set, you may see runtime errors like `ReferenceError: process
is not defined` in the browser.
</Note>
Then run the dev server:
```bash terminal icon="terminal"
PUBLIC_API_URL=https://api.example.com bun ./index.html
```
### Build for production
When building static HTML for production, use the `env` option to inline environment variables:
<Tabs>
<Tab title="CLI">
```bash terminal icon="terminal"
# Inline all environment variables
bun build ./index.html --outdir=dist --env=inline
# Only inline env vars with a specific prefix (recommended)
bun build ./index.html --outdir=dist --env=PUBLIC_*
```
</Tab>
<Tab title="API">
```ts title="build.ts" icon="/icons/typescript.svg"
// Inline all environment variables
await Bun.build({
entrypoints: ["./index.html"],
outdir: "./dist",
env: "inline", // [!code highlight]
});
// Only inline env vars with a specific prefix (recommended)
await Bun.build({
entrypoints: ["./index.html"],
outdir: "./dist",
env: "PUBLIC_*", // [!code highlight]
});
```
</Tab>
</Tabs>
### Example
Given this source file:
```ts title="app.ts" icon="/icons/typescript.svg"
const apiUrl = process.env.PUBLIC_API_URL;
console.log(`API URL: ${apiUrl}`);
```
And running with `PUBLIC_API_URL=https://api.example.com`:
```bash terminal icon="terminal"
PUBLIC_API_URL=https://api.example.com bun build ./index.html --outdir=dist --env=PUBLIC_*
```
The bundled output will contain:
```js title="dist/app.js" icon="/icons/javascript.svg"
const apiUrl = "https://api.example.com";
console.log(`API URL: ${apiUrl}`);
```
## Echo console logs from browser to terminal
Bun's dev server supports streaming console logs from the browser to the terminal.

View File

@@ -9,18 +9,42 @@ In Bun, `fetch` supports sending requests through an HTTP or HTTPS proxy. This i
```ts proxy.ts icon="/icons/typescript.svg"
await fetch("https://example.com", {
// The URL of the proxy server
proxy: "https://usertitle:password@proxy.example.com:8080",
proxy: "https://username:password@proxy.example.com:8080",
});
```
---
The `proxy` option is a URL string that specifies the proxy server. It can include the username and password if the proxy requires authentication. It can be `http://` or `https://`.
The `proxy` option can be a URL string or an object with `url` and optional `headers`. The URL can include the username and password if the proxy requires authentication. It can be `http://` or `https://`.
---
## Custom proxy headers
To send custom headers to the proxy server (useful for proxy authentication tokens, custom routing, etc.), use the object format:
```ts proxy-headers.ts icon="/icons/typescript.svg"
await fetch("https://example.com", {
proxy: {
url: "https://proxy.example.com:8080",
headers: {
"Proxy-Authorization": "Bearer my-token",
"X-Proxy-Region": "us-east-1",
},
},
});
```
The `headers` property accepts a plain object or a `Headers` instance. These headers are sent directly to the proxy server in `CONNECT` requests (for HTTPS targets) or in the proxy request (for HTTP targets).
If you provide a `Proxy-Authorization` header, it will override any credentials specified in the proxy URL.
---
## Environment variables
You can also set the `$HTTP_PROXY` or `$HTTPS_PROXY` environment variable to the proxy URL. This is useful when you want to use the same proxy for all requests.
```sh terminal icon="terminal"
HTTPS_PROXY=https://usertitle:password@proxy.example.com:8080 bun run index.ts
HTTPS_PROXY=https://username:password@proxy.example.com:8080 bun run index.ts
```

View File

@@ -76,7 +76,7 @@ declare module "bun:test" {
You should now be able to use Testing Library in your tests
```ts matchers.d.ts icon="/icons/typescript.svg"
```tsx myComponent.test.tsx icon="/icons/typescript.svg"
import { test, expect } from "bun:test";
import { screen, render } from "@testing-library/react";
import { MyComponent } from "./myComponent";

View File

@@ -51,7 +51,7 @@ const response = await fetch("http://example.com", {
### Proxying requests
To proxy a request, pass an object with the `proxy` property set to a URL.
To proxy a request, pass an object with the `proxy` property set to a URL string:
```ts
const response = await fetch("http://example.com", {
@@ -59,6 +59,22 @@ const response = await fetch("http://example.com", {
});
```
You can also use an object format to send custom headers to the proxy server:
```ts
const response = await fetch("http://example.com", {
proxy: {
url: "http://proxy.com",
headers: {
"Proxy-Authorization": "Bearer my-token",
"X-Custom-Proxy-Header": "value",
},
},
});
```
The `headers` are sent directly to the proxy in `CONNECT` requests (for HTTPS targets) or in the proxy request (for HTTP targets). If you provide a `Proxy-Authorization` header, it overrides any credentials in the proxy URL.
### Custom headers
To set custom headers, pass an object with the `headers` property set to an object.

View File

@@ -87,7 +87,7 @@
"node:test:cp": "bun ./scripts/fetch-node-test.ts ",
"clean:zig": "rm -rf build/debug/cache/zig build/debug/CMakeCache.txt 'build/debug/*.o' .zig-cache zig-out || true",
"machine:linux:ubuntu": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=linux --distro=ubuntu --release=25.04",
"machine:linux:debian": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=linux --distro=debian --release=12",
"machine:linux:debian": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=linux --distro=debian --release=13",
"machine:linux:alpine": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=linux --distro=alpine --release=3.22",
"machine:linux:amazonlinux": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=linux --distro=amazonlinux --release=2023",
"machine:windows:2019": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=windows --release=2019",

View File

@@ -1740,10 +1740,9 @@ declare module "bun" {
* @default "esm"
*/
format?: /**
* ECMAScript Module format
*/
| "esm"
* ECMAScript Module format
*/
| "esm"
/**
* CommonJS format
* **Experimental**
@@ -3317,10 +3316,10 @@ declare module "bun" {
function color(
input: ColorInput,
outputFormat?: /**
* True color ANSI color string, for use in terminals
* @example \x1b[38;2;100;200;200m
*/
| "ansi"
* True color ANSI color string, for use in terminals
* @example \x1b[38;2;100;200;200m
*/
| "ansi"
| "ansi-16"
| "ansi-16m"
/**
@@ -5651,11 +5650,17 @@ declare module "bun" {
maxBuffer?: number;
}
interface SpawnSyncOptions<In extends Writable, Out extends Readable, Err extends Readable>
extends BaseOptions<In, Out, Err> {}
interface SpawnSyncOptions<In extends Writable, Out extends Readable, Err extends Readable> extends BaseOptions<
In,
Out,
Err
> {}
interface SpawnOptions<In extends Writable, Out extends Readable, Err extends Readable>
extends BaseOptions<In, Out, Err> {
interface SpawnOptions<In extends Writable, Out extends Readable, Err extends Readable> extends BaseOptions<
In,
Out,
Err
> {
/**
* If true, stdout and stderr pipes will not automatically start reading
* data. Reading will only begin when you access the `stdout` or `stderr`

View File

@@ -1920,14 +1920,44 @@ interface BunFetchRequestInit extends RequestInit {
* Override http_proxy or HTTPS_PROXY
* This is a custom property that is not part of the Fetch API specification.
*
* Can be a string URL or an object with `url` and optional `headers`.
*
* @example
* ```js
* // String format
* const response = await fetch("http://example.com", {
* proxy: "https://username:password@127.0.0.1:8080"
* });
*
* // Object format with custom headers sent to the proxy
* const response = await fetch("http://example.com", {
* proxy: {
* url: "https://127.0.0.1:8080",
* headers: {
* "Proxy-Authorization": "Bearer token",
* "X-Custom-Proxy-Header": "value"
* }
* }
* });
* ```
*
* If a `Proxy-Authorization` header is provided in `proxy.headers`, it takes
* precedence over credentials parsed from the proxy URL.
*/
proxy?: string;
proxy?:
| string
| {
/**
* The proxy URL
*/
url: string;
/**
* Custom headers to send to the proxy server.
* These headers are sent in the CONNECT request (for HTTPS targets)
* or in the proxy request (for HTTP targets).
*/
headers?: Bun.HeadersInit;
};
/**
* Override the default S3 options

View File

@@ -213,6 +213,16 @@ namespace uWS {
emitSoon = std::string_view(data.data(), chunkSize(state) - 2);
shouldEmit = true;
}
// Validate that the chunk terminator is \r\n to prevent request smuggling
// The last 2 bytes of the chunk must be exactly \r\n
// Note: chunkSize always includes +2 for the terminator (added in consumeHexNumber),
// and chunks with size 0 (chunkSize == 2) are handled earlier at line 190.
// Therefore chunkSize >= 3 here, so no underflow is possible.
size_t terminatorOffset = chunkSize(state) - 2;
if (data[terminatorOffset] != '\r' || data[terminatorOffset + 1] != '\n') {
state = STATE_IS_ERROR;
return std::nullopt;
}
data.remove_prefix(chunkSize(state));
state = STATE_IS_CHUNKED;
if (shouldEmit) {

View File

@@ -1,5 +1,5 @@
#!/bin/sh
# Version: 20
# Version: 21
# A script that installs the dependencies needed to build and test Bun.
# This should work on macOS and Linux with a POSIX shell.
@@ -1565,7 +1565,7 @@ install_buildkite() {
return
fi
buildkite_version="3.87.0"
buildkite_version="3.114.0"
case "$arch" in
aarch64)
buildkite_arch="arm64"
@@ -1766,7 +1766,7 @@ ensure_no_tmpfs() {
if ! [ "$os" = "linux" ]; then
return
fi
if ! [ "$distro" = "ubuntu" ]; then
if ! ( [ "$distro" = "ubuntu" ] || [ "$distro" = "debian" ] ); then
return
fi

View File

@@ -2842,7 +2842,7 @@ export function printEnvironment() {
if (isCI) {
startGroup("Environment", () => {
for (const [key, value] of Object.entries(process.env)) {
for (const [key, value] of Object.entries(process.env).toSorted()) {
console.log(`${key}:`, value);
}
});

View File

@@ -3512,8 +3512,7 @@ pub const IPCInstanceUnion = union(enum) {
/// IPC is put in this "enabled but not started" state when IPC is detected
/// but the client JavaScript has not yet done `.on("message")`
waiting: struct {
// TODO: rename to `fd`
info: bun.FD,
fd: bun.FD,
mode: IPC.Mode,
},
initialized: *IPCInstance,
@@ -3586,9 +3585,9 @@ pub const IPCInstance = struct {
pub const Handlers = IPC.NewIPCHandler(IPCInstance);
};
pub fn initIPCInstance(this: *VirtualMachine, info: bun.FD, mode: IPC.Mode) void {
IPC.log("initIPCInstance {f}", .{info});
this.ipc = .{ .waiting = .{ .info = info, .mode = mode } };
pub fn initIPCInstance(this: *VirtualMachine, fd: bun.FD, mode: IPC.Mode) void {
IPC.log("initIPCInstance {f}", .{fd});
this.ipc = .{ .waiting = .{ .fd = fd, .mode = mode } };
}
pub fn getIPCInstance(this: *VirtualMachine) ?*IPCInstance {
@@ -3596,7 +3595,7 @@ pub fn getIPCInstance(this: *VirtualMachine) ?*IPCInstance {
if (this.ipc.? != .waiting) return this.ipc.?.initialized;
const opts = this.ipc.?.waiting;
IPC.log("getIPCInstance {f}", .{opts.info});
IPC.log("getIPCInstance {f}", .{opts.fd});
this.event_loop.ensureWaker();
@@ -3615,7 +3614,7 @@ pub fn getIPCInstance(this: *VirtualMachine) ?*IPCInstance {
instance.data = .init(opts.mode, .{ .virtual_machine = instance }, .uninitialized);
const socket = IPC.Socket.fromFd(context, opts.info, IPC.SendQueue, &instance.data, null, true) orelse {
const socket = IPC.Socket.fromFd(context, opts.fd, IPC.SendQueue, &instance.data, null, true) orelse {
instance.deinit();
this.ipc = null;
Output.warn("Unable to start IPC socket", .{});
@@ -3637,10 +3636,10 @@ pub fn getIPCInstance(this: *VirtualMachine) ?*IPCInstance {
this.ipc = .{ .initialized = instance };
instance.data.windowsConfigureClient(opts.info) catch {
instance.data.windowsConfigureClient(opts.fd) catch {
instance.deinit();
this.ipc = null;
Output.warn("Unable to start IPC pipe '{f}'", .{opts.info});
Output.warn("Unable to start IPC pipe '{f}'", .{opts.fd});
return null;
};

View File

@@ -1212,11 +1212,19 @@ pub fn mmapFile(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.
}
if (try opts.get(globalThis, "size")) |value| {
map_size = @as(usize, @intCast(value.toInt64()));
const size_value = try value.coerceToInt64(globalThis);
if (size_value < 0) {
return globalThis.throwInvalidArguments("size must be a non-negative integer", .{});
}
map_size = @intCast(size_value);
}
if (try opts.get(globalThis, "offset")) |value| {
offset = @as(usize, @intCast(value.toInt64()));
const offset_value = try value.coerceToInt64(globalThis);
if (offset_value < 0) {
return globalThis.throwInvalidArguments("offset must be a non-negative integer", .{});
}
offset = @intCast(offset_value);
offset = std.mem.alignBackwardAnyAlign(usize, offset, std.heap.pageSize());
}
}

View File

@@ -24,7 +24,7 @@ JS_EXPORT_PRIVATE JSWrappingFunction* JSWrappingFunction::create(
Zig::NativeFunctionPtr functionPointer,
JSC::JSValue wrappedFnValue)
{
JSC::JSFunction* wrappedFn = jsCast<JSC::JSFunction*>(wrappedFnValue.asCell());
JSC::JSObject* wrappedFn = wrappedFnValue.getObject();
ASSERT(wrappedFn != nullptr);
auto nameStr = symbolName->tag == BunStringTag::Empty ? WTF::emptyString() : symbolName->toWTFString();
@@ -75,9 +75,9 @@ extern "C" JSC::EncodedJSValue Bun__JSWrappingFunction__getWrappedFunction(
Zig::GlobalObject* globalObject)
{
JSC::JSValue thisValue = JSC::JSValue::decode(thisValueEncoded);
JSWrappingFunction* thisObject = jsCast<JSWrappingFunction*>(thisValue.asCell());
JSWrappingFunction* thisObject = jsDynamicCast<JSWrappingFunction*>(thisValue.asCell());
if (thisObject != nullptr) {
JSC::JSFunction* wrappedFn = thisObject->m_wrappedFn.get();
JSC::JSObject* wrappedFn = thisObject->m_wrappedFn.get();
return JSC::JSValue::encode(wrappedFn);
}
return {};

View File

@@ -59,7 +59,7 @@ public:
}
private:
JSWrappingFunction(JSC::VM& vm, JSC::NativeExecutable* native, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSFunction* wrappedFn)
JSWrappingFunction(JSC::VM& vm, JSC::NativeExecutable* native, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* wrappedFn)
: Base(vm, native, globalObject, structure)
, m_wrappedFn(wrappedFn, JSC::WriteBarrierEarlyInit)
{
@@ -69,7 +69,7 @@ private:
DECLARE_VISIT_CHILDREN;
JSC::WriteBarrier<JSC::JSFunction> m_wrappedFn;
JSC::WriteBarrier<JSC::JSObject> m_wrappedFn;
};
}

View File

@@ -166,7 +166,7 @@ String URLDecomposition::port() const
}
// Outer optional is whether we could parse at all. Inner optional is "no port specified".
static std::optional<std::optional<uint16_t>> parsePort(StringView string, StringView protocol)
std::optional<std::optional<uint16_t>> URLDecomposition::parsePort(StringView string, StringView protocol)
{
// https://url.spec.whatwg.org/#port-state with state override given.
uint32_t port { 0 };

View File

@@ -35,6 +35,10 @@ namespace WebCore {
class URLDecomposition {
public:
// Parse a port string with optional protocol for default port detection
// Returns nullopt on parse error, or optional<uint16_t> (nullopt means empty/default port)
static std::optional<std::optional<uint16_t>> parsePort(StringView port, StringView protocol);
String origin() const;
WEBCORE_EXPORT String protocol() const;

View File

@@ -130,6 +130,7 @@
#include "JSTextDecoderStream.h"
#include "JSTransformStream.h"
#include "JSTransformStreamDefaultController.h"
#include "JSURLPattern.h"
#include "JSURLSearchParams.h"
#include "JSWasmStreamingCompiler.h"
#include "JSWebSocket.h"
@@ -1009,6 +1010,7 @@ WEBCORE_GENERATED_CONSTRUCTOR_GETTER(TextEncoderStream);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(TextDecoderStream);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(TransformStream)
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(TransformStreamDefaultController)
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(URLPattern);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(URLSearchParams);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(WebSocket);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(Worker);

View File

@@ -84,6 +84,7 @@
TransformStream TransformStreamConstructorCallback PropertyCallback
TransformStreamDefaultController TransformStreamDefaultControllerConstructorCallback PropertyCallback
URL DOMURLConstructorCallback DontEnum|PropertyCallback
URLPattern URLPatternConstructorCallback PropertyCallback
URLSearchParams URLSearchParamsConstructorCallback DontEnum|PropertyCallback
WebSocket WebSocketConstructorCallback PropertyCallback
Worker WorkerConstructorCallback PropertyCallback

View File

@@ -5677,7 +5677,15 @@ CPP_DECL JSC::EncodedJSValue WebCore__DOMFormData__createFromURLQuery(JSC::JSGlo
{
Zig::GlobalObject* globalObject = static_cast<Zig::GlobalObject*>(arg0);
// don't need to copy the string because it internally does.
auto formData = DOMFormData::create(globalObject->scriptExecutionContext(), toString(*arg1));
auto str = toString(*arg1);
// toString() in helpers.h returns an empty string when the input exceeds
// String::MaxLength or Bun's synthetic allocation limit. This is the only
// condition under which toString() returns empty for non-empty input.
if (str.isEmpty() && arg1->len > 0) {
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
return Bun::ERR::STRING_TOO_LONG(scope, globalObject);
}
auto formData = DOMFormData::create(globalObject->scriptExecutionContext(), WTFMove(str));
return JSValue::encode(toJSNewlyCreated(arg0, globalObject, WTFMove(formData)));
}

View File

@@ -2,6 +2,7 @@
#include "root.h"
#include "wtf/text/ASCIILiteral.h"
#include "wtf/SIMDUTF.h"
#include <JavaScriptCore/Error.h>
#include <JavaScriptCore/Exception.h>
@@ -79,12 +80,24 @@ static const WTF::String toString(ZigString str)
}
if (isTaggedUTF8Ptr(str.ptr)) [[unlikely]] {
ASSERT_WITH_MESSAGE(!isTaggedExternalPtr(str.ptr), "UTF8 and external ptr are mutually exclusive. The external will never be freed.");
// Check if the resulting UTF-16 string could possibly exceed the maximum length.
// For valid UTF-8, the number of UTF-16 code units is <= the number of UTF-8 bytes
// (ASCII is 1:1; other code points use multiple UTF-8 bytes per UTF-16 code unit).
// We only need to compute the actual UTF-16 length when the byte length exceeds the limit.
size_t maxLength = std::min(Bun__stringSyntheticAllocationLimit, static_cast<size_t>(WTF::String::MaxLength));
if (str.len > maxLength) [[unlikely]] {
// UTF-8 byte length != UTF-16 length, so use simdutf to calculate the actual UTF-16 length.
size_t utf16Length = simdutf::utf16_length_from_utf8(reinterpret_cast<const char*>(untag(str.ptr)), str.len);
if (utf16Length > maxLength) {
return {};
}
}
return WTF::String::fromUTF8ReplacingInvalidSequences(std::span { untag(str.ptr), str.len });
}
if (isTaggedExternalPtr(str.ptr)) [[unlikely]] {
// This will fail if the string is too long. Let's make it explicit instead of an ASSERT.
if (str.len > Bun__stringSyntheticAllocationLimit) [[unlikely]] {
if (str.len > Bun__stringSyntheticAllocationLimit || str.len > WTF::String::MaxLength) [[unlikely]] {
free_global_string(nullptr, reinterpret_cast<void*>(const_cast<unsigned char*>(untag(str.ptr))), static_cast<unsigned>(str.len));
return {};
}
@@ -95,7 +108,7 @@ static const WTF::String toString(ZigString str)
}
// This will fail if the string is too long. Let's make it explicit instead of an ASSERT.
if (str.len > Bun__stringSyntheticAllocationLimit) [[unlikely]] {
if (str.len > Bun__stringSyntheticAllocationLimit || str.len > WTF::String::MaxLength) [[unlikely]] {
return {};
}
@@ -121,11 +134,19 @@ static const WTF::String toString(ZigString str, StringPointer ptr)
return WTF::String();
}
if (isTaggedUTF8Ptr(str.ptr)) [[unlikely]] {
// Check if the resulting UTF-16 string could possibly exceed the maximum length.
size_t maxLength = std::min(Bun__stringSyntheticAllocationLimit, static_cast<size_t>(WTF::String::MaxLength));
if (ptr.len > maxLength) [[unlikely]] {
size_t utf16Length = simdutf::utf16_length_from_utf8(reinterpret_cast<const char*>(&untag(str.ptr)[ptr.off]), ptr.len);
if (utf16Length > maxLength) {
return {};
}
}
return WTF::String::fromUTF8ReplacingInvalidSequences(std::span { &untag(str.ptr)[ptr.off], ptr.len });
}
// This will fail if the string is too long. Let's make it explicit instead of an ASSERT.
if (str.len > Bun__stringSyntheticAllocationLimit) [[unlikely]] {
if (ptr.len > Bun__stringSyntheticAllocationLimit || ptr.len > WTF::String::MaxLength) [[unlikely]] {
return {};
}
@@ -141,11 +162,19 @@ static const WTF::String toStringCopy(ZigString str, StringPointer ptr)
return WTF::String();
}
if (isTaggedUTF8Ptr(str.ptr)) [[unlikely]] {
// Check if the resulting UTF-16 string could possibly exceed the maximum length.
size_t maxLength = std::min(Bun__stringSyntheticAllocationLimit, static_cast<size_t>(WTF::String::MaxLength));
if (ptr.len > maxLength) [[unlikely]] {
size_t utf16Length = simdutf::utf16_length_from_utf8(reinterpret_cast<const char*>(&untag(str.ptr)[ptr.off]), ptr.len);
if (utf16Length > maxLength) {
return {};
}
}
return WTF::String::fromUTF8ReplacingInvalidSequences(std::span { &untag(str.ptr)[ptr.off], ptr.len });
}
// This will fail if the string is too long. Let's make it explicit instead of an ASSERT.
if (str.len > Bun__stringSyntheticAllocationLimit) [[unlikely]] {
if (ptr.len > Bun__stringSyntheticAllocationLimit || ptr.len > WTF::String::MaxLength) [[unlikely]] {
return {};
}
@@ -161,6 +190,14 @@ static const WTF::String toStringCopy(ZigString str)
return WTF::String();
}
if (isTaggedUTF8Ptr(str.ptr)) [[unlikely]] {
// Check if the resulting UTF-16 string could possibly exceed the maximum length.
size_t maxLength = std::min(Bun__stringSyntheticAllocationLimit, static_cast<size_t>(WTF::String::MaxLength));
if (str.len > maxLength) [[unlikely]] {
size_t utf16Length = simdutf::utf16_length_from_utf8(reinterpret_cast<const char*>(untag(str.ptr)), str.len);
if (utf16Length > maxLength) {
return {};
}
}
return WTF::String::fromUTF8ReplacingInvalidSequences(std::span { untag(str.ptr), str.len });
}
@@ -188,6 +225,14 @@ static void appendToBuilder(ZigString str, WTF::StringBuilder& builder)
return;
}
if (isTaggedUTF8Ptr(str.ptr)) [[unlikely]] {
// Check if the resulting UTF-16 string could possibly exceed the maximum length.
size_t maxLength = std::min(Bun__stringSyntheticAllocationLimit, static_cast<size_t>(WTF::String::MaxLength));
if (str.len > maxLength) [[unlikely]] {
size_t utf16Length = simdutf::utf16_length_from_utf8(reinterpret_cast<const char*>(untag(str.ptr)), str.len);
if (utf16Length > maxLength) {
return;
}
}
WTF::String converted = WTF::String::fromUTF8ReplacingInvalidSequences(std::span { untag(str.ptr), str.len });
builder.append(converted);
return;

View File

@@ -81,6 +81,7 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDOMFormData;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDOMFormDataIterator;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDOMURL;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForURLPattern;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForURLSearchParams;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForURLSearchParamsIterator;

View File

@@ -860,11 +860,12 @@ enum class DOMConstructorID : uint16_t {
Cookie,
CookieMap,
EventEmitter,
URLPattern,
};
static constexpr unsigned numberOfDOMConstructorsBase = 848;
static constexpr unsigned bunExtraConstructors = 3;
static constexpr unsigned bunExtraConstructors = 4;
static constexpr unsigned numberOfDOMConstructors = numberOfDOMConstructorsBase + bunExtraConstructors;

View File

@@ -938,6 +938,7 @@ public:
// std::unique_ptr<IsoSubspace> m_subspaceForDOMFormData;
// std::unique_ptr<IsoSubspace> m_subspaceForDOMFormDataIterator;
std::unique_ptr<IsoSubspace> m_subspaceForDOMURL;
std::unique_ptr<IsoSubspace> m_subspaceForURLPattern;
std::unique_ptr<IsoSubspace> m_subspaceForJSSign;
std::unique_ptr<IsoSubspace> m_subspaceForJSVerify;
std::unique_ptr<IsoSubspace> m_subspaceForJSHmac;

View File

@@ -0,0 +1,545 @@
/*
This file is part of the WebKit open source project.
This file has been generated by generate-bindings.pl. DO NOT MODIFY!
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "JSURLPattern.h"
#include "ActiveDOMObject.h"
#include "ExtendedDOMClientIsoSubspaces.h"
#include "ExtendedDOMIsoSubspaces.h"
#include "JSDOMAttribute.h"
#include "JSDOMBinding.h"
#include "JSDOMConstructor.h"
#include "JSDOMConvertBoolean.h"
#include "JSDOMConvertDictionary.h"
#include "JSDOMConvertInterface.h"
#include "JSDOMConvertNullable.h"
#include "JSDOMConvertOptional.h"
#include "JSDOMConvertStrings.h"
#include "JSDOMConvertUnion.h"
#include "JSDOMExceptionHandling.h"
#include "JSDOMGlobalObject.h"
#include "JSDOMGlobalObjectInlines.h"
#include "JSDOMOperation.h"
#include "JSDOMWrapperCache.h"
#include "JSURLPatternInit.h"
#include "JSURLPatternOptions.h"
#include "JSURLPatternResult.h"
#include "ScriptExecutionContext.h"
#include "WebCoreJSClientData.h"
#include <JavaScriptCore/FunctionPrototype.h>
#include <JavaScriptCore/HeapAnalyzer.h>
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/JSDestructibleObjectHeapCellType.h>
#include <JavaScriptCore/SlotVisitorMacros.h>
#include <JavaScriptCore/SubspaceInlines.h>
#include <wtf/GetPtr.h>
#include <wtf/PointerPreparations.h>
#include <wtf/URL.h>
#include <wtf/Variant.h>
#include <wtf/text/MakeString.h>
namespace WebCore {
using namespace JSC;
// Helper to convert from IDL's std::variant to WTF's Variant
static URLPattern::URLPatternInput convertToWTFVariant(std::variant<String, URLPatternInit>&& input)
{
if (std::holds_alternative<String>(input))
return URLPattern::URLPatternInput(std::get<String>(std::move(input)));
return URLPattern::URLPatternInput(std::get<URLPatternInit>(std::move(input)));
}
static std::optional<URLPattern::URLPatternInput> convertToOptionalWTFVariant(std::optional<std::variant<String, URLPatternInit>>&& input)
{
if (!input)
return std::nullopt;
return convertToWTFVariant(std::move(*input));
}
// Functions
static JSC_DECLARE_HOST_FUNCTION(jsURLPatternPrototypeFunction_test);
static JSC_DECLARE_HOST_FUNCTION(jsURLPatternPrototypeFunction_exec);
// Attributes
static JSC_DECLARE_CUSTOM_GETTER(jsURLPatternConstructor);
static JSC_DECLARE_CUSTOM_GETTER(jsURLPattern_protocol);
static JSC_DECLARE_CUSTOM_GETTER(jsURLPattern_username);
static JSC_DECLARE_CUSTOM_GETTER(jsURLPattern_password);
static JSC_DECLARE_CUSTOM_GETTER(jsURLPattern_hostname);
static JSC_DECLARE_CUSTOM_GETTER(jsURLPattern_port);
static JSC_DECLARE_CUSTOM_GETTER(jsURLPattern_pathname);
static JSC_DECLARE_CUSTOM_GETTER(jsURLPattern_search);
static JSC_DECLARE_CUSTOM_GETTER(jsURLPattern_hash);
static JSC_DECLARE_CUSTOM_GETTER(jsURLPattern_hasRegExpGroups);
class JSURLPatternPrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static JSURLPatternPrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure)
{
JSURLPatternPrototype* ptr = new (NotNull, JSC::allocateCell<JSURLPatternPrototype>(vm)) JSURLPatternPrototype(vm, globalObject, structure);
ptr->finishCreation(vm);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSURLPatternPrototype, Base);
return &vm.plainObjectSpace();
}
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
private:
JSURLPatternPrototype(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure)
: JSC::JSNonFinalObject(vm, structure)
{
}
void finishCreation(JSC::VM&);
};
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSURLPatternPrototype, JSURLPatternPrototype::Base);
using JSURLPatternDOMConstructor = JSDOMConstructor<JSURLPattern>;
static inline EncodedJSValue constructJSURLPattern1(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)
{
auto& vm = lexicalGlobalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* castedThis = jsCast<JSURLPatternDOMConstructor*>(callFrame->jsCallee());
ASSERT(castedThis);
RefPtr context = castedThis->scriptExecutionContext();
if (!context) [[unlikely]]
return throwConstructorScriptExecutionContextUnavailableError(*lexicalGlobalObject, throwScope, "URLPattern"_s);
EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0);
auto input = convert<IDLUnion<IDLUSVString, IDLDictionary<URLPatternInit>>>(*lexicalGlobalObject, argument0.value());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
EnsureStillAliveScope argument1 = callFrame->uncheckedArgument(1);
auto baseURL = convert<IDLUSVString>(*lexicalGlobalObject, argument1.value());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
EnsureStillAliveScope argument2 = callFrame->argument(2);
auto options = convert<IDLDictionary<URLPatternOptions>>(*lexicalGlobalObject, argument2.value());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
auto object = URLPattern::create(*context, convertToWTFVariant(WTFMove(input)), WTFMove(baseURL), WTFMove(options));
if constexpr (IsExceptionOr<decltype(object)>)
RETURN_IF_EXCEPTION(throwScope, {});
static_assert(TypeOrExceptionOrUnderlyingType<decltype(object)>::isRef);
auto jsValue = toJSNewlyCreated<IDLInterface<URLPattern>>(*lexicalGlobalObject, *castedThis->globalObject(), throwScope, WTFMove(object));
if constexpr (IsExceptionOr<decltype(object)>)
RETURN_IF_EXCEPTION(throwScope, {});
setSubclassStructureIfNeeded<URLPattern>(lexicalGlobalObject, callFrame, asObject(jsValue));
RETURN_IF_EXCEPTION(throwScope, {});
return JSValue::encode(jsValue);
}
static inline EncodedJSValue constructJSURLPattern2(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)
{
auto& vm = lexicalGlobalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* castedThis = jsCast<JSURLPatternDOMConstructor*>(callFrame->jsCallee());
ASSERT(castedThis);
RefPtr context = castedThis->scriptExecutionContext();
if (!context) [[unlikely]]
return throwConstructorScriptExecutionContextUnavailableError(*lexicalGlobalObject, throwScope, "URLPattern"_s);
EnsureStillAliveScope argument0 = callFrame->argument(0);
auto input = convert<IDLOptional<IDLUnion<IDLUSVString, IDLDictionary<URLPatternInit>>>>(*lexicalGlobalObject, argument0.value());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
EnsureStillAliveScope argument1 = callFrame->argument(1);
auto options = convert<IDLDictionary<URLPatternOptions>>(*lexicalGlobalObject, argument1.value());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
auto object = URLPattern::create(*context, convertToOptionalWTFVariant(WTFMove(input)), WTFMove(options));
if constexpr (IsExceptionOr<decltype(object)>)
RETURN_IF_EXCEPTION(throwScope, {});
static_assert(TypeOrExceptionOrUnderlyingType<decltype(object)>::isRef);
auto jsValue = toJSNewlyCreated<IDLInterface<URLPattern>>(*lexicalGlobalObject, *castedThis->globalObject(), throwScope, WTFMove(object));
if constexpr (IsExceptionOr<decltype(object)>)
RETURN_IF_EXCEPTION(throwScope, {});
setSubclassStructureIfNeeded<URLPattern>(lexicalGlobalObject, callFrame, asObject(jsValue));
RETURN_IF_EXCEPTION(throwScope, {});
return JSValue::encode(jsValue);
}
template<> EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSURLPatternDOMConstructor::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)
{
SUPPRESS_UNCOUNTED_LOCAL auto& vm = lexicalGlobalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
UNUSED_PARAM(throwScope);
size_t argsCount = std::min<size_t>(3, callFrame->argumentCount());
if (argsCount == 0) {
RELEASE_AND_RETURN(throwScope, (constructJSURLPattern2(lexicalGlobalObject, callFrame)));
}
if (argsCount == 1) {
RELEASE_AND_RETURN(throwScope, (constructJSURLPattern2(lexicalGlobalObject, callFrame)));
}
if (argsCount == 2) {
JSValue distinguishingArg = callFrame->uncheckedArgument(1);
if (distinguishingArg.isUndefined())
RELEASE_AND_RETURN(throwScope, (constructJSURLPattern2(lexicalGlobalObject, callFrame)));
if (distinguishingArg.isUndefinedOrNull())
RELEASE_AND_RETURN(throwScope, (constructJSURLPattern2(lexicalGlobalObject, callFrame)));
if (distinguishingArg.isObject())
RELEASE_AND_RETURN(throwScope, (constructJSURLPattern2(lexicalGlobalObject, callFrame)));
RELEASE_AND_RETURN(throwScope, (constructJSURLPattern1(lexicalGlobalObject, callFrame)));
}
if (argsCount == 3) {
RELEASE_AND_RETURN(throwScope, (constructJSURLPattern1(lexicalGlobalObject, callFrame)));
}
return throwVMTypeError(lexicalGlobalObject, throwScope);
}
JSC_ANNOTATE_HOST_FUNCTION(JSURLPatternConstructorConstruct, JSURLPatternDOMConstructor::construct);
template<> const ClassInfo JSURLPatternDOMConstructor::s_info = { "URLPattern"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSURLPatternDOMConstructor) };
template<> JSValue JSURLPatternDOMConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject)
{
UNUSED_PARAM(vm);
return globalObject.functionPrototype();
}
template<> void JSURLPatternDOMConstructor::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject)
{
putDirect(vm, vm.propertyNames->length, jsNumber(0), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
JSString* nameString = jsNontrivialString(vm, "URLPattern"_s);
m_originalName.set(vm, this, nameString);
putDirect(vm, vm.propertyNames->name, nameString, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
putDirect(vm, vm.propertyNames->prototype, JSURLPattern::prototype(vm, globalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete);
}
/* Hash table for prototype */
static const std::array<HashTableValue, 12> JSURLPatternPrototypeTableValues {
HashTableValue { "constructor"_s, static_cast<unsigned>(PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsURLPatternConstructor, 0 } },
HashTableValue { "protocol"_s, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute, NoIntrinsic, { HashTableValue::GetterSetterType, jsURLPattern_protocol, 0 } },
HashTableValue { "username"_s, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute, NoIntrinsic, { HashTableValue::GetterSetterType, jsURLPattern_username, 0 } },
HashTableValue { "password"_s, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute, NoIntrinsic, { HashTableValue::GetterSetterType, jsURLPattern_password, 0 } },
HashTableValue { "hostname"_s, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute, NoIntrinsic, { HashTableValue::GetterSetterType, jsURLPattern_hostname, 0 } },
HashTableValue { "port"_s, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute, NoIntrinsic, { HashTableValue::GetterSetterType, jsURLPattern_port, 0 } },
HashTableValue { "pathname"_s, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute, NoIntrinsic, { HashTableValue::GetterSetterType, jsURLPattern_pathname, 0 } },
HashTableValue { "search"_s, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute, NoIntrinsic, { HashTableValue::GetterSetterType, jsURLPattern_search, 0 } },
HashTableValue { "hash"_s, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute, NoIntrinsic, { HashTableValue::GetterSetterType, jsURLPattern_hash, 0 } },
HashTableValue { "hasRegExpGroups"_s, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute, NoIntrinsic, { HashTableValue::GetterSetterType, jsURLPattern_hasRegExpGroups, 0 } },
HashTableValue { "test"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsURLPatternPrototypeFunction_test, 0 } },
HashTableValue { "exec"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsURLPatternPrototypeFunction_exec, 0 } },
};
const ClassInfo JSURLPatternPrototype::s_info = { "URLPattern"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSURLPatternPrototype) };
void JSURLPatternPrototype::finishCreation(VM& vm)
{
Base::finishCreation(vm);
reifyStaticProperties(vm, JSURLPattern::info(), JSURLPatternPrototypeTableValues, *this);
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}
const ClassInfo JSURLPattern::s_info = { "URLPattern"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSURLPattern) };
JSURLPattern::JSURLPattern(Structure* structure, JSDOMGlobalObject& globalObject, Ref<URLPattern>&& impl)
: JSDOMWrapper<URLPattern>(structure, globalObject, WTFMove(impl))
{
}
JSObject* JSURLPattern::createPrototype(VM& vm, JSDOMGlobalObject& globalObject)
{
auto* structure = JSURLPatternPrototype::createStructure(vm, &globalObject, globalObject.objectPrototype());
structure->setMayBePrototype(true);
return JSURLPatternPrototype::create(vm, &globalObject, structure);
}
JSObject* JSURLPattern::prototype(VM& vm, JSDOMGlobalObject& globalObject)
{
return getDOMPrototype<JSURLPattern>(vm, globalObject);
}
JSValue JSURLPattern::getConstructor(VM& vm, const JSGlobalObject* globalObject)
{
return getDOMConstructor<JSURLPatternDOMConstructor, DOMConstructorID::URLPattern>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject));
}
void JSURLPattern::destroy(JSC::JSCell* cell)
{
SUPPRESS_MEMORY_UNSAFE_CAST JSURLPattern* thisObject = static_cast<JSURLPattern*>(cell);
thisObject->JSURLPattern::~JSURLPattern();
}
JSC_DEFINE_CUSTOM_GETTER(jsURLPatternConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName))
{
SUPPRESS_UNCOUNTED_LOCAL auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* prototype = jsDynamicCast<JSURLPatternPrototype*>(JSValue::decode(thisValue));
if (!prototype) [[unlikely]]
return throwVMTypeError(lexicalGlobalObject, throwScope);
return JSValue::encode(JSURLPattern::getConstructor(vm, prototype->globalObject()));
}
static inline JSValue jsURLPattern_protocolGetter(JSGlobalObject& lexicalGlobalObject, JSURLPattern& thisObject)
{
SUPPRESS_UNCOUNTED_LOCAL auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
SUPPRESS_UNCOUNTED_LOCAL auto& impl = thisObject.wrapped();
RELEASE_AND_RETURN(throwScope, (toJS<IDLUSVString>(lexicalGlobalObject, throwScope, impl.protocol())));
}
JSC_DEFINE_CUSTOM_GETTER(jsURLPattern_protocol, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
{
return IDLAttribute<JSURLPattern>::get<jsURLPattern_protocolGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName);
}
static inline JSValue jsURLPattern_usernameGetter(JSGlobalObject& lexicalGlobalObject, JSURLPattern& thisObject)
{
SUPPRESS_UNCOUNTED_LOCAL auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
SUPPRESS_UNCOUNTED_LOCAL auto& impl = thisObject.wrapped();
RELEASE_AND_RETURN(throwScope, (toJS<IDLUSVString>(lexicalGlobalObject, throwScope, impl.username())));
}
JSC_DEFINE_CUSTOM_GETTER(jsURLPattern_username, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
{
return IDLAttribute<JSURLPattern>::get<jsURLPattern_usernameGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName);
}
static inline JSValue jsURLPattern_passwordGetter(JSGlobalObject& lexicalGlobalObject, JSURLPattern& thisObject)
{
SUPPRESS_UNCOUNTED_LOCAL auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
SUPPRESS_UNCOUNTED_LOCAL auto& impl = thisObject.wrapped();
RELEASE_AND_RETURN(throwScope, (toJS<IDLUSVString>(lexicalGlobalObject, throwScope, impl.password())));
}
JSC_DEFINE_CUSTOM_GETTER(jsURLPattern_password, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
{
return IDLAttribute<JSURLPattern>::get<jsURLPattern_passwordGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName);
}
static inline JSValue jsURLPattern_hostnameGetter(JSGlobalObject& lexicalGlobalObject, JSURLPattern& thisObject)
{
SUPPRESS_UNCOUNTED_LOCAL auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
SUPPRESS_UNCOUNTED_LOCAL auto& impl = thisObject.wrapped();
RELEASE_AND_RETURN(throwScope, (toJS<IDLUSVString>(lexicalGlobalObject, throwScope, impl.hostname())));
}
JSC_DEFINE_CUSTOM_GETTER(jsURLPattern_hostname, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
{
return IDLAttribute<JSURLPattern>::get<jsURLPattern_hostnameGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName);
}
static inline JSValue jsURLPattern_portGetter(JSGlobalObject& lexicalGlobalObject, JSURLPattern& thisObject)
{
SUPPRESS_UNCOUNTED_LOCAL auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
SUPPRESS_UNCOUNTED_LOCAL auto& impl = thisObject.wrapped();
RELEASE_AND_RETURN(throwScope, (toJS<IDLUSVString>(lexicalGlobalObject, throwScope, impl.port())));
}
JSC_DEFINE_CUSTOM_GETTER(jsURLPattern_port, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
{
return IDLAttribute<JSURLPattern>::get<jsURLPattern_portGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName);
}
static inline JSValue jsURLPattern_pathnameGetter(JSGlobalObject& lexicalGlobalObject, JSURLPattern& thisObject)
{
SUPPRESS_UNCOUNTED_LOCAL auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
SUPPRESS_UNCOUNTED_LOCAL auto& impl = thisObject.wrapped();
RELEASE_AND_RETURN(throwScope, (toJS<IDLUSVString>(lexicalGlobalObject, throwScope, impl.pathname())));
}
JSC_DEFINE_CUSTOM_GETTER(jsURLPattern_pathname, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
{
return IDLAttribute<JSURLPattern>::get<jsURLPattern_pathnameGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName);
}
static inline JSValue jsURLPattern_searchGetter(JSGlobalObject& lexicalGlobalObject, JSURLPattern& thisObject)
{
SUPPRESS_UNCOUNTED_LOCAL auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
SUPPRESS_UNCOUNTED_LOCAL auto& impl = thisObject.wrapped();
RELEASE_AND_RETURN(throwScope, (toJS<IDLUSVString>(lexicalGlobalObject, throwScope, impl.search())));
}
JSC_DEFINE_CUSTOM_GETTER(jsURLPattern_search, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
{
return IDLAttribute<JSURLPattern>::get<jsURLPattern_searchGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName);
}
static inline JSValue jsURLPattern_hashGetter(JSGlobalObject& lexicalGlobalObject, JSURLPattern& thisObject)
{
SUPPRESS_UNCOUNTED_LOCAL auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
SUPPRESS_UNCOUNTED_LOCAL auto& impl = thisObject.wrapped();
RELEASE_AND_RETURN(throwScope, (toJS<IDLUSVString>(lexicalGlobalObject, throwScope, impl.hash())));
}
JSC_DEFINE_CUSTOM_GETTER(jsURLPattern_hash, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
{
return IDLAttribute<JSURLPattern>::get<jsURLPattern_hashGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName);
}
static inline JSValue jsURLPattern_hasRegExpGroupsGetter(JSGlobalObject& lexicalGlobalObject, JSURLPattern& thisObject)
{
SUPPRESS_UNCOUNTED_LOCAL auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
SUPPRESS_UNCOUNTED_LOCAL auto& impl = thisObject.wrapped();
RELEASE_AND_RETURN(throwScope, (toJS<IDLBoolean>(lexicalGlobalObject, throwScope, impl.hasRegExpGroups())));
}
JSC_DEFINE_CUSTOM_GETTER(jsURLPattern_hasRegExpGroups, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
{
return IDLAttribute<JSURLPattern>::get<jsURLPattern_hasRegExpGroupsGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName);
}
static inline JSC::EncodedJSValue jsURLPatternPrototypeFunction_testBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSURLPattern>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
UNUSED_PARAM(throwScope);
UNUSED_PARAM(callFrame);
auto& impl = castedThis->wrapped();
RefPtr context = jsCast<JSDOMGlobalObject*>(lexicalGlobalObject)->scriptExecutionContext();
if (!context) [[unlikely]]
return JSValue::encode(jsUndefined());
EnsureStillAliveScope argument0 = callFrame->argument(0);
auto input = convert<IDLOptional<IDLUnion<IDLUSVString, IDLDictionary<URLPatternInit>>>>(*lexicalGlobalObject, argument0.value());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
EnsureStillAliveScope argument1 = callFrame->argument(1);
auto baseURL = argument1.value().isUndefined() ? String() : convert<IDLUSVString>(*lexicalGlobalObject, argument1.value());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLBoolean>(*lexicalGlobalObject, throwScope, impl.test(*context, convertToOptionalWTFVariant(WTFMove(input)), WTFMove(baseURL)))));
}
JSC_DEFINE_HOST_FUNCTION(jsURLPatternPrototypeFunction_test, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
return IDLOperation<JSURLPattern>::call<jsURLPatternPrototypeFunction_testBody>(*lexicalGlobalObject, *callFrame, "test");
}
static inline JSC::EncodedJSValue jsURLPatternPrototypeFunction_execBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSURLPattern>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
UNUSED_PARAM(throwScope);
UNUSED_PARAM(callFrame);
auto& impl = castedThis->wrapped();
RefPtr context = jsCast<JSDOMGlobalObject*>(lexicalGlobalObject)->scriptExecutionContext();
if (!context) [[unlikely]]
return JSValue::encode(jsUndefined());
EnsureStillAliveScope argument0 = callFrame->argument(0);
auto input = convert<IDLOptional<IDLUnion<IDLUSVString, IDLDictionary<URLPatternInit>>>>(*lexicalGlobalObject, argument0.value());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
EnsureStillAliveScope argument1 = callFrame->argument(1);
auto baseURL = argument1.value().isUndefined() ? String() : convert<IDLUSVString>(*lexicalGlobalObject, argument1.value());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLNullable<IDLDictionary<URLPatternResult>>>(*lexicalGlobalObject, *castedThis->globalObject(), throwScope, impl.exec(*context, convertToOptionalWTFVariant(WTFMove(input)), WTFMove(baseURL)))));
}
JSC_DEFINE_HOST_FUNCTION(jsURLPatternPrototypeFunction_exec, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
return IDLOperation<JSURLPattern>::call<jsURLPatternPrototypeFunction_execBody>(*lexicalGlobalObject, *callFrame, "exec");
}
JSC::GCClient::IsoSubspace* JSURLPattern::subspaceForImpl(JSC::VM& vm)
{
return WebCore::subspaceForImpl<JSURLPattern, UseCustomHeapCellType::No>(vm, [](auto& spaces) { return spaces.m_clientSubspaceForURLPattern.get(); }, [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForURLPattern = std::forward<decltype(space)>(space); }, [](auto& spaces) { return spaces.m_subspaceForURLPattern.get(); }, [](auto& spaces, auto&& space) { spaces.m_subspaceForURLPattern = std::forward<decltype(space)>(space); });
}
void JSURLPattern::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)
{
auto* thisObject = jsCast<JSURLPattern*>(cell);
analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped());
if (RefPtr context = thisObject->scriptExecutionContext())
analyzer.setLabelForCell(cell, makeString("url "_s, context->url().string()));
Base::analyzeHeap(cell, analyzer);
}
bool JSURLPatternOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, AbstractSlotVisitor& visitor, ASCIILiteral* reason)
{
UNUSED_PARAM(handle);
UNUSED_PARAM(visitor);
UNUSED_PARAM(reason);
return false;
}
void JSURLPatternOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context)
{
SUPPRESS_MEMORY_UNSAFE_CAST auto* jsURLPattern = static_cast<JSURLPattern*>(handle.slot()->asCell());
auto& world = *static_cast<DOMWrapperWorld*>(context);
uncacheWrapper(world, jsURLPattern->protectedWrapped().ptr(), jsURLPattern);
}
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
#if ENABLE(BINDING_INTEGRITY)
#if PLATFORM(WIN)
#pragma warning(disable : 4483)
extern "C" {
extern void (*const __identifier("??_7URLPattern@WebCore@@6B@")[])();
}
#else
extern "C" {
extern void* _ZTVN7WebCore10URLPatternE[];
}
#endif
template<std::same_as<URLPattern> T>
static inline void verifyVTable(URLPattern* ptr)
{
if constexpr (std::is_polymorphic_v<T>) {
const void* actualVTablePointer = getVTablePointer<T>(ptr);
#if PLATFORM(WIN)
void* expectedVTablePointer = __identifier("??_7URLPattern@WebCore@@6B@");
#else
void* expectedVTablePointer = &_ZTVN7WebCore10URLPatternE[2];
#endif
// If you hit this assertion you either have a use after free bug, or
// URLPattern has subclasses. If URLPattern has subclasses that get passed
// to toJS() we currently require URLPattern you to opt out of binding hardening
// by adding the SkipVTableValidation attribute to the interface IDL definition
RELEASE_ASSERT(actualVTablePointer == expectedVTablePointer);
}
}
#endif
WTF_ALLOW_UNSAFE_BUFFER_USAGE_END
JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject* globalObject, Ref<URLPattern>&& impl)
{
#if ENABLE(BINDING_INTEGRITY)
verifyVTable<URLPattern>(impl.ptr());
#endif
return createWrapper<URLPattern>(globalObject, WTFMove(impl));
}
JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, URLPattern& impl)
{
return wrap(lexicalGlobalObject, globalObject, impl);
}
URLPattern* JSURLPattern::toWrapped(JSC::VM&, JSC::JSValue value)
{
if (auto* wrapper = jsDynamicCast<JSURLPattern*>(value))
return &wrapper->wrapped();
return nullptr;
}
}

View File

@@ -0,0 +1,96 @@
/*
This file is part of the WebKit open source project.
This file has been generated by generate-bindings.pl. DO NOT MODIFY!
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#pragma once
#include "URLPattern.h"
#include "JSDOMWrapper.h"
#include <wtf/NeverDestroyed.h>
namespace WebCore {
class JSURLPattern : public JSDOMWrapper<URLPattern> {
public:
using Base = JSDOMWrapper<URLPattern>;
static JSURLPattern* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject, Ref<URLPattern>&& impl)
{
SUPPRESS_UNCOUNTED_LOCAL auto& vm = globalObject->vm();
JSURLPattern* ptr = new (NotNull, JSC::allocateCell<JSURLPattern>(vm)) JSURLPattern(structure, *globalObject, WTFMove(impl));
ptr->finishCreation(vm);
return ptr;
}
static JSC::JSObject* createPrototype(JSC::VM&, JSDOMGlobalObject&);
static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&);
static URLPattern* toWrapped(JSC::VM&, JSC::JSValue);
static void destroy(JSC::JSCell*);
DECLARE_INFO;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info(), JSC::NonArray);
}
static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*);
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return subspaceForImpl(vm);
}
static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm);
static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
protected:
JSURLPattern(JSC::Structure*, JSDOMGlobalObject&, Ref<URLPattern>&&);
DECLARE_DEFAULT_FINISH_CREATION;
};
class JSURLPatternOwner final : public JSC::WeakHandleOwner {
public:
bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::AbstractSlotVisitor&, ASCIILiteral*) final;
void finalize(JSC::Handle<JSC::Unknown>, void* context) final;
};
inline JSC::WeakHandleOwner* wrapperOwner(DOMWrapperWorld&, URLPattern*)
{
static NeverDestroyed<JSURLPatternOwner> owner;
return &owner.get();
}
inline void* wrapperKey(URLPattern* wrappableObject)
{
return wrappableObject;
}
JSC::JSValue toJS(JSC::JSGlobalObject*, JSDOMGlobalObject*, URLPattern&);
inline JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, URLPattern* impl) { return impl ? toJS(lexicalGlobalObject, globalObject, *impl) : JSC::jsNull(); }
JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject*, Ref<URLPattern>&&);
ALWAYS_INLINE JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, URLPattern& impl) { return toJSNewlyCreated(lexicalGlobalObject, globalObject, Ref { impl }); }
inline JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, RefPtr<URLPattern>&& impl) { return impl ? toJSNewlyCreated(lexicalGlobalObject, globalObject, impl.releaseNonNull()) : JSC::jsNull(); }
template<> struct JSDOMWrapperConverterTraits<URLPattern> {
using WrapperClass = JSURLPattern;
using ToWrappedReturnType = URLPattern*;
};
} // namespace WebCore

View File

@@ -0,0 +1,200 @@
/*
This file is part of the WebKit open source project.
This file has been generated by generate-bindings.pl. DO NOT MODIFY!
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "JSURLPatternInit.h"
#include "JSDOMConvertStrings.h"
#include "JSDOMGlobalObject.h"
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/ObjectConstructor.h>
namespace WebCore {
using namespace JSC;
template<> URLPatternInit convertDictionary<URLPatternInit>(JSGlobalObject& lexicalGlobalObject, JSValue value)
{
auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
bool isNullOrUndefined = value.isUndefinedOrNull();
auto* object = isNullOrUndefined ? nullptr : value.getObject();
if (!isNullOrUndefined && !object) [[unlikely]] {
throwTypeError(&lexicalGlobalObject, throwScope);
return {};
}
URLPatternInit result;
JSValue baseURLValue;
if (isNullOrUndefined)
baseURLValue = jsUndefined();
else {
baseURLValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "baseURL"_s));
RETURN_IF_EXCEPTION(throwScope, {});
}
if (!baseURLValue.isUndefined()) {
result.baseURL = convert<IDLUSVString>(lexicalGlobalObject, baseURLValue);
RETURN_IF_EXCEPTION(throwScope, {});
}
JSValue hashValue;
if (isNullOrUndefined)
hashValue = jsUndefined();
else {
hashValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "hash"_s));
RETURN_IF_EXCEPTION(throwScope, {});
}
if (!hashValue.isUndefined()) {
result.hash = convert<IDLUSVString>(lexicalGlobalObject, hashValue);
RETURN_IF_EXCEPTION(throwScope, {});
}
JSValue hostnameValue;
if (isNullOrUndefined)
hostnameValue = jsUndefined();
else {
hostnameValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "hostname"_s));
RETURN_IF_EXCEPTION(throwScope, {});
}
if (!hostnameValue.isUndefined()) {
result.hostname = convert<IDLUSVString>(lexicalGlobalObject, hostnameValue);
RETURN_IF_EXCEPTION(throwScope, {});
}
JSValue passwordValue;
if (isNullOrUndefined)
passwordValue = jsUndefined();
else {
passwordValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "password"_s));
RETURN_IF_EXCEPTION(throwScope, {});
}
if (!passwordValue.isUndefined()) {
result.password = convert<IDLUSVString>(lexicalGlobalObject, passwordValue);
RETURN_IF_EXCEPTION(throwScope, {});
}
JSValue pathnameValue;
if (isNullOrUndefined)
pathnameValue = jsUndefined();
else {
pathnameValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "pathname"_s));
RETURN_IF_EXCEPTION(throwScope, {});
}
if (!pathnameValue.isUndefined()) {
result.pathname = convert<IDLUSVString>(lexicalGlobalObject, pathnameValue);
RETURN_IF_EXCEPTION(throwScope, {});
}
JSValue portValue;
if (isNullOrUndefined)
portValue = jsUndefined();
else {
portValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "port"_s));
RETURN_IF_EXCEPTION(throwScope, {});
}
if (!portValue.isUndefined()) {
result.port = convert<IDLUSVString>(lexicalGlobalObject, portValue);
RETURN_IF_EXCEPTION(throwScope, {});
}
JSValue protocolValue;
if (isNullOrUndefined)
protocolValue = jsUndefined();
else {
protocolValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "protocol"_s));
RETURN_IF_EXCEPTION(throwScope, {});
}
if (!protocolValue.isUndefined()) {
result.protocol = convert<IDLUSVString>(lexicalGlobalObject, protocolValue);
RETURN_IF_EXCEPTION(throwScope, {});
}
JSValue searchValue;
if (isNullOrUndefined)
searchValue = jsUndefined();
else {
searchValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "search"_s));
RETURN_IF_EXCEPTION(throwScope, {});
}
if (!searchValue.isUndefined()) {
result.search = convert<IDLUSVString>(lexicalGlobalObject, searchValue);
RETURN_IF_EXCEPTION(throwScope, {});
}
JSValue usernameValue;
if (isNullOrUndefined)
usernameValue = jsUndefined();
else {
usernameValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "username"_s));
RETURN_IF_EXCEPTION(throwScope, {});
}
if (!usernameValue.isUndefined()) {
result.username = convert<IDLUSVString>(lexicalGlobalObject, usernameValue);
RETURN_IF_EXCEPTION(throwScope, {});
}
return result;
}
JSC::JSObject* convertDictionaryToJS(JSC::JSGlobalObject& lexicalGlobalObject, JSDOMGlobalObject& globalObject, const URLPatternInit& dictionary)
{
auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto result = constructEmptyObject(&lexicalGlobalObject, globalObject.objectPrototype());
if (!IDLUSVString::isNullValue(dictionary.baseURL)) {
auto baseURLValue = toJS<IDLUSVString>(lexicalGlobalObject, throwScope, IDLUSVString::extractValueFromNullable(dictionary.baseURL));
RETURN_IF_EXCEPTION(throwScope, {});
result->putDirect(vm, JSC::Identifier::fromString(vm, "baseURL"_s), baseURLValue);
}
if (!IDLUSVString::isNullValue(dictionary.hash)) {
auto hashValue = toJS<IDLUSVString>(lexicalGlobalObject, throwScope, IDLUSVString::extractValueFromNullable(dictionary.hash));
RETURN_IF_EXCEPTION(throwScope, {});
result->putDirect(vm, JSC::Identifier::fromString(vm, "hash"_s), hashValue);
}
if (!IDLUSVString::isNullValue(dictionary.hostname)) {
auto hostnameValue = toJS<IDLUSVString>(lexicalGlobalObject, throwScope, IDLUSVString::extractValueFromNullable(dictionary.hostname));
RETURN_IF_EXCEPTION(throwScope, {});
result->putDirect(vm, JSC::Identifier::fromString(vm, "hostname"_s), hostnameValue);
}
if (!IDLUSVString::isNullValue(dictionary.password)) {
auto passwordValue = toJS<IDLUSVString>(lexicalGlobalObject, throwScope, IDLUSVString::extractValueFromNullable(dictionary.password));
RETURN_IF_EXCEPTION(throwScope, {});
result->putDirect(vm, JSC::Identifier::fromString(vm, "password"_s), passwordValue);
}
if (!IDLUSVString::isNullValue(dictionary.pathname)) {
auto pathnameValue = toJS<IDLUSVString>(lexicalGlobalObject, throwScope, IDLUSVString::extractValueFromNullable(dictionary.pathname));
RETURN_IF_EXCEPTION(throwScope, {});
result->putDirect(vm, JSC::Identifier::fromString(vm, "pathname"_s), pathnameValue);
}
if (!IDLUSVString::isNullValue(dictionary.port)) {
auto portValue = toJS<IDLUSVString>(lexicalGlobalObject, throwScope, IDLUSVString::extractValueFromNullable(dictionary.port));
RETURN_IF_EXCEPTION(throwScope, {});
result->putDirect(vm, JSC::Identifier::fromString(vm, "port"_s), portValue);
}
if (!IDLUSVString::isNullValue(dictionary.protocol)) {
auto protocolValue = toJS<IDLUSVString>(lexicalGlobalObject, throwScope, IDLUSVString::extractValueFromNullable(dictionary.protocol));
RETURN_IF_EXCEPTION(throwScope, {});
result->putDirect(vm, JSC::Identifier::fromString(vm, "protocol"_s), protocolValue);
}
if (!IDLUSVString::isNullValue(dictionary.search)) {
auto searchValue = toJS<IDLUSVString>(lexicalGlobalObject, throwScope, IDLUSVString::extractValueFromNullable(dictionary.search));
RETURN_IF_EXCEPTION(throwScope, {});
result->putDirect(vm, JSC::Identifier::fromString(vm, "search"_s), searchValue);
}
if (!IDLUSVString::isNullValue(dictionary.username)) {
auto usernameValue = toJS<IDLUSVString>(lexicalGlobalObject, throwScope, IDLUSVString::extractValueFromNullable(dictionary.username));
RETURN_IF_EXCEPTION(throwScope, {});
result->putDirect(vm, JSC::Identifier::fromString(vm, "username"_s), usernameValue);
}
return result;
}
} // namespace WebCore

View File

@@ -0,0 +1,32 @@
/*
This file is part of the WebKit open source project.
This file has been generated by generate-bindings.pl. DO NOT MODIFY!
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#pragma once
#include "JSDOMConvertDictionary.h"
#include "URLPatternInit.h"
namespace WebCore {
template<> URLPatternInit convertDictionary<URLPatternInit>(JSC::JSGlobalObject&, JSC::JSValue);
JSC::JSObject* convertDictionaryToJS(JSC::JSGlobalObject&, JSDOMGlobalObject&, const URLPatternInit&);
} // namespace WebCore

View File

@@ -0,0 +1,56 @@
/*
This file is part of the WebKit open source project.
This file has been generated by generate-bindings.pl. DO NOT MODIFY!
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "JSURLPatternOptions.h"
#include "JSDOMConvertBoolean.h"
#include <JavaScriptCore/JSCInlines.h>
namespace WebCore {
using namespace JSC;
template<> URLPatternOptions convertDictionary<URLPatternOptions>(JSGlobalObject& lexicalGlobalObject, JSValue value)
{
auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
bool isNullOrUndefined = value.isUndefinedOrNull();
auto* object = isNullOrUndefined ? nullptr : value.getObject();
if (!isNullOrUndefined && !object) [[unlikely]] {
throwTypeError(&lexicalGlobalObject, throwScope);
return {};
}
URLPatternOptions result;
JSValue ignoreCaseValue;
if (isNullOrUndefined)
ignoreCaseValue = jsUndefined();
else {
ignoreCaseValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "ignoreCase"_s));
RETURN_IF_EXCEPTION(throwScope, {});
}
if (!ignoreCaseValue.isUndefined()) {
result.ignoreCase = convert<IDLBoolean>(lexicalGlobalObject, ignoreCaseValue);
RETURN_IF_EXCEPTION(throwScope, {});
} else
result.ignoreCase = false;
return result;
}
} // namespace WebCore

View File

@@ -0,0 +1,30 @@
/*
This file is part of the WebKit open source project.
This file has been generated by generate-bindings.pl. DO NOT MODIFY!
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#pragma once
#include "JSDOMConvertDictionary.h"
#include "URLPatternOptions.h"
namespace WebCore {
template<> URLPatternOptions convertDictionary<URLPatternOptions>(JSC::JSGlobalObject&, JSC::JSValue);
} // namespace WebCore

View File

@@ -0,0 +1,175 @@
/*
This file is part of the WebKit open source project.
This file has been generated by generate-bindings.pl. DO NOT MODIFY!
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "root.h"
#include "JSURLPatternResult.h"
#include "IDLTypes.h"
#include "JSDOMConvertBase.h"
#include "JSDOMConvertStrings.h"
#include "JSDOMGlobalObject.h"
#include "JSURLPatternInit.h"
#include <JavaScriptCore/JSArray.h>
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/ObjectConstructor.h>
namespace WebCore {
using namespace JSC;
// URLPatternResult and URLPatternComponentResult are output-only dictionaries that are
// returned from exec() but never accepted as input from JavaScript. These convertDictionary
// template specializations are required to satisfy template instantiation in the binding
// infrastructure. They intentionally throw TypeErrors to catch any invalid JS→native
// conversion attempts, as these types should never be constructed from JavaScript values.
template<> URLPatternResult convertDictionary<URLPatternResult>(JSGlobalObject& lexicalGlobalObject, JSValue value)
{
auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
UNUSED_PARAM(value);
throwTypeError(&lexicalGlobalObject, throwScope, "URLPatternResult cannot be converted from JavaScript"_s);
return {};
}
template<> URLPatternComponentResult convertDictionary<URLPatternComponentResult>(JSGlobalObject& lexicalGlobalObject, JSValue value)
{
auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
UNUSED_PARAM(value);
throwTypeError(&lexicalGlobalObject, throwScope, "URLPatternComponentResult cannot be converted from JavaScript"_s);
return {};
}
// Helper to convert the groups record to JS
static JSC::JSObject* convertGroupsToJS(JSC::JSGlobalObject& lexicalGlobalObject, JSDOMGlobalObject& globalObject, const URLPatternComponentResult::GroupsRecord& groups)
{
auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto result = constructEmptyObject(&lexicalGlobalObject, globalObject.objectPrototype());
for (const auto& pair : groups) {
JSValue jsValue = WTF::switchOn(pair.value, [&](std::monostate) -> JSValue { return jsUndefined(); }, [&](const String& str) -> JSValue { return toJS<IDLUSVString>(lexicalGlobalObject, throwScope, str); });
RETURN_IF_EXCEPTION(throwScope, nullptr);
// Check if the key is an array index
auto identifier = Identifier::fromString(vm, pair.key);
if (auto index = parseIndex(identifier)) {
result->putDirectIndex(&lexicalGlobalObject, index.value(), jsValue);
RETURN_IF_EXCEPTION(throwScope, nullptr);
} else {
result->putDirect(vm, identifier, jsValue);
}
}
return result;
}
// Helper to convert URLPatternInput (variant) to JS
static JSC::JSValue convertURLPatternInputToJS(JSC::JSGlobalObject& lexicalGlobalObject, JSDOMGlobalObject& globalObject, const URLPattern::URLPatternInput& input)
{
auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
return WTF::switchOn(input, [&](const String& str) -> JSValue { return toJS<IDLUSVString>(lexicalGlobalObject, throwScope, str); }, [&](const URLPatternInit& init) -> JSValue { return convertDictionaryToJS(lexicalGlobalObject, globalObject, init); });
}
JSC::JSObject* convertDictionaryToJS(JSC::JSGlobalObject& lexicalGlobalObject, JSDOMGlobalObject& globalObject, const URLPatternComponentResult& dictionary)
{
auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto result = constructEmptyObject(&lexicalGlobalObject, globalObject.objectPrototype());
// Output input
auto inputValue = toJS<IDLUSVString>(lexicalGlobalObject, throwScope, dictionary.input);
RETURN_IF_EXCEPTION(throwScope, nullptr);
result->putDirect(vm, Identifier::fromString(vm, "input"_s), inputValue);
// Output groups - record<USVString, (undefined or USVString)>
auto groupsValue = convertGroupsToJS(lexicalGlobalObject, globalObject, dictionary.groups);
RETURN_IF_EXCEPTION(throwScope, nullptr);
result->putDirect(vm, Identifier::fromString(vm, "groups"_s), groupsValue);
return result;
}
JSC::JSObject* convertDictionaryToJS(JSC::JSGlobalObject& lexicalGlobalObject, JSDOMGlobalObject& globalObject, const URLPatternResult& dictionary)
{
auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto result = constructEmptyObject(&lexicalGlobalObject, globalObject.objectPrototype());
// Output inputs - sequence<(USVString or URLPatternInit)>
auto inputsArray = JSC::constructEmptyArray(&lexicalGlobalObject, nullptr, dictionary.inputs.size());
RETURN_IF_EXCEPTION(throwScope, nullptr);
for (size_t i = 0; i < dictionary.inputs.size(); ++i) {
auto inputValue = convertURLPatternInputToJS(lexicalGlobalObject, globalObject, dictionary.inputs[i]);
RETURN_IF_EXCEPTION(throwScope, nullptr);
inputsArray->putDirectIndex(&lexicalGlobalObject, i, inputValue);
RETURN_IF_EXCEPTION(throwScope, nullptr);
}
result->putDirect(vm, Identifier::fromString(vm, "inputs"_s), inputsArray);
// Output protocol
auto protocolValue = convertDictionaryToJS(lexicalGlobalObject, globalObject, dictionary.protocol);
RETURN_IF_EXCEPTION(throwScope, nullptr);
result->putDirect(vm, Identifier::fromString(vm, "protocol"_s), protocolValue);
// Output username
auto usernameValue = convertDictionaryToJS(lexicalGlobalObject, globalObject, dictionary.username);
RETURN_IF_EXCEPTION(throwScope, nullptr);
result->putDirect(vm, Identifier::fromString(vm, "username"_s), usernameValue);
// Output password
auto passwordValue = convertDictionaryToJS(lexicalGlobalObject, globalObject, dictionary.password);
RETURN_IF_EXCEPTION(throwScope, nullptr);
result->putDirect(vm, Identifier::fromString(vm, "password"_s), passwordValue);
// Output hostname
auto hostnameValue = convertDictionaryToJS(lexicalGlobalObject, globalObject, dictionary.hostname);
RETURN_IF_EXCEPTION(throwScope, nullptr);
result->putDirect(vm, Identifier::fromString(vm, "hostname"_s), hostnameValue);
// Output port
auto portValue = convertDictionaryToJS(lexicalGlobalObject, globalObject, dictionary.port);
RETURN_IF_EXCEPTION(throwScope, nullptr);
result->putDirect(vm, Identifier::fromString(vm, "port"_s), portValue);
// Output pathname
auto pathnameValue = convertDictionaryToJS(lexicalGlobalObject, globalObject, dictionary.pathname);
RETURN_IF_EXCEPTION(throwScope, nullptr);
result->putDirect(vm, Identifier::fromString(vm, "pathname"_s), pathnameValue);
// Output search
auto searchValue = convertDictionaryToJS(lexicalGlobalObject, globalObject, dictionary.search);
RETURN_IF_EXCEPTION(throwScope, nullptr);
result->putDirect(vm, Identifier::fromString(vm, "search"_s), searchValue);
// Output hash
auto hashValue = convertDictionaryToJS(lexicalGlobalObject, globalObject, dictionary.hash);
RETURN_IF_EXCEPTION(throwScope, nullptr);
result->putDirect(vm, Identifier::fromString(vm, "hash"_s), hashValue);
return result;
}
} // namespace WebCore

View File

@@ -0,0 +1,36 @@
/*
This file is part of the WebKit open source project.
This file has been generated by generate-bindings.pl. DO NOT MODIFY!
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#pragma once
#include "JSDOMConvertDictionary.h"
#include "URLPatternResult.h"
namespace WebCore {
template<> URLPatternResult convertDictionary<URLPatternResult>(JSC::JSGlobalObject&, JSC::JSValue);
JSC::JSObject* convertDictionaryToJS(JSC::JSGlobalObject&, JSDOMGlobalObject&, const URLPatternResult&);
template<> URLPatternComponentResult convertDictionary<URLPatternComponentResult>(JSC::JSGlobalObject&, JSC::JSValue);
JSC::JSObject* convertDictionaryToJS(JSC::JSGlobalObject&, JSDOMGlobalObject&, const URLPatternComponentResult&);
} // namespace WebCore

View File

@@ -0,0 +1,493 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "URLPattern.h"
#include "ExceptionOr.h"
#include "ScriptExecutionContext.h"
#include "URLPatternCanonical.h"
#include "URLPatternConstructorStringParser.h"
#include "URLPatternInit.h"
#include "URLPatternOptions.h"
#include "URLPatternParser.h"
#include "URLPatternResult.h"
#include <JavaScriptCore/RegExp.h>
#include <wtf/RefCounted.h>
#include <wtf/TZoneMallocInlines.h>
#include <wtf/URL.h>
#include <wtf/URLParser.h>
#include <wtf/text/MakeString.h>
#include <wtf/text/StringToIntegerConversion.h>
namespace WebCore {
using namespace JSC;
WTF_MAKE_TZONE_OR_ISO_ALLOCATED_IMPL(URLPattern);
// https://urlpattern.spec.whatwg.org/#process-a-base-url-string
static String processBaseURLString(StringView input, BaseURLStringType type)
{
if (type != BaseURLStringType::Pattern)
return input.toString();
return URLPatternUtilities::escapePatternString(input);
}
// https://urlpattern.spec.whatwg.org/#hostname-pattern-is-an-ipv6-address
static bool isHostnamePatternIPv6(StringView hostname)
{
if (hostname.length() < 2)
return false;
if (hostname[0] == '[')
return true;
if (hostname[0] == '{' && hostname[1] == '[')
return true;
if (hostname[0] == '\\' && hostname[1] == '[')
return true;
return false;
}
URLPattern::URLPattern() = default;
// https://urlpattern.spec.whatwg.org/#process-a-urlpatterninit
static ExceptionOr<URLPatternInit> processInit(URLPatternInit&& init, BaseURLStringType type, String&& protocol = {}, String&& username = {}, String&& password = {}, String&& hostname = {}, String&& port = {}, String&& pathname = {}, String&& search = {}, String&& hash = {})
{
URLPatternInit result { WTFMove(protocol), WTFMove(username), WTFMove(password), WTFMove(hostname), WTFMove(port), WTFMove(pathname), WTFMove(search), WTFMove(hash), {} };
URL baseURL;
if (!init.baseURL.isNull()) {
baseURL = URL(init.baseURL);
if (!baseURL.isValid())
return Exception { ExceptionCode::TypeError, "Invalid baseURL."_s };
if (init.protocol.isNull())
result.protocol = processBaseURLString(baseURL.protocol(), type);
if (type != BaseURLStringType::Pattern
&& init.protocol.isNull()
&& init.hostname.isNull()
&& init.port.isNull()
&& init.username.isNull())
result.username = processBaseURLString(baseURL.user(), type);
if (type != BaseURLStringType::Pattern
&& init.protocol.isNull()
&& init.hostname.isNull()
&& init.port.isNull()
&& init.username.isNull()
&& init.password.isNull())
result.password = processBaseURLString(baseURL.password(), type);
if (init.protocol.isNull()
&& init.hostname.isNull()) {
result.hostname = processBaseURLString(!baseURL.host().isNull() ? baseURL.host() : StringView { emptyString() }, type);
}
if (init.protocol.isNull()
&& init.hostname.isNull()
&& init.port.isNull()) {
auto port = baseURL.port();
result.port = port ? String::number(*port) : emptyString();
}
if (init.protocol.isNull()
&& init.hostname.isNull()
&& init.port.isNull()
&& init.pathname.isNull()) {
result.pathname = processBaseURLString(baseURL.path(), type);
}
if (init.protocol.isNull()
&& init.hostname.isNull()
&& init.port.isNull()
&& init.pathname.isNull()
&& init.search.isNull()) {
result.search = processBaseURLString(baseURL.hasQuery() ? baseURL.query() : StringView { emptyString() }, type);
}
if (init.protocol.isNull()
&& init.hostname.isNull()
&& init.port.isNull()
&& init.pathname.isNull()
&& init.search.isNull()
&& init.hash.isNull()) {
result.hash = processBaseURLString(baseURL.hasFragmentIdentifier() ? baseURL.fragmentIdentifier() : StringView { emptyString() }, type);
}
}
if (!init.protocol.isNull()) {
auto protocolResult = canonicalizeProtocol(init.protocol, type);
if (protocolResult.hasException())
return protocolResult.releaseException();
result.protocol = protocolResult.releaseReturnValue();
}
if (!init.username.isNull())
result.username = canonicalizeUsername(init.username, type);
if (!init.password.isNull())
result.password = canonicalizePassword(init.password, type);
if (!init.hostname.isNull()) {
auto hostResult = canonicalizeHostname(init.hostname, type);
if (hostResult.hasException())
return hostResult.releaseException();
result.hostname = hostResult.releaseReturnValue();
}
if (!init.port.isNull()) {
auto portResult = canonicalizePort(init.port, result.protocol, type);
if (portResult.hasException())
return portResult.releaseException();
result.port = portResult.releaseReturnValue();
}
if (!init.pathname.isNull()) {
result.pathname = init.pathname;
if (!baseURL.isNull() && !baseURL.hasOpaquePath() && !isAbsolutePathname(result.pathname, type)) {
auto baseURLPath = processBaseURLString(baseURL.path(), type);
size_t slashIndex = baseURLPath.reverseFind('/');
if (slashIndex != notFound)
result.pathname = makeString(StringView { baseURLPath }.left(slashIndex + 1), result.pathname);
}
auto pathResult = processPathname(result.pathname, result.protocol, type);
if (pathResult.hasException())
return pathResult.releaseException();
result.pathname = pathResult.releaseReturnValue();
}
if (!init.search.isNull()) {
auto queryResult = canonicalizeSearch(init.search, type);
if (queryResult.hasException())
return queryResult.releaseException();
result.search = queryResult.releaseReturnValue();
}
if (!init.hash.isNull()) {
auto fragmentResult = canonicalizeHash(init.hash, type);
if (fragmentResult.hasException())
return fragmentResult.releaseException();
result.hash = fragmentResult.releaseReturnValue();
}
return result;
}
// https://urlpattern.spec.whatwg.org/#url-pattern-create
ExceptionOr<Ref<URLPattern>> URLPattern::create(ScriptExecutionContext& context, URLPatternInput&& input, String&& baseURL, URLPatternOptions&& options)
{
URLPatternInit init;
if (std::holds_alternative<String>(input) && !std::get<String>(input).isNull()) {
auto maybeInit = URLPatternConstructorStringParser(WTFMove(std::get<String>(input))).parse(context);
if (maybeInit.hasException())
return maybeInit.releaseException();
init = maybeInit.releaseReturnValue();
if (baseURL.isNull() && init.protocol.isEmpty())
return Exception { ExceptionCode::TypeError, "Relative constructor string must have additional baseURL argument."_s };
init.baseURL = WTFMove(baseURL);
} else if (std::holds_alternative<URLPatternInit>(input)) {
if (!baseURL.isNull())
return Exception { ExceptionCode::TypeError, "Constructor with a URLPatternInit should have a null baseURL argument."_s };
init = std::get<URLPatternInit>(input);
}
auto maybeProcessedInit = processInit(WTFMove(init), BaseURLStringType::Pattern);
if (maybeProcessedInit.hasException())
return maybeProcessedInit.releaseException();
auto processedInit = maybeProcessedInit.releaseReturnValue();
if (!processedInit.protocol)
processedInit.protocol = "*"_s;
if (!processedInit.username)
processedInit.username = "*"_s;
if (!processedInit.password)
processedInit.password = "*"_s;
if (!processedInit.hostname)
processedInit.hostname = "*"_s;
if (!processedInit.pathname)
processedInit.pathname = "*"_s;
if (!processedInit.search)
processedInit.search = "*"_s;
if (!processedInit.hash)
processedInit.hash = "*"_s;
if (!processedInit.port)
processedInit.port = "*"_s;
if (auto parsedPort = parseInteger<uint16_t>(processedInit.port, 10, WTF::ParseIntegerWhitespacePolicy::Disallow)) {
if (WTF::URLParser::isSpecialScheme(processedInit.protocol) && isDefaultPortForProtocol(*parsedPort, processedInit.protocol))
processedInit.port = emptyString();
}
Ref result = adoptRef(*new URLPattern);
auto maybeCompileException = result->compileAllComponents(context, WTFMove(processedInit), options);
if (maybeCompileException.hasException())
return maybeCompileException.releaseException();
return result;
}
// https://urlpattern.spec.whatwg.org/#urlpattern-initialize
ExceptionOr<Ref<URLPattern>> URLPattern::create(ScriptExecutionContext& context, std::optional<URLPatternInput>&& input, URLPatternOptions&& options)
{
if (!input)
input = URLPatternInit {};
return create(context, WTFMove(*input), String {}, WTFMove(options));
}
// https://urlpattern.spec.whatwg.org/#build-a-url-pattern-from-a-web-idl-value
ExceptionOr<Ref<URLPattern>> URLPattern::create(ScriptExecutionContext& context, Compatible&& value, const String& baseURL)
{
return switchOn(WTFMove(value), [&](RefPtr<URLPattern>&& pattern) -> ExceptionOr<Ref<URLPattern>> { return pattern.releaseNonNull(); }, [&](URLPatternInit&& init) -> ExceptionOr<Ref<URLPattern>> { return URLPattern::create(context, WTFMove(init), {}, {}); }, [&](String&& string) -> ExceptionOr<Ref<URLPattern>> { return URLPattern::create(context, WTFMove(string), String { baseURL }, {}); });
}
URLPattern::~URLPattern() = default;
// https://urlpattern.spec.whatwg.org/#dom-urlpattern-test
ExceptionOr<bool> URLPattern::test(ScriptExecutionContext& context, std::optional<URLPatternInput>&& input, String&& baseURL) const
{
if (!input)
input = URLPatternInit {};
auto maybeResult = match(context, WTFMove(*input), WTFMove(baseURL));
if (maybeResult.hasException())
return maybeResult.releaseException();
return !!maybeResult.returnValue();
}
// https://urlpattern.spec.whatwg.org/#dom-urlpattern-exec
ExceptionOr<std::optional<URLPatternResult>> URLPattern::exec(ScriptExecutionContext& context, std::optional<URLPatternInput>&& input, String&& baseURL) const
{
if (!input)
input = URLPatternInit {};
return match(context, WTFMove(*input), WTFMove(baseURL));
}
ExceptionOr<void> URLPattern::compileAllComponents(ScriptExecutionContext& context, URLPatternInit&& processedInit, const URLPatternOptions& options)
{
Ref vm = context.vm();
JSC::JSLockHolder lock(vm);
auto maybeProtocolComponent = URLPatternUtilities::URLPatternComponent::compile(vm, processedInit.protocol, EncodingCallbackType::Protocol, URLPatternUtilities::URLPatternStringOptions {});
if (maybeProtocolComponent.hasException())
return maybeProtocolComponent.releaseException();
m_protocolComponent = maybeProtocolComponent.releaseReturnValue();
auto maybeUsernameComponent = URLPatternUtilities::URLPatternComponent::compile(vm, processedInit.username, EncodingCallbackType::Username, URLPatternUtilities::URLPatternStringOptions {});
if (maybeUsernameComponent.hasException())
return maybeUsernameComponent.releaseException();
m_usernameComponent = maybeUsernameComponent.releaseReturnValue();
auto maybePasswordComponent = URLPatternUtilities::URLPatternComponent::compile(vm, processedInit.password, EncodingCallbackType::Password, URLPatternUtilities::URLPatternStringOptions {});
if (maybePasswordComponent.hasException())
return maybePasswordComponent.releaseException();
m_passwordComponent = maybePasswordComponent.releaseReturnValue();
auto hostnameEncodingCallbackType = isHostnamePatternIPv6(processedInit.hostname) ? EncodingCallbackType::IPv6Host : EncodingCallbackType::Host;
auto maybeHostnameComponent = URLPatternUtilities::URLPatternComponent::compile(vm, processedInit.hostname, hostnameEncodingCallbackType, URLPatternUtilities::URLPatternStringOptions { .delimiterCodepoint = "."_s });
if (maybeHostnameComponent.hasException())
return maybeHostnameComponent.releaseException();
m_hostnameComponent = maybeHostnameComponent.releaseReturnValue();
auto maybePortComponent = URLPatternUtilities::URLPatternComponent::compile(vm, processedInit.port, EncodingCallbackType::Port, URLPatternUtilities::URLPatternStringOptions {});
if (maybePortComponent.hasException())
return maybePortComponent.releaseException();
m_portComponent = maybePortComponent.releaseReturnValue();
URLPatternUtilities::URLPatternStringOptions compileOptions { .ignoreCase = options.ignoreCase };
auto maybePathnameComponent = m_protocolComponent.matchSpecialSchemeProtocol(context)
? URLPatternUtilities::URLPatternComponent::compile(vm, processedInit.pathname, EncodingCallbackType::Path, URLPatternUtilities::URLPatternStringOptions { "/"_s, "/"_s, options.ignoreCase })
: URLPatternUtilities::URLPatternComponent::compile(vm, processedInit.pathname, EncodingCallbackType::OpaquePath, compileOptions);
if (maybePathnameComponent.hasException())
return maybePathnameComponent.releaseException();
m_pathnameComponent = maybePathnameComponent.releaseReturnValue();
auto maybeSearchComponent = URLPatternUtilities::URLPatternComponent::compile(vm, processedInit.search, EncodingCallbackType::Search, compileOptions);
if (maybeSearchComponent.hasException())
return maybeSearchComponent.releaseException();
m_searchComponent = maybeSearchComponent.releaseReturnValue();
auto maybeHashComponent = URLPatternUtilities::URLPatternComponent::compile(vm, processedInit.hash, EncodingCallbackType::Hash, compileOptions);
if (maybeHashComponent.hasException())
return maybeHashComponent.releaseException();
m_hashComponent = maybeHashComponent.releaseReturnValue();
return {};
}
static inline void matchHelperAssignInputsFromURL(const URL& input, String& protocol, String& username, String& password, String& hostname, String& port, String& pathname, String& search, String& hash)
{
protocol = input.protocol().toString();
username = input.user();
password = input.password();
hostname = input.host().toString();
port = input.port() ? String::number(*input.port()) : emptyString();
pathname = input.path().toString();
search = input.query().toString();
hash = input.fragmentIdentifier().toString();
}
static inline void matchHelperAssignInputsFromInit(const URLPatternInit& input, String& protocol, String& username, String& password, String& hostname, String& port, String& pathname, String& search, String& hash)
{
protocol = input.protocol;
username = input.username;
password = input.password;
hostname = input.hostname;
port = input.port;
pathname = input.pathname;
search = input.search;
hash = input.hash;
}
// https://urlpattern.spec.whatwg.org/#url-pattern-match
ExceptionOr<std::optional<URLPatternResult>> URLPattern::match(ScriptExecutionContext& context, Variant<URL, URLPatternInput>&& input, String&& baseURLString) const
{
URLPatternResult result;
String protocol, username, password, hostname, port, pathname, search, hash;
if (URL* inputURL = std::get_if<URL>(&input)) {
ASSERT(!inputURL->isEmpty() && inputURL->isValid());
matchHelperAssignInputsFromURL(*inputURL, protocol, username, password, hostname, port, pathname, search, hash);
result.inputs = Vector<URLPatternInput> { String { inputURL->string() } };
} else {
URLPatternInput* inputPattern = std::get_if<URLPatternInput>(&input);
result.inputs.append(*inputPattern);
auto hasError = WTF::switchOn(*inputPattern, [&](const URLPatternInit& value) -> ExceptionOr<bool> {
if (!baseURLString.isNull())
return Exception { ExceptionCode::TypeError, "Base URL string is provided with a URLPatternInit. If URLPatternInit is provided, please use URLPatternInit.baseURL property instead"_s };
URLPatternInit initCopy = value;
auto maybeResult = processInit(WTFMove(initCopy), BaseURLStringType::URL);
if (maybeResult.hasException())
return true;
matchHelperAssignInputsFromInit(maybeResult.releaseReturnValue(), protocol, username, password, hostname, port, pathname, search, hash);
return false; }, [&](const String& value) -> ExceptionOr<bool> {
URL baseURL;
if (!baseURLString.isNull()) {
baseURL = URL { baseURLString };
if (!baseURL.isValid())
return true;
result.inputs.append(baseURLString);
}
URL url { baseURL, value };
if (!url.isValid())
return true;
matchHelperAssignInputsFromURL(url, protocol, username, password, hostname, port, pathname, search, hash);
return false; });
if (hasError.hasException())
return hasError.releaseException();
if (hasError.returnValue())
return { std::nullopt };
}
auto protocolExecResult = m_protocolComponent.componentExec(context, protocol);
if (protocolExecResult.isNull() || protocolExecResult.isUndefined())
return { std::nullopt };
auto* globalObject = context.globalObject();
if (!globalObject)
return { std::nullopt };
result.protocol = m_protocolComponent.createComponentMatchResult(globalObject, WTFMove(protocol), protocolExecResult);
auto usernameExecResult = m_usernameComponent.componentExec(context, username);
if (usernameExecResult.isNull() || usernameExecResult.isUndefined())
return { std::nullopt };
result.username = m_usernameComponent.createComponentMatchResult(globalObject, WTFMove(username), usernameExecResult);
auto passwordExecResult = m_passwordComponent.componentExec(context, password);
if (passwordExecResult.isNull() || passwordExecResult.isUndefined())
return { std::nullopt };
result.password = m_passwordComponent.createComponentMatchResult(globalObject, WTFMove(password), passwordExecResult);
auto hostnameExecResult = m_hostnameComponent.componentExec(context, hostname);
if (hostnameExecResult.isNull() || hostnameExecResult.isUndefined())
return { std::nullopt };
result.hostname = m_hostnameComponent.createComponentMatchResult(globalObject, WTFMove(hostname), hostnameExecResult);
auto pathnameExecResult = m_pathnameComponent.componentExec(context, pathname);
if (pathnameExecResult.isNull() || pathnameExecResult.isUndefined())
return { std::nullopt };
result.pathname = m_pathnameComponent.createComponentMatchResult(globalObject, WTFMove(pathname), pathnameExecResult);
auto portExecResult = m_portComponent.componentExec(context, port);
if (portExecResult.isNull() || portExecResult.isUndefined())
return { std::nullopt };
result.port = m_portComponent.createComponentMatchResult(globalObject, WTFMove(port), portExecResult);
auto searchExecResult = m_searchComponent.componentExec(context, search);
if (searchExecResult.isNull() || searchExecResult.isUndefined())
return { std::nullopt };
result.search = m_searchComponent.createComponentMatchResult(globalObject, WTFMove(search), searchExecResult);
auto hashExecResult = m_hashComponent.componentExec(context, hash);
if (hashExecResult.isNull() || hashExecResult.isUndefined())
return { std::nullopt };
result.hash = m_hashComponent.createComponentMatchResult(globalObject, WTFMove(hash), hashExecResult);
return { result };
}
// https://urlpattern.spec.whatwg.org/#url-pattern-has-regexp-groups
bool URLPattern::hasRegExpGroups() const
{
return m_protocolComponent.hasRegexGroupsFromPartList()
|| m_usernameComponent.hasRegexGroupsFromPartList()
|| m_passwordComponent.hasRegexGroupsFromPartList()
|| m_hostnameComponent.hasRegexGroupsFromPartList()
|| m_pathnameComponent.hasRegexGroupsFromPartList()
|| m_portComponent.hasRegexGroupsFromPartList()
|| m_searchComponent.hasRegexGroupsFromPartList()
|| m_hashComponent.hasRegexGroupsFromPartList();
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "root.h"
#include "URLPatternComponent.h"
#include "URLPatternInit.h"
#include <wtf/Forward.h>
#include <wtf/Ref.h>
#include <wtf/RefCounted.h>
#include <wtf/RefPtr.h>
#include <wtf/TZoneMalloc.h>
namespace WebCore {
class ScriptExecutionContext;
struct URLPatternOptions;
struct URLPatternResult;
template<typename> class ExceptionOr;
enum class BaseURLStringType : bool { Pattern,
URL };
namespace URLPatternUtilities {
class URLPatternComponent;
}
class URLPattern final : public RefCounted<URLPattern> {
WTF_MAKE_TZONE_OR_ISO_ALLOCATED(URLPattern);
public:
using URLPatternInput = Variant<String, URLPatternInit>;
static ExceptionOr<Ref<URLPattern>> create(ScriptExecutionContext&, URLPatternInput&&, String&& baseURL, URLPatternOptions&&);
static ExceptionOr<Ref<URLPattern>> create(ScriptExecutionContext&, std::optional<URLPatternInput>&&, URLPatternOptions&&);
using Compatible = Variant<String, URLPatternInit, RefPtr<URLPattern>>;
static ExceptionOr<Ref<URLPattern>> create(ScriptExecutionContext&, Compatible&&, const String&);
~URLPattern();
ExceptionOr<bool> test(ScriptExecutionContext&, std::optional<URLPatternInput>&&, String&& baseURL) const;
ExceptionOr<std::optional<URLPatternResult>> exec(ScriptExecutionContext&, std::optional<URLPatternInput>&&, String&& baseURL) const;
const String& protocol() const { return m_protocolComponent.patternString(); }
const String& username() const { return m_usernameComponent.patternString(); }
const String& password() const { return m_passwordComponent.patternString(); }
const String& hostname() const { return m_hostnameComponent.patternString(); }
const String& port() const { return m_portComponent.patternString(); }
const String& pathname() const { return m_pathnameComponent.patternString(); }
const String& search() const { return m_searchComponent.patternString(); }
const String& hash() const { return m_hashComponent.patternString(); }
bool hasRegExpGroups() const;
private:
URLPattern();
ExceptionOr<void> compileAllComponents(ScriptExecutionContext&, URLPatternInit&&, const URLPatternOptions&);
ExceptionOr<std::optional<URLPatternResult>> match(ScriptExecutionContext&, Variant<URL, URLPatternInput>&&, String&& baseURLString) const;
URLPatternUtilities::URLPatternComponent m_protocolComponent;
URLPatternUtilities::URLPatternComponent m_usernameComponent;
URLPatternUtilities::URLPatternComponent m_passwordComponent;
URLPatternUtilities::URLPatternComponent m_hostnameComponent;
URLPatternUtilities::URLPatternComponent m_pathnameComponent;
URLPatternUtilities::URLPatternComponent m_portComponent;
URLPatternUtilities::URLPatternComponent m_searchComponent;
URLPatternUtilities::URLPatternComponent m_hashComponent;
};
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// https://urlpattern.spec.whatwg.org/#urlpattern
typedef (USVString or URLPatternInit) URLPatternInput;
[
EnabledBySetting=URLPatternAPIEnabled,
Exposed=(Window,Worker)
] interface URLPattern {
[CallWith=CurrentScriptExecutionContext] constructor(URLPatternInput input, USVString baseURL, optional URLPatternOptions options);
[CallWith=CurrentScriptExecutionContext] constructor(optional URLPatternInput input, optional URLPatternOptions options);
[CallWith=CurrentScriptExecutionContext] boolean test(optional URLPatternInput input, optional USVString baseURL);
[CallWith=CurrentScriptExecutionContext] URLPatternResult? exec(optional URLPatternInput input, optional USVString baseURL);
readonly attribute USVString protocol;
readonly attribute USVString username;
readonly attribute USVString password;
readonly attribute USVString hostname;
readonly attribute USVString port;
readonly attribute USVString pathname;
readonly attribute USVString search;
readonly attribute USVString hash;
readonly attribute boolean hasRegExpGroups;
};

View File

@@ -0,0 +1,289 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "URLPatternCanonical.h"
#include "ExceptionOr.h"
#include "URLDecomposition.h"
#include "URLPattern.h"
#include <wtf/URL.h>
#include <wtf/URLParser.h>
#include <wtf/text/MakeString.h>
namespace WebCore {
static constexpr auto dummyURLCharacters { "https://w/"_s };
static bool isValidIPv6HostCodePoint(auto codepoint)
{
static constexpr std::array validSpecialCodepoints { '[', ']', ':' };
return isASCIIHexDigit(codepoint) || std::find(validSpecialCodepoints.begin(), validSpecialCodepoints.end(), codepoint) != validSpecialCodepoints.end();
}
// https://urlpattern.spec.whatwg.org/#is-an-absolute-pathname
bool isAbsolutePathname(StringView input, BaseURLStringType inputType)
{
if (input.isEmpty())
return false;
if (input[0] == '/')
return true;
if (inputType == BaseURLStringType::URL)
return false;
if (input.length() < 2)
return false;
if (input.startsWith("\\/"_s))
return true;
if (input.startsWith("{/"_s))
return true;
return false;
}
// https://urlpattern.spec.whatwg.org/#canonicalize-a-protocol, combined with https://urlpattern.spec.whatwg.org/#process-protocol-for-init
ExceptionOr<String> canonicalizeProtocol(StringView value, BaseURLStringType valueType)
{
if (value.isEmpty())
return value.toString();
auto strippedValue = value.endsWith(':') ? value.left(value.length() - 1) : value;
if (valueType == BaseURLStringType::Pattern)
return strippedValue.toString();
URL dummyURL(makeString(strippedValue, "://w/"_s));
if (!dummyURL.isValid())
return Exception { ExceptionCode::TypeError, "Invalid input to canonicalize a URL protocol string."_s };
return dummyURL.protocol().toString();
}
// https://urlpattern.spec.whatwg.org/#canonicalize-a-username, combined with https://urlpattern.spec.whatwg.org/#process-username-for-init
String canonicalizeUsername(StringView value, BaseURLStringType valueType)
{
if (value.isEmpty())
return value.toString();
if (valueType == BaseURLStringType::Pattern)
return value.toString();
URL dummyURL(dummyURLCharacters);
dummyURL.setUser(value);
return dummyURL.encodedUser().toString();
}
// https://urlpattern.spec.whatwg.org/#canonicalize-a-password, combined with https://urlpattern.spec.whatwg.org/#process-password-for-init
String canonicalizePassword(StringView value, BaseURLStringType valueType)
{
if (value.isEmpty())
return value.toString();
if (valueType == BaseURLStringType::Pattern)
return value.toString();
URL dummyURL(dummyURLCharacters);
dummyURL.setPassword(value);
return dummyURL.encodedPassword().toString();
}
// https://urlpattern.spec.whatwg.org/#canonicalize-a-hostname, combined with https://urlpattern.spec.whatwg.org/#process-hostname-for-init
ExceptionOr<String> canonicalizeHostname(StringView value, BaseURLStringType valueType)
{
if (value.isEmpty())
return value.toString();
if (valueType == BaseURLStringType::Pattern)
return value.toString();
URL dummyURL(dummyURLCharacters);
if (!dummyURL.setHost(value))
return Exception { ExceptionCode::TypeError, "Invalid input to canonicalize a URL host string."_s };
return dummyURL.host().toString();
}
// https://urlpattern.spec.whatwg.org/#canonicalize-an-ipv6-hostname
ExceptionOr<String> canonicalizeIPv6Hostname(StringView value, BaseURLStringType valueType)
{
if (valueType == BaseURLStringType::Pattern)
return value.toString();
StringBuilder result;
result.reserveCapacity(value.length());
for (auto codepoint : value.codePoints()) {
if (!isValidIPv6HostCodePoint(codepoint))
return Exception { ExceptionCode::TypeError, "Invalid input to canonicalize a URL IPv6 host string."_s };
result.append(toASCIILower(codepoint));
}
return String { result.toString() };
}
// https://urlpattern.spec.whatwg.org/#canonicalize-a-port, combined with https://urlpattern.spec.whatwg.org/#process-port-for-init
ExceptionOr<String> canonicalizePort(StringView portValue, StringView protocolValue, BaseURLStringType portValueType)
{
if (portValue.isEmpty())
return portValue.toString();
if (portValueType == BaseURLStringType::Pattern)
return portValue.toString();
auto maybePort = URLDecomposition::parsePort(portValue, protocolValue);
if (!maybePort)
return Exception { ExceptionCode::TypeError, "Invalid input to canonicalize a URL port string."_s };
auto maybePortNumber = *maybePort;
if (!maybePortNumber)
return String { emptyString() };
return String::number(*maybePortNumber);
}
// https://urlpattern.spec.whatwg.org/#canonicalize-an-opaque-pathname
ExceptionOr<String> canonicalizeOpaquePathname(StringView value)
{
if (value.isEmpty())
return value.toString();
URL dummyURL(makeString("a:"_s, value));
if (!dummyURL.isValid())
return Exception { ExceptionCode::TypeError, "Invalid input to canonicalize a URL opaque path string."_s };
return dummyURL.path().toString();
}
// https://urlpattern.spec.whatwg.org/#canonicalize-a-pathname
ExceptionOr<String> canonicalizePathname(StringView pathnameValue)
{
if (pathnameValue.isEmpty())
return pathnameValue.toString();
bool hasLeadingSlash = pathnameValue[0] == '/';
String maybeAddSlashPrefix = hasLeadingSlash ? pathnameValue.toString() : makeString("/-"_s, pathnameValue);
// FIXME: Set state override to State::PathStart after URLParser supports state override.
URL dummyURL(dummyURLCharacters);
dummyURL.setPath(maybeAddSlashPrefix);
ASSERT(dummyURL.isValid());
auto result = dummyURL.path();
if (!hasLeadingSlash)
result = result.substring(2);
return result.toString();
}
// https://urlpattern.spec.whatwg.org/#process-pathname-for-init
ExceptionOr<String> processPathname(StringView pathnameValue, const StringView protocolValue, BaseURLStringType pathnameValueType)
{
if (pathnameValue.isEmpty())
return pathnameValue.toString();
if (pathnameValueType == BaseURLStringType::Pattern)
return pathnameValue.toString();
if (WTF::URLParser::isSpecialScheme(protocolValue) || protocolValue.isEmpty())
return canonicalizePathname(pathnameValue);
return canonicalizeOpaquePathname(pathnameValue);
}
// https://urlpattern.spec.whatwg.org/#canonicalize-a-search, combined with https://urlpattern.spec.whatwg.org/#process-search-for-init
ExceptionOr<String> canonicalizeSearch(StringView value, BaseURLStringType valueType)
{
if (value.isEmpty())
return value.toString();
auto strippedValue = value[0] == '?' ? value.substring(1) : value;
if (valueType == BaseURLStringType::Pattern)
return strippedValue.toString();
URL dummyURL(dummyURLCharacters);
dummyURL.setQuery(strippedValue);
ASSERT(dummyURL.isValid());
return dummyURL.query().toString();
}
// https://urlpattern.spec.whatwg.org/#canonicalize-a-hash, combined with https://urlpattern.spec.whatwg.org/#process-hash-for-init
ExceptionOr<String> canonicalizeHash(StringView value, BaseURLStringType valueType)
{
if (value.isEmpty())
return value.toString();
auto strippedValue = value[0] == '#' ? value.substring(1) : value;
if (valueType == BaseURLStringType::Pattern)
return strippedValue.toString();
URL dummyURL(dummyURLCharacters);
dummyURL.setFragmentIdentifier(strippedValue);
ASSERT(dummyURL.isValid());
return dummyURL.fragmentIdentifier().toString();
}
ExceptionOr<String> callEncodingCallback(EncodingCallbackType type, StringView input)
{
switch (type) {
case EncodingCallbackType::Protocol:
return canonicalizeProtocol(input, BaseURLStringType::URL);
case EncodingCallbackType::Username:
return canonicalizeUsername(input, BaseURLStringType::URL);
case EncodingCallbackType::Password:
return canonicalizePassword(input, BaseURLStringType::URL);
case EncodingCallbackType::Host:
return canonicalizeHostname(input, BaseURLStringType::URL);
case EncodingCallbackType::IPv6Host:
return canonicalizeIPv6Hostname(input, BaseURLStringType::URL);
case EncodingCallbackType::Port:
return canonicalizePort(input, {}, BaseURLStringType::URL);
case EncodingCallbackType::Path:
return canonicalizePathname(input);
case EncodingCallbackType::OpaquePath:
return canonicalizeOpaquePathname(input);
case EncodingCallbackType::Search:
return canonicalizeSearch(input, BaseURLStringType::URL);
case EncodingCallbackType::Hash:
return canonicalizeHash(input, BaseURLStringType::URL);
default:
ASSERT_NOT_REACHED();
return Exception { ExceptionCode::TypeError, "Invalid input type for encoding callback."_s };
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <wtf/text/StringView.h>
#include <wtf/text/WTFString.h>
namespace WebCore {
template<typename> class ExceptionOr;
enum class BaseURLStringType : bool;
enum class EncodingCallbackType : uint8_t { Protocol,
Username,
Password,
Host,
IPv6Host,
Port,
Path,
OpaquePath,
Search,
Hash };
bool isAbsolutePathname(StringView input, BaseURLStringType inputType);
ExceptionOr<String> canonicalizeProtocol(StringView, BaseURLStringType valueType);
String canonicalizeUsername(StringView value, BaseURLStringType valueType);
String canonicalizePassword(StringView value, BaseURLStringType valueType);
ExceptionOr<String> canonicalizeHostname(StringView value, BaseURLStringType valueType);
ExceptionOr<String> canonicalizeIPv6Hostname(StringView value, BaseURLStringType valueType);
ExceptionOr<String> canonicalizePort(StringView portValue, StringView protocolValue, BaseURLStringType portValueType);
ExceptionOr<String> processPathname(StringView pathnameValue, const StringView protocolValue, BaseURLStringType pathnameValueType);
ExceptionOr<String> canonicalizePathname(StringView pathnameValue);
ExceptionOr<String> canonicalizeOpaquePathname(StringView value);
ExceptionOr<String> canonicalizeSearch(StringView value, BaseURLStringType valueType);
ExceptionOr<String> canonicalizeHash(StringView value, BaseURLStringType valueType);
ExceptionOr<String> callEncodingCallback(EncodingCallbackType, StringView input);
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "URLPatternComponent.h"
#include "ExceptionOr.h"
#include "ScriptExecutionContext.h"
#include "URLPatternCanonical.h"
#include "URLPatternParser.h"
#include "URLPatternResult.h"
#include <JavaScriptCore/JSCJSValue.h>
#include <JavaScriptCore/JSString.h>
#include <JavaScriptCore/RegExpObject.h>
#include <ranges>
namespace WebCore {
using namespace JSC;
namespace URLPatternUtilities {
URLPatternComponent::URLPatternComponent(String&& patternString, JSC::Strong<JSC::RegExp>&& regex, Vector<String>&& groupNameList, bool hasRegexpGroupsFromPartsList)
: m_patternString(WTFMove(patternString))
, m_regularExpression(WTFMove(regex))
, m_groupNameList(WTFMove(groupNameList))
, m_hasRegexGroupsFromPartList(hasRegexpGroupsFromPartsList)
{
}
// https://urlpattern.spec.whatwg.org/#compile-a-component
ExceptionOr<URLPatternComponent> URLPatternComponent::compile(Ref<JSC::VM> vm, StringView input, EncodingCallbackType type, const URLPatternStringOptions& options)
{
auto maybePartList = URLPatternParser::parse(input, options, type);
if (maybePartList.hasException())
return maybePartList.releaseException();
Vector<Part> partList = maybePartList.releaseReturnValue();
auto [regularExpressionString, nameList] = generateRegexAndNameList(partList, options);
OptionSet<JSC::Yarr::Flags> flags = { JSC::Yarr::Flags::UnicodeSets };
if (options.ignoreCase)
flags.add(JSC::Yarr::Flags::IgnoreCase);
JSC::RegExp* regularExpression = JSC::RegExp::create(vm, regularExpressionString, flags);
if (!regularExpression->isValid())
return Exception { ExceptionCode::TypeError, "Unable to create RegExp object regular expression from provided URLPattern string."_s };
String patternString = generatePatternString(partList, options);
bool hasRegexGroups = partList.containsIf([](auto& part) {
return part.type == PartType::Regexp;
});
return URLPatternComponent { WTFMove(patternString), JSC::Strong<JSC::RegExp> { vm, regularExpression }, WTFMove(nameList), hasRegexGroups };
}
// https://urlpattern.spec.whatwg.org/#protocol-component-matches-a-special-scheme
bool URLPatternComponent::matchSpecialSchemeProtocol(ScriptExecutionContext& context) const
{
Ref vm = context.vm();
JSC::JSLockHolder lock(vm);
static constexpr std::array specialSchemeList { "ftp"_s, "file"_s, "http"_s, "https"_s, "ws"_s, "wss"_s };
auto contextObject = context.globalObject();
if (!contextObject)
return false;
auto protocolRegex = JSC::RegExpObject::create(vm, contextObject->regExpStructure(), m_regularExpression.get(), true);
auto isSchemeMatch = std::ranges::find_if(specialSchemeList, [context = Ref { context }, &vm, &protocolRegex](const String& scheme) {
auto maybeMatch = protocolRegex->exec(context->globalObject(), JSC::jsString(vm, scheme));
return !maybeMatch.isNull();
});
return isSchemeMatch != specialSchemeList.end();
}
JSC::JSValue URLPatternComponent::componentExec(ScriptExecutionContext& context, StringView comparedString) const
{
Ref vm = context.vm();
JSC::JSLockHolder lock(vm);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto contextObject = context.globalObject();
if (!contextObject) {
throwTypeError(contextObject, throwScope, "URLPattern execution requires a valid execution context"_s);
return {};
}
auto regex = JSC::RegExpObject::create(vm, contextObject->regExpStructure(), m_regularExpression.get(), true);
return regex->exec(contextObject, JSC::jsString(vm, comparedString));
}
// https://urlpattern.spec.whatwg.org/#create-a-component-match-result
URLPatternComponentResult URLPatternComponent::createComponentMatchResult(JSC::JSGlobalObject* globalObject, String&& input, const JSC::JSValue& execResult) const
{
URLPatternComponentResult::GroupsRecord groups;
Ref vm = globalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto lengthValue = execResult.get(globalObject, vm->propertyNames->length);
RETURN_IF_EXCEPTION(throwScope, {});
auto length = lengthValue.toIntegerOrInfinity(globalObject);
RETURN_IF_EXCEPTION(throwScope, {});
ASSERT(length >= 0 && std::isfinite(length));
for (unsigned index = 1; index < length; ++index) {
auto match = execResult.get(globalObject, index);
RETURN_IF_EXCEPTION(throwScope, {});
Variant<std::monostate, String> value;
if (!match.isNull() && !match.isUndefined()) {
value = match.toWTFString(globalObject);
RETURN_IF_EXCEPTION(throwScope, {});
}
size_t groupIndex = index - 1;
String groupName = groupIndex < m_groupNameList.size() ? m_groupNameList[groupIndex] : emptyString();
groups.append(URLPatternComponentResult::NameMatchPair { WTFMove(groupName), WTFMove(value) });
}
return URLPatternComponentResult { !input.isEmpty() ? WTFMove(input) : emptyString(), WTFMove(groups) };
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <JavaScriptCore/Strong.h>
#include <JavaScriptCore/StrongInlines.h>
namespace JSC {
class RegExp;
class VM;
class JSValue;
}
namespace WebCore {
class ScriptExecutionContext;
struct URLPatternComponentResult;
enum class EncodingCallbackType : uint8_t;
template<typename> class ExceptionOr;
namespace URLPatternUtilities {
struct URLPatternStringOptions;
class URLPatternComponent {
public:
static ExceptionOr<URLPatternComponent> compile(Ref<JSC::VM>, StringView, EncodingCallbackType, const URLPatternStringOptions&);
const String& patternString() const { return m_patternString; }
bool hasRegexGroupsFromPartList() const { return m_hasRegexGroupsFromPartList; }
bool matchSpecialSchemeProtocol(ScriptExecutionContext&) const;
JSC::JSValue componentExec(ScriptExecutionContext&, StringView) const;
URLPatternComponentResult createComponentMatchResult(JSC::JSGlobalObject*, String&& input, const JSC::JSValue& execResult) const;
URLPatternComponent() = default;
private:
URLPatternComponent(String&&, JSC::Strong<JSC::RegExp>&&, Vector<String>&&, bool);
String m_patternString;
JSC::Strong<JSC::RegExp> m_regularExpression;
Vector<String> m_groupNameList;
bool m_hasRegexGroupsFromPartList { false };
};
}
}

View File

@@ -0,0 +1,368 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "URLPatternConstructorStringParser.h"
#include "ExceptionOr.h"
#include "URLPatternCanonical.h"
#include "URLPatternComponent.h"
#include "URLPatternInit.h"
#include "URLPatternParser.h"
#include "URLPatternTokenizer.h"
namespace WebCore {
using namespace JSC;
URLPatternConstructorStringParser::URLPatternConstructorStringParser(String&& input)
: m_input(WTFMove(input))
{
}
// https://urlpattern.spec.whatwg.org/#rewind
void URLPatternConstructorStringParser::rewind()
{
m_tokenIndex = m_componentStart;
m_tokenIncrement = 0;
}
// https://urlpattern.spec.whatwg.org/#get-a-safe-token
const URLPatternUtilities::Token& URLPatternConstructorStringParser::getSafeToken(size_t index) const
{
if (index < m_tokenList.size())
return m_tokenList[index];
ASSERT(m_tokenList.last().type == URLPatternUtilities::TokenType::End);
return m_tokenList.last();
}
// https://urlpattern.spec.whatwg.org/#is-a-non-special-pattern-char
bool URLPatternConstructorStringParser::isNonSpecialPatternCharacter(size_t index, char value) const
{
auto token = getSafeToken(index);
return token.value.length() == 1 && token.value[0] == value
&& (token.type == URLPatternUtilities::TokenType::Char
|| token.type == URLPatternUtilities::TokenType::EscapedChar
|| token.type == URLPatternUtilities::TokenType::InvalidChar);
}
// https://urlpattern.spec.whatwg.org/#is-a-search-prefix
bool URLPatternConstructorStringParser::isSearchPrefix() const
{
if (isNonSpecialPatternCharacter(m_tokenIndex, '?'))
return true;
if (m_tokenList[m_tokenIndex].value != "?"_s)
return false;
if (m_tokenIndex == 0)
return true;
size_t previousIndex = m_tokenIndex - 1;
auto previousToken = getSafeToken(previousIndex);
if (previousToken.type == URLPatternUtilities::TokenType::Name
|| previousToken.type == URLPatternUtilities::TokenType::Regexp
|| previousToken.type == URLPatternUtilities::TokenType::Close
|| previousToken.type == URLPatternUtilities::TokenType::Asterisk) {
return false;
}
return true;
}
// https://urlpattern.spec.whatwg.org/#next-is-authority-slashes
bool URLPatternConstructorStringParser::isAuthoritySlashesNext() const
{
if (!isNonSpecialPatternCharacter(m_tokenIndex + 1, '/'))
return false;
if (!isNonSpecialPatternCharacter(m_tokenIndex + 2, '/'))
return false;
return true;
}
// https://urlpattern.spec.whatwg.org/#make-a-component-string
String URLPatternConstructorStringParser::makeComponentString() const
{
const auto& token = m_tokenList[m_tokenIndex];
auto componentStartToken = getSafeToken(m_componentStart);
auto componentStartIndex = *componentStartToken.index;
return m_input.substring(componentStartIndex, *token.index - componentStartIndex).toString();
}
static inline void setInitComponentFromState(URLPatternInit& init, URLPatternConstructorStringParserState state, String&& componentString)
{
switch (state) {
case URLPatternConstructorStringParserState::Protocol:
init.protocol = WTFMove(componentString);
break;
case URLPatternConstructorStringParserState::Username:
init.username = WTFMove(componentString);
break;
case URLPatternConstructorStringParserState::Password:
init.password = WTFMove(componentString);
break;
case URLPatternConstructorStringParserState::Hostname:
init.hostname = WTFMove(componentString);
break;
case URLPatternConstructorStringParserState::Port:
init.port = WTFMove(componentString);
break;
case URLPatternConstructorStringParserState::Pathname:
init.pathname = WTFMove(componentString);
break;
case URLPatternConstructorStringParserState::Search:
init.search = WTFMove(componentString);
break;
case URLPatternConstructorStringParserState::Hash:
init.hash = WTFMove(componentString);
break;
default:
break;
}
}
// https://urlpattern.spec.whatwg.org/#compute-protocol-matches-a-special-scheme-flag
ExceptionOr<void> URLPatternConstructorStringParser::computeProtocolMatchSpecialSchemeFlag(ScriptExecutionContext& context)
{
Ref vm = context.vm();
JSC::JSLockHolder lock(vm);
auto maybeProtocolComponent = URLPatternUtilities::URLPatternComponent::compile(vm, makeComponentString(), EncodingCallbackType::Protocol, URLPatternUtilities::URLPatternStringOptions {});
if (maybeProtocolComponent.hasException())
return maybeProtocolComponent.releaseException();
auto protocolComponent = maybeProtocolComponent.releaseReturnValue();
m_protocolMatchesSpecialSchemeFlag = protocolComponent.matchSpecialSchemeProtocol(context);
return {};
}
// https://urlpattern.spec.whatwg.org/#change-state
void URLPatternConstructorStringParser::changeState(URLPatternConstructorStringParserState newState, size_t skip)
{
if (m_state != URLPatternConstructorStringParserState::Init
&& m_state != URLPatternConstructorStringParserState::Authority
&& m_state != URLPatternConstructorStringParserState::Done)
setInitComponentFromState(m_result, m_state, makeComponentString());
if (m_state != URLPatternConstructorStringParserState::Init && newState != URLPatternConstructorStringParserState::Done) {
// Set init's hostname to empty if conditions are met.
static constexpr std::array validStateConditionsForEmptyHostname { URLPatternConstructorStringParserState::Protocol, URLPatternConstructorStringParserState::Authority, URLPatternConstructorStringParserState::Username, URLPatternConstructorStringParserState::Password };
static constexpr std::array validNewStateConditionsForEmptyHostname { URLPatternConstructorStringParserState::Port, URLPatternConstructorStringParserState::Pathname, URLPatternConstructorStringParserState::Search, URLPatternConstructorStringParserState::Hash };
if (std::ranges::find(validStateConditionsForEmptyHostname, m_state) != validStateConditionsForEmptyHostname.end()
&& std::ranges::find(validNewStateConditionsForEmptyHostname, newState) != validNewStateConditionsForEmptyHostname.end()
&& m_result.hostname.isNull()) {
m_result.hostname = emptyString();
}
// Set init's pathname to empty if conditions are met.
static constexpr std::array validStateConditionsForEmptyPathname { URLPatternConstructorStringParserState::Protocol, URLPatternConstructorStringParserState::Authority, URLPatternConstructorStringParserState::Username, URLPatternConstructorStringParserState::Password, URLPatternConstructorStringParserState::Hostname, URLPatternConstructorStringParserState::Port };
static constexpr std::array validNewStateConditionsForEmptyPathname { URLPatternConstructorStringParserState::Search, URLPatternConstructorStringParserState::Hash };
if (std::ranges::find(validStateConditionsForEmptyPathname, m_state) != validStateConditionsForEmptyPathname.end()
&& std::ranges::find(validNewStateConditionsForEmptyPathname, newState) != validNewStateConditionsForEmptyPathname.end()
&& m_result.pathname.isNull()) {
m_result.pathname = m_protocolMatchesSpecialSchemeFlag ? "/"_s : emptyString();
}
// Set init's search to empty if conditions are met.
static constexpr std::array validStateConditionsForEmptySearch { URLPatternConstructorStringParserState::Protocol, URLPatternConstructorStringParserState::Authority, URLPatternConstructorStringParserState::Username, URLPatternConstructorStringParserState::Password, URLPatternConstructorStringParserState::Hostname, URLPatternConstructorStringParserState::Port, URLPatternConstructorStringParserState::Pathname };
if (std::ranges::find(validStateConditionsForEmptySearch, m_state) != validStateConditionsForEmptySearch.end()
&& newState == URLPatternConstructorStringParserState::Hash
&& m_result.search.isNull()) {
m_result.search = emptyString();
}
}
m_state = newState;
m_tokenIndex += skip;
m_componentStart = m_tokenIndex;
m_tokenIncrement = 0;
}
void URLPatternConstructorStringParser::updateState(ScriptExecutionContext& context)
{
switch (m_state) {
case URLPatternConstructorStringParserState::Init:
// Look for protocol prefix.
if (isNonSpecialPatternCharacter(m_tokenIndex, ':')) {
rewind();
m_state = URLPatternConstructorStringParserState::Protocol;
}
break;
case URLPatternConstructorStringParserState::Protocol:
// Look for protocol prefix.
if (isNonSpecialPatternCharacter(m_tokenIndex, ':')) {
auto maybeMatchesSpecialSchemeProtocol = computeProtocolMatchSpecialSchemeFlag(context);
if (maybeMatchesSpecialSchemeProtocol.hasException())
break; // FIXME: Return exceptions.
auto nextState = URLPatternConstructorStringParserState::Pathname;
auto skip = 1;
if (isAuthoritySlashesNext()) {
nextState = URLPatternConstructorStringParserState::Authority;
skip = 3;
} else if (m_protocolMatchesSpecialSchemeFlag)
nextState = URLPatternConstructorStringParserState::Authority;
changeState(nextState, skip);
}
break;
case URLPatternConstructorStringParserState::Authority:
// Look for identity terminator.
if (isNonSpecialPatternCharacter(m_tokenIndex, '@')) {
rewind();
m_state = URLPatternConstructorStringParserState::Username;
} else if (isNonSpecialPatternCharacter(m_tokenIndex, '/') || isSearchPrefix() || isNonSpecialPatternCharacter(m_tokenIndex, '#')) { // Look for pathname start, search prefix or hash prefix.
rewind();
m_state = URLPatternConstructorStringParserState::Hostname;
}
break;
case URLPatternConstructorStringParserState::Username:
// Look for password prefix.
if (isNonSpecialPatternCharacter(m_tokenIndex, ':'))
changeState(URLPatternConstructorStringParserState::Password, 1);
// Look for identity terminator.
else if (isNonSpecialPatternCharacter(m_tokenIndex, '@'))
changeState(URLPatternConstructorStringParserState::Hostname, 1);
break;
case URLPatternConstructorStringParserState::Password:
// Look for identity terminator.
if (isNonSpecialPatternCharacter(m_tokenIndex, '@'))
changeState(URLPatternConstructorStringParserState::Hostname, 1);
break;
case URLPatternConstructorStringParserState::Hostname:
// Look for an IPv6 open.
if (isNonSpecialPatternCharacter(m_tokenIndex, '['))
++m_hostnameIPv6BracketDepth;
// Look for an IPv6 close.
else if (isNonSpecialPatternCharacter(m_tokenIndex, ']') && m_hostnameIPv6BracketDepth > 0)
--m_hostnameIPv6BracketDepth;
// Look for port prefix.
else if (isNonSpecialPatternCharacter(m_tokenIndex, ':') && !m_hostnameIPv6BracketDepth)
changeState(URLPatternConstructorStringParserState::Port, 1);
// Look for pathname start.
else if (isNonSpecialPatternCharacter(m_tokenIndex, '/'))
changeState(URLPatternConstructorStringParserState::Pathname, 0);
// Look for search prefix.
else if (isSearchPrefix())
changeState(URLPatternConstructorStringParserState::Search, 1);
// Look for hash prefix.
else if (isNonSpecialPatternCharacter(m_tokenIndex, '#'))
changeState(URLPatternConstructorStringParserState::Hash, 1);
break;
case URLPatternConstructorStringParserState::Port:
// Look for pathname start.
if (isNonSpecialPatternCharacter(m_tokenIndex, '/'))
changeState(URLPatternConstructorStringParserState::Pathname, 0);
else if (isSearchPrefix())
changeState(URLPatternConstructorStringParserState::Search, 1);
// Look for hash prefix.
else if (isNonSpecialPatternCharacter(m_tokenIndex, '#'))
changeState(URLPatternConstructorStringParserState::Hash, 1);
break;
case URLPatternConstructorStringParserState::Pathname:
if (isSearchPrefix())
changeState(URLPatternConstructorStringParserState::Search, 1);
// Look for hash prefix.
else if (isNonSpecialPatternCharacter(m_tokenIndex, '#'))
changeState(URLPatternConstructorStringParserState::Hash, 1);
break;
case URLPatternConstructorStringParserState::Search:
// Look for hash prefix.
if (isNonSpecialPatternCharacter(m_tokenIndex, '#'))
changeState(URLPatternConstructorStringParserState::Hash, 1);
break;
case URLPatternConstructorStringParserState::Hash:
break;
case URLPatternConstructorStringParserState::Done:
ASSERT_NOT_REACHED();
break;
default:
break;
}
}
void URLPatternConstructorStringParser::performParse(ScriptExecutionContext& context)
{
while (m_tokenIndex < m_tokenList.size()) {
m_tokenIncrement = 1;
if (m_tokenList[m_tokenIndex].type == URLPatternUtilities::TokenType::End) {
if (m_state == URLPatternConstructorStringParserState::Init) {
rewind();
if (isNonSpecialPatternCharacter(m_tokenIndex, '#'))
changeState(URLPatternConstructorStringParserState::Hash, 1);
else if (isSearchPrefix())
changeState(URLPatternConstructorStringParserState::Search, 1);
else
changeState(URLPatternConstructorStringParserState::Pathname, 0);
m_tokenIndex += m_tokenIncrement;
continue;
}
if (m_state == URLPatternConstructorStringParserState::Authority) {
rewind();
m_state = URLPatternConstructorStringParserState::Hostname;
m_tokenIndex += m_tokenIncrement;
continue;
}
changeState(URLPatternConstructorStringParserState::Done, 0);
break;
}
if (m_tokenList[m_tokenIndex].type == URLPatternUtilities::TokenType::Open) {
++m_groupDepth;
++m_tokenIndex;
continue;
}
if (m_groupDepth) {
if (m_tokenList[m_tokenIndex].type == URLPatternUtilities::TokenType::Close)
--m_groupDepth;
else {
m_tokenIndex += m_tokenIncrement;
continue;
}
}
updateState(context);
m_tokenIndex += m_tokenIncrement;
}
if (!m_result.hostname.isNull() && m_result.port.isNull())
m_result.port = emptyString();
}
// https://urlpattern.spec.whatwg.org/#parse-a-constructor-string
ExceptionOr<URLPatternInit> URLPatternConstructorStringParser::parse(ScriptExecutionContext& context)
{
auto maybeTokenList = URLPatternUtilities::Tokenizer(m_input, URLPatternUtilities::TokenizePolicy::Lenient).tokenize();
if (maybeTokenList.hasException())
return maybeTokenList.releaseException();
m_tokenList = maybeTokenList.releaseReturnValue();
performParse(context);
return URLPatternInit { m_result };
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "ScriptExecutionContext.h"
#include "URLPatternInit.h"
namespace WebCore {
template<typename> class ExceptionOr;
enum class EncodingCallbackType : uint8_t;
namespace URLPatternUtilities {
struct Token;
enum class TokenType : uint8_t;
struct URLPatternStringOptions;
struct URLPatternInit;
}
enum class URLPatternConstructorStringParserState : uint8_t { Init,
Protocol,
Authority,
Username,
Password,
Hostname,
Port,
Pathname,
Search,
Hash,
Done };
class URLPatternConstructorStringParser {
public:
explicit URLPatternConstructorStringParser(String&& input);
ExceptionOr<URLPatternInit> parse(ScriptExecutionContext&);
private:
void performParse(ScriptExecutionContext&);
void rewind();
const URLPatternUtilities::Token& getSafeToken(size_t index) const;
bool isNonSpecialPatternCharacter(size_t index, char value) const;
bool isSearchPrefix() const;
bool isAuthoritySlashesNext() const;
String makeComponentString() const;
void changeState(URLPatternConstructorStringParserState, size_t skip);
void updateState(ScriptExecutionContext&);
ExceptionOr<void> computeProtocolMatchSpecialSchemeFlag(ScriptExecutionContext&);
StringView m_input;
Vector<URLPatternUtilities::Token> m_tokenList;
URLPatternInit m_result;
size_t m_componentStart { 0 };
size_t m_tokenIndex { 0 };
size_t m_tokenIncrement { 1 };
size_t m_groupDepth { 0 };
int m_hostnameIPv6BracketDepth { 0 };
bool m_protocolMatchesSpecialSchemeFlag { false };
URLPatternConstructorStringParserState m_state { URLPatternConstructorStringParserState::Init };
};
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <wtf/text/WTFString.h>
namespace WebCore {
struct URLPatternInit {
String protocol;
String username;
String password;
String hostname;
String port;
String pathname;
String search;
String hash;
String baseURL;
};
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
// https://urlpattern.spec.whatwg.org/#dictdef-urlpatterninit
[
JSGenerateToJSObject,
JSGenerateToNativeObject
] dictionary URLPatternInit {
USVString protocol;
USVString username;
USVString password;
USVString hostname;
USVString port;
USVString pathname;
USVString search;
USVString hash;
USVString baseURL;
};

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
namespace WebCore {
struct URLPatternOptions {
bool ignoreCase { false };
};
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
// https://urlpattern.spec.whatwg.org/#dictdef-urlpatternoptions
dictionary URLPatternOptions {
boolean ignoreCase = false;
};

View File

@@ -0,0 +1,553 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "URLPatternParser.h"
#include "ExceptionOr.h"
#include "URLPatternCanonical.h"
#include "URLPatternTokenizer.h"
#include <ranges>
#include <wtf/text/MakeString.h>
namespace WebCore {
namespace URLPatternUtilities {
URLPatternParser::URLPatternParser(EncodingCallbackType type, String&& segmentWildcardRegexp)
: m_callbackType(type)
, m_segmentWildcardRegexp(WTFMove(segmentWildcardRegexp))
{
}
ExceptionOr<void> URLPatternParser::performParse(const URLPatternStringOptions& options)
{
ExceptionOr<void> maybeFunctionException;
while (m_index < m_tokenList.size()) {
auto charToken = tryToConsumeToken(TokenType::Char);
auto nameToken = tryToConsumeToken(TokenType::Name);
auto regexOrWildcardToken = tryToConsumeRegexOrWildcardToken(nameToken);
if (!nameToken.isNull() || !regexOrWildcardToken.isNull()) {
String prefix;
if (!charToken.isNull())
prefix = charToken.value.toString();
if (!prefix.isEmpty() && prefix != options.prefixCodepoint)
m_pendingFixedValue.append(std::exchange(prefix, {}));
maybeFunctionException = maybeAddPartFromPendingFixedValue();
if (maybeFunctionException.hasException())
return maybeFunctionException.releaseException();
auto modifierToken = tryToConsumeModifierToken();
maybeFunctionException = addPart(WTFMove(prefix), nameToken, regexOrWildcardToken, {}, modifierToken);
if (maybeFunctionException.hasException())
return maybeFunctionException.releaseException();
continue;
}
auto fixedToken = charToken;
if (fixedToken.isNull())
fixedToken = tryToConsumeToken(TokenType::EscapedChar);
if (!fixedToken.isNull()) {
m_pendingFixedValue.append(WTFMove(fixedToken.value));
continue;
}
auto openToken = tryToConsumeToken(TokenType::Open);
if (!openToken.isNull()) {
String prefix = consumeText();
nameToken = tryToConsumeToken(TokenType::Name);
regexOrWildcardToken = tryToConsumeRegexOrWildcardToken(nameToken);
String suffix = consumeText();
auto maybeCloseError = consumeRequiredToken(TokenType::Close);
if (maybeCloseError.hasException())
return maybeCloseError.releaseException();
auto modifierToken = tryToConsumeModifierToken();
maybeFunctionException = addPart(WTFMove(prefix), nameToken, regexOrWildcardToken, WTFMove(suffix), modifierToken);
if (maybeFunctionException.hasException())
return maybeFunctionException.releaseException();
continue;
}
maybeFunctionException = maybeAddPartFromPendingFixedValue();
if (maybeFunctionException.hasException())
return maybeFunctionException.releaseException();
auto maybeSyntaxError = consumeRequiredToken(TokenType::End);
if (maybeSyntaxError.hasException())
return maybeSyntaxError.releaseException();
}
return {};
}
// https://urlpattern.spec.whatwg.org/#try-to-consume-a-token
Token URLPatternParser::tryToConsumeToken(TokenType type)
{
if (m_index >= m_tokenList.size())
return {};
auto& nextToken = m_tokenList[m_index];
if (nextToken.type != type)
return {};
++m_index;
return nextToken;
}
// https://urlpattern.spec.whatwg.org/#try-to-consume-a-regexp-or-wildcard-token
Token URLPatternParser::tryToConsumeRegexOrWildcardToken(const Token& token)
{
auto tokenResult = tryToConsumeToken(TokenType::Regexp);
if (tokenResult.isNull() && token.isNull())
tokenResult = tryToConsumeToken(TokenType::Asterisk);
return tokenResult;
}
// https://urlpattern.spec.whatwg.org/#try-to-consume-a-modifier-token
Token URLPatternParser::tryToConsumeModifierToken()
{
auto token = tryToConsumeToken(TokenType::OtherModifier);
if (!token.isNull())
return token;
return tryToConsumeToken(TokenType::Asterisk);
}
// https://urlpattern.spec.whatwg.org/#consume-text
String URLPatternParser::consumeText()
{
StringBuilder result;
while (true) {
auto consumedToken = tryToConsumeToken(TokenType::Char);
if (consumedToken.isNull())
consumedToken = tryToConsumeToken(TokenType::EscapedChar);
if (consumedToken.isNull())
break;
result.append(consumedToken.value);
}
return result.toString();
}
// https://urlpattern.spec.whatwg.org/#consume-a-required-token
ExceptionOr<Token> URLPatternParser::consumeRequiredToken(TokenType type)
{
auto result = tryToConsumeToken(type);
if (result.isNull())
return Exception { ExceptionCode::TypeError, "Null token was produced when consuming a required token."_s };
return result;
}
// https://urlpattern.spec.whatwg.org/#maybe-add-a-part-from-the-pending-fixed-value
ExceptionOr<void> URLPatternParser::maybeAddPartFromPendingFixedValue()
{
if (m_pendingFixedValue.isEmpty())
return {};
auto encodedValue = callEncodingCallback(m_callbackType, m_pendingFixedValue.toString());
m_pendingFixedValue.clear();
if (encodedValue.hasException())
return encodedValue.releaseException();
m_partList.append(Part { .type = PartType::FixedText, .value = encodedValue.releaseReturnValue(), .modifier = Modifier::None });
return {};
}
// https://urlpattern.spec.whatwg.org/#add-a-part
ExceptionOr<void> URLPatternParser::addPart(String&& prefix, const Token& nameToken, const Token& regexpOrWildcardToken, String&& suffix, const Token& modifierToken)
{
Modifier modifier = Modifier::None;
if (!modifierToken.isNull()) {
if (modifierToken.value == "?"_s)
modifier = Modifier::Optional;
else if (modifierToken.value == "*"_s)
modifier = Modifier::ZeroOrMore;
else if (modifierToken.value == "+"_s)
modifier = Modifier::OneOrMore;
}
if (nameToken.isNull() && regexpOrWildcardToken.isNull() && modifier == Modifier::None) {
m_pendingFixedValue.append(WTFMove(prefix));
return {};
}
auto maybeFunctionException = maybeAddPartFromPendingFixedValue();
if (maybeFunctionException.hasException())
return maybeFunctionException.releaseException();
if (nameToken.isNull() && regexpOrWildcardToken.isNull()) {
ASSERT(suffix.isEmpty());
if (prefix.isEmpty())
return {};
auto encodedValue = callEncodingCallback(m_callbackType, WTFMove(prefix));
if (encodedValue.hasException())
return encodedValue.releaseException();
m_partList.append(Part { .type = PartType::FixedText, .value = encodedValue.releaseReturnValue(), .modifier = modifier });
return {};
}
String regexValue;
if (regexpOrWildcardToken.isNull())
regexValue = m_segmentWildcardRegexp;
else if (regexpOrWildcardToken.type == TokenType::Asterisk)
regexValue = ".*"_s;
else
regexValue = regexpOrWildcardToken.value.toString();
PartType type = PartType::Regexp;
if (regexValue == m_segmentWildcardRegexp) {
type = PartType::SegmentWildcard;
regexValue = {};
} else if (regexValue == ".*"_s) {
type = PartType::FullWildcard;
regexValue = {};
}
String name;
if (!nameToken.isNull())
name = nameToken.value.toString();
else if (!regexpOrWildcardToken.isNull()) {
name = String::number(m_nextNumericName);
++m_nextNumericName;
}
if (isDuplicateName(name))
return Exception { ExceptionCode::TypeError, "Duplicate name token produced when adding to parser part list."_s };
auto encodedPrefix = callEncodingCallback(m_callbackType, WTFMove(prefix));
if (encodedPrefix.hasException())
return encodedPrefix.releaseException();
auto encodedSuffix = callEncodingCallback(m_callbackType, WTFMove(suffix));
if (encodedSuffix.hasException())
return encodedSuffix.releaseException();
m_partList.append(Part { type, WTFMove(regexValue), modifier, WTFMove(name), encodedPrefix.releaseReturnValue(), encodedSuffix.releaseReturnValue() });
return {};
}
// https://urlpattern.spec.whatwg.org/#is-a-duplicate-name
bool URLPatternParser::isDuplicateName(StringView name) const
{
return m_partList.containsIf([&](auto& part) {
return part.name == name;
});
}
// https://urlpattern.spec.whatwg.org/#parse-a-pattern-string
ExceptionOr<Vector<Part>> URLPatternParser::parse(StringView patternStringInput, const URLPatternStringOptions& options, EncodingCallbackType type)
{
URLPatternParser tokenParser { type, generateSegmentWildcardRegexp(options) };
auto maybeParserTokenList = Tokenizer(patternStringInput, TokenizePolicy::Strict).tokenize();
if (maybeParserTokenList.hasException())
return maybeParserTokenList.releaseException();
tokenParser.setTokenList(maybeParserTokenList.releaseReturnValue());
auto maybePerformParseError = tokenParser.performParse(options);
if (maybePerformParseError.hasException())
return maybePerformParseError.releaseException();
return tokenParser.takePartList();
}
// https://urlpattern.spec.whatwg.org/#generate-a-segment-wildcard-regexp
String generateSegmentWildcardRegexp(const URLPatternStringOptions& options)
{
return makeString("[^"_s, escapeRegexString(options.delimiterCodepoint), "]+?"_s);
}
template<typename CharacterType>
static String escapeRegexStringForCharacters(std::span<const CharacterType> characters)
{
static constexpr auto regexEscapeCharacters = std::to_array<const CharacterType>({ '.', '+', '*', '?', '^', '$', '{', '}', '(', ')', '[', ']', '|', '/', '\\' }); // NOLINT
StringBuilder result;
result.reserveCapacity(characters.size());
for (auto character : characters) {
if (std::ranges::find(regexEscapeCharacters, character) != regexEscapeCharacters.end())
result.append('\\');
result.append(character);
}
return result.toString();
}
// https://urlpattern.spec.whatwg.org/#escape-a-regexp-string
String escapeRegexString(StringView input)
{
// FIXME: Ensure input only contains ASCII based on spec after the parser (or tokenizer) knows to filter non-ASCII input.
if (input.is8Bit())
return escapeRegexStringForCharacters(input.span8());
return escapeRegexStringForCharacters(input.span16());
}
// https://urlpattern.spec.whatwg.org/#convert-a-modifier-to-a-string
ASCIILiteral convertModifierToString(Modifier modifier)
{
switch (modifier) {
case Modifier::ZeroOrMore:
return "*"_s;
case Modifier::Optional:
return "?"_s;
case Modifier::OneOrMore:
return "+"_s;
default:
return {};
}
}
// https://urlpattern.spec.whatwg.org/#generate-a-regular-expression-and-name-list
std::pair<String, Vector<String>> generateRegexAndNameList(const Vector<Part>& partList, const URLPatternStringOptions& options)
{
StringBuilder result;
result.append('^');
Vector<String> nameList;
for (auto& part : partList) {
if (part.type == PartType::FixedText) {
if (part.modifier == Modifier::None)
result.append(escapeRegexString(part.value));
else
result.append("(?:"_s, escapeRegexString(part.value), ')', convertModifierToString(part.modifier));
continue;
}
ASSERT(!part.name.isEmpty());
nameList.append(part.name);
String regexpValue;
if (part.type == PartType::SegmentWildcard)
regexpValue = generateSegmentWildcardRegexp(options);
else if (part.type == PartType::FullWildcard)
regexpValue = ".*"_s;
else
regexpValue = part.value;
if (part.prefix.isEmpty() && part.suffix.isEmpty()) {
if (part.modifier == Modifier::None || part.modifier == Modifier::Optional)
result.append('(', regexpValue, ')', convertModifierToString(part.modifier));
else
result.append("((?:"_s, regexpValue, ')', convertModifierToString(part.modifier), ')');
continue;
}
if (part.modifier == Modifier::None || part.modifier == Modifier::Optional) {
result.append("(?:"_s, escapeRegexString(part.prefix), '(', regexpValue, ')', escapeRegexString(part.suffix), ')', convertModifierToString(part.modifier));
continue;
}
ASSERT(part.modifier == Modifier::ZeroOrMore || part.modifier == Modifier::OneOrMore);
ASSERT(!part.prefix.isEmpty() || !part.suffix.isEmpty());
result.append("(?:"_s,
escapeRegexString(part.prefix),
"((?:"_s,
regexpValue,
")(?:"_s,
escapeRegexString(part.suffix),
escapeRegexString(part.prefix),
"(?:"_s,
regexpValue,
"))*)"_s,
escapeRegexString(part.suffix),
')');
if (part.modifier == Modifier::ZeroOrMore)
result.append('?');
}
result.append('$');
return { result.toString(), WTFMove(nameList) };
}
// https://urlpattern.spec.whatwg.org/#generate-a-pattern-string
String generatePatternString(const Vector<Part>& partList, const URLPatternStringOptions& options)
{
StringBuilder result;
for (size_t index = 0; index < partList.size(); ++index) {
auto& part = partList[index];
std::optional<Part> previousPart;
if (index > 0)
previousPart = partList[index - 1];
std::optional<Part> nextPart;
if (index < partList.size() - 1)
nextPart = partList[index + 1];
if (part.type == PartType::FixedText) {
if (part.modifier == Modifier::None) {
result.append(escapePatternString(part.value));
continue;
}
result.append('{', escapePatternString(part.value), '}', convertModifierToString(part.modifier));
continue;
}
bool hasCustomName = !part.name.isEmpty() && !isASCIIDigit(part.name[0]);
bool needsGrouping = !part.suffix.isEmpty() || (!part.prefix.isEmpty() && part.prefix != options.prefixCodepoint);
if (!needsGrouping && hasCustomName
&& part.type == PartType::SegmentWildcard && part.modifier == Modifier::None
&& nextPart && nextPart->prefix.isEmpty() && nextPart->suffix.isEmpty()) {
if (nextPart->type == PartType::FixedText) {
if (!nextPart->value.isEmpty())
needsGrouping = isValidNameCodepoint(*StringView(nextPart->value).codePoints().begin(), IsFirst::No);
} else
needsGrouping = !nextPart->name.isEmpty() && isASCIIDigit(nextPart->name[0]);
}
if (!needsGrouping && part.prefix.isEmpty() && previousPart && previousPart->type == PartType::FixedText && !previousPart->value.isEmpty()) {
if (options.prefixCodepoint.length() == 1
&& options.prefixCodepoint.startsWith(*StringView(previousPart->value).codePoints().codePointAt(previousPart->value.length() - 1)))
needsGrouping = true;
}
ASSERT(!part.name.isEmpty());
if (needsGrouping)
result.append('{');
result.append(escapePatternString(part.prefix));
if (hasCustomName)
result.append(':', part.name);
if (part.type == PartType::Regexp)
result.append('(', part.value, ')');
else if (part.type == PartType::SegmentWildcard && !hasCustomName)
result.append('(', generateSegmentWildcardRegexp(options), ')');
else if (part.type == PartType::FullWildcard) {
if (!hasCustomName
&& (!previousPart || previousPart->type == PartType::FixedText || previousPart->modifier != Modifier::None
|| needsGrouping || !part.prefix.isEmpty()))
result.append('*');
else
result.append("(.*)"_s);
}
if (part.type == PartType::SegmentWildcard && hasCustomName && !part.suffix.isEmpty() && isValidNameCodepoint(*StringView(part.suffix).codePoints().begin(), IsFirst::Yes))
result.append('\\');
result.append(escapePatternString(part.suffix));
if (needsGrouping)
result.append('}');
result.append(convertModifierToString(part.modifier));
}
return result.toString();
}
template<typename CharacterType>
static String escapePatternStringForCharacters(std::span<const CharacterType> characters)
{
static constexpr auto escapeCharacters = std::to_array<const CharacterType>({ '+', '*', '?', ':', '(', ')', '\\', '{', '}' }); // NOLINT
StringBuilder result;
result.reserveCapacity(characters.size());
for (auto character : characters) {
if (std::ranges::find(escapeCharacters, character) != escapeCharacters.end())
result.append('\\');
result.append(character);
}
return result.toString();
}
// https://urlpattern.spec.whatwg.org/#escape-a-pattern-string
String escapePatternString(StringView input)
{
// FIXME: Ensure input only contains ASCII based on spec after the parser (or tokenizer) knows to filter non-ASCII input.
if (input.is8Bit())
return escapePatternStringForCharacters(input.span8());
return escapePatternStringForCharacters(input.span16());
}
// https://urlpattern.spec.whatwg.org/#is-a-valid-name-code-point
bool isValidNameCodepoint(char16_t codepoint, URLPatternUtilities::IsFirst first)
{
if (first == URLPatternUtilities::IsFirst::Yes)
return u_hasBinaryProperty(codepoint, UCHAR_ID_START) || codepoint == '_' || codepoint == '$';
return u_hasBinaryProperty(codepoint, UCHAR_ID_CONTINUE) || codepoint == '_' || codepoint == '$' || codepoint == 0x200c || codepoint == 0x200d;
}
} // namespace URLPatternUtilities
} // namespace WebCore

View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "URLPatternTokenizer.h"
#include <wtf/text/StringBuilder.h>
#include <wtf/text/WTFString.h>
namespace WebCore {
enum class EncodingCallbackType : uint8_t;
template<typename> class ExceptionOr;
namespace URLPatternUtilities {
struct Token;
enum class TokenType : uint8_t;
enum class PartType : uint8_t { FixedText,
Regexp,
SegmentWildcard,
FullWildcard };
enum class Modifier : uint8_t { None,
Optional,
ZeroOrMore,
OneOrMore };
enum class IsFirst : bool { No,
Yes };
struct Part {
PartType type;
String value;
Modifier modifier;
String name {};
String prefix {};
String suffix {};
};
struct URLPatternStringOptions {
String delimiterCodepoint {};
String prefixCodepoint {};
bool ignoreCase { false };
};
class URLPatternParser {
public:
URLPatternParser(EncodingCallbackType, String&& segmentWildcardRegexp);
ExceptionOr<void> performParse(const URLPatternStringOptions&);
void setTokenList(Vector<Token>&& tokenList) { m_tokenList = WTFMove(tokenList); }
static ExceptionOr<Vector<Part>> parse(StringView, const URLPatternStringOptions&, EncodingCallbackType);
private:
Token tryToConsumeToken(TokenType);
Token tryToConsumeRegexOrWildcardToken(const Token&);
Token tryToConsumeModifierToken();
String consumeText();
ExceptionOr<Token> consumeRequiredToken(TokenType);
ExceptionOr<void> maybeAddPartFromPendingFixedValue();
ExceptionOr<void> addPart(String&& prefix, const Token& nameToken, const Token& regexpOrWildcardToken, String&& suffix, const Token& modifierToken);
bool isDuplicateName(StringView) const;
Vector<Part> takePartList() { return std::exchange(m_partList, {}); }
Vector<Token> m_tokenList;
Vector<Part> m_partList;
EncodingCallbackType m_callbackType;
String m_segmentWildcardRegexp;
StringBuilder m_pendingFixedValue;
size_t m_index { 0 };
int m_nextNumericName { 0 };
};
// FIXME: Consider moving functions to somewhere generic, perhaps refactor Part to its own class.
String generateSegmentWildcardRegexp(const URLPatternStringOptions&);
String escapeRegexString(StringView);
ASCIILiteral convertModifierToString(Modifier);
std::pair<String, Vector<String>> generateRegexAndNameList(const Vector<Part>& partList, const URLPatternStringOptions&);
String generatePatternString(const Vector<Part>& partList, const URLPatternStringOptions&);
String escapePatternString(StringView input);
bool isValidNameCodepoint(char16_t codepoint, URLPatternUtilities::IsFirst);
} // namespace URLPatternUtilities
} // namespace WebCore

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "URLPattern.h"
namespace WebCore {
struct URLPatternComponentResult {
using NameMatchPair = KeyValuePair<String, Variant<std::monostate, String>>;
using GroupsRecord = Vector<NameMatchPair>;
String input;
GroupsRecord groups;
};
struct URLPatternResult {
Vector<URLPattern::URLPatternInput> inputs;
URLPatternComponentResult protocol;
URLPatternComponentResult username;
URLPatternComponentResult password;
URLPatternComponentResult hostname;
URLPatternComponentResult port;
URLPatternComponentResult pathname;
URLPatternComponentResult search;
URLPatternComponentResult hash;
};
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
// https://urlpattern.spec.whatwg.org/#dictdef-urlpatterncomponentresult
// https://urlpattern.spec.whatwg.org/#dictdef-urlpatternresult
typedef (USVString or URLPatternInit) URLPatternInput;
[
JSGenerateToJSObject,
JSGenerateToNativeObject,
ImplementedAs=URLPatternComponentResult
] dictionary URLPatternComponentResult {
USVString input;
record<USVString, (undefined or USVString)> groups;
};
[
JSGenerateToJSObject,
JSGenerateToNativeObject
] dictionary URLPatternResult {
sequence<URLPatternInput> inputs;
URLPatternComponentResult protocol;
URLPatternComponentResult username;
URLPatternComponentResult password;
URLPatternComponentResult hostname;
URLPatternComponentResult port;
URLPatternComponentResult pathname;
URLPatternComponentResult search;
URLPatternComponentResult hash;
};

View File

@@ -0,0 +1,273 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "URLPatternTokenizer.h"
#include "ExceptionOr.h"
#include "URLPatternParser.h"
#include <unicode/utf16.h>
#include <wtf/text/MakeString.h>
namespace WebCore {
namespace URLPatternUtilities {
bool Token::isNull() const
{
if (!index) {
ASSERT(value.isNull());
return true;
}
return false;
}
// https://urlpattern.spec.whatwg.org/#get-the-next-code-point
void Tokenizer::getNextCodePoint()
{
m_codepoint = m_input[m_nextIndex++];
if (m_input.is8Bit() || !U16_IS_LEAD(m_codepoint) || m_nextIndex >= m_input.length())
return;
auto next = m_input[m_nextIndex];
if (!U16_IS_TRAIL(next))
return;
m_nextIndex++;
m_codepoint = U16_GET_SUPPLEMENTARY(m_codepoint, next);
}
// https://urlpattern.spec.whatwg.org/#seek-and-get-the-next-code-point
void Tokenizer::seekNextCodePoint(size_t index)
{
m_nextIndex = index;
getNextCodePoint();
}
// https://urlpattern.spec.whatwg.org/#add-a-token
void Tokenizer::addToken(TokenType currentType, size_t nextPosition, size_t valuePosition, size_t valueLength)
{
m_tokenList.append(Token { currentType, m_index, m_input.substring(valuePosition, valueLength) });
m_index = nextPosition;
}
// https://urlpattern.spec.whatwg.org/#add-a-token-with-default-length
void Tokenizer::addToken(TokenType currentType, size_t nextPosition, size_t valuePosition)
{
addToken(currentType, nextPosition, valuePosition, nextPosition - valuePosition);
}
// https://urlpattern.spec.whatwg.org/#add-a-token-with-default-position-and-length
void Tokenizer::addToken(TokenType currentType)
{
addToken(currentType, m_nextIndex, m_index);
}
// https://urlpattern.spec.whatwg.org/#process-a-tokenizing-error
ExceptionOr<void> Tokenizer::processTokenizingError(size_t nextPosition, size_t valuePosition, const String& callerErrorInfo)
{
if (m_policy == TokenizePolicy::Strict)
return Exception { ExceptionCode::TypeError, callerErrorInfo };
ASSERT(m_policy == TokenizePolicy::Lenient);
addToken(TokenType::InvalidChar, nextPosition, valuePosition);
return {};
}
Tokenizer::Tokenizer(StringView input, TokenizePolicy tokenizerPolicy)
: m_input(input)
, m_policy(tokenizerPolicy)
{
}
// https://urlpattern.spec.whatwg.org/#tokenize
ExceptionOr<Vector<Token>> Tokenizer::tokenize()
{
ExceptionOr<void> maybeException;
while (m_index < m_input.length()) {
if (m_policy == TokenizePolicy::Strict && maybeException.hasException())
return maybeException.releaseException();
seekNextCodePoint(m_index);
if (m_codepoint == '*') {
addToken(TokenType::Asterisk);
continue;
}
if (m_codepoint == '+' || m_codepoint == '?') {
addToken(TokenType::OtherModifier);
continue;
}
if (m_codepoint == '\\') {
if (m_index == m_input.length() - 1) {
maybeException = processTokenizingError(m_nextIndex, m_index, "No character is provided after escape."_s);
continue;
}
auto escapedIndex = m_nextIndex;
getNextCodePoint();
addToken(TokenType::EscapedChar, m_nextIndex, escapedIndex);
continue;
}
if (m_codepoint == '{') {
addToken(TokenType::Open);
continue;
}
if (m_codepoint == '}') {
addToken(TokenType::Close);
continue;
}
if (m_codepoint == ':') {
auto namePosition = m_nextIndex;
auto nameStart = namePosition;
while (namePosition < m_input.length()) {
seekNextCodePoint(namePosition);
bool isValidCodepoint = isValidNameCodepoint(m_codepoint, namePosition == nameStart ? IsFirst::Yes : IsFirst::No);
if (!isValidCodepoint)
break;
namePosition = m_nextIndex;
}
if (namePosition <= nameStart) {
maybeException = processTokenizingError(nameStart, m_index, makeString("Name position "_s, String::number(namePosition), " is less than name start "_s, String::number(nameStart)));
continue;
}
addToken(TokenType::Name, namePosition, nameStart);
continue;
}
if (m_codepoint == '(') {
int depth = 1;
auto regexPosition = m_nextIndex;
auto regexStart = regexPosition;
bool hasError = false;
while (regexPosition < m_input.length()) {
seekNextCodePoint(regexPosition);
if (!isASCII(m_codepoint)) {
maybeException = processTokenizingError(regexStart, m_index, "Current codepoint is not ascii"_s);
hasError = true;
break;
}
if (regexPosition == regexStart && m_codepoint == '?') {
maybeException = processTokenizingError(regexStart, m_index, "Regex cannot start with modifier."_s);
hasError = true;
break;
}
if (m_codepoint == '\\') {
if (regexPosition == m_input.length() - 1) {
maybeException = processTokenizingError(regexStart, m_index, "No character is provided after escape."_s);
hasError = true;
break;
}
getNextCodePoint();
if (!isASCII(m_codepoint)) {
maybeException = processTokenizingError(regexStart, m_index, "Current codepoint is not ascii"_s);
hasError = true;
break;
}
regexPosition = m_nextIndex;
continue;
}
if (m_codepoint == ')') {
depth = depth - 1;
if (!depth) {
regexPosition = m_nextIndex;
break;
}
}
if (m_codepoint == '(') {
depth = depth + 1;
if (regexPosition == m_input.length() - 1) {
maybeException = processTokenizingError(regexStart, m_index, "No closing token is provided by end of string."_s);
hasError = true;
break;
}
int temporaryPosition = m_nextIndex;
getNextCodePoint();
if (m_codepoint != '?') {
maybeException = processTokenizingError(regexStart, m_index, "Required OtherModifier token is not provided in regex."_s);
hasError = true;
break;
}
m_nextIndex = temporaryPosition;
}
regexPosition = m_nextIndex;
}
if (hasError)
continue;
if (depth) {
maybeException = processTokenizingError(regexStart, m_index, "Current open token does not have a corresponding close token."_s);
continue;
}
auto regexLength = regexPosition - regexStart - 1;
if (!regexLength)
maybeException = processTokenizingError(regexStart, m_index, "Regex length is zero."_s);
addToken(TokenType::Regexp, regexPosition, regexStart, regexLength);
continue;
}
addToken(TokenType::Char);
}
addToken(TokenType::End, m_index, m_index);
return WTFMove(m_tokenList);
}
} // namespace URLPatternUtilities
} // namespace WebCore

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <wtf/text/StringView.h>
namespace WebCore {
template<typename> class ExceptionOr;
namespace URLPatternUtilities {
enum class TokenType : uint8_t { Open,
Close,
Regexp,
Name,
Char,
EscapedChar,
OtherModifier,
Asterisk,
End,
InvalidChar };
enum class TokenizePolicy : bool { Strict,
Lenient };
struct Token {
TokenType type;
std::optional<size_t> index;
StringView value;
bool isNull() const;
};
class Tokenizer {
public:
Tokenizer(StringView input, TokenizePolicy tokenizerPolicy);
ExceptionOr<Vector<Token>> tokenize();
private:
StringView m_input;
TokenizePolicy m_policy { TokenizePolicy::Strict };
Vector<Token> m_tokenList;
size_t m_index { 0 };
size_t m_nextIndex { 0 };
char32_t m_codepoint;
void getNextCodePoint();
void seekNextCodePoint(size_t index);
void addToken(TokenType currentType, size_t nextPosition, size_t valuePosition, size_t valueLength);
void addToken(TokenType currentType, size_t nextPosition, size_t valuePosition);
void addToken(TokenType currentType);
ExceptionOr<void> processTokenizingError(size_t nextPosition, size_t valuePosition, const String&);
};
} // namespace URLPatternUtilities
} // namespace WebCore

View File

@@ -632,7 +632,11 @@ pub fn Bun__fetch_(
break :extract_verbose verbose;
};
// proxy: string | undefined;
// proxy: string | { url: string, headers?: Headers } | undefined;
var proxy_headers: ?Headers = null;
defer if (proxy_headers) |*hdrs| {
hdrs.deinit();
};
url_proxy_buffer = extract_proxy: {
const objects_to_try = [_]jsc.JSValue{
options_object orelse .zero,
@@ -641,6 +645,7 @@ pub fn Bun__fetch_(
inline for (0..2) |i| {
if (objects_to_try[i] != .zero) {
if (try objects_to_try[i].get(globalThis, "proxy")) |proxy_arg| {
// Handle string format: proxy: "http://proxy.example.com:8080"
if (proxy_arg.isString() and try proxy_arg.getLength(ctx) > 0) {
var href = try jsc.URL.hrefFromJS(proxy_arg, globalThis);
if (href.tag == .Dead) {
@@ -661,6 +666,54 @@ pub fn Bun__fetch_(
allocator.free(url_proxy_buffer);
break :extract_proxy buffer;
}
// Handle object format: proxy: { url: "http://proxy.example.com:8080", headers?: Headers }
if (proxy_arg.isObject()) {
// Get the URL from the proxy object
const proxy_url_arg = try proxy_arg.get(globalThis, "url");
if (proxy_url_arg == null or proxy_url_arg.?.isUndefinedOrNull()) {
const err = ctx.toTypeError(.INVALID_ARG_VALUE, "fetch() proxy object requires a 'url' property", .{});
is_error = true;
return JSPromise.dangerouslyCreateRejectedPromiseValueWithoutNotifyingVM(globalThis, err);
}
if (proxy_url_arg.?.isString() and try proxy_url_arg.?.getLength(ctx) > 0) {
var href = try jsc.URL.hrefFromJS(proxy_url_arg.?, globalThis);
if (href.tag == .Dead) {
const err = ctx.toTypeError(.INVALID_ARG_VALUE, "fetch() proxy URL is invalid", .{});
is_error = true;
return JSPromise.dangerouslyCreateRejectedPromiseValueWithoutNotifyingVM(globalThis, err);
}
defer href.deref();
const buffer = try std.fmt.allocPrint(allocator, "{s}{f}", .{ url_proxy_buffer, href });
url = ZigURL.parse(buffer[0..url.href.len]);
if (url.isFile()) {
url_type = URLType.file;
} else if (url.isBlob()) {
url_type = URLType.blob;
}
proxy = ZigURL.parse(buffer[url.href.len..]);
allocator.free(url_proxy_buffer);
url_proxy_buffer = buffer;
// Get the headers from the proxy object (optional)
if (try proxy_arg.get(globalThis, "headers")) |headers_value| {
if (!headers_value.isUndefinedOrNull()) {
if (headers_value.as(FetchHeaders)) |fetch_hdrs| {
proxy_headers = Headers.from(fetch_hdrs, allocator, .{}) catch |err| bun.handleOom(err);
} else if (try FetchHeaders.createFromJS(ctx, headers_value)) |fetch_hdrs| {
defer fetch_hdrs.deref();
proxy_headers = Headers.from(fetch_hdrs, allocator, .{}) catch |err| bun.handleOom(err);
}
}
}
break :extract_proxy url_proxy_buffer;
} else {
const err = ctx.toTypeError(.INVALID_ARG_VALUE, "fetch() proxy.url must be a non-empty string", .{});
is_error = true;
return JSPromise.dangerouslyCreateRejectedPromiseValueWithoutNotifyingVM(globalThis, err);
}
}
}
if (globalThis.hasException()) {
@@ -1338,6 +1391,7 @@ pub fn Bun__fetch_(
.redirect_type = redirect_type,
.verbose = verbose,
.proxy = proxy,
.proxy_headers = proxy_headers,
.url_proxy_buffer = url_proxy_buffer,
.signal = signal,
.globalThis = globalThis,
@@ -1372,6 +1426,7 @@ pub fn Bun__fetch_(
body = FetchTasklet.HTTPRequestBody.Empty;
}
proxy = null;
proxy_headers = null;
url_proxy_buffer = "";
signal = null;
ssl_config = null;

View File

@@ -1049,6 +1049,7 @@ pub const FetchTasklet = struct {
fetch_options.redirect_type,
.{
.http_proxy = proxy,
.proxy_headers = fetch_options.proxy_headers,
.hostname = fetch_options.hostname,
.signals = fetch_tasklet.signals,
.unix_socket_path = fetch_options.unix_socket_path,
@@ -1222,6 +1223,7 @@ pub const FetchTasklet = struct {
verbose: http.HTTPVerboseLevel = .none,
redirect_type: FetchRedirect = FetchRedirect.follow,
proxy: ?ZigURL = null,
proxy_headers: ?Headers = null,
url_proxy_buffer: []const u8 = "",
signal: ?*jsc.WebCore.AbortSignal = null,
globalThis: ?*JSGlobalObject,

View File

@@ -328,10 +328,29 @@ fn writeProxyConnect(
_ = writer.write("\r\nProxy-Connection: Keep-Alive\r\n") catch 0;
// Check if user provided Proxy-Authorization in custom headers
const user_provided_proxy_auth = if (client.proxy_headers) |hdrs| hdrs.get("proxy-authorization") != null else false;
// Only write auto-generated proxy_authorization if user didn't provide one
if (client.proxy_authorization) |auth| {
_ = writer.write("Proxy-Authorization: ") catch 0;
_ = writer.write(auth) catch 0;
_ = writer.write("\r\n") catch 0;
if (!user_provided_proxy_auth) {
_ = writer.write("Proxy-Authorization: ") catch 0;
_ = writer.write(auth) catch 0;
_ = writer.write("\r\n") catch 0;
}
}
// Write custom proxy headers
if (client.proxy_headers) |hdrs| {
const slice = hdrs.entries.slice();
const names = slice.items(.name);
const values = slice.items(.value);
for (names, 0..) |name_ptr, idx| {
_ = writer.write(hdrs.asStr(name_ptr)) catch 0;
_ = writer.write(": ") catch 0;
_ = writer.write(hdrs.asStr(values[idx])) catch 0;
_ = writer.write("\r\n") catch 0;
}
}
_ = writer.write("\r\n") catch 0;
@@ -359,11 +378,31 @@ fn writeProxyRequest(
_ = writer.write(request.path) catch 0;
_ = writer.write(" HTTP/1.1\r\nProxy-Connection: Keep-Alive\r\n") catch 0;
// Check if user provided Proxy-Authorization in custom headers
const user_provided_proxy_auth = if (client.proxy_headers) |hdrs| hdrs.get("proxy-authorization") != null else false;
// Only write auto-generated proxy_authorization if user didn't provide one
if (client.proxy_authorization) |auth| {
_ = writer.write("Proxy-Authorization: ") catch 0;
_ = writer.write(auth) catch 0;
_ = writer.write("\r\n") catch 0;
if (!user_provided_proxy_auth) {
_ = writer.write("Proxy-Authorization: ") catch 0;
_ = writer.write(auth) catch 0;
_ = writer.write("\r\n") catch 0;
}
}
// Write custom proxy headers
if (client.proxy_headers) |hdrs| {
const slice = hdrs.entries.slice();
const names = slice.items(.name);
const values = slice.items(.value);
for (names, 0..) |name_ptr, idx| {
_ = writer.write(hdrs.asStr(name_ptr)) catch 0;
_ = writer.write(": ") catch 0;
_ = writer.write(hdrs.asStr(values[idx])) catch 0;
_ = writer.write("\r\n") catch 0;
}
}
for (request.headers) |header| {
_ = writer.write(header.name) catch 0;
_ = writer.write(": ") catch 0;
@@ -450,6 +489,7 @@ if_modified_since: string = "",
request_content_len_buf: ["-4294967295".len]u8 = undefined,
http_proxy: ?URL = null,
proxy_headers: ?Headers = null,
proxy_authorization: ?[]u8 = null,
proxy_tunnel: ?*ProxyTunnel = null,
signals: Signals = .{},
@@ -466,6 +506,10 @@ pub fn deinit(this: *HTTPClient) void {
this.allocator.free(auth);
this.proxy_authorization = null;
}
if (this.proxy_headers) |*hdrs| {
hdrs.deinit();
this.proxy_headers = null;
}
if (this.proxy_tunnel) |tunnel| {
this.proxy_tunnel = null;
tunnel.detachAndDeref();

View File

@@ -93,6 +93,7 @@ const AtomicState = std.atomic.Value(State);
pub const Options = struct {
http_proxy: ?URL = null,
proxy_headers: ?Headers = null,
hostname: ?[]u8 = null,
signals: ?Signals = null,
unix_socket_path: ?jsc.ZigString.Slice = null,
@@ -185,6 +186,7 @@ pub fn init(
.signals = options.signals orelse this.signals,
.async_http_id = this.async_http_id,
.http_proxy = this.http_proxy,
.proxy_headers = options.proxy_headers,
.redirect_type = redirect_type,
};
if (options.unix_socket_path) |val| {

View File

@@ -27,7 +27,7 @@ const shared_params = [_]ParamType{
clap.parseParam("--save Save to package.json (true by default)") catch unreachable,
clap.parseParam("--ca <STR>... Provide a Certificate Authority signing certificate") catch unreachable,
clap.parseParam("--cafile <STR> The same as `--ca`, but is a file path to the certificate") catch unreachable,
clap.parseParam("--dry-run Don't install anything") catch unreachable,
clap.parseParam("--dry-run Perform a dry run without making changes") catch unreachable,
clap.parseParam("--frozen-lockfile Disallow changes to lockfile") catch unreachable,
clap.parseParam("-f, --force Always request the latest versions from the registry & reinstall all dependencies") catch unreachable,
clap.parseParam("--cache-dir <PATH> Store & load cached data from a specific directory path") catch unreachable,

View File

@@ -666,33 +666,19 @@ pub fn runTasks(
switch (version.tag) {
.git => {
version.value.git.package_name = pkg.name;
try manager.processDependencyListItem(dep, &any_root, install_peer);
},
.github => {
version.value.github.package_name = pkg.name;
try manager.processDependencyListItem(dep, &any_root, install_peer);
},
.tarball => {
version.value.tarball.package_name = pkg.name;
try manager.processDependencyListItem(dep, &any_root, install_peer);
},
// `else` is reachable if this package is from `overrides`. Version in `lockfile.buffer.dependencies`
// will still have the original (e.g., npm semver like "^14.14.1" overridden with "github:...").
// In this case, we can directly assign the resolution since we just extracted this package.
else => {
switch (dep) {
.root_dependency => {
assignRootResolution(manager, id, package_id);
any_root = true;
},
.dependency => {
assignResolution(manager, id, package_id);
},
else => {},
}
},
// will still have the original.
else => {},
}
try manager.processDependencyListItem(dep, &any_root, install_peer);
},
else => {
// if it's a node_module folder to install, handle that after we process all the dependencies within the onExtract callback.
@@ -1136,5 +1122,3 @@ const PackageManager = bun.install.PackageManager;
const Options = PackageManager.Options;
const PackageInstaller = PackageManager.PackageInstaller;
const ProgressStrings = PackageManager.ProgressStrings;
const assignResolution = PackageManager.assignResolution;
const assignRootResolution = PackageManager.assignRootResolution;

View File

@@ -455,10 +455,11 @@ class PooledMySQLConnection {
}
}
class MySQLAdapter
implements
DatabaseAdapter<PooledMySQLConnection, $ZigGeneratedClasses.MySQLConnection, $ZigGeneratedClasses.MySQLQuery>
{
class MySQLAdapter implements DatabaseAdapter<
PooledMySQLConnection,
$ZigGeneratedClasses.MySQLConnection,
$ZigGeneratedClasses.MySQLQuery
> {
public readonly connectionInfo: Bun.SQL.__internal.DefinedPostgresOrMySQLOptions;
public readonly connections: PooledMySQLConnection[];

View File

@@ -672,14 +672,11 @@ class PooledPostgresConnection {
}
}
class PostgresAdapter
implements
DatabaseAdapter<
PooledPostgresConnection,
$ZigGeneratedClasses.PostgresSQLConnection,
$ZigGeneratedClasses.PostgresSQLQuery
>
{
class PostgresAdapter implements DatabaseAdapter<
PooledPostgresConnection,
$ZigGeneratedClasses.PostgresSQLConnection,
$ZigGeneratedClasses.PostgresSQLQuery
> {
public readonly connectionInfo: Bun.SQL.__internal.DefinedPostgresOrMySQLOptions;
public readonly connections: PooledPostgresConnection[];

View File

@@ -3807,6 +3807,7 @@ class Http2Server extends net.Server {
if (typeof callback === "function") {
this.on("timeout", callback);
}
return this;
}
updateSettings(settings) {
assertSettings(settings);
@@ -3900,6 +3901,7 @@ class Http2SecureServer extends tls.Server {
if (typeof callback === "function") {
this.on("timeout", callback);
}
return this;
}
updateSettings(settings) {
assertSettings(settings);

View File

@@ -984,7 +984,12 @@ pub const FormData = struct {
switch (encoding) {
.URLEncoded => {
var str = jsc.ZigString.fromUTF8(strings.withoutUTF8BOM(input));
return jsc.DOMFormData.createFromURLQuery(globalThis, &str);
const result = jsc.DOMFormData.createFromURLQuery(globalThis, &str);
// Check if an exception was thrown (e.g., string too long)
if (result == .zero) {
return error.JSError;
}
return result;
},
.Multipart => |boundary| return toJSFromMultipartData(globalThis, input, boundary),
}
@@ -1041,7 +1046,11 @@ pub const FormData = struct {
return globalThis.throwInvalidArguments("input must be a string or ArrayBufferView", .{});
}
return FormData.toJS(globalThis, input, encoding) catch |err| return globalThis.throwError(err, "while parsing FormData");
return FormData.toJS(globalThis, input, encoding) catch |err| {
if (err == error.JSError) return error.JSError;
if (err == error.JSTerminated) return error.JSTerminated;
return globalThis.throwError(err, "while parsing FormData");
};
}
comptime {

View File

@@ -11,17 +11,17 @@ describe("Bun.build compile with wasm", () => {
"app.js": `
// Import a wasm module and properly instantiate it
import wasmPath from "./test.wasm";
async function main() {
try {
// Read the wasm file as ArrayBuffer
const wasmBuffer = await Bun.file(wasmPath).arrayBuffer();
const { instance } = await WebAssembly.instantiate(wasmBuffer);
// Call the add function from wasm
const result = instance.exports.add(2, 3);
console.log("WASM result:", result);
if (result === 5) {
console.log("WASM module loaded successfully");
process.exit(0);
@@ -34,7 +34,7 @@ describe("Bun.build compile with wasm", () => {
process.exit(1);
}
}
main();
`,
// A real WebAssembly module that exports an 'add' function

View File

@@ -247,61 +247,3 @@ test("overrides do not apply to workspaces", async () => {
expect(await exited).toBe(0);
expect(await stderr.text()).not.toContain("Saved lockfile");
});
test("resolutions with github dependency override (monorepo)", async () => {
// This test verifies that using "resolutions" to override a semver dependency
// with a GitHub dependency works correctly, even when the GitHub repo's
// root package.json has a DIFFERENT name than the requested package.
// This is the case for monorepos like discord.js where:
// - The user requests "discord.js" (the npm package name)
// - The GitHub repo's root package.json has name "@discordjs/discord.js"
const tmp = tmpdirSync();
writeFileSync(
join(tmp, "package.json"),
JSON.stringify({
dependencies: {
// Use a semver version that would normally resolve from npm
"discord.js": "^14.14.1",
},
resolutions: {
// Override with a GitHub commit from the monorepo
// The root package.json in this repo has name "@discordjs/discord.js"
"discord.js": "github:discordjs/discord.js#8dd69cf2811d86ed8da2a7b1e9a6a94022554e96",
},
}),
);
// Install should succeed (previously this would fail with "discord.js@^14.14.1 failed to resolve")
install(tmp, ["install"]);
// Verify the package was installed from GitHub
// Note: The installed package will have the name from the root package.json
const discordPkg = JSON.parse(readFileSync(join(tmp, "node_modules/discord.js/package.json"), "utf-8"));
expect(discordPkg.name).toBe("@discordjs/discord.js");
// Verify the lockfile is stable
ensureLockfileDoesntChangeOnBunI(tmp);
});
test("overrides with github dependency (using overrides field, monorepo)", async () => {
// Same as above but using the npm-style "overrides" field instead of "resolutions"
const tmp = tmpdirSync();
writeFileSync(
join(tmp, "package.json"),
JSON.stringify({
dependencies: {
"discord.js": "^14.14.1",
},
overrides: {
"discord.js": "github:discordjs/discord.js#8dd69cf2811d86ed8da2a7b1e9a6a94022554e96",
},
}),
);
install(tmp, ["install"]);
const discordPkg = JSON.parse(readFileSync(join(tmp, "node_modules/discord.js/package.json"), "utf-8"));
expect(discordPkg.name).toBe("@discordjs/discord.js");
ensureLockfileDoesntChangeOnBunI(tmp);
});

View File

@@ -246,3 +246,80 @@ if (typeof process !== "undefined") {
// @ts-expect-error - -Infinity
fetch("https://example.com", { body: -Infinity });
}
// Proxy option types
{
// String proxy URL is valid
fetch("https://example.com", { proxy: "http://proxy.example.com:8080" });
fetch("https://example.com", { proxy: "https://user:pass@proxy.example.com:8080" });
}
{
// Object proxy with url is valid
fetch("https://example.com", {
proxy: {
url: "http://proxy.example.com:8080",
},
});
}
{
// Object proxy with url and headers (plain object) is valid
fetch("https://example.com", {
proxy: {
url: "http://proxy.example.com:8080",
headers: {
"Proxy-Authorization": "Bearer token",
"X-Custom-Header": "value",
},
},
});
}
{
// Object proxy with url and headers (Headers instance) is valid
fetch("https://example.com", {
proxy: {
url: "http://proxy.example.com:8080",
headers: new Headers({ "Proxy-Authorization": "Bearer token" }),
},
});
}
{
// Object proxy with url and headers (array of tuples) is valid
fetch("https://example.com", {
proxy: {
url: "http://proxy.example.com:8080",
headers: [
["Proxy-Authorization", "Bearer token"],
["X-Custom", "value"],
],
},
});
}
{
// @ts-expect-error - Proxy object without url is invalid
fetch("https://example.com", { proxy: { headers: { "X-Custom": "value" } } });
}
{
// @ts-expect-error - Proxy url must be string, not number
fetch("https://example.com", { proxy: { url: 8080 } });
}
{
// @ts-expect-error - Proxy must be string or object, not number
fetch("https://example.com", { proxy: 8080 });
}
{
// @ts-expect-error - Proxy must be string or object, not boolean
fetch("https://example.com", { proxy: true });
}
{
// @ts-expect-error - Proxy must be string or object, not array
fetch("https://example.com", { proxy: ["http://proxy.example.com"] });
}

View File

@@ -337,3 +337,366 @@ test("HTTPS origin close-delimited body via HTTP proxy does not ECONNRESET", asy
await once(originServer, "close");
}
});
describe("proxy object format with headers", () => {
test("proxy object with url string works same as string proxy", async () => {
const response = await fetch(httpServer.url, {
method: "GET",
proxy: {
url: httpProxyServer.url,
},
keepalive: false,
});
expect(response.ok).toBe(true);
expect(response.status).toBe(200);
});
test("proxy object with url and headers sends headers to proxy (HTTP proxy)", async () => {
// Create a proxy server that captures headers
const capturedHeaders: string[] = [];
const proxyServerWithCapture = net.createServer((clientSocket: net.Socket) => {
clientSocket.once("data", data => {
const request = data.toString();
// Capture headers
const lines = request.split("\r\n");
for (const line of lines) {
if (line.toLowerCase().startsWith("x-proxy-")) {
capturedHeaders.push(line.toLowerCase());
}
}
const [method, path] = request.split(" ");
let host: string;
let port: number | string = 0;
let request_path = "";
if (path.indexOf("http") !== -1) {
const url = new URL(path);
host = url.hostname;
port = url.port;
request_path = url.pathname + (url.search || "");
} else {
[host, port] = path.split(":");
}
const destinationPort = Number.parseInt((port || (method === "CONNECT" ? "443" : "80")).toString(), 10);
const destinationHost = host || "";
const serverSocket = net.connect(destinationPort, destinationHost, () => {
if (method === "CONNECT") {
clientSocket.write("HTTP/1.1 200 OK\r\nHost: localhost\r\n\r\n");
clientSocket.pipe(serverSocket);
serverSocket.pipe(clientSocket);
} else {
serverSocket.write(`${method} ${request_path} HTTP/1.1\r\n`);
serverSocket.write(data.slice(request.indexOf("\r\n") + 2));
serverSocket.pipe(clientSocket);
}
});
clientSocket.on("error", () => {});
serverSocket.on("error", () => {
clientSocket.end();
});
});
});
proxyServerWithCapture.listen(0);
await once(proxyServerWithCapture, "listening");
const proxyPort = (proxyServerWithCapture.address() as net.AddressInfo).port;
const proxyUrl = `http://localhost:${proxyPort}`;
try {
const response = await fetch(httpServer.url, {
method: "GET",
proxy: {
url: proxyUrl,
headers: {
"X-Proxy-Custom-Header": "custom-value",
"X-Proxy-Another": "another-value",
},
},
keepalive: false,
});
expect(response.ok).toBe(true);
expect(response.status).toBe(200);
// Verify the custom headers were sent to the proxy (case-insensitive check)
expect(capturedHeaders).toContainEqual(expect.stringContaining("x-proxy-custom-header: custom-value"));
expect(capturedHeaders).toContainEqual(expect.stringContaining("x-proxy-another: another-value"));
} finally {
proxyServerWithCapture.close();
await once(proxyServerWithCapture, "close");
}
});
test("proxy object with url and headers sends headers in CONNECT request (HTTPS target)", async () => {
// Create a proxy server that captures headers
const capturedHeaders: string[] = [];
const proxyServerWithCapture = net.createServer((clientSocket: net.Socket) => {
clientSocket.once("data", data => {
const request = data.toString();
// Capture headers
const lines = request.split("\r\n");
for (const line of lines) {
if (line.toLowerCase().startsWith("x-proxy-")) {
capturedHeaders.push(line.toLowerCase());
}
}
const [method, path] = request.split(" ");
let host: string;
let port: number | string = 0;
if (path.indexOf("http") !== -1) {
const url = new URL(path);
host = url.hostname;
port = url.port;
} else {
[host, port] = path.split(":");
}
const destinationPort = Number.parseInt((port || (method === "CONNECT" ? "443" : "80")).toString(), 10);
const destinationHost = host || "";
const serverSocket = net.connect(destinationPort, destinationHost, () => {
if (method === "CONNECT") {
clientSocket.write("HTTP/1.1 200 OK\r\nHost: localhost\r\n\r\n");
clientSocket.pipe(serverSocket);
serverSocket.pipe(clientSocket);
} else {
clientSocket.write("HTTP/1.1 502 Bad Gateway\r\n\r\n");
clientSocket.end();
}
});
clientSocket.on("error", () => {});
serverSocket.on("error", () => {
clientSocket.end();
});
});
});
proxyServerWithCapture.listen(0);
await once(proxyServerWithCapture, "listening");
const proxyPort = (proxyServerWithCapture.address() as net.AddressInfo).port;
const proxyUrl = `http://localhost:${proxyPort}`;
try {
const response = await fetch(httpsServer.url, {
method: "GET",
proxy: {
url: proxyUrl,
headers: new Headers({
"X-Proxy-Auth-Token": "secret-token-123",
}),
},
keepalive: false,
tls: {
ca: tlsCert.cert,
rejectUnauthorized: false,
},
});
expect(response.ok).toBe(true);
expect(response.status).toBe(200);
// Verify the custom headers were sent in the CONNECT request (case-insensitive check)
expect(capturedHeaders).toContainEqual(expect.stringContaining("x-proxy-auth-token: secret-token-123"));
} finally {
proxyServerWithCapture.close();
await once(proxyServerWithCapture, "close");
}
});
test("proxy object without url throws error", async () => {
await expect(
fetch(httpServer.url, {
method: "GET",
proxy: {
headers: { "X-Test": "value" },
} as any,
keepalive: false,
}),
).rejects.toThrow("fetch() proxy object requires a 'url' property");
});
test("proxy object with null url throws error", async () => {
await expect(
fetch(httpServer.url, {
method: "GET",
proxy: {
url: null,
headers: { "X-Test": "value" },
} as any,
keepalive: false,
}),
).rejects.toThrow("fetch() proxy object requires a 'url' property");
});
test("proxy object with empty string url throws error", async () => {
await expect(
fetch(httpServer.url, {
method: "GET",
proxy: {
url: "",
headers: { "X-Test": "value" },
} as any,
keepalive: false,
}),
).rejects.toThrow("fetch() proxy.url must be a non-empty string");
});
test("proxy object with empty headers object works", async () => {
const response = await fetch(httpServer.url, {
method: "GET",
proxy: {
url: httpProxyServer.url,
headers: {},
},
keepalive: false,
});
expect(response.ok).toBe(true);
expect(response.status).toBe(200);
});
test("proxy object with undefined headers works", async () => {
const response = await fetch(httpServer.url, {
method: "GET",
proxy: {
url: httpProxyServer.url,
headers: undefined,
},
keepalive: false,
});
expect(response.ok).toBe(true);
expect(response.status).toBe(200);
});
test("proxy object with headers as Headers instance", async () => {
const capturedHeaders: string[] = [];
const proxyServerWithCapture = net.createServer((clientSocket: net.Socket) => {
clientSocket.once("data", data => {
const request = data.toString();
const lines = request.split("\r\n");
for (const line of lines) {
if (line.toLowerCase().startsWith("x-custom-")) {
capturedHeaders.push(line.toLowerCase());
}
}
const [method, path] = request.split(" ");
let host: string;
let port: number | string = 0;
let request_path = "";
if (path.indexOf("http") !== -1) {
const url = new URL(path);
host = url.hostname;
port = url.port;
request_path = url.pathname + (url.search || "");
} else {
[host, port] = path.split(":");
}
const destinationPort = Number.parseInt((port || "80").toString(), 10);
const destinationHost = host || "";
const serverSocket = net.connect(destinationPort, destinationHost, () => {
serverSocket.write(`${method} ${request_path} HTTP/1.1\r\n`);
serverSocket.write(data.slice(request.indexOf("\r\n") + 2));
serverSocket.pipe(clientSocket);
});
clientSocket.on("error", () => {});
serverSocket.on("error", () => {
clientSocket.end();
});
});
});
proxyServerWithCapture.listen(0);
await once(proxyServerWithCapture, "listening");
const proxyPort = (proxyServerWithCapture.address() as net.AddressInfo).port;
const proxyUrl = `http://localhost:${proxyPort}`;
try {
const headers = new Headers();
headers.set("X-Custom-Header-1", "value1");
headers.set("X-Custom-Header-2", "value2");
const response = await fetch(httpServer.url, {
method: "GET",
proxy: {
url: proxyUrl,
headers: headers,
},
keepalive: false,
});
expect(response.ok).toBe(true);
expect(response.status).toBe(200);
// Case-insensitive check
expect(capturedHeaders).toContainEqual(expect.stringContaining("x-custom-header-1: value1"));
expect(capturedHeaders).toContainEqual(expect.stringContaining("x-custom-header-2: value2"));
} finally {
proxyServerWithCapture.close();
await once(proxyServerWithCapture, "close");
}
});
test("user-provided Proxy-Authorization header overrides URL credentials", async () => {
const capturedHeaders: string[] = [];
const proxyServerWithCapture = net.createServer((clientSocket: net.Socket) => {
clientSocket.once("data", data => {
const request = data.toString();
const lines = request.split("\r\n");
for (const line of lines) {
if (line.toLowerCase().startsWith("proxy-authorization:")) {
capturedHeaders.push(line.toLowerCase());
}
}
const [method, path] = request.split(" ");
let host: string;
let port: number | string = 0;
let request_path = "";
if (path.indexOf("http") !== -1) {
const url = new URL(path);
host = url.hostname;
port = url.port;
request_path = url.pathname + (url.search || "");
} else {
[host, port] = path.split(":");
}
const destinationPort = Number.parseInt((port || "80").toString(), 10);
const destinationHost = host || "";
const serverSocket = net.connect(destinationPort, destinationHost, () => {
serverSocket.write(`${method} ${request_path} HTTP/1.1\r\n`);
serverSocket.write(data.slice(request.indexOf("\r\n") + 2));
serverSocket.pipe(clientSocket);
});
clientSocket.on("error", () => {});
serverSocket.on("error", () => {
clientSocket.end();
});
});
});
proxyServerWithCapture.listen(0);
await once(proxyServerWithCapture, "listening");
const proxyPort = (proxyServerWithCapture.address() as net.AddressInfo).port;
// Proxy URL with credentials that would generate Basic auth
const proxyUrl = `http://urluser:urlpass@localhost:${proxyPort}`;
try {
const response = await fetch(httpServer.url, {
method: "GET",
proxy: {
url: proxyUrl,
headers: {
// User-provided Proxy-Authorization should override the URL-based one
"Proxy-Authorization": "Bearer custom-token-12345",
},
},
keepalive: false,
});
expect(response.ok).toBe(true);
expect(response.status).toBe(200);
// Should only have one Proxy-Authorization header (the user-provided one)
expect(capturedHeaders.length).toBe(1);
expect(capturedHeaders[0]).toBe("proxy-authorization: bearer custom-token-12345");
} finally {
proxyServerWithCapture.close();
await once(proxyServerWithCapture, "close");
}
});
});

View File

@@ -1,4 +1,4 @@
import { expect, test } from "bun:test";
import { describe, expect, test } from "bun:test";
import net from "net";
// CVE-2020-8287 style request smuggling tests
@@ -387,3 +387,176 @@ test("handles multiple valid Transfer-Encoding headers", async () => {
client.write(validRequest);
});
});
// Tests for SPILL.TERM technique - invalid chunk terminators
// Reference: https://portswigger.net/research/chunked-coding-converter-abusing-http-to-smuggle-requests
describe("SPILL.TERM - invalid chunk terminators", () => {
test("rejects chunk with invalid terminator bytes", async () => {
// This tests the SPILL.TERM technique where an attacker uses invalid
// chunk terminators (e.g., "XY" instead of "\r\n") to desync parsers.
let bodyReadSucceeded = false;
await using server = Bun.serve({
port: 0,
async fetch(req) {
try {
await req.text();
bodyReadSucceeded = true;
} catch {
// Expected: body read should fail due to invalid chunk terminator
}
return new Response("OK");
},
});
const client = net.connect(server.port, "127.0.0.1");
// Chunk size 5, but terminator is "XY" instead of "\r\n"
const maliciousRequest =
"POST / HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Transfer-Encoding: chunked\r\n" +
"\r\n" +
"5\r\n" +
"AAAAAXY" + // 5 bytes "AAAAA" + invalid terminator "XY"
"0\r\n" +
"\r\n";
await new Promise<void>((resolve, reject) => {
let responseData = "";
client.on("error", reject);
client.on("data", data => {
responseData += data.toString();
});
client.on("close", () => {
expect(responseData).toContain("HTTP/1.1 400");
expect(bodyReadSucceeded).toBe(false);
resolve();
});
client.write(maliciousRequest);
});
});
test("rejects chunk with CR but wrong second byte", async () => {
let bodyReadSucceeded = false;
await using server = Bun.serve({
port: 0,
async fetch(req) {
try {
await req.text();
bodyReadSucceeded = true;
} catch {
// Expected: body read should fail due to invalid chunk terminator
}
return new Response("OK");
},
});
const client = net.connect(server.port, "127.0.0.1");
// Chunk size 3, terminator is "\rX" instead of "\r\n"
const maliciousRequest =
"POST / HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Transfer-Encoding: chunked\r\n" +
"\r\n" +
"3\r\n" +
"ABC\rX" + // 3 bytes "ABC" + invalid terminator "\rX"
"0\r\n" +
"\r\n";
await new Promise<void>((resolve, reject) => {
let responseData = "";
client.on("error", reject);
client.on("data", data => {
responseData += data.toString();
});
client.on("close", () => {
expect(responseData).toContain("HTTP/1.1 400");
expect(bodyReadSucceeded).toBe(false);
resolve();
});
client.write(maliciousRequest);
});
});
test("rejects chunk with LF but wrong first byte", async () => {
let bodyReadSucceeded = false;
await using server = Bun.serve({
port: 0,
async fetch(req) {
try {
await req.text();
bodyReadSucceeded = true;
} catch {
// Expected: body read should fail due to invalid chunk terminator
}
return new Response("OK");
},
});
const client = net.connect(server.port, "127.0.0.1");
// Chunk size 3, terminator is "X\n" instead of "\r\n"
const maliciousRequest =
"POST / HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Transfer-Encoding: chunked\r\n" +
"\r\n" +
"3\r\n" +
"ABCX\n" + // 3 bytes "ABC" + invalid terminator "X\n"
"0\r\n" +
"\r\n";
await new Promise<void>((resolve, reject) => {
let responseData = "";
client.on("error", reject);
client.on("data", data => {
responseData += data.toString();
});
client.on("close", () => {
expect(responseData).toContain("HTTP/1.1 400");
expect(bodyReadSucceeded).toBe(false);
resolve();
});
client.write(maliciousRequest);
});
});
test("accepts valid chunk terminators", async () => {
let receivedBody = "";
await using server = Bun.serve({
port: 0,
async fetch(req) {
receivedBody = await req.text();
return new Response("Success");
},
});
const client = net.connect(server.port, "127.0.0.1");
const validRequest =
"POST / HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Transfer-Encoding: chunked\r\n" +
"\r\n" +
"5\r\n" +
"Hello\r\n" +
"6\r\n" +
" World\r\n" +
"0\r\n" +
"\r\n";
await new Promise<void>((resolve, reject) => {
client.on("error", reject);
client.on("data", data => {
const response = data.toString();
expect(response).toContain("HTTP/1.1 200");
expect(receivedBody).toBe("Hello World");
client.end();
resolve();
});
client.write(validRequest);
});
});
});

View File

@@ -68,4 +68,32 @@ describe.skipIf(isWindows)("Bun.mmap", async () => {
expect(map[0]).toBe(old);
await gcTick();
});
it("mmap rejects negative offset", () => {
expect(() => Bun.mmap(path, { offset: -1 })).toThrow("offset must be a non-negative integer");
});
it("mmap rejects negative size", () => {
expect(() => Bun.mmap(path, { size: -1 })).toThrow("size must be a non-negative integer");
});
it("mmap handles non-number offset/size without crashing", () => {
// These should not crash - non-number values coerce to 0 per JavaScript semantics
// Previously these caused assertion failures (issue ENG-22413)
// null coerces to 0, which is valid for offset
expect(() => {
Bun.mmap(path, { offset: null });
}).not.toThrow();
// size: null coerces to 0, which is invalid (EINVAL), but shouldn't crash
expect(() => {
Bun.mmap(path, { size: null });
}).toThrow("EINVAL");
// undefined is ignored (property not set)
expect(() => {
Bun.mmap(path, { offset: undefined });
}).not.toThrow();
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -277,6 +277,23 @@ describe("FormData", () => {
expect(fd.toJSON()).toEqual({ "1": "1" });
});
test("FormData.from throws on very large input instead of crashing", () => {
// This test verifies that FormData.from throws an exception instead of crashing
// when given input larger than WebKit's String::MaxLength (INT32_MAX ~= 2GB).
// We use a smaller test case with the synthetic limit to avoid actually allocating 2GB+.
const { setSyntheticAllocationLimitForTesting } = require("bun:internal-for-testing");
// Set a small limit so we can test the boundary without allocating gigabytes
const originalLimit = setSyntheticAllocationLimitForTesting(1024 * 1024); // 1MB limit
try {
// Create a buffer larger than the limit
const largeBuffer = new Uint8Array(2 * 1024 * 1024); // 2MB
// @ts-expect-error - FormData.from is a Bun extension
expect(() => FormData.from(largeBuffer)).toThrow("Cannot create a string longer than");
} finally {
setSyntheticAllocationLimitForTesting(originalLimit);
}
});
it("should throw on bad boundary", async () => {
const response = new Response('foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n', {
headers: {

View File

@@ -0,0 +1,209 @@
// Test data from Web Platform Tests
// https://github.com/web-platform-tests/wpt/blob/master/LICENSE.md
import { describe, expect, test } from "bun:test";
import testData from "./urlpatterntestdata.json";
const kComponents = ["protocol", "username", "password", "hostname", "port", "pathname", "search", "hash"] as const;
type Component = (typeof kComponents)[number];
interface TestEntry {
pattern: any[];
inputs?: any[];
expected_obj?: Record<string, string> | "error";
expected_match?: Record<string, any> | null | "error";
exactly_empty_components?: string[];
}
function getExpectedPatternString(entry: TestEntry, component: Component): string {
// If the test case explicitly provides an expected pattern string, use that
if (entry.expected_obj && typeof entry.expected_obj === "object" && entry.expected_obj[component] !== undefined) {
return entry.expected_obj[component];
}
// Determine if there is a baseURL present
let baseURL: URL | null = null;
if (entry.pattern.length > 0 && entry.pattern[0].baseURL) {
baseURL = new URL(entry.pattern[0].baseURL);
} else if (entry.pattern.length > 1 && typeof entry.pattern[1] === "string") {
baseURL = new URL(entry.pattern[1]);
}
const EARLIER_COMPONENTS: Record<Component, Component[]> = {
protocol: [],
hostname: ["protocol"],
port: ["protocol", "hostname"],
username: [],
password: [],
pathname: ["protocol", "hostname", "port"],
search: ["protocol", "hostname", "port", "pathname"],
hash: ["protocol", "hostname", "port", "pathname", "search"],
};
if (entry.exactly_empty_components?.includes(component)) {
return "";
} else if (typeof entry.pattern[0] === "object" && entry.pattern[0][component]) {
return entry.pattern[0][component];
} else if (typeof entry.pattern[0] === "object" && EARLIER_COMPONENTS[component].some(c => c in entry.pattern[0])) {
return "*";
} else if (baseURL && component !== "username" && component !== "password") {
let base_value = (baseURL as any)[component] as string;
if (component === "protocol") base_value = base_value.substring(0, base_value.length - 1);
else if (component === "search" || component === "hash") base_value = base_value.substring(1);
return base_value;
} else {
return "*";
}
}
function getExpectedComponentResult(
entry: TestEntry,
component: Component,
): { input: string; groups: Record<string, string | undefined> } {
let expected_obj = entry.expected_match?.[component];
if (!expected_obj) {
expected_obj = { input: "", groups: {} as Record<string, string | undefined> };
if (!entry.exactly_empty_components?.includes(component)) {
expected_obj.groups["0"] = "";
}
}
// Convert null to undefined in groups
for (const key in expected_obj.groups) {
if (expected_obj.groups[key] === null) {
expected_obj.groups[key] = undefined;
}
}
return expected_obj;
}
describe("URLPattern", () => {
describe("WPT tests", () => {
for (const entry of testData as TestEntry[]) {
const testName = `Pattern: ${JSON.stringify(entry.pattern)} Inputs: ${JSON.stringify(entry.inputs)}`;
test(testName, () => {
// Test construction error
if (entry.expected_obj === "error") {
expect(() => new URLPattern(...entry.pattern)).toThrow(TypeError);
return;
}
const pattern = new URLPattern(...entry.pattern);
// Verify compiled pattern properties
for (const component of kComponents) {
const expected = getExpectedPatternString(entry, component);
expect(pattern[component]).toBe(expected);
}
// Test match error
if (entry.expected_match === "error") {
expect(() => pattern.test(...(entry.inputs ?? []))).toThrow(TypeError);
expect(() => pattern.exec(...(entry.inputs ?? []))).toThrow(TypeError);
return;
}
// Test test() method
expect(pattern.test(...(entry.inputs ?? []))).toBe(!!entry.expected_match);
// Test exec() method
const exec_result = pattern.exec(...(entry.inputs ?? []));
if (!entry.expected_match || typeof entry.expected_match !== "object") {
expect(exec_result).toBe(entry.expected_match);
return;
}
const expected_inputs = entry.expected_match.inputs ?? entry.inputs;
// Verify inputs
expect(exec_result!.inputs.length).toBe(expected_inputs!.length);
for (let i = 0; i < exec_result!.inputs.length; i++) {
const input = exec_result!.inputs[i];
const expected_input = expected_inputs![i];
if (typeof input === "string") {
expect(input).toBe(expected_input);
} else {
for (const component of kComponents) {
expect(input[component]).toBe(expected_input[component]);
}
}
}
// Verify component results
for (const component of kComponents) {
const expected = getExpectedComponentResult(entry, component);
expect(exec_result![component]).toEqual(expected);
}
});
}
});
describe("constructor edge cases", () => {
test("unclosed token with URL object - %(", () => {
expect(() => new URLPattern(new URL("https://example.org/%("))).toThrow(TypeError);
});
test("unclosed token with URL object - %((", () => {
expect(() => new URLPattern(new URL("https://example.org/%(("))).toThrow(TypeError);
});
test("unclosed token with string - (\\", () => {
expect(() => new URLPattern("(\\")).toThrow(TypeError);
});
test("constructor with undefined arguments", () => {
// Should not throw
new URLPattern(undefined, undefined);
});
});
describe("hasRegExpGroups", () => {
test("match-everything pattern", () => {
expect(new URLPattern({}).hasRegExpGroups).toBe(false);
});
for (const component of kComponents) {
test(`wildcard in ${component}`, () => {
expect(new URLPattern({ [component]: "*" }).hasRegExpGroups).toBe(false);
});
test(`segment wildcard in ${component}`, () => {
expect(new URLPattern({ [component]: ":foo" }).hasRegExpGroups).toBe(false);
});
test(`optional segment wildcard in ${component}`, () => {
expect(new URLPattern({ [component]: ":foo?" }).hasRegExpGroups).toBe(false);
});
test(`named regexp group in ${component}`, () => {
expect(new URLPattern({ [component]: ":foo(hi)" }).hasRegExpGroups).toBe(true);
});
test(`anonymous regexp group in ${component}`, () => {
expect(new URLPattern({ [component]: "(hi)" }).hasRegExpGroups).toBe(true);
});
if (component !== "protocol" && component !== "port") {
test(`wildcards mixed with fixed text in ${component}`, () => {
expect(new URLPattern({ [component]: "a-{:hello}-z-*-a" }).hasRegExpGroups).toBe(false);
});
test(`regexp groups mixed with fixed text in ${component}`, () => {
expect(new URLPattern({ [component]: "a-(hi)-z-(lo)-a" }).hasRegExpGroups).toBe(true);
});
}
}
test("complex pathname with no regexp", () => {
expect(new URLPattern({ pathname: "/a/:foo/:baz?/b/*" }).hasRegExpGroups).toBe(false);
});
test("complex pathname with regexp", () => {
expect(new URLPattern({ pathname: "/a/:foo/:baz([a-z]+)?/b/*" }).hasRegExpGroups).toBe(true);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -155,3 +155,6 @@ vendor/elysia/test/validator/body.test.ts
vendor/elysia/test/ws/message.test.ts
test/js/node/test/parallel/test-worker-abort-on-uncaught-exception.js
# TODO: WebCore fixes
test/js/web/urlpattern/urlpattern.test.ts

View File

@@ -193,6 +193,7 @@ test/regression/issue/02499/02499.test.ts
test/js/node/test/parallel/test-http-server-stale-close.js
test/js/third_party/comlink/comlink.test.ts
test/regression/issue/22635/22635.test.ts
test/js/node/test/parallel/test-http-url.parse-https.request.js
# Bun::JSNodeHTTPServerSocket::clearSocketData

View File

@@ -10,9 +10,7 @@ for (let key in process.env) {
for (let key in harness.bunEnv) {
if (key === "TZ") continue;
if (harness.bunEnv[key] === undefined) {
continue;
}
if (harness.bunEnv[key] === undefined) continue;
process.env[key] = harness.bunEnv[key] + "";
}

View File

@@ -0,0 +1,23 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
test("bun publish --help shows correct message for --dry-run", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "publish", "--help"],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// The --dry-run flag should have a generic description that works for all commands
// It should NOT say "Don't install anything" when used with "bun publish"
expect(stdout).toContain("--dry-run");
expect(stdout).toContain("Perform a dry run without making changes");
// Make sure it doesn't contain the old incorrect message
expect(stdout).not.toContain("Don't install anything");
expect(exitCode).toBe(0);
});

View File

@@ -0,0 +1,75 @@
import { expect, test } from "bun:test";
import * as http2 from "http2";
test("Http2Server.setTimeout returns server instance for method chaining", () => {
const server = http2.createServer();
try {
const result = server.setTimeout(1000);
expect(result).toBe(server);
} finally {
server.close();
}
});
test("Http2Server.setTimeout with callback returns server instance", () => {
const server = http2.createServer();
const callback = () => {};
try {
const result = server.setTimeout(1000, callback);
expect(result).toBe(server);
} finally {
server.close();
}
});
test("Http2Server.setTimeout allows method chaining with close", () => {
const server = http2.createServer();
// This should not throw - chaining should work
expect(() => {
server.setTimeout(1000).close();
}).not.toThrow();
});
test("Http2SecureServer.setTimeout returns server instance for method chaining", () => {
const server = http2.createSecureServer({
key: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7VJTUt9Us8cKj\nMzEfYyjiWA4R4/M2bS1+fWIcPm15A8+raZ4dp+/PJE0NOsmuurvzNCS138P6AqQ0\njdsEYDbPszN+X9XYqXqYfKmSBGbGwPqaqCGhqtRaKNl1s1x5dn/7CJPxr/vHNQXL\nJZ+kP1Mb9FsMqmHbYJFuJB/T8fJuMJRKqY7J1W3J+cVvvvQy2eJXqZqVj1/lXZXR\nUH0WqWdQnfnPj3Lh6qKm8Cf8qh3Uug2o3GWG0UE6YwLqNfBCULrOeRLtCqkbMdHb\nF7XvL0kHNvf3eFiVZ8cJ/KcIVIEhiqOQkZj3qMRqGqEP0H9dqYHK1Dj5cLxqxXCL\nLKxKP0SZAgMBAAECggEAZj3P3V3fKKvqZgPpzLQrjwHpBKZOhRWTR+zQfAVqN7rW\nc3MrdOzP7cVLN7CYsPZF7JpXY5/hYvjOXPQr5tqKJZ/+T5hYJ4CUYhJbMlxL0pIR\njXNLJFVz3ZQJNmWAe3h3H0TpE7oZcYR7nHVWKZ4I3WxX1TxT3a8QN6BXGCbJwYEk\n5QI4SLFiV3f3Hh0xQZPPfZGP/M3EVGfEOxZWqkZxkDCj5F9DH+r0J2VxLM5zWMjR\n5xNLkH1TlJ3Lg6w2YWmLKV5KdQcX+fKJJCx7oGhPIkvHCfKJ1MK3Y/QG9TFG3qF0\ndJVWBVLLU7K7iLaBN3jZHJJhZqhGXPxGUMaZ6EfgQQKBgQDmrE3PVB8uZLb0Gjzn\nNx0C+A8MX4J4fW0r5WfXZfL7V7fR9V9P3bN6Q9FVyp3mZJ9T/xHpCQQJ1KhKqS0k\nV7NcJL5ZWU1nX1MxFcJMCYYRVpXVp3Jh0O2Q7tWxR+zYV9JxJL1Q1Q3Z1dFqL9JQ\nK8qVxV3hGgXJFqKGdKxKqKqKqQKBgQDQfWZI/YQqv8JhxVWdJbP8O5J1Z0xVFqQP\n6LF3x6vJ0Z+7xI9QxJpQF9xN1K6hJ9JpF+ZQ7J3V1F0L5Q0J9V0xL+F1J0xF9F1J\nV0J0F1J9V0J0F1J9V0J0F9V1J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1J\nV0J0F1JV0J0F1JQKBgFqF3V1F0L5Q0J9V0xL+F1J0xF9F1JV0J0F1J9V0J0F1J9\nV0J0F9V1J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV\n0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV\n0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1Jp\nAoGBAKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqK\nqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqK\nqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqK\nqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqp\nAoGAV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0\nF1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F\n1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1\nJV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1J\nV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV\n-----END PRIVATE KEY-----",
cert: "-----BEGIN CERTIFICATE-----\nMIIDazCCAlOgAwIBAgIUXqYYXq7XqYq7q7q7q7q7q7q7q7owDQYJKoZIhvcNAQEL\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAxMDEwMDAwMDBaFw0yNTAx\nMDEwMDAwMDBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQC7VJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1+fWIcPm15\nA8+raZ4dp+/PJE0NOsmuurvzNCS138P6AqQ0jdsEYDbPszN+X9XYqXqYfKmSBGbG\nwPqaqCGhqtRaKNl1s1x5dn/7CJPxr/vHNQXLJZ+kP1Mb9FsMqmHbYJFuJB/T8fJu\nMJRKqY7J1W3J+cVvvvQy2eJXqZqVj1/lXZXRUH0WqWdQnfnPj3Lh6qKm8Cf8qh3U\nug2o3GWG0UE6YwLqNfBCULrOeRLtCqkbMdHbF7XvL0kHNvf3eFiVZ8cJ/KcIVIEh\niqOQkZj3qMRqGqEP0H9dqYHK1Dj5cLxqxXCLLKxKP0SZAgMBAAGjUzBRMB0GA1Ud\nDgQWBBQQXqYYXq7XqYq7q7q7q7q7q7q7qzAfBgNVHSMEGDAWgBQQXqYYXq7XqYq7\nq7q7q7q7q7q7qzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQC7\nVJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1+fWIcPm15A8+raZ4dp+/PJE0NOsmuurvz\nNCS138P6AqQ0jdsEYDbPszN+X9XYqXqYfKmSBGbGwPqaqCGhqtRaKNl1s1x5dn/7\nCJPxr/vHNQXLJZ+kP1Mb9FsMqmHbYJFuJB/T8fJuMJRKqY7J1W3J+cVvvvQy2eJX\nqZqVj1/lXZXRUH0WqWdQnfnPj3Lh6qKm8Cf8qh3Uug2o3GWG0UE6YwLqNfBCULrO\neRLtCqkbMdHbF7XvL0kHNvf3eFiVZ8cJ/KcIVIEhiqOQkZj3qMRqGqEP0H9dqYHK\n1Dj5cLxqxXCLLKxKP0SZ\n-----END CERTIFICATE-----",
});
try {
const result = server.setTimeout(1000);
expect(result).toBe(server);
} finally {
server.close();
}
});
test("Http2SecureServer.setTimeout with callback returns server instance", () => {
const server = http2.createSecureServer({
key: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7VJTUt9Us8cKj\nMzEfYyjiWA4R4/M2bS1+fWIcPm15A8+raZ4dp+/PJE0NOsmuurvzNCS138P6AqQ0\njdsEYDbPszN+X9XYqXqYfKmSBGbGwPqaqCGhqtRaKNl1s1x5dn/7CJPxr/vHNQXL\nJZ+kP1Mb9FsMqmHbYJFuJB/T8fJuMJRKqY7J1W3J+cVvvvQy2eJXqZqVj1/lXZXR\nUH0WqWdQnfnPj3Lh6qKm8Cf8qh3Uug2o3GWG0UE6YwLqNfBCULrOeRLtCqkbMdHb\nF7XvL0kHNvf3eFiVZ8cJ/KcIVIEhiqOQkZj3qMRqGqEP0H9dqYHK1Dj5cLxqxXCL\nLKxKP0SZAgMBAAECggEAZj3P3V3fKKvqZgPpzLQrjwHpBKZOhRWTR+zQfAVqN7rW\nc3MrdOzP7cVLN7CYsPZF7JpXY5/hYvjOXPQr5tqKJZ/+T5hYJ4CUYhJbMlxL0pIR\njXNLJFVz3ZQJNmWAe3h3H0TpE7oZcYR7nHVWKZ4I3WxX1TxT3a8QN6BXGCbJwYEk\n5QI4SLFiV3f3Hh0xQZPPfZGP/M3EVGfEOxZWqkZxkDCj5F9DH+r0J2VxLM5zWMjR\n5xNLkH1TlJ3Lg6w2YWmLKV5KdQcX+fKJJCx7oGhPIkvHCfKJ1MK3Y/QG9TFG3qF0\ndJVWBVLLU7K7iLaBN3jZHJJhZqhGXPxGUMaZ6EfgQQKBgQDmrE3PVB8uZLb0Gjzn\nNx0C+A8MX4J4fW0r5WfXZfL7V7fR9V9P3bN6Q9FVyp3mZJ9T/xHpCQQJ1KhKqS0k\nV7NcJL5ZWU1nX1MxFcJMCYYRVpXVp3Jh0O2Q7tWxR+zYV9JxJL1Q1Q3Z1dFqL9JQ\nK8qVxV3hGgXJFqKGdKxKqKqKqQKBgQDQfWZI/YQqv8JhxVWdJbP8O5J1Z0xVFqQP\n6LF3x6vJ0Z+7xI9QxJpQF9xN1K6hJ9JpF+ZQ7J3V1F0L5Q0J9V0xL+F1J0xF9F1J\nV0J0F1J9V0J0F1J9V0J0F9V1J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1J\nV0J0F1JV0J0F1JQKBgFqF3V1F0L5Q0J9V0xL+F1J0xF9F1JV0J0F1J9V0J0F1J9\nV0J0F9V1J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV\n0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV\n0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1Jp\nAoGBAKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqK\nqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqK\nqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqK\nqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqp\nAoGAV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0\nF1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F\n1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1\nJV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1J\nV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV\n-----END PRIVATE KEY-----",
cert: "-----BEGIN CERTIFICATE-----\nMIIDazCCAlOgAwIBAgIUXqYYXq7XqYq7q7q7q7q7q7q7q7owDQYJKoZIhvcNAQEL\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAxMDEwMDAwMDBaFw0yNTAx\nMDEwMDAwMDBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQC7VJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1+fWIcPm15\nA8+raZ4dp+/PJE0NOsmuurvzNCS138P6AqQ0jdsEYDbPszN+X9XYqXqYfKmSBGbG\nwPqaqCGhqtRaKNl1s1x5dn/7CJPxr/vHNQXLJZ+kP1Mb9FsMqmHbYJFuJB/T8fJu\nMJRKqY7J1W3J+cVvvvQy2eJXqZqVj1/lXZXRUH0WqWdQnfnPj3Lh6qKm8Cf8qh3U\nug2o3GWG0UE6YwLqNfBCULrOeRLtCqkbMdHbF7XvL0kHNvf3eFiVZ8cJ/KcIVIEh\niqOQkZj3qMRqGqEP0H9dqYHK1Dj5cLxqxXCLLKxKP0SZAgMBAAGjUzBRMB0GA1Ud\nDgQWBBQQXqYYXq7XqYq7q7q7q7q7q7q7qzAfBgNVHSMEGDAWgBQQXqYYXq7XqYq7\nq7q7q7q7q7q7qzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQC7\nVJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1+fWIcPm15A8+raZ4dp+/PJE0NOsmuurvz\nNCS138P6AqQ0jdsEYDbPszN+X9XYqXqYfKmSBGbGwPqaqCGhqtRaKNl1s1x5dn/7\nCJPxr/vHNQXLJZ+kP1Mb9FsMqmHbYJFuJB/T8fJuMJRKqY7J1W3J+cVvvvQy2eJX\nqZqVj1/lXZXRUH0WqWdQnfnPj3Lh6qKm8Cf8qh3Uug2o3GWG0UE6YwLqNfBCULrO\neRLtCqkbMdHbF7XvL0kHNvf3eFiVZ8cJ/KcIVIEhiqOQkZj3qMRqGqEP0H9dqYHK\n1Dj5cLxqxXCLLKxKP0SZ\n-----END CERTIFICATE-----",
});
const callback = () => {};
try {
const result = server.setTimeout(1000, callback);
expect(result).toBe(server);
} finally {
server.close();
}
});
test("Http2SecureServer.setTimeout allows method chaining with close", () => {
const server = http2.createSecureServer({
key: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7VJTUt9Us8cKj\nMzEfYyjiWA4R4/M2bS1+fWIcPm15A8+raZ4dp+/PJE0NOsmuurvzNCS138P6AqQ0\njdsEYDbPszN+X9XYqXqYfKmSBGbGwPqaqCGhqtRaKNl1s1x5dn/7CJPxr/vHNQXL\nJZ+kP1Mb9FsMqmHbYJFuJB/T8fJuMJRKqY7J1W3J+cVvvvQy2eJXqZqVj1/lXZXR\nUH0WqWdQnfnPj3Lh6qKm8Cf8qh3Uug2o3GWG0UE6YwLqNfBCULrOeRLtCqkbMdHb\nF7XvL0kHNvf3eFiVZ8cJ/KcIVIEhiqOQkZj3qMRqGqEP0H9dqYHK1Dj5cLxqxXCL\nLKxKP0SZAgMBAAECggEAZj3P3V3fKKvqZgPpzLQrjwHpBKZOhRWTR+zQfAVqN7rW\nc3MrdOzP7cVLN7CYsPZF7JpXY5/hYvjOXPQr5tqKJZ/+T5hYJ4CUYhJbMlxL0pIR\njXNLJFVz3ZQJNmWAe3h3H0TpE7oZcYR7nHVWKZ4I3WxX1TxT3a8QN6BXGCbJwYEk\n5QI4SLFiV3f3Hh0xQZPPfZGP/M3EVGfEOxZWqkZxkDCj5F9DH+r0J2VxLM5zWMjR\n5xNLkH1TlJ3Lg6w2YWmLKV5KdQcX+fKJJCx7oGhPIkvHCfKJ1MK3Y/QG9TFG3qF0\ndJVWBVLLU7K7iLaBN3jZHJJhZqhGXPxGUMaZ6EfgQQKBgQDmrE3PVB8uZLb0Gjzn\nNx0C+A8MX4J4fW0r5WfXZfL7V7fR9V9P3bN6Q9FVyp3mZJ9T/xHpCQQJ1KhKqS0k\nV7NcJL5ZWU1nX1MxFcJMCYYRVpXVp3Jh0O2Q7tWxR+zYV9JxJL1Q1Q3Z1dFqL9JQ\nK8qVxV3hGgXJFqKGdKxKqKqKqQKBgQDQfWZI/YQqv8JhxVWdJbP8O5J1Z0xVFqQP\n6LF3x6vJ0Z+7xI9QxJpQF9xN1K6hJ9JpF+ZQ7J3V1F0L5Q0J9V0xL+F1J0xF9F1J\nV0J0F1J9V0J0F1J9V0J0F9V1J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1J\nV0J0F1JV0J0F1JQKBgFqF3V1F0L5Q0J9V0xL+F1J0xF9F1JV0J0F1J9V0J0F1J9\nV0J0F9V1J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV\n0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV\n0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1Jp\nAoGBAKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqK\nqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqK\nqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqK\nqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqp\nAoGAV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0\nF1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F\n1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1\nJV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1J\nV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV0J0F1JV\n-----END PRIVATE KEY-----",
cert: "-----BEGIN CERTIFICATE-----\nMIIDazCCAlOgAwIBAgIUXqYYXq7XqYq7q7q7q7q7q7q7q7owDQYJKoZIhvcNAQEL\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAxMDEwMDAwMDBaFw0yNTAx\nMDEwMDAwMDBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQC7VJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1+fWIcPm15\nA8+raZ4dp+/PJE0NOsmuurvzNCS138P6AqQ0jdsEYDbPszN+X9XYqXqYfKmSBGbG\nwPqaqCGhqtRaKNl1s1x5dn/7CJPxr/vHNQXLJZ+kP1Mb9FsMqmHbYJFuJB/T8fJu\nMJRKqY7J1W3J+cVvvvQy2eJXqZqVj1/lXZXRUH0WqWdQnfnPj3Lh6qKm8Cf8qh3U\nug2o3GWG0UE6YwLqNfBCULrOeRLtCqkbMdHbF7XvL0kHNvf3eFiVZ8cJ/KcIVIEh\niqOQkZj3qMRqGqEP0H9dqYHK1Dj5cLxqxXCLLKxKP0SZAgMBAAGjUzBRMB0GA1Ud\nDgQWBBQQXqYYXq7XqYq7q7q7q7q7q7q7qzAfBgNVHSMEGDAWgBQQXqYYXq7XqYq7\nq7q7q7q7q7q7qzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQC7\nVJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1+fWIcPm15A8+raZ4dp+/PJE0NOsmuurvz\nNCS138P6AqQ0jdsEYDbPszN+X9XYqXqYfKmSBGbGwPqaqCGhqtRaKNl1s1x5dn/7\nCJPxr/vHNQXLJZ+kP1Mb9FsMqmHbYJFuJB/T8fJuMJRKqY7J1W3J+cVvvvQy2eJX\nqZqVj1/lXZXRUH0WqWdQnfnPj3Lh6qKm8Cf8qh3Uug2o3GWG0UE6YwLqNfBCULrO\neRLtCqkbMdHbF7XvL0kHNvf3eFiVZ8cJ/KcIVIEhiqOQkZj3qMRqGqEP0H9dqYHK\n1Dj5cLxqxXCLLKxKP0SZ\n-----END CERTIFICATE-----",
});
// This should not throw - chaining should work
expect(() => {
server.setTimeout(1000).close();
}).not.toThrow();
});

View File

@@ -0,0 +1,40 @@
import { expect, test } from "bun:test";
// Regression test for ENG-22942: Crash when calling expect.extend with non-function values
// The crash occurred because JSWrappingFunction assumed all callable objects are JSFunction,
// but class constructors like Expect are callable but not JSFunction instances.
test("expect.extend with jest object should throw TypeError, not crash", () => {
const jest = Bun.jest(import.meta.path);
expect(() => {
jest.expect.extend(jest);
}).toThrow(TypeError);
});
test("expect.extend with object containing non-function values should throw", () => {
const jest = Bun.jest(import.meta.path);
expect(() => {
jest.expect.extend({
notAFunction: "string value",
});
}).toThrow("expect.extend: `notAFunction` is not a valid matcher");
});
test("expect.extend with valid matchers still works", () => {
const jest = Bun.jest(import.meta.path);
jest.expect.extend({
toBeEven(received: number) {
const pass = received % 2 === 0;
return {
message: () => `expected ${received} ${pass ? "not " : ""}to be even`,
pass,
};
},
});
jest.expect(4).toBeEven();
jest.expect(3).not.toBeEven();
});