diff --git a/js/fetch.ts b/js/fetch.ts index d5f01b8707..3659710c50 100644 --- a/js/fetch.ts +++ b/js/fetch.ts @@ -247,7 +247,7 @@ export class Response implements domTypes.Response { readonly url: string = ""; statusText = "FIXME"; // TODO readonly type = "basic"; // TODO - redirected = false; // TODO + readonly redirected: boolean; headers: domTypes.Headers; readonly trailer: Promise; bodyUsed = false; @@ -257,6 +257,7 @@ export class Response implements domTypes.Response { readonly status: number, headersList: Array<[string, string]>, rid: number, + redirected_: boolean, body_: null | Body = null ) { this.trailer = createResolvable(); @@ -268,6 +269,8 @@ export class Response implements domTypes.Response { } else { this.body = body_; } + + this.redirected = redirected_; } async arrayBuffer(): Promise { @@ -308,7 +311,13 @@ export class Response implements domTypes.Response { headersList.push(header); } - return new Response(this.status, headersList, -1, this.body); + return new Response( + this.status, + headersList, + -1, + this.redirected, + this.body + ); } } @@ -352,6 +361,29 @@ function deserializeHeaderFields(m: msg.HttpHeader): Array<[string, string]> { return out; } +async function getFetchRes( + url: string, + method: string | null, + headers: domTypes.Headers | null, + body: ArrayBufferView | undefined +): Promise { + // Send Fetch message + const builder = flatbuffers.createBuilder(); + const headerOff = msgHttpRequest(builder, url, method, headers); + const resBase = await sendAsync( + builder, + msg.Any.Fetch, + msg.Fetch.createFetch(builder, headerOff), + body + ); + + // Decode FetchRes + assert(msg.Any.FetchRes === resBase.innerType()); + const inner = new msg.FetchRes(); + assert(resBase.inner(inner) != null); + return inner; +} + /** Fetch a resource from the network. */ export async function fetch( input: domTypes.Request | string, @@ -361,6 +393,8 @@ export async function fetch( let method: string | null = null; let headers: domTypes.Headers | null = null; let body: ArrayBufferView | undefined; + let redirected = false; + let remRedirectCount = 20; // TODO: use a better way to handle if (typeof input === "string") { url = input; @@ -414,28 +448,48 @@ export async function fetch( } } - // Send Fetch message - const builder = flatbuffers.createBuilder(); - const headerOff = msgHttpRequest(builder, url, method, headers); - const resBase = await sendAsync( - builder, - msg.Any.Fetch, - msg.Fetch.createFetch(builder, headerOff), - body - ); + while (remRedirectCount) { + const inner = await getFetchRes(url, method, headers, body); - // Decode FetchRes - assert(msg.Any.FetchRes === resBase.innerType()); - const inner = new msg.FetchRes(); - assert(resBase.inner(inner) != null); + const header = inner.header()!; + const bodyRid = inner.bodyRid(); + assert(!header.isRequest()); + const status = header.status(); - const header = inner.header()!; - const bodyRid = inner.bodyRid(); - assert(!header.isRequest()); - const status = header.status(); + const headersList = deserializeHeaderFields(header); - const headersList = deserializeHeaderFields(header); - - const response = new Response(status, headersList, bodyRid); - return response; + const response = new Response(status, headersList, bodyRid, redirected); + if ([301, 302, 303, 307, 308].includes(response.status)) { + // We're in a redirect status + switch ((init && init.redirect) || "follow") { + case "error": + throw notImplemented(); + case "manual": + throw notImplemented(); + case "follow": + default: + let redirectUrl = response.headers.get("Location"); + if (redirectUrl == null) { + return response; // Unspecified + } + if ( + !redirectUrl.startsWith("http://") && + !redirectUrl.startsWith("https://") + ) { + redirectUrl = + url.split("//")[0] + + "//" + + url.split("//")[1].split("/")[0] + + redirectUrl; // TODO: handle relative redirection more gracefully + } + url = redirectUrl; + redirected = true; + remRedirectCount--; + } + } else { + return response; + } + } + // Return a network error due to too many redirections + throw notImplemented(); } diff --git a/js/fetch_test.ts b/js/fetch_test.ts index 032727dbc0..2020716bf0 100644 --- a/js/fetch_test.ts +++ b/js/fetch_test.ts @@ -99,6 +99,32 @@ testPerm( } ); +testPerm({ net: true }, async function fetchWithRedirection(): Promise { + const response = await fetch("http://localhost:4546/"); // will redirect to http://localhost:4545/ + assertEquals(response.status, 200); + const body = await response.text(); + assert(body.includes("Directory listing for /")); +}); + +testPerm({ net: true }, async function fetchWithRelativeRedirection(): Promise< + void +> { + const response = await fetch("http://localhost:4545/tests"); // will redirect to /tests/ + assertEquals(response.status, 200); + const body = await response.text(); + assert(body.includes("Directory listing for /tests/")); +}); + +// The feature below is not implemented, but the test should work after implementation +/* +testPerm({ net: true }, async function fetchWithInfRedirection(): Promise< + void +> { + const response = await fetch("http://localhost:4549/tests"); // will redirect to the same place + assertEquals(response.status, 0); // network error +}); +*/ + testPerm({ net: true }, async function fetchInitStringBody(): Promise { const data = "Hello World"; const response = await fetch("http://localhost:4545/echo_server", { diff --git a/tools/http_server.py b/tools/http_server.py index 116169e2fc..dc3bbe00be 100755 --- a/tools/http_server.py +++ b/tools/http_server.py @@ -16,6 +16,7 @@ PORT = 4545 REDIRECT_PORT = 4546 ANOTHER_REDIRECT_PORT = 4547 DOUBLE_REDIRECTS_PORT = 4548 +INF_REDIRECTS_PORT = 4549 QUIET = '-v' not in sys.argv and '--verbose' not in sys.argv @@ -153,6 +154,11 @@ def double_redirects_server(): return base_redirect_server(DOUBLE_REDIRECTS_PORT, REDIRECT_PORT) +# redirect server that points to itself +def inf_redirects_server(): + return base_redirect_server(INF_REDIRECTS_PORT, INF_REDIRECTS_PORT) + + def start(s): thread = Thread(target=s.serve_forever, kwargs={"poll_interval": 0.05}) thread.daemon = True @@ -175,7 +181,7 @@ def spawn(): def main(): servers = (server(), redirect_server(), another_redirect_server(), - double_redirects_server()) + double_redirects_server(), inf_redirects_server()) try: while all(s.thread.is_alive() for s in servers): sleep(10)