Merge pull request #19114 from Snuffleupagus/issue-19075
Ensure that a missing/invalid "Content-Range" header is handled in `PDFNetworkStream` (issue 19075)
This commit is contained in:
commit
60eba287d4
@ -13,7 +13,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert, stringToBytes } from "../shared/util.js";
|
import { assert, stringToBytes, warn } from "../shared/util.js";
|
||||||
import {
|
import {
|
||||||
createHeaders,
|
createHeaders,
|
||||||
createResponseStatusError,
|
createResponseStatusError,
|
||||||
@ -85,11 +85,10 @@ class NetworkManager {
|
|||||||
}
|
}
|
||||||
xhr.responseType = "arraybuffer";
|
xhr.responseType = "arraybuffer";
|
||||||
|
|
||||||
if (args.onError) {
|
assert(args.onError, "Expected `onError` callback to be provided.");
|
||||||
xhr.onerror = function (evt) {
|
xhr.onerror = () => {
|
||||||
args.onError(xhr.status);
|
args.onError(xhr.status);
|
||||||
};
|
};
|
||||||
}
|
|
||||||
xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
|
xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
|
||||||
xhr.onprogress = this.onProgress.bind(this, xhrId);
|
xhr.onprogress = this.onProgress.bind(this, xhrId);
|
||||||
|
|
||||||
@ -137,7 +136,7 @@ class NetworkManager {
|
|||||||
|
|
||||||
// Success status == 0 can be on ftp, file and other protocols.
|
// Success status == 0 can be on ftp, file and other protocols.
|
||||||
if (xhr.status === 0 && this.isHttp) {
|
if (xhr.status === 0 && this.isHttp) {
|
||||||
pendingRequest.onError?.(xhr.status);
|
pendingRequest.onError(xhr.status);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const xhrStatus = xhr.status || OK_RESPONSE;
|
const xhrStatus = xhr.status || OK_RESPONSE;
|
||||||
@ -153,7 +152,7 @@ class NetworkManager {
|
|||||||
!ok_response_on_range_request &&
|
!ok_response_on_range_request &&
|
||||||
xhrStatus !== pendingRequest.expectedStatus
|
xhrStatus !== pendingRequest.expectedStatus
|
||||||
) {
|
) {
|
||||||
pendingRequest.onError?.(xhr.status);
|
pendingRequest.onError(xhr.status);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,17 +160,22 @@ class NetworkManager {
|
|||||||
if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
|
if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
|
||||||
const rangeHeader = xhr.getResponseHeader("Content-Range");
|
const rangeHeader = xhr.getResponseHeader("Content-Range");
|
||||||
const matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
|
const matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
|
||||||
pendingRequest.onDone({
|
if (matches) {
|
||||||
begin: parseInt(matches[1], 10),
|
pendingRequest.onDone({
|
||||||
chunk,
|
begin: parseInt(matches[1], 10),
|
||||||
});
|
chunk,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
warn(`Missing or invalid "Content-Range" header.`);
|
||||||
|
pendingRequest.onError(0);
|
||||||
|
}
|
||||||
} else if (chunk) {
|
} else if (chunk) {
|
||||||
pendingRequest.onDone({
|
pendingRequest.onDone({
|
||||||
begin: 0,
|
begin: 0,
|
||||||
chunk,
|
chunk,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
pendingRequest.onError?.(xhr.status);
|
pendingRequest.onError(xhr.status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AbortException } from "../../src/shared/util.js";
|
import {
|
||||||
|
AbortException,
|
||||||
|
UnexpectedResponseException,
|
||||||
|
} from "../../src/shared/util.js";
|
||||||
import { PDFNetworkStream } from "../../src/display/network.js";
|
import { PDFNetworkStream } from "../../src/display/network.js";
|
||||||
import { testCrossOriginRedirects } from "./common_pdfstream_tests.js";
|
import { testCrossOriginRedirects } from "./common_pdfstream_tests.js";
|
||||||
import { TestPdfsServer } from "./test_utils.js";
|
import { TestPdfsServer } from "./test_utils.js";
|
||||||
@ -118,6 +121,44 @@ describe("network", function () {
|
|||||||
expect(fullReaderCancelled).toEqual(true);
|
expect(fullReaderCancelled).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it(`handle reading ranges with missing/invalid "Content-Range" header`, async function () {
|
||||||
|
async function readRanges(mode) {
|
||||||
|
const rangeSize = 32768;
|
||||||
|
const stream = new PDFNetworkStream({
|
||||||
|
url: `${pdf1}?test-network-break-ranges=${mode}`,
|
||||||
|
length: pdf1Length,
|
||||||
|
rangeChunkSize: rangeSize,
|
||||||
|
disableStream: true,
|
||||||
|
disableRange: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fullReader = stream.getFullReader();
|
||||||
|
|
||||||
|
await fullReader.headersReady;
|
||||||
|
// Ensure that range requests are supported.
|
||||||
|
expect(fullReader.isRangeSupported).toEqual(true);
|
||||||
|
// We shall be able to close the full reader without issues.
|
||||||
|
fullReader.cancel(new AbortException("Don't need fullReader."));
|
||||||
|
|
||||||
|
const rangeReader = stream.getRangeReader(
|
||||||
|
pdf1Length - rangeSize,
|
||||||
|
pdf1Length
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await rangeReader.read();
|
||||||
|
|
||||||
|
// Shouldn't get here.
|
||||||
|
expect(false).toEqual(true);
|
||||||
|
} catch (ex) {
|
||||||
|
expect(ex instanceof UnexpectedResponseException).toEqual(true);
|
||||||
|
expect(ex.status).toEqual(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all([readRanges("missing"), readRanges("invalid")]);
|
||||||
|
});
|
||||||
|
|
||||||
describe("Redirects", function () {
|
describe("Redirects", function () {
|
||||||
beforeAll(async function () {
|
beforeAll(async function () {
|
||||||
await TestPdfsServer.ensureStarted();
|
await TestPdfsServer.ensureStarted();
|
||||||
|
|||||||
@ -177,6 +177,7 @@ class WebServer {
|
|||||||
this.#serveFileRange(
|
this.#serveFileRange(
|
||||||
response,
|
response,
|
||||||
localURL,
|
localURL,
|
||||||
|
url.searchParams,
|
||||||
fileSize,
|
fileSize,
|
||||||
start,
|
start,
|
||||||
isNaN(end) ? fileSize : end + 1
|
isNaN(end) ? fileSize : end + 1
|
||||||
@ -307,7 +308,7 @@ class WebServer {
|
|||||||
stream.pipe(response);
|
stream.pipe(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
#serveFileRange(response, fileURL, fileSize, start, end) {
|
#serveFileRange(response, fileURL, searchParams, fileSize, start, end) {
|
||||||
if (end > fileSize || start > end) {
|
if (end > fileSize || start > end) {
|
||||||
response.writeHead(416);
|
response.writeHead(416);
|
||||||
response.end();
|
response.end();
|
||||||
@ -330,6 +331,16 @@ class WebServer {
|
|||||||
"Content-Range",
|
"Content-Range",
|
||||||
`bytes ${start}-${end - 1}/${fileSize}`
|
`bytes ${start}-${end - 1}/${fileSize}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Support test in `test/unit/network_spec.js`.
|
||||||
|
switch (searchParams.get("test-network-break-ranges")) {
|
||||||
|
case "missing":
|
||||||
|
response.removeHeader("Content-Range");
|
||||||
|
break;
|
||||||
|
case "invalid":
|
||||||
|
response.setHeader("Content-Range", "bytes abc-def/qwerty");
|
||||||
|
break;
|
||||||
|
}
|
||||||
response.writeHead(206);
|
response.writeHead(206);
|
||||||
stream.pipe(response);
|
stream.pipe(response);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user