Ensure that the response-origin of range requests match the full request (issue 12744)
The following cases are excluded in the patch: - The Firefox PDF Viewer, since it has been fixed on the platform side already; please see https://bugzilla.mozilla.org/show_bug.cgi?id=1683940 - The `PDFNodeStream`-implementation, used in Node.js environments, since after recent changes that code only supports `file://`-URLs. Also updates the `PDFNetworkStreamFullRequestReader.read`-method to await the headers before returning any data, similar to the implementation in `src/display/fetch_stream.js`. *Note:* The relevant unit-tests are updated to await the `headersReady` Promise before dispatching range requests, since that's consistent with the actual usage in the `src/`-folder.
This commit is contained in:
parent
1f6cc85134
commit
6a015588b9
@ -18,6 +18,7 @@ import {
|
|||||||
createHeaders,
|
createHeaders,
|
||||||
createResponseStatusError,
|
createResponseStatusError,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
|
getResponseOrigin,
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
validateResponseStatus,
|
validateResponseStatus,
|
||||||
} from "./network_utils.js";
|
} from "./network_utils.js";
|
||||||
@ -52,6 +53,8 @@ function getArrayBuffer(val) {
|
|||||||
|
|
||||||
/** @implements {IPDFStream} */
|
/** @implements {IPDFStream} */
|
||||||
class PDFFetchStream {
|
class PDFFetchStream {
|
||||||
|
_responseOrigin = null;
|
||||||
|
|
||||||
constructor(source) {
|
constructor(source) {
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.isHttp = /^https?:/i.test(source.url);
|
this.isHttp = /^https?:/i.test(source.url);
|
||||||
@ -121,6 +124,8 @@ class PDFFetchStreamReader {
|
|||||||
createFetchOptions(headers, this._withCredentials, this._abortController)
|
createFetchOptions(headers, this._withCredentials, this._abortController)
|
||||||
)
|
)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
stream._responseOrigin = getResponseOrigin(response.url);
|
||||||
|
|
||||||
if (!validateResponseStatus(response.status)) {
|
if (!validateResponseStatus(response.status)) {
|
||||||
throw createResponseStatusError(response.status, url);
|
throw createResponseStatusError(response.status, url);
|
||||||
}
|
}
|
||||||
@ -217,6 +222,13 @@ class PDFFetchStreamRangeReader {
|
|||||||
createFetchOptions(headers, this._withCredentials, this._abortController)
|
createFetchOptions(headers, this._withCredentials, this._abortController)
|
||||||
)
|
)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
const responseOrigin = getResponseOrigin(response.url);
|
||||||
|
|
||||||
|
if (responseOrigin !== stream._responseOrigin) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected range response-origin "${responseOrigin}" to match "${stream._responseOrigin}".`
|
||||||
|
);
|
||||||
|
}
|
||||||
if (!validateResponseStatus(response.status)) {
|
if (!validateResponseStatus(response.status)) {
|
||||||
throw createResponseStatusError(response.status, url);
|
throw createResponseStatusError(response.status, url);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import {
|
|||||||
createHeaders,
|
createHeaders,
|
||||||
createResponseStatusError,
|
createResponseStatusError,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
|
getResponseOrigin,
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
} from "./network_utils.js";
|
} from "./network_utils.js";
|
||||||
|
|
||||||
@ -39,6 +40,8 @@ function getArrayBuffer(xhr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class NetworkManager {
|
class NetworkManager {
|
||||||
|
_responseOrigin = null;
|
||||||
|
|
||||||
constructor({ url, httpHeaders, withCredentials }) {
|
constructor({ url, httpHeaders, withCredentials }) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.isHttp = /^https?:/i.test(url);
|
this.isHttp = /^https?:/i.test(url);
|
||||||
@ -273,6 +276,10 @@ class PDFNetworkStreamFullRequestReader {
|
|||||||
const fullRequestXhrId = this._fullRequestId;
|
const fullRequestXhrId = this._fullRequestId;
|
||||||
const fullRequestXhr = this._manager.getRequestXhr(fullRequestXhrId);
|
const fullRequestXhr = this._manager.getRequestXhr(fullRequestXhrId);
|
||||||
|
|
||||||
|
this._manager._responseOrigin = getResponseOrigin(
|
||||||
|
fullRequestXhr.responseURL
|
||||||
|
);
|
||||||
|
|
||||||
const rawResponseHeaders = fullRequestXhr.getAllResponseHeaders();
|
const rawResponseHeaders = fullRequestXhr.getAllResponseHeaders();
|
||||||
const responseHeaders = new Headers(
|
const responseHeaders = new Headers(
|
||||||
rawResponseHeaders
|
rawResponseHeaders
|
||||||
@ -370,6 +377,8 @@ class PDFNetworkStreamFullRequestReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async read() {
|
async read() {
|
||||||
|
await this._headersCapability.promise;
|
||||||
|
|
||||||
if (this._storedError) {
|
if (this._storedError) {
|
||||||
throw this._storedError;
|
throw this._storedError;
|
||||||
}
|
}
|
||||||
@ -405,6 +414,7 @@ class PDFNetworkStreamRangeRequestReader {
|
|||||||
this._manager = manager;
|
this._manager = manager;
|
||||||
|
|
||||||
const args = {
|
const args = {
|
||||||
|
onHeadersReceived: this._onHeadersReceived.bind(this),
|
||||||
onDone: this._onDone.bind(this),
|
onDone: this._onDone.bind(this),
|
||||||
onError: this._onError.bind(this),
|
onError: this._onError.bind(this),
|
||||||
onProgress: this._onProgress.bind(this),
|
onProgress: this._onProgress.bind(this),
|
||||||
@ -420,6 +430,19 @@ class PDFNetworkStreamRangeRequestReader {
|
|||||||
this.onClosed = null;
|
this.onClosed = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onHeadersReceived() {
|
||||||
|
const responseOrigin = getResponseOrigin(
|
||||||
|
this._manager.getRequestXhr(this._requestId)?.responseURL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (responseOrigin !== this._manager._responseOrigin) {
|
||||||
|
this._storedError = new Error(
|
||||||
|
`Expected range response-origin "${responseOrigin}" to match "${this._manager._responseOrigin}".`
|
||||||
|
);
|
||||||
|
this._onError(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_close() {
|
_close() {
|
||||||
this.onClosed?.(this);
|
this.onClosed?.(this);
|
||||||
}
|
}
|
||||||
@ -441,7 +464,7 @@ class PDFNetworkStreamRangeRequestReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onError(status) {
|
_onError(status) {
|
||||||
this._storedError = createResponseStatusError(status, this._url);
|
this._storedError ??= createResponseStatusError(status, this._url);
|
||||||
for (const requestCapability of this._requests) {
|
for (const requestCapability of this._requests) {
|
||||||
requestCapability.reject(this._storedError);
|
requestCapability.reject(this._storedError);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,16 @@ function createHeaders(isHttp, httpHeaders) {
|
|||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getResponseOrigin(url) {
|
||||||
|
try {
|
||||||
|
return new URL(url).origin;
|
||||||
|
} catch {
|
||||||
|
// `new URL()` will throw on incorrect data.
|
||||||
|
}
|
||||||
|
// Notably, null is distinct from "null" string (e.g. from file:-URLs).
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function validateRangeRequestCapabilities({
|
function validateRangeRequestCapabilities({
|
||||||
responseHeaders,
|
responseHeaders,
|
||||||
isHttp,
|
isHttp,
|
||||||
@ -116,6 +126,7 @@ export {
|
|||||||
createHeaders,
|
createHeaders,
|
||||||
createResponseStatusError,
|
createResponseStatusError,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
|
getResponseOrigin,
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
validateResponseStatus,
|
validateResponseStatus,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -30,6 +30,10 @@ class IPDFStream {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a reader for the range of the PDF data.
|
* Gets a reader for the range of the PDF data.
|
||||||
|
*
|
||||||
|
* NOTE: Currently this method is only expected to be invoked *after*
|
||||||
|
* the `IPDFStreamReader.prototype.headersReady` promise has resolved.
|
||||||
|
*
|
||||||
* @param {number} begin - the start offset of the data.
|
* @param {number} begin - the start offset of the data.
|
||||||
* @param {number} end - the end offset of the data.
|
* @param {number} end - the end offset of the data.
|
||||||
* @returns {IPDFStreamRangeReader}
|
* @returns {IPDFStreamRangeReader}
|
||||||
|
|||||||
@ -54,7 +54,7 @@ describe("fetch_stream", function () {
|
|||||||
const fullReader = stream.getFullReader();
|
const fullReader = stream.getFullReader();
|
||||||
|
|
||||||
let isStreamingSupported, isRangeSupported;
|
let isStreamingSupported, isRangeSupported;
|
||||||
const promise = fullReader.headersReady.then(function () {
|
await fullReader.headersReady.then(function () {
|
||||||
isStreamingSupported = fullReader.isStreamingSupported;
|
isStreamingSupported = fullReader.isStreamingSupported;
|
||||||
isRangeSupported = fullReader.isRangeSupported;
|
isRangeSupported = fullReader.isRangeSupported;
|
||||||
});
|
});
|
||||||
@ -71,7 +71,7 @@ describe("fetch_stream", function () {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
await Promise.all([read(), promise]);
|
await read();
|
||||||
|
|
||||||
expect(len).toEqual(pdfLength);
|
expect(len).toEqual(pdfLength);
|
||||||
expect(isStreamingSupported).toEqual(true);
|
expect(isStreamingSupported).toEqual(true);
|
||||||
@ -90,7 +90,7 @@ describe("fetch_stream", function () {
|
|||||||
const fullReader = stream.getFullReader();
|
const fullReader = stream.getFullReader();
|
||||||
|
|
||||||
let isStreamingSupported, isRangeSupported, fullReaderCancelled;
|
let isStreamingSupported, isRangeSupported, fullReaderCancelled;
|
||||||
const promise = fullReader.headersReady.then(function () {
|
await fullReader.headersReady.then(function () {
|
||||||
isStreamingSupported = fullReader.isStreamingSupported;
|
isStreamingSupported = fullReader.isStreamingSupported;
|
||||||
isRangeSupported = fullReader.isRangeSupported;
|
isRangeSupported = fullReader.isRangeSupported;
|
||||||
// We shall be able to close full reader without any issue.
|
// We shall be able to close full reader without any issue.
|
||||||
@ -121,7 +121,6 @@ describe("fetch_stream", function () {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
read(rangeReader1, result1),
|
read(rangeReader1, result1),
|
||||||
read(rangeReader2, result2),
|
read(rangeReader2, result2),
|
||||||
promise,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(isStreamingSupported).toEqual(true);
|
expect(isStreamingSupported).toEqual(true);
|
||||||
|
|||||||
@ -31,7 +31,7 @@ describe("network", function () {
|
|||||||
const fullReader = stream.getFullReader();
|
const fullReader = stream.getFullReader();
|
||||||
|
|
||||||
let isStreamingSupported, isRangeSupported;
|
let isStreamingSupported, isRangeSupported;
|
||||||
const promise = fullReader.headersReady.then(function () {
|
await fullReader.headersReady.then(function () {
|
||||||
isStreamingSupported = fullReader.isStreamingSupported;
|
isStreamingSupported = fullReader.isStreamingSupported;
|
||||||
isRangeSupported = fullReader.isRangeSupported;
|
isRangeSupported = fullReader.isRangeSupported;
|
||||||
});
|
});
|
||||||
@ -49,7 +49,7 @@ describe("network", function () {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
await Promise.all([read(), promise]);
|
await read();
|
||||||
|
|
||||||
expect(len).toEqual(pdf1Length);
|
expect(len).toEqual(pdf1Length);
|
||||||
expect(count).toEqual(1);
|
expect(count).toEqual(1);
|
||||||
@ -72,7 +72,7 @@ describe("network", function () {
|
|||||||
const fullReader = stream.getFullReader();
|
const fullReader = stream.getFullReader();
|
||||||
|
|
||||||
let isStreamingSupported, isRangeSupported, fullReaderCancelled;
|
let isStreamingSupported, isRangeSupported, fullReaderCancelled;
|
||||||
const promise = fullReader.headersReady.then(function () {
|
await fullReader.headersReady.then(function () {
|
||||||
isStreamingSupported = fullReader.isStreamingSupported;
|
isStreamingSupported = fullReader.isStreamingSupported;
|
||||||
isRangeSupported = fullReader.isRangeSupported;
|
isRangeSupported = fullReader.isRangeSupported;
|
||||||
// we shall be able to close the full reader without issues
|
// we shall be able to close the full reader without issues
|
||||||
@ -107,7 +107,6 @@ describe("network", function () {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
read(range1Reader, result1),
|
read(range1Reader, result1),
|
||||||
read(range2Reader, result2),
|
read(range2Reader, result2),
|
||||||
promise,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(result1.value).toEqual(rangeSize);
|
expect(result1.value).toEqual(rangeSize);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user