diff --git a/src/display/api.js b/src/display/api.js index 012e45a6373b4..e2a3a17333697 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -673,6 +673,26 @@ class PDFDocumentLoadingTask { this._worker = null; } } + + /** + * @returns {Promise} + */ + async getResponseHeaders() { + if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("GENERIC")) { + throw new Error("Not implemented: getResponseHeaders"); + } + try { + await this.promise; + } catch {} + + if (this.destroyed) { + throw new Error("LoadingTask destroyed."); + } + if (!this._transport) { + throw new Error("WorkerTransport unset."); + } + return this._transport._responseHeaders; + } } /** @@ -2450,6 +2470,23 @@ class WorkerTransport { this.setupMessageHandler(); + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { + // NOTE: This getter should *not* be invoked until after + // the `IPDFStreamReader.headersReady`-promise has settled. + Object.defineProperty(this, "_responseHeaders", { + get() { + if (!networkStream) { + return null; + } + assert(this._fullReader, "No `IPDFStreamReader`-instance available."); + + const headers = this._fullReader.responseHeaders; + // Always create a copy of the *internal* response headers. + return headers ? new Headers(headers) : null; + }, + }); + } + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { // For testing purposes. Object.defineProperty(this, "getNetworkStreamName", { diff --git a/src/display/fetch_stream.js b/src/display/fetch_stream.js index 24604e43b68df..2ce017a19ed57 100644 --- a/src/display/fetch_stream.js +++ b/src/display/fetch_stream.js @@ -97,6 +97,8 @@ class PDFFetchStream { /** @implements {IPDFStreamReader} */ class PDFFetchStreamReader { + #responseHeaders = null; + constructor(stream) { this._stream = stream; this._reader = null; @@ -126,14 +128,14 @@ class PDFFetchStreamReader { .then(response => { stream._responseOrigin = getResponseOrigin(response.url); + const responseHeaders = (this.#responseHeaders = response.headers); + if (!validateResponseStatus(response.status)) { throw createResponseStatusError(response.status, url); } this._reader = response.body.getReader(); this._headersCapability.resolve(); - const responseHeaders = response.headers; - const { allowRangeRequests, suggestedLength } = validateRangeRequestCapabilities({ responseHeaders, @@ -163,6 +165,10 @@ class PDFFetchStreamReader { return this._headersCapability.promise; } + get responseHeaders() { + return this.#responseHeaders; + } + get filename() { return this._filename; } diff --git a/src/display/network.js b/src/display/network.js index 3beb3257bd546..6f612b554dcff 100644 --- a/src/display/network.js +++ b/src/display/network.js @@ -241,6 +241,8 @@ class PDFNetworkStream { /** @implements {IPDFStreamReader} */ class PDFNetworkStreamFullRequestReader { + #responseHeaders = null; + constructor(manager, source) { this._manager = manager; @@ -281,7 +283,7 @@ class PDFNetworkStreamFullRequestReader { ); const rawResponseHeaders = fullRequestXhr.getAllResponseHeaders(); - const responseHeaders = new Headers( + const responseHeaders = (this.#responseHeaders = new Headers( rawResponseHeaders ? rawResponseHeaders .trim() @@ -291,7 +293,7 @@ class PDFNetworkStreamFullRequestReader { return [key, val.join(": ")]; }) : [] - ); + )); const { allowRangeRequests, suggestedLength } = validateRangeRequestCapabilities({ @@ -376,6 +378,10 @@ class PDFNetworkStreamFullRequestReader { return this._headersCapability.promise; } + get responseHeaders() { + return this.#responseHeaders; + } + async read() { await this._headersCapability.promise; diff --git a/src/display/node_stream.js b/src/display/node_stream.js index fdc686d732788..37c74964e37f2 100644 --- a/src/display/node_stream.js +++ b/src/display/node_stream.js @@ -123,6 +123,10 @@ class PDFNodeStreamFsFullReader { return this._headersCapability.promise; } + get responseHeaders() { + return null; + } + get filename() { return this._filename; } diff --git a/src/display/transport_stream.js b/src/display/transport_stream.js index 36bef6dc10eb0..5f1e5d0e25f58 100644 --- a/src/display/transport_stream.js +++ b/src/display/transport_stream.js @@ -211,6 +211,10 @@ class PDFDataTransportStreamReader { return this._headersReady; } + get responseHeaders() { + return null; + } + get filename() { return this._filename; } diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index d73d23225725f..1ec28869c1e01 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -163,6 +163,9 @@ describe("api", function () { // Ensure that the Fetch API was used to load the PDF document. expect(pdfDocument.getNetworkStreamName()).toEqual("PDFFetchStream"); + const responseHeaders = await loadingTask.getResponseHeaders(); + expect([...responseHeaders.keys()].length).toBeGreaterThan(0); + await loadingTask.destroy(); }); @@ -235,16 +238,19 @@ describe("api", function () { progressReportedCapability.resolve(data); }; - const data = await Promise.all([ + const [pdfDocument, progress] = await Promise.all([ loadingTask.promise, progressReportedCapability.promise, ]); - expect(data[0] instanceof PDFDocumentProxy).toEqual(true); - expect(data[1].loaded / data[1].total).toEqual(1); + expect(pdfDocument instanceof PDFDocumentProxy).toEqual(true); + expect(progress.loaded / progress.total).toEqual(1); // Check that the TypedArray was transferred. expect(typedArrayPdf.length).toEqual(0); + const responseHeaders = await loadingTask.getResponseHeaders(); + expect(responseHeaders).toBeNull(); + await loadingTask.destroy(); }); @@ -311,6 +317,13 @@ describe("api", function () { expect(reason instanceof MissingPDFException).toEqual(true); } + const responseHeaders = await loadingTask.getResponseHeaders(); + if (isNodeJS) { + expect(responseHeaders).toBeNull(); + } else { + expect([...responseHeaders.keys()].length).toBeGreaterThan(0); + } + await loadingTask.destroy(); });