/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // Match WebNavigation URL Filters. export class MatchURLFilters { constructor(filters) { if (!Array.isArray(filters)) { throw new TypeError("filters should be an array"); } if (!filters.length) { throw new Error("filters array should not be empty"); } this.filters = filters; } matches(url) { let uri = Services.io.newURI(url); // Set uriURL to an empty object (needed because some schemes, e.g. about doesn't support nsIURL). let uriURL = {}; if (uri instanceof Ci.nsIURL) { uriURL = uri; } // Set host to a empty string by default (needed so that schemes without an host, // e.g. about, can pass an empty string for host based event filtering as expected). let host = ""; try { host = uri.host; } catch (e) { // 'uri.host' throws an exception with some uri schemes (e.g. about). } let port; try { port = uri.port; } catch (e) { // 'uri.port' throws an exception with some uri schemes (e.g. about), // in which case it will be |undefined|. } let data = { // NOTE: This properties are named after the name of their related // filters (e.g. `pathContains/pathEquals/...` will be tested against the // `data.path` property, and the same is done for the `host`, `query` and `url` // components as well). path: uriURL.filePath, query: uriURL.query, host, port, url, }; // If any of the filters matches, matches returns true. return this.filters.some(filter => this.matchURLFilter({ filter, data, uri, uriURL }) ); } matchURLFilter({ filter, data, uri, uriURL }) { // Test for scheme based filtering. if (filter.schemes) { // Return false if none of the schemes matches. if (!filter.schemes.some(scheme => uri.schemeIs(scheme))) { return false; } } // Test for exact port matching or included in a range of ports. if (filter.ports) { let port = data.port; if (port === -1) { // NOTE: currently defaultPort for "resource" and "chrome" schemes defaults to -1, // for "about", "data" and "javascript" schemes defaults to undefined. if (["resource", "chrome"].includes(uri.scheme)) { port = undefined; } else { port = Services.io.getDefaultPort(uri.scheme); } } // Return false if none of the ports (or port ranges) is verified const portMatch = filter.ports.some(filterPort => { if (Array.isArray(filterPort)) { let [lower, upper] = filterPort; return port >= lower && port <= upper; } return port === filterPort; }); if (!portMatch) { return false; } } // Filters on host, url, path, query: // hostContains, hostEquals, hostSuffix, hostPrefix, // urlContains, urlEquals, ... for (let urlComponent of ["host", "path", "query", "url"]) { if (!this.testMatchOnURLComponent({ urlComponent, data, filter })) { return false; } } // urlMatches is a regular expression string and it is tested for matches // on the "url without the ref". if (filter.urlMatches) { let urlWithoutRef = uri.specIgnoringRef; if (!urlWithoutRef.match(filter.urlMatches)) { return false; } } // originAndPathMatches is a regular expression string and it is tested for matches // on the "url without the query and the ref". if (filter.originAndPathMatches) { let urlWithoutQueryAndRef = uri.resolve(uriURL.filePath); // The above 'uri.resolve(...)' will be null for some URI schemes // (e.g. about). // TODO: handle schemes which will not be able to resolve the filePath // (e.g. for "about:blank", 'urlWithoutQueryAndRef' should be "about:blank" instead // of null) if ( !urlWithoutQueryAndRef || !urlWithoutQueryAndRef.match(filter.originAndPathMatches) ) { return false; } } return true; } testMatchOnURLComponent({ urlComponent: key, data, filter }) { // Test for equals. // NOTE: an empty string should not be considered a filter to skip. if (filter[`${key}Equals`] != null) { if (data[key] !== filter[`${key}Equals`]) { return false; } } // Test for contains. if (filter[`${key}Contains`]) { let value = (key == "host" ? "." : "") + data[key]; if (!data[key] || !value.includes(filter[`${key}Contains`])) { return false; } } // Test for prefix. if (filter[`${key}Prefix`]) { if (!data[key] || !data[key].startsWith(filter[`${key}Prefix`])) { return false; } } // Test for suffix. if (filter[`${key}Suffix`]) { if (!data[key] || !data[key].endsWith(filter[`${key}Suffix`])) { return false; } } return true; } serialize() { return this.filters; } }