diff --git a/src/display/network.js b/src/display/network.js index 3beb3257bd546..40690eb34900a 100644 --- a/src/display/network.js +++ b/src/display/network.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { assert, stringToBytes } from "../shared/util.js"; +import { assert, stringToBytes, warn } from "../shared/util.js"; import { createHeaders, createResponseStatusError, @@ -159,12 +159,17 @@ class NetworkManager { const chunk = getArrayBuffer(xhr); if (xhrStatus === PARTIAL_CONTENT_RESPONSE) { - const rangeHeader = xhr.getResponseHeader("Content-Range"); - const matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader); - pendingRequest.onDone({ - begin: parseInt(matches[1], 10), - chunk, - }); + try { + const rangeHeader = xhr.getResponseHeader("Content-Range"); + const matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader); + pendingRequest.onDone({ + begin: parseInt(matches[1], 10), + chunk, + }); + } catch (ex) { + warn(`Missing or invalid "Content-Range" header: "${ex}".`); + pendingRequest.onError?.(0); + } } else if (chunk) { pendingRequest.onDone({ begin: 0, diff --git a/test/unit/network_spec.js b/test/unit/network_spec.js index 9a55b4771ff0e..77e49f3bc8891 100644 --- a/test/unit/network_spec.js +++ b/test/unit/network_spec.js @@ -13,7 +13,10 @@ * 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"; describe("network", function () { @@ -115,4 +118,40 @@ describe("network", function () { expect(isRangeSupported).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(); + } catch (ex) { + expect(ex instanceof UnexpectedResponseException).toEqual(true); + expect(ex.status).toEqual(0); + } + rangeReader.cancel(new AbortException("Don't need rangeReader.")); + } + + await Promise.all([readRanges("missing"), readRanges("invalid")]); + }); }); diff --git a/test/webserver.mjs b/test/webserver.mjs index e0295f3a2865e..be87d9bb26943 100644 --- a/test/webserver.mjs +++ b/test/webserver.mjs @@ -177,6 +177,7 @@ class WebServer { this.#serveFileRange( response, localURL, + url.searchParams fileSize, start, isNaN(end) ? fileSize : end + 1 @@ -307,7 +308,7 @@ class WebServer { stream.pipe(response); } - #serveFileRange(response, fileURL, fileSize, start, end) { + #serveFileRange(response, fileURL, searchParams, fileSize, start, end) { const stream = fs.createReadStream(fileURL, { flags: "rs", start, @@ -325,6 +326,16 @@ class WebServer { "Content-Range", `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); stream.pipe(response); }