Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test cases for redirected responses #19074

Merged
merged 2 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 11 additions & 25 deletions test/unit/api_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ import {
import {
buildGetDocumentParams,
CMAP_URL,
createTemporaryNodeServer,
DefaultFileReaderFactory,
getCrossOriginHostname,
TEST_PDFS_PATH,
TestPdfsServer,
} from "./test_utils.js";
import {
DefaultCanvasFactory,
Expand Down Expand Up @@ -67,27 +68,17 @@ describe("api", function () {
buildGetDocumentParams(tracemonkeyFileName);

let CanvasFactory;
let tempServer = null;

beforeAll(function () {
beforeAll(async function () {
CanvasFactory = new DefaultCanvasFactory({});

if (isNodeJS) {
tempServer = createTemporaryNodeServer();
}
await TestPdfsServer.ensureStarted();
});

afterAll(function () {
afterAll(async function () {
CanvasFactory = null;

if (isNodeJS) {
// Close the server from accepting new connections after all test
// finishes.
const { server } = tempServer;
server.close();

tempServer = null;
}
await TestPdfsServer.ensureStopped();
});

function waitSome(callback) {
Expand Down Expand Up @@ -148,9 +139,7 @@ describe("api", function () {
});

it("creates pdf doc from URL-object", async function () {
const urlObj = isNodeJS
? new URL(`http://127.0.0.1:${tempServer.port}/${basicApiFileName}`)
: new URL(TEST_PDFS_PATH + basicApiFileName, window.location);
const urlObj = TestPdfsServer.resolveURL(basicApiFileName);

const loadingTask = getDocument(urlObj);
expect(loadingTask instanceof PDFDocumentLoadingTask).toEqual(true);
Expand Down Expand Up @@ -2989,17 +2978,14 @@ describe("api", function () {
let loadingTask;
function _checkCanLoad(expectSuccess, filename, options) {
if (isNodeJS) {
// We can simulate cross-origin requests, but since Node.js does not
// enforce the Same Origin Policy, requests are expected to be allowed
// independently of withCredentials.
pending("Cannot simulate cross-origin requests in Node.js");
}
const params = buildGetDocumentParams(filename, options);
const url = new URL(params.url);
if (url.hostname === "localhost") {
url.hostname = "127.0.0.1";
} else if (params.url.hostname === "127.0.0.1") {
Snuffleupagus marked this conversation as resolved.
Show resolved Hide resolved
url.hostname = "localhost";
} else {
pending("Can only run cross-origin test on localhost!");
}
url.hostname = getCrossOriginHostname(url.hostname);
params.url = url.href;
loadingTask = getDocument(params);
return loadingTask.promise
Expand Down
90 changes: 90 additions & 0 deletions test/unit/common_pdfstream_tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* Copyright 2024 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { AbortException, isNodeJS } from "../../src/shared/util.js";
import { getCrossOriginHostname, TestPdfsServer } from "./test_utils.js";

// Common tests to verify behavior across implementations of the IPDFStream
// interface:
// - PDFNetworkStream by network_spec.js
// - PDFFetchStream by fetch_stream_spec.js
async function testCrossOriginRedirects({
PDFStreamClass,
redirectIfRange,
testRangeReader,
}) {
const basicApiUrl = TestPdfsServer.resolveURL("basicapi.pdf").href;
const basicApiFileLength = 105779;

const rangeSize = 32768;
const stream = new PDFStreamClass({
url: getCrossOriginUrlWithRedirects(basicApiUrl, redirectIfRange),
length: basicApiFileLength,
rangeChunkSize: rangeSize,
disableStream: true,
disableRange: false,
});

const fullReader = stream.getFullReader();

await fullReader.headersReady;
// Sanity check: We can only test range requests if supported:
expect(fullReader.isRangeSupported).toEqual(true);
// ^ When range requests are supported (and streaming is disabled), the full
// initial request is aborted and we do not need to call fullReader.cancel().

const rangeReader = stream.getRangeReader(
basicApiFileLength - rangeSize,
basicApiFileLength
);

try {
await testRangeReader(rangeReader);
} finally {
rangeReader.cancel(new AbortException("Don't need rangeReader"));
}
}

/**
* @param {string} testserverUrl - A URL handled that supports CORS and
* redirects (see crossOriginHandler and redirectHandler in webserver.mjs).
* @param {boolean} redirectIfRange - Whether Range requests should be
* redirected to a different origin compared to the initial request.
* @returns {string} A URL that will be redirected by the server.
*/
function getCrossOriginUrlWithRedirects(testserverUrl, redirectIfRange) {
const url = new URL(testserverUrl);
if (!isNodeJS) {
// The responses are going to be cross-origin. In Node.js, fetch() allows
// cross-origin requests for any request, but in browser environments we
// need to enable CORS.
// This option depends on crossOriginHandler in webserver.mjs.
url.searchParams.set("cors", "withoutCredentials");
}

// This redirect options depend on redirectHandler in webserver.mjs.

// We will change the host to a cross-origin domain so that the initial
// request will be cross-origin. Set "redirectToHost" to the original host
// to force a cross-origin redirect (relative to the initial URL).
url.searchParams.set("redirectToHost", url.hostname);
url.hostname = getCrossOriginHostname(url.hostname);
if (redirectIfRange) {
url.searchParams.set("redirectIfRange", "1");
}
return url.href;
}

export { testCrossOriginRedirects };
57 changes: 37 additions & 20 deletions test/unit/fetch_stream_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,23 @@
* limitations under the License.
*/

import { AbortException, isNodeJS } from "../../src/shared/util.js";
import { createTemporaryNodeServer } from "./test_utils.js";
import { AbortException } from "../../src/shared/util.js";
import { PDFFetchStream } from "../../src/display/fetch_stream.js";
import { testCrossOriginRedirects } from "./common_pdfstream_tests.js";
import { TestPdfsServer } from "./test_utils.js";

describe("fetch_stream", function () {
let tempServer = null;

function getPdfUrl() {
return isNodeJS
? `http://127.0.0.1:${tempServer.port}/tracemonkey.pdf`
: new URL("../pdfs/tracemonkey.pdf", window.location).href;
return TestPdfsServer.resolveURL("tracemonkey.pdf").href;
}
const pdfLength = 1016315;

beforeAll(function () {
if (isNodeJS) {
tempServer = createTemporaryNodeServer();
}
beforeAll(async function () {
await TestPdfsServer.ensureStarted();
});

afterAll(function () {
if (isNodeJS) {
// Close the server from accepting new connections after all test
// finishes.
const { server } = tempServer;
server.close();

tempServer = null;
}
afterAll(async function () {
await TestPdfsServer.ensureStopped();
});

it("read with streaming", async function () {
Expand Down Expand Up @@ -129,4 +117,33 @@ describe("fetch_stream", function () {
expect(result1.value).toEqual(rangeSize);
expect(result2.value).toEqual(tailSize);
});

describe("Redirects", function () {
it("redirects allowed if all responses are same-origin", async function () {
await testCrossOriginRedirects({
PDFStreamClass: PDFFetchStream,
redirectIfRange: false,
async testRangeReader(rangeReader) {
await expectAsync(rangeReader.read()).toBeResolved();
},
});
});

it("redirects blocked if any response is cross-origin", async function () {
await testCrossOriginRedirects({
PDFStreamClass: PDFFetchStream,
redirectIfRange: true,
async testRangeReader(rangeReader) {
// When read (sync), error should be reported.
await expectAsync(rangeReader.read()).toBeRejectedWithError(
/^Expected range response-origin "http:.*" to match "http:.*"\.$/
);
// When read again (async), error should be consistent.
await expectAsync(rangeReader.read()).toBeRejectedWithError(
/^Expected range response-origin "http:.*" to match "http:.*"\.$/
);
},
});
});
});
});
39 changes: 39 additions & 0 deletions test/unit/network_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

import { AbortException } from "../../src/shared/util.js";
import { PDFNetworkStream } from "../../src/display/network.js";
import { testCrossOriginRedirects } from "./common_pdfstream_tests.js";
import { TestPdfsServer } from "./test_utils.js";

describe("network", function () {
const pdf1 = new URL("../pdfs/tracemonkey.pdf", window.location).href;
Expand Down Expand Up @@ -115,4 +117,41 @@ describe("network", function () {
expect(isRangeSupported).toEqual(true);
expect(fullReaderCancelled).toEqual(true);
});

describe("Redirects", function () {
beforeAll(async function () {
await TestPdfsServer.ensureStarted();
});

afterAll(async function () {
await TestPdfsServer.ensureStopped();
});

it("redirects allowed if all responses are same-origin", async function () {
await testCrossOriginRedirects({
PDFStreamClass: PDFNetworkStream,
redirectIfRange: false,
async testRangeReader(rangeReader) {
await expectAsync(rangeReader.read()).toBeResolved();
},
});
});

it("redirects blocked if any response is cross-origin", async function () {
await testCrossOriginRedirects({
PDFStreamClass: PDFNetworkStream,
redirectIfRange: true,
async testRangeReader(rangeReader) {
// When read (sync), error should be reported.
await expectAsync(rangeReader.read()).toBeRejectedWithError(
/^Expected range response-origin "http:.*" to match "http:.*"\.$/
);
// When read again (async), error should be consistent.
await expectAsync(rangeReader.read()).toBeRejectedWithError(
/^Expected range response-origin "http:.*" to match "http:.*"\.$/
);
},
});
});
});
});
Loading
Loading