mirror of
https://github.com/oven-sh/bun
synced 2026-02-26 03:27:23 +01:00
Compare commits
28 Commits
claude/fix
...
claude/can
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81960146ec | ||
|
|
a096619f4a | ||
|
|
a84466baf4 | ||
|
|
aad00c9097 | ||
|
|
56da7c4fd9 | ||
|
|
a5d7f36310 | ||
|
|
7f9b7b1a1a | ||
|
|
b4d5aa76f8 | ||
|
|
a122164334 | ||
|
|
6c17c90f8d | ||
|
|
dc5d302c52 | ||
|
|
98fcf2ade3 | ||
|
|
7d578ed7fa | ||
|
|
5bdb8ec0cb | ||
|
|
4cf9b794c9 | ||
|
|
998ec54da9 | ||
|
|
0305f3d4d2 | ||
|
|
1006a4fac2 | ||
|
|
c7f7d9bb82 | ||
|
|
37bce389a0 | ||
|
|
bab583497c | ||
|
|
a83fceafc7 | ||
|
|
ef8eef3df8 | ||
|
|
69b571da41 | ||
|
|
908ab9ce30 | ||
|
|
43c46b1f77 | ||
|
|
a0c5f3dc69 | ||
|
|
5965ff18ea |
@@ -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" },
|
||||
|
||||
2
.github/workflows/auto-assign-types.yml
vendored
2
.github/workflows/auto-assign-types.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/auto-label-claude-prs.yml
vendored
2
.github/workflows/auto-label-claude-prs.yml
vendored
@@ -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
|
||||
|
||||
190
.github/workflows/canary-test-modified.yml
vendored
Normal file
190
.github/workflows/canary-test-modified.yml
vendored
Normal 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
|
||||
66
.github/workflows/claude.yml
vendored
66
.github/workflows/claude.yml
vendored
@@ -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 }}
|
||||
2
.github/workflows/test-bump.yml
vendored
2
.github/workflows/test-bump.yml
vendored
@@ -14,7 +14,7 @@ env:
|
||||
jobs:
|
||||
bump:
|
||||
name: "Bump version"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
2
.github/workflows/update-cares.yml
vendored
2
.github/workflows/update-cares.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
check-update:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
2
.github/workflows/update-hdrhistogram.yml
vendored
2
.github/workflows/update-hdrhistogram.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
check-update:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
2
.github/workflows/update-highway.yml
vendored
2
.github/workflows/update-highway.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
check-update:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
2
.github/workflows/update-libarchive.yml
vendored
2
.github/workflows/update-libarchive.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
check-update:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
2
.github/workflows/update-libdeflate.yml
vendored
2
.github/workflows/update-libdeflate.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
check-update:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
2
.github/workflows/update-lolhtml.yml
vendored
2
.github/workflows/update-lolhtml.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
check-update:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
2
.github/workflows/update-lshpack.yml
vendored
2
.github/workflows/update-lshpack.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
check-update:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
2
.github/workflows/update-root-certs.yml
vendored
2
.github/workflows/update-root-certs.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
check-and-update:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
2
.github/workflows/update-sqlite3.yml
vendored
2
.github/workflows/update-sqlite3.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
check-update:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
2
.github/workflows/update-vendor.yml
vendored
2
.github/workflows/update-vendor.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
check-update:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
2
.github/workflows/update-zstd.yml
vendored
2
.github/workflows/update-zstd.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
check-update:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
2
.github/workflows/vscode-release.yml
vendored
2
.github/workflows/vscode-release.yml
vendored
@@ -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
16
.vscode/settings.json
vendored
@@ -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,
|
||||
|
||||
48
bench/snippets/urlpattern.js
Normal file
48
bench/snippets/urlpattern.js
Normal 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();
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
29
packages/bun-types/bun.d.ts
vendored
29
packages/bun-types/bun.d.ts
vendored
@@ -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`
|
||||
|
||||
32
packages/bun-types/globals.d.ts
vendored
32
packages/bun-types/globals.d.ts
vendored
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
545
src/bun.js/bindings/webcore/JSURLPattern.cpp
Normal file
545
src/bun.js/bindings/webcore/JSURLPattern.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
96
src/bun.js/bindings/webcore/JSURLPattern.h
Normal file
96
src/bun.js/bindings/webcore/JSURLPattern.h
Normal 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
|
||||
200
src/bun.js/bindings/webcore/JSURLPatternInit.cpp
Normal file
200
src/bun.js/bindings/webcore/JSURLPatternInit.cpp
Normal 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
|
||||
32
src/bun.js/bindings/webcore/JSURLPatternInit.h
Normal file
32
src/bun.js/bindings/webcore/JSURLPatternInit.h
Normal 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
|
||||
56
src/bun.js/bindings/webcore/JSURLPatternOptions.cpp
Normal file
56
src/bun.js/bindings/webcore/JSURLPatternOptions.cpp
Normal 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
|
||||
30
src/bun.js/bindings/webcore/JSURLPatternOptions.h
Normal file
30
src/bun.js/bindings/webcore/JSURLPatternOptions.h
Normal 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
|
||||
175
src/bun.js/bindings/webcore/JSURLPatternResult.cpp
Normal file
175
src/bun.js/bindings/webcore/JSURLPatternResult.cpp
Normal 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
|
||||
36
src/bun.js/bindings/webcore/JSURLPatternResult.h
Normal file
36
src/bun.js/bindings/webcore/JSURLPatternResult.h
Normal 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
|
||||
493
src/bun.js/bindings/webcore/URLPattern.cpp
Normal file
493
src/bun.js/bindings/webcore/URLPattern.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
95
src/bun.js/bindings/webcore/URLPattern.h
Normal file
95
src/bun.js/bindings/webcore/URLPattern.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
54
src/bun.js/bindings/webcore/URLPattern.idl
Normal file
54
src/bun.js/bindings/webcore/URLPattern.idl
Normal 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;
|
||||
};
|
||||
289
src/bun.js/bindings/webcore/URLPatternCanonical.cpp
Normal file
289
src/bun.js/bindings/webcore/URLPatternCanonical.cpp
Normal 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 };
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
60
src/bun.js/bindings/webcore/URLPatternCanonical.h
Normal file
60
src/bun.js/bindings/webcore/URLPatternCanonical.h
Normal 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);
|
||||
}
|
||||
146
src/bun.js/bindings/webcore/URLPatternComponent.cpp
Normal file
146
src/bun.js/bindings/webcore/URLPatternComponent.cpp
Normal 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) };
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
67
src/bun.js/bindings/webcore/URLPatternComponent.h
Normal file
67
src/bun.js/bindings/webcore/URLPatternComponent.h
Normal 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 };
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 };
|
||||
};
|
||||
|
||||
}
|
||||
44
src/bun.js/bindings/webcore/URLPatternInit.h
Normal file
44
src/bun.js/bindings/webcore/URLPatternInit.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
41
src/bun.js/bindings/webcore/URLPatternInit.idl
Normal file
41
src/bun.js/bindings/webcore/URLPatternInit.idl
Normal 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;
|
||||
};
|
||||
34
src/bun.js/bindings/webcore/URLPatternOptions.h
Normal file
34
src/bun.js/bindings/webcore/URLPatternOptions.h
Normal 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 };
|
||||
};
|
||||
|
||||
}
|
||||
30
src/bun.js/bindings/webcore/URLPatternOptions.idl
Normal file
30
src/bun.js/bindings/webcore/URLPatternOptions.idl
Normal 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;
|
||||
};
|
||||
553
src/bun.js/bindings/webcore/URLPatternParser.cpp
Normal file
553
src/bun.js/bindings/webcore/URLPatternParser.cpp
Normal 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
|
||||
110
src/bun.js/bindings/webcore/URLPatternParser.h
Normal file
110
src/bun.js/bindings/webcore/URLPatternParser.h
Normal 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
|
||||
52
src/bun.js/bindings/webcore/URLPatternResult.h
Normal file
52
src/bun.js/bindings/webcore/URLPatternResult.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
54
src/bun.js/bindings/webcore/URLPatternResult.idl
Normal file
54
src/bun.js/bindings/webcore/URLPatternResult.idl
Normal 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;
|
||||
};
|
||||
273
src/bun.js/bindings/webcore/URLPatternTokenizer.cpp
Normal file
273
src/bun.js/bindings/webcore/URLPatternTokenizer.cpp
Normal 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
|
||||
82
src/bun.js/bindings/webcore/URLPatternTokenizer.h
Normal file
82
src/bun.js/bindings/webcore/URLPatternTokenizer.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
56
src/http.zig
56
src/http.zig
@@ -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();
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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);
|
||||
|
||||
13
src/url.zig
13
src/url.zig
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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"] });
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
@@ -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: {
|
||||
|
||||
209
test/js/web/urlpattern/urlpattern.test.ts
Normal file
209
test/js/web/urlpattern/urlpattern.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
2967
test/js/web/urlpattern/urlpatterntestdata.json
Normal file
2967
test/js/web/urlpattern/urlpatterntestdata.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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] + "";
|
||||
}
|
||||
|
||||
|
||||
23
test/regression/issue/24806.test.ts
Normal file
23
test/regression/issue/24806.test.ts
Normal 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);
|
||||
});
|
||||
75
test/regression/issue/24924.test.ts
Normal file
75
test/regression/issue/24924.test.ts
Normal 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();
|
||||
});
|
||||
40
test/regression/issue/fuzzer-ENG-22942.test.ts
Normal file
40
test/regression/issue/fuzzer-ENG-22942.test.ts
Normal 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();
|
||||
});
|
||||
Reference in New Issue
Block a user