diff --git a/src/display/display_utils.js b/src/display/display_utils.js index f83c090a5..7d9155319 100644 --- a/src/display/display_utils.js +++ b/src/display/display_utils.js @@ -188,10 +188,22 @@ function getPdfFilenameFromUrl(url, defaultFilename = "document.pdf") { } if (newURL.hash) { - const reFilename = /[^/?#=]+\.pdf\b(?!.*\.pdf\b)/i; - const hashFilename = reFilename.exec(newURL.hash); - if (hashFilename) { - return decode(hashFilename[0]); + // Locate the last ".pdf" and then extend it to the left, up to the closest + // separator. Both steps are linear, whereas a single pattern starting with + // `[^/?#=]+` is quadratic on a hash which contains no ".pdf" at all. + const { hash } = newURL; + let extensionStart = -1; + for (const { index } of hash.matchAll(/\.pdf\b/gi)) { + extensionStart = index; + } + if (extensionStart > 0) { + let filenameStart = extensionStart; + while (filenameStart > 0 && !"/?#=".includes(hash[filenameStart - 1])) { + filenameStart--; + } + if (filenameStart < extensionStart) { + return decode(hash.slice(filenameStart, extensionStart + 4)); + } } } diff --git a/test/unit/display_utils_spec.js b/test/unit/display_utils_spec.js index 480930121..de8af9aee 100644 --- a/test/unit/display_utils_spec.js +++ b/test/unit/display_utils_spec.js @@ -122,6 +122,30 @@ describe("display_utils", function () { expect( getPdfFilenameFromUrl("http://www.example.com/pdfs/pdf.html#file2.pdf") ).toEqual("file2.pdf"); + // Only the last ".pdf" of the hash is used. + expect(getPdfFilenameFromUrl("/pdfs/pdfs.html#a.pdf/b.pdf")).toEqual( + "b.pdf" + ); + // A ".pdf" which isn't preceded by a name is ignored. + expect(getPdfFilenameFromUrl("/pdfs/pdfs.html#=.pdf")).toEqual( + "document.pdf" + ); + // An invalid last ".pdf" prevents an earlier valid one from being used. + expect(getPdfFilenameFromUrl("/pdfs/pdfs.html#a.pdf/=.pdf")).toEqual( + "document.pdf" + ); + }); + + it("gets PDF filename from a long hash string efficiently", function () { + // Scanning the hash for a name is quadratic when it contains no ".pdf". + const url = `/pdfs/pdfs.html#${"a".repeat(200000)}`; + + const startTime = performance.now(); + const filename = getPdfFilenameFromUrl(url); + const duration = performance.now() - startTime; + + expect(filename).toEqual("document.pdf"); + expect(duration).toBeLessThan(1000); }); it("gets correct PDF filename when multiple ones are present", function () {