mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-04 23:57:54 +00:00
fix: preserve explicit Cookie headers in extension requests
This commit is contained in:
1 parent
ba84a04a43
commit
fe2f45e40d
4 files changed
+114
-3
No files matched your search
@@ -105,6 +105,95 @@ func TestXMLHttpRequestUsesFetch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtensionRequestsSendExplicitCookie(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) {
|
||||
_, _ = w.Write([]byte(request.Header.Get("Cookie")))
|
||||
}))
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
type requestTest struct {
|
||||
name string
|
||||
script string
|
||||
}
|
||||
tests := []requestTest{
|
||||
{"fetch record", `const response = await fetch(url, {headers: {Cookie: cookie}}); return response.text();`},
|
||||
{"fetch Headers", `const response = await fetch(url, {headers: new Headers({cookie})}); return response.text();`},
|
||||
{"fetch pairs", `const response = await fetch(url, {headers: [["cOoKiE", cookie]]}); return response.text();`},
|
||||
{"fetch Request clone", `const request = new Request(url, {headers: {Cookie: cookie}}); const response = await fetch(request.clone()); return response.text();`},
|
||||
{"fetch Request set", `const request = new Request(url); request.headers.set("Cookie", cookie); const response = await fetch(request); return response.text();`},
|
||||
{"fetch Request append", `const request = new Request(url); request.headers.append("Cookie", cookie); const response = await fetch(request); return response.text();`},
|
||||
{"fetch no-cors mutations", `const request = new Request(url, {mode: "no-cors"}); request.headers.set("Cookie", "removed=1"); request.headers.delete("COOKIE"); if (request.headers.has("cookie")) throw new Error("Cookie was not deleted"); request.headers.append("Cookie", cookie); const response = await fetch(request.clone()); return response.text();`},
|
||||
}
|
||||
for _, mode := range []string{"cors", "same-origin", "no-cors"} {
|
||||
for _, credentials := range []string{"omit", "same-origin", "include"} {
|
||||
tests = append(tests, requestTest{
|
||||
name: "fetch " + mode + " " + credentials,
|
||||
script: fmt.Sprintf(`const response = await fetch(url, {headers: {Cookie: cookie}, mode: %q, credentials: %q}); return response.text();`, mode, credentials),
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, withCredentials := range []bool{false, true} {
|
||||
tests = append(tests, requestTest{
|
||||
name: fmt.Sprintf("XHR withCredentials=%t", withCredentials),
|
||||
script: fmt.Sprintf(`return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", url);
|
||||
xhr.withCredentials = %t;
|
||||
xhr.setRequestHeader("cOoKiE", cookie);
|
||||
xhr.onload = () => resolve(xhr.responseText);
|
||||
xhr.onerror = () => reject(new Error("XHR failed"));
|
||||
xhr.send();
|
||||
});`, withCredentials),
|
||||
})
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
runtime := engine.NewEngine(nil)
|
||||
t.Cleanup(runtime.Close)
|
||||
const cookie = "sid=manual; token=provided"
|
||||
value, err := runtime.RunString(fmt.Sprintf(`(async () => {
|
||||
const url = %q;
|
||||
const cookie = %q;
|
||||
%s
|
||||
})()`, server.URL, cookie, test.script))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if value != cookie {
|
||||
t.Fatalf("server received Cookie %q, want %q", value, cookie)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchExplicitCookieWithCookieJar(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) {
|
||||
if request.URL.Path == "/seed" {
|
||||
http.SetCookie(w, &http.Cookie{Name: "stored", Value: "jar", Path: "/"})
|
||||
}
|
||||
_, _ = w.Write([]byte(request.Header.Get("Cookie")))
|
||||
}))
|
||||
t.Cleanup(server.Close)
|
||||
runtime := engine.NewEngine(nil)
|
||||
t.Cleanup(runtime.Close)
|
||||
value, err := runtime.RunString(fmt.Sprintf(`(async () => {
|
||||
const url = %q;
|
||||
await (await fetch(url + "/seed")).text();
|
||||
const automatic = await (await fetch(url)).text();
|
||||
const combined = await (await fetch(url, {headers: {Cookie: "manual=provided"}})).text();
|
||||
const omitted = await (await fetch(url, {credentials: "omit", headers: {Cookie: "manual=provided"}})).text();
|
||||
const empty = await (await fetch(url, {credentials: "omit"})).text();
|
||||
return JSON.stringify({automatic, combined, omitted, empty});
|
||||
})()`, server.URL))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
const want = `{"automatic":"stored=jar","combined":"manual=provided; stored=jar","omitted":"manual=provided","empty":""}`
|
||||
if value != want {
|
||||
t.Fatalf("unexpected CookieJar result: got %#v want %s", value, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchResponseAndRequestCloneHaveIndependentBodies(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
if request.Method == http.MethodPost {
|
||||
|
||||
@@ -195,6 +195,9 @@
|
||||
|
||||
function blockedByGuard(headers, name, value) {
|
||||
if (headers._guard === "immutable") throw new TypeError("Headers are immutable");
|
||||
// Extensions are privileged HTTP clients and may supply their own cookies,
|
||||
// including in no-cors mode. Keep browser guards for other header names.
|
||||
if (name === "cookie" && (headers._guard === "request" || headers._guard === "request-no-cors")) return false;
|
||||
if (headers._guard === "request") return forbiddenRequestHeader(name, value);
|
||||
if (headers._guard === "request-no-cors") {
|
||||
return forbiddenRequestHeader(name, value) || !noCORSSafelistedRequestHeader(name, value);
|
||||
@@ -244,7 +247,7 @@
|
||||
delete(name) {
|
||||
name = normalizeHeaderName(name);
|
||||
if (this._guard === "immutable") throw new TypeError("Headers are immutable");
|
||||
if (this._guard === "request-no-cors") {
|
||||
if (this._guard === "request-no-cors" && name !== "cookie") {
|
||||
if (forbiddenRequestHeader(name, "") || !["accept", "accept-language", "content-language", "content-type", "range"].includes(name)) return;
|
||||
this._values.delete(name);
|
||||
return;
|
||||
|
||||
+8
-1
@@ -19,10 +19,17 @@ a claim that Gopeed is a browser. Browser-only suites that require a document,
|
||||
CORS origin enforcement or preflight, CSP, Mixed Content, Service Workers,
|
||||
browser HTTP cache, navigation, browser authentication UI, or WPT's
|
||||
multi-origin server infrastructure are intentionally excluded. Gopeed also
|
||||
keeps one extension-specific behavior: `redirect: "manual"` exposes the HTTP
|
||||
keeps extension-specific behavior: `redirect: "manual"` exposes the HTTP
|
||||
redirect response so download extensions can inspect `Location`, instead of
|
||||
returning a browser `opaqueredirect` response.
|
||||
|
||||
Extensions may also supply an explicit `Cookie` request header, including in
|
||||
`no-cors` mode. The runner excludes the single upstream assertion that treats
|
||||
`Cookie` as an invalid request header; Gopeed integration tests instead verify
|
||||
that fetch and XMLHttpRequest send it to the server. `credentials: "omit"`
|
||||
disables automatic CookieJar cookies but preserves an explicit header. Other
|
||||
request header guards remain covered by the unchanged upstream tests.
|
||||
|
||||
`wpt_harness.js` is a small Goja adapter maintained by Gopeed; it is not copied
|
||||
from WPT. Test logic below `fetch/` is unchanged; the checked-in copy may only
|
||||
normalize a final newline.
|
||||
@@ -110,7 +110,19 @@ func runWPTFile(t *testing.T, files embed.FS, name, setup string) {
|
||||
}
|
||||
runtime := engine.NewEngine(nil)
|
||||
t.Cleanup(runtime.Close)
|
||||
value, err := runtime.RunString(setup + "\n" + wptHarness + "\n" + wptFetchUtils + "\n" + wptRequestError + "\n" + string(source) + "\n__wptFinish();")
|
||||
adaptation := ""
|
||||
if name == "testdata/wpt/fetch/api/request/request-headers.any.js" {
|
||||
// Gopeed extensions intentionally allow explicit Cookie request headers.
|
||||
// Keep the upstream file unchanged and exclude only its browser restriction.
|
||||
adaptation = `
|
||||
const upstreamTest = globalThis.test;
|
||||
globalThis.test = function (callback, name) {
|
||||
if (name === 'Adding invalid request header "Cookie: KO"') return;
|
||||
upstreamTest(callback, name);
|
||||
};
|
||||
`
|
||||
}
|
||||
value, err := runtime.RunString(setup + "\n" + wptHarness + "\n" + wptFetchUtils + "\n" + wptRequestError + "\n" + adaptation + "\n" + string(source) + "\n__wptFinish();")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user