Skip to content

Commit

Permalink
fix: forbid opening file:// (#128)
Browse files Browse the repository at this point in the history
Firefox restrict add-ons to open certain protocols for security reasons.
This change replace the error message with a more clear one.
  • Loading branch information
ueokande authored Apr 24, 2023
1 parent 01605c7 commit 00bbc2b
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 59 deletions.
48 changes: 27 additions & 21 deletions src/shared/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,48 @@ const trimStart = (str: string): string => {
};

const SUPPORTED_PROTOCOLS = ["http:", "https:", "ftp:", "mailto:", "about:"];
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/create
const UNSUPPORTED_PROTOCOLS = ["chrome:", "javascript:", "data:", "file:"];

const isLocalhost = (url: string): boolean => {
if (url === "localhost") {
return true;
}

const [host, port] = url.split(":", 2);
return host === "localhost" && !isNaN(Number(port));
const isHostname = (src: string): boolean => {
return src === "localhost" || (src.includes(".") && !src.includes(" "));
};

const isMissingHttp = (keywords: string): boolean => {
if (keywords.includes(".") && !keywords.includes(" ")) {
return true;
const isHost = (src: string): boolean => {
if (!src.includes(":")) {
return isHostname(src);
}
const [hostname, port] = src.split(":", 2);
return isHostname(hostname) && !isNaN(Number(port));
};

const parseURL = (src: string): URL | undefined => {
try {
const u = new URL("http://" + keywords);
return isLocalhost(u.host);
return new URL(src);
} catch (e) {
// fallthrough
}
return false;
return undefined;
};

const searchUrl = (keywords: string, search: SearchEngine): string => {
try {
const u = new URL(keywords);
if (SUPPORTED_PROTOCOLS.includes(u.protocol.toLowerCase())) {
return u.href;
const url = parseURL(keywords);
if (typeof url !== "undefined") {
// URL parser recognize example.com:12345 as a valid URL which has a
// protocol 'example.com'.

if (SUPPORTED_PROTOCOLS.includes(url.protocol)) {
return url.href;
} else if (UNSUPPORTED_PROTOCOLS.includes(url.protocol)) {
throw new Error(
`Opening protocol '${url.protocol}' is forbidden for security reasons`
);
}
} catch (e) {
// fallthrough
}

if (isMissingHttp(keywords)) {
return "http://" + keywords;
const urlWithHttp = parseURL("http://" + keywords);
if (typeof urlWithHttp !== "undefined" && isHost(urlWithHttp.host)) {
return urlWithHttp.href;
}

let template = search.engines[search.defaultEngine];
Expand Down
64 changes: 26 additions & 38 deletions test/shared/urls.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,34 @@ describe("shared/commands/parsers", () => {
yahoo: "https://yahoo.com/search?q={}",
});

it("convertes search url", () => {
expect(parsers.searchUrl("google.com", config)).toEqual(
"http://google.com"
);
expect(parsers.searchUrl("google apple", config)).toEqual(
"https://google.com/search?q=apple"
);
expect(parsers.searchUrl("yahoo apple", config)).toEqual(
"https://yahoo.com/search?q=apple"
);
expect(parsers.searchUrl("google apple banana", config)).toEqual(
"https://google.com/search?q=apple%20banana"
);
expect(parsers.searchUrl("yahoo C++CLI", config)).toEqual(
"https://yahoo.com/search?q=C%2B%2BCLI"
);
it.each([
["http://google.com", "http://google.com/"],
["google.com", "http://google.com/"],
["google apple", "https://google.com/search?q=apple"],
["yahoo apple", "https://yahoo.com/search?q=apple"],
["google apple banana", "https://google.com/search?q=apple%20banana"],
["yahoo C++CLI", "https://yahoo.com/search?q=C%2B%2BCLI"],
["localhost", "http://localhost/"],
["http://localhost", "http://localhost/"],
["localhost:8080", "http://localhost:8080/"],
["localhost:80nan", "https://google.com/search?q=localhost%3A80nan"],
["localhost 8080", "https://google.com/search?q=localhost%208080"],
["localhost:80/build", "http://localhost/build"],
["http://127.0.0.1", "http://127.0.0.1/"],
["http://127.0.0.1:8080", "http://127.0.0.1:8080/"],
["http://[::1]", "http://[::1]/"],
["http://[::1]:8080", "http://[::1]:8080/"],
])("converts URL '%s'", (src, expected) => {
expect(parsers.searchUrl(src, config)).toEqual(expected);
});

it("throws an error on unsupported protocols", () => {
expect(() =>
parsers.searchUrl("file:///tmp/table.csv", config)
).toThrowError("forbidden");
});

it("user default search engine", () => {
it("user default search engine", () => {
expect(parsers.searchUrl("apple banana", config)).toEqual(
"https://google.com/search?q=apple%20banana"
);
Expand All @@ -40,27 +49,6 @@ describe("shared/commands/parsers", () => {
"https://google.com/search?q=std%3A%3Avector"
);
});

it("localhost urls", () => {
expect(parsers.searchUrl("localhost", config)).toEqual(
"http://localhost"
);
expect(parsers.searchUrl("http://localhost", config)).toEqual(
"http://localhost/"
);
expect(parsers.searchUrl("localhost:8080", config)).toEqual(
"http://localhost:8080"
);
expect(parsers.searchUrl("localhost:80nan", config)).toEqual(
"https://google.com/search?q=localhost%3A80nan"
);
expect(parsers.searchUrl("localhost 8080", config)).toEqual(
"https://google.com/search?q=localhost%208080"
);
expect(parsers.searchUrl("localhost:80/build", config)).toEqual(
"http://localhost:80/build"
);
});
});

describe("#normalizeUrl", () => {
Expand Down

0 comments on commit 00bbc2b

Please sign in to comment.