mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-08-04 05:17:24 +02:00
Trim the response headers with a backward scan
Removing the trailing whitespace with a `$`-anchored regex is quadratic in the length of the run, which a server controls. The helper lives in network_utils.js, to be unit testable.
This commit is contained in:
parent
7fc7072f9c
commit
cba911df86
@ -25,6 +25,7 @@ import {
|
|||||||
ensureResponseOrigin,
|
ensureResponseOrigin,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
getResponseOrigin,
|
getResponseOrigin,
|
||||||
|
trimHeadersEnd,
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
} from "./network_utils.js";
|
} from "./network_utils.js";
|
||||||
import { endRequests } from "./transport_stream.js";
|
import { endRequests } from "./transport_stream.js";
|
||||||
@ -208,9 +209,7 @@ class PDFNetworkStreamReader extends BasePDFStreamReader {
|
|||||||
const rawResponseHeaders = fullRequestXhr.getAllResponseHeaders();
|
const rawResponseHeaders = fullRequestXhr.getAllResponseHeaders();
|
||||||
const responseHeaders = new Headers(
|
const responseHeaders = new Headers(
|
||||||
rawResponseHeaders
|
rawResponseHeaders
|
||||||
? rawResponseHeaders
|
? trimHeadersEnd(rawResponseHeaders.trimStart())
|
||||||
.trimStart()
|
|
||||||
.replace(/[^\S ]+$/, "") // Not `trimEnd`, to keep regular spaces.
|
|
||||||
.split(/[\r\n]+/)
|
.split(/[\r\n]+/)
|
||||||
.map(x => {
|
.map(x => {
|
||||||
const [key, ...val] = x.split(": ");
|
const [key, ...val] = x.split(": ");
|
||||||
|
|||||||
@ -32,6 +32,17 @@ function createHeaders(isHttp, httpHeaders) {
|
|||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trim the trailing whitespace of the raw response headers, but keep the
|
||||||
|
// regular spaces (hence no `trimEnd`). Scanning backwards keeps this linear,
|
||||||
|
// whereas a `$`-anchored regex is quadratic in the length of the run.
|
||||||
|
function trimHeadersEnd(str) {
|
||||||
|
let end = str.length;
|
||||||
|
while (end > 0 && str[end - 1] !== " " && /\s/.test(str[end - 1])) {
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
return str.slice(0, end);
|
||||||
|
}
|
||||||
|
|
||||||
function getResponseOrigin(url) {
|
function getResponseOrigin(url) {
|
||||||
// Notably, null is distinct from "null" string (e.g. from file:-URLs).
|
// Notably, null is distinct from "null" string (e.g. from file:-URLs).
|
||||||
return URL.parse(url)?.origin ?? null;
|
return URL.parse(url)?.origin ?? null;
|
||||||
@ -117,5 +128,6 @@ export {
|
|||||||
ensureResponseOrigin,
|
ensureResponseOrigin,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
getResponseOrigin,
|
getResponseOrigin,
|
||||||
|
trimHeadersEnd,
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
createHeaders,
|
createHeaders,
|
||||||
createResponseError,
|
createResponseError,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
|
trimHeadersEnd,
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
} from "../../src/display/network_utils.js";
|
} from "../../src/display/network_utils.js";
|
||||||
import { ResponseException } from "../../src/shared/util.js";
|
import { ResponseException } from "../../src/shared/util.js";
|
||||||
@ -386,4 +387,38 @@ describe("network_utils", function () {
|
|||||||
testCreateResponseError(new URL("https://foo.com/bar.pdf"), 0, false);
|
testCreateResponseError(new URL("https://foo.com/bar.pdf"), 0, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("trimHeadersEnd", function () {
|
||||||
|
it("removes the trailing whitespace", function () {
|
||||||
|
expect(trimHeadersEnd("a: 1\r\nb: 2\r\n")).toEqual("a: 1\r\nb: 2");
|
||||||
|
expect(trimHeadersEnd("a: 1\n\n")).toEqual("a: 1");
|
||||||
|
expect(trimHeadersEnd("a: 1\t\r\n")).toEqual("a: 1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps the regular spaces", function () {
|
||||||
|
expect(trimHeadersEnd("a: 1 ")).toEqual("a: 1 ");
|
||||||
|
expect(trimHeadersEnd("a: 1\r\n ")).toEqual("a: 1\r\n ");
|
||||||
|
expect(trimHeadersEnd(" ")).toEqual(" ");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles strings without trailing whitespace", function () {
|
||||||
|
expect(trimHeadersEnd("")).toEqual("");
|
||||||
|
expect(trimHeadersEnd("a: 1")).toEqual("a: 1");
|
||||||
|
expect(trimHeadersEnd("\r\na: 1")).toEqual("\r\na: 1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles a long run of whitespace efficiently", function () {
|
||||||
|
// Removing the run with a `$`-anchored regex is quadratic in its length,
|
||||||
|
// and a server controls how long the headers are.
|
||||||
|
const run = "\t".repeat(100000);
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
// The run is trailing, hence removed.
|
||||||
|
expect(trimHeadersEnd(`a: 1${run}`)).toEqual("a: 1");
|
||||||
|
// The run is followed by a non-whitespace, hence kept: this is the case
|
||||||
|
// which a regex has to backtrack over.
|
||||||
|
expect(trimHeadersEnd(`a: 1${run}b`)).toEqual(`a: 1${run}b`);
|
||||||
|
expect(performance.now() - startTime).toBeLessThan(1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user