Skip to content

Commit 8c7e825

Browse files
committed
[puter] merge upstream
2 parents c0b0047 + d63f8a7 commit 8c7e825

File tree

8 files changed

+843
-82
lines changed

8 files changed

+843
-82
lines changed

.github/workflows/main.yml

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
name: CI
22

3-
on: [push, pull_request, workflow_dispatch]
3+
on:
4+
push:
5+
paths-ignore:
6+
- ".github/workflows/**"
7+
pull_request:
8+
paths-ignore:
9+
- ".github/workflows/**"
10+
workflow_dispatch:
411

512
concurrency:
613
group: ${{ github.workflow }}-${{ github.ref }}
@@ -26,10 +33,16 @@ jobs:
2633
node-version: "22"
2734
cache: "pnpm"
2835

29-
- name: Install dependencies
36+
- name: Install pnpm dependencies
3037
run: pnpm install
3138

32-
- name: install wbg
39+
- name: Cache Rust dependencies
40+
uses: Swatinem/rust-cache@v2
41+
with:
42+
workspaces: "rewriter"
43+
cache-all-crates: true
44+
45+
- name: Install wbg
3346
uses: jetli/[email protected]
3447
with:
3548
version: "0.2.100"

.zed/settings.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"lsp": {
3+
"biome": {
4+
"enabled": false
5+
}
6+
},
7+
"format_on_save": "on",
8+
"formatter": "prettier"
9+
}

src/controller/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@ export class ScramjetController {
117117
if (!res.objectStoreNames.contains("cookies")) {
118118
res.createObjectStore("cookies");
119119
}
120+
if (!res.objectStoreNames.contains("redirectTrackers")) {
121+
res.createObjectStore("redirectTrackers");
122+
}
123+
if (!res.objectStoreNames.contains("referrerPolicies")) {
124+
res.createObjectStore("referrerPolicies");
125+
}
126+
if (!res.objectStoreNames.contains("publicSuffixList")) {
127+
res.createObjectStore("publicSuffixList");
128+
}
120129
};
121130
db.onerror = () => reject(db.error);
122131
});

src/shared/rewriters/headers.ts

Lines changed: 124 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
1-
import { URLMeta, rewriteUrl } from "./url";
2-
import { BareHeaders } from "@mercuryworkshop/bare-mux";
3-
const cspHeaders = [
1+
import type {
2+
default as BareClient,
3+
BareHeaders,
4+
} from "@mercuryworkshop/bare-mux";
5+
import { rewriteUrl, type URLMeta } from "./url";
6+
import { getSiteDirective } from "../security/siteTests";
7+
8+
interface StoredReferrerPolicies {
9+
get(url: string): Promise<{ policy: string; referrer: string } | null>;
10+
set(url: string, policy: string, referrer: string): Promise<void>;
11+
}
12+
13+
/**
14+
* Headers for security policy features that haven't been emulated yet
15+
*/
16+
const SEC_HEADERS = new Set([
417
"cross-origin-embedder-policy",
518
"cross-origin-opener-policy",
619
"cross-origin-resource-policy",
@@ -20,29 +33,47 @@ const cspHeaders = [
2033
// This needs to be emulated, but for right now it isn't that important of a feature to be worried about
2134
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data
2235
"clear-site-data",
23-
];
36+
]);
2437

25-
const urlHeaders = ["location", "content-location", "referer"];
38+
/**
39+
* Headers that are actually URLs that need to be rewritten
40+
*/
41+
const URL_HEADERS = new Set(["location", "content-location", "referer"]);
2642

2743
function rewriteLinkHeader(link: string, meta: URLMeta) {
2844
return link.replace(/<(.*)>/gi, (match) => rewriteUrl(match, meta));
2945
}
3046

31-
export function rewriteHeaders(rawHeaders: BareHeaders, meta: URLMeta) {
47+
/**
48+
* Rewrites response headers
49+
* @param rawHeaders Headers before they were rewritten
50+
* @param meta Parsed Proxy URL
51+
* @param client `BareClient` instance used for fetching
52+
* @param isNavigationRequest Whether the request is a navigation request
53+
*/
54+
export async function rewriteHeaders(
55+
rawHeaders: BareHeaders,
56+
meta: URLMeta,
57+
client: BareClient,
58+
storedReferrerPolicies: StoredReferrerPolicies
59+
) {
3260
const headers = {};
3361

3462
for (const key in rawHeaders) {
3563
headers[key.toLowerCase()] = rawHeaders[key];
3664
}
3765

38-
cspHeaders.forEach((header) => {
39-
delete headers[header];
40-
});
66+
for (const cspHeader of SEC_HEADERS) {
67+
delete headers[cspHeader];
68+
}
4169

42-
urlHeaders.forEach((header) => {
43-
if (headers[header])
44-
headers[header] = rewriteUrl(headers[header]?.toString() as string, meta);
45-
});
70+
for (const urlHeader of URL_HEADERS) {
71+
if (headers[urlHeader])
72+
headers[urlHeader] = rewriteUrl(
73+
headers[urlHeader]?.toString() as string,
74+
meta
75+
);
76+
}
4677

4778
if (typeof headers["link"] === "string") {
4879
headers["link"] = rewriteLinkHeader(headers["link"], meta);
@@ -52,5 +83,85 @@ export function rewriteHeaders(rawHeaders: BareHeaders, meta: URLMeta) {
5283
);
5384
}
5485

86+
// Emulate the referrer policy to set it back to what it should've been without Force Referrer in place
87+
if (typeof headers["referer"] === "string") {
88+
const referrerUrl = new URL(headers["referer"]);
89+
const storedPolicyData = await storedReferrerPolicies.get(referrerUrl.href);
90+
if (storedPolicyData) {
91+
const storedReferrerPolicy = storedPolicyData.policy
92+
.toLowerCase()
93+
.split(",")
94+
.map((rawDir) => rawDir.trim());
95+
if (
96+
storedReferrerPolicy.includes("no-referrer") ||
97+
(storedReferrerPolicy.includes("no-referrer-when-downgrade") &&
98+
meta.origin.protocol === "http:" &&
99+
referrerUrl.protocol === "https:")
100+
) {
101+
delete headers["referer"];
102+
} else if (storedReferrerPolicy.includes("origin")) {
103+
headers["referer"] = referrerUrl.origin;
104+
} else if (storedReferrerPolicy.includes("origin-when-cross-origin")) {
105+
if (referrerUrl.origin !== meta.origin.origin) {
106+
headers["referer"] = referrerUrl.origin;
107+
} else {
108+
headers["referer"] = referrerUrl.href;
109+
}
110+
} else if (storedReferrerPolicy.includes("same-origin")) {
111+
if (referrerUrl.origin === meta.origin.origin) {
112+
headers["referer"] = referrerUrl.href;
113+
} else {
114+
delete headers["referer"];
115+
}
116+
} else if (storedReferrerPolicy.includes("strict-origin")) {
117+
if (
118+
meta.origin.protocol === "http:" &&
119+
referrerUrl.protocol === "https:"
120+
) {
121+
delete headers["referer"];
122+
} else {
123+
headers["referer"] = referrerUrl.origin;
124+
}
125+
}
126+
// `strict-origin-when-cross-origin` is the default behavior anyway
127+
else {
128+
if (referrerUrl.origin === meta.origin.origin) {
129+
headers["referer"] = referrerUrl.href;
130+
} else if (
131+
meta.origin.protocol === "http:" &&
132+
referrerUrl.protocol === "https:"
133+
) {
134+
delete headers["referer"];
135+
} else {
136+
headers["referer"] = referrerUrl.origin;
137+
}
138+
}
139+
}
140+
}
141+
if (
142+
typeof headers["sec-fetch-dest"] === "string" &&
143+
headers["sec-fetch-dest"] === ""
144+
) {
145+
headers["sec-fetch-dest"] = "empty";
146+
}
147+
148+
if (
149+
typeof headers["sec-fetch-site"] === "string" &&
150+
headers["sec-fetch-site"] !== "none"
151+
) {
152+
if (typeof headers["referer"] === "string") {
153+
headers["sec-fetch-site"] = await getSiteDirective(
154+
meta,
155+
new URL(headers["referer"]),
156+
client
157+
);
158+
} else {
159+
console.warn(
160+
"Missing referrer header; can't rewrite sec-fetch-site properly. Falling back to unsafe deletion."
161+
);
162+
delete headers["sec-fetch-site"];
163+
}
164+
}
165+
55166
return headers;
56167
}

0 commit comments

Comments
 (0)