Remove user and password from URLs (bug 2025109)

This commit is contained in:
Calixte Denizet 2026-04-20 19:06:49 +02:00
parent 3ccb263857
commit 8ddbeda4a0
No known key found for this signature in database
GPG Key ID: 0C5442631EE0691F
4 changed files with 129 additions and 2 deletions

View File

@ -42,6 +42,7 @@
"pdf_find_controller_spec.js",
"pdf_find_utils_spec.js",
"pdf_history_spec.js",
"pdf_link_service_spec.js",
"pdf_spec.js",
"pdf_viewer.component_spec.js",
"pdf_viewer_spec.js",

View File

@ -85,6 +85,7 @@ async function initializePDFJS(callback) {
"pdfjs-test/unit/pdf_find_controller_spec.js",
"pdfjs-test/unit/pdf_find_utils_spec.js",
"pdfjs-test/unit/pdf_history_spec.js",
"pdfjs-test/unit/pdf_link_service_spec.js",
"pdfjs-test/unit/pdf_spec.js",
"pdfjs-test/unit/pdf_viewer.component_spec.js",
"pdfjs-test/unit/pdf_viewer_spec.js",

View File

@ -0,0 +1,115 @@
/* Copyright 2026 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PDFLinkService } from "../../web/pdf_link_service.js";
describe("PDFLinkService", function () {
describe("addLinkAttributes", function () {
function createLinkService({ externalLinkEnabled = true } = {}) {
const linkService = new PDFLinkService();
linkService.externalLinkEnabled = externalLinkEnabled;
return linkService;
}
// Use a plain object instead of a real DOM element so tests run in Node.js.
function createLink() {
return { href: "", title: "", target: "", rel: "" };
}
it("sets href and title for a plain URL", function () {
const linkService = createLinkService();
const link = createLink();
linkService.addLinkAttributes(link, "https://example.com/path");
expect(link.href).toEqual("https://example.com/path");
expect(link.title).toEqual("https://example.com/path");
});
it("strips userinfo from the title to prevent hostname spoofing", function () {
const linkService = createLinkService();
const link = createLink();
// URL with username that looks like a trusted domain.
linkService.addLinkAttributes(
link,
"https://trusted.example@attacker.example/path"
);
expect(link.href).toEqual(
"https://trusted.example@attacker.example/path"
);
expect(link.title).toEqual("https://attacker.example/path");
});
it("strips username and password from the title", function () {
const linkService = createLinkService();
const link = createLink();
linkService.addLinkAttributes(
link,
"https://user:password@example.com/path"
);
expect(link.href).toEqual("https://user:password@example.com/path");
expect(link.title).toEqual("https://example.com/path");
});
it("strips only username (no password) from the title", function () {
const linkService = createLinkService();
const link = createLink();
linkService.addLinkAttributes(link, "https://user@example.com/page");
expect(link.href).toEqual("https://user@example.com/page");
expect(link.title).toEqual("https://example.com/page");
});
it("does not alter the title when there is no userinfo", function () {
const linkService = createLinkService();
const link = createLink();
linkService.addLinkAttributes(
link,
"https://example.com/path?q=1#anchor"
);
expect(link.title).toEqual("https://example.com/path?q=1#anchor");
});
it("disables the link and prefixes the title when externalLinkEnabled is false", function () {
const linkService = createLinkService({ externalLinkEnabled: false });
const link = createLink();
linkService.addLinkAttributes(link, "https://example.com/path");
expect(link.href).toEqual("");
expect(link.title).toEqual("Disabled: https://example.com/path");
});
it("strips userinfo from the title even when the link is disabled", function () {
const linkService = createLinkService({ externalLinkEnabled: false });
const link = createLink();
linkService.addLinkAttributes(
link,
"https://trusted.example@attacker.example/path"
);
expect(link.href).toEqual("");
expect(link.title).toEqual("Disabled: https://attacker.example/path");
});
});
});

View File

@ -266,11 +266,21 @@ class PDFLinkService {
const target = newWindow ? LinkTarget.BLANK : this.externalLinkTarget,
rel = this.externalLinkRel;
// Strip userinfo (user:password@) from URLs used for display, to prevent
// phishing via hostname-spoofing (e.g. https://trusted.example@attacker.example/).
let displayUrl = url;
const parsedUrl = URL.parse(url);
if (parsedUrl?.username || parsedUrl?.password) {
parsedUrl.username = parsedUrl.password = "";
displayUrl = parsedUrl.href;
}
if (this.externalLinkEnabled) {
link.href = link.title = url;
link.href = url;
link.title = displayUrl;
} else {
link.href = "";
link.title = `Disabled: ${url}`;
link.title = `Disabled: ${displayUrl}`;
link.onclick = () => false;
}