michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: this.EXPORTED_SYMBOLS = [ "UserAgentOverrides" ]; michael@0: michael@0: const Ci = Components.interfaces; michael@0: const Cc = Components.classes; michael@0: michael@0: Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Components.utils.import("resource://gre/modules/Services.jsm"); michael@0: Components.utils.import("resource://gre/modules/UserAgentUpdates.jsm"); michael@0: michael@0: const OVERRIDE_MESSAGE = "Useragent:GetOverride"; michael@0: const PREF_OVERRIDES_ENABLED = "general.useragent.site_specific_overrides"; michael@0: const DEFAULT_UA = Cc["@mozilla.org/network/protocol;1?name=http"] michael@0: .getService(Ci.nsIHttpProtocolHandler) michael@0: .userAgent; michael@0: const MAX_OVERRIDE_FOR_HOST_CACHE_SIZE = 250; michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "ppmm", michael@0: "@mozilla.org/parentprocessmessagemanager;1", michael@0: "nsIMessageListenerManager"); // Might have to make this broadcast? michael@0: michael@0: var gPrefBranch; michael@0: var gOverrides = new Map; michael@0: var gUpdatedOverrides; michael@0: var gOverrideForHostCache = new Map; michael@0: var gInitialized = false; michael@0: var gOverrideFunctions = [ michael@0: function (aHttpChannel) UserAgentOverrides.getOverrideForURI(aHttpChannel.URI) michael@0: ]; michael@0: var gBuiltUAs = new Map; michael@0: michael@0: this.UserAgentOverrides = { michael@0: init: function uao_init() { michael@0: if (gInitialized) michael@0: return; michael@0: michael@0: gPrefBranch = Services.prefs.getBranch("general.useragent.override."); michael@0: gPrefBranch.addObserver("", buildOverrides, false); michael@0: michael@0: ppmm.addMessageListener(OVERRIDE_MESSAGE, this); michael@0: Services.prefs.addObserver(PREF_OVERRIDES_ENABLED, buildOverrides, false); michael@0: michael@0: try { michael@0: Services.obs.addObserver(HTTP_on_modify_request, "http-on-modify-request", false); michael@0: } catch (x) { michael@0: // The http-on-modify-request notification is disallowed in content processes. michael@0: } michael@0: michael@0: UserAgentUpdates.init(function(overrides) { michael@0: gOverrideForHostCache.clear(); michael@0: if (overrides) { michael@0: for (let domain in overrides) { michael@0: overrides[domain] = getUserAgentFromOverride(overrides[domain]); michael@0: } michael@0: overrides.get = function(key) this[key]; michael@0: } michael@0: gUpdatedOverrides = overrides; michael@0: }); michael@0: michael@0: buildOverrides(); michael@0: gInitialized = true; michael@0: }, michael@0: michael@0: addComplexOverride: function uao_addComplexOverride(callback) { michael@0: // Add to front of array so complex overrides have precedence michael@0: gOverrideFunctions.unshift(callback); michael@0: }, michael@0: michael@0: getOverrideForURI: function uao_getOverrideForURI(aURI) { michael@0: let host = aURI.asciiHost; michael@0: if (!gInitialized || michael@0: (!gOverrides.size && !gUpdatedOverrides) || michael@0: !(host)) { michael@0: return null; michael@0: } michael@0: michael@0: let override = gOverrideForHostCache.get(host); michael@0: if (override !== undefined) michael@0: return override; michael@0: michael@0: function findOverride(overrides) { michael@0: let searchHost = host; michael@0: let userAgent = overrides.get(searchHost); michael@0: michael@0: while (!userAgent) { michael@0: let dot = searchHost.indexOf('.'); michael@0: if (dot === -1) { michael@0: return null; michael@0: } michael@0: searchHost = searchHost.slice(dot + 1); michael@0: userAgent = overrides.get(searchHost); michael@0: } michael@0: return userAgent; michael@0: } michael@0: michael@0: override = (gOverrides.size && findOverride(gOverrides)) michael@0: || (gUpdatedOverrides && findOverride(gUpdatedOverrides)); michael@0: michael@0: if (gOverrideForHostCache.size >= MAX_OVERRIDE_FOR_HOST_CACHE_SIZE) { michael@0: gOverrideForHostCache.clear(); michael@0: } michael@0: gOverrideForHostCache.set(host, override); michael@0: michael@0: return override; michael@0: }, michael@0: michael@0: uninit: function uao_uninit() { michael@0: if (!gInitialized) michael@0: return; michael@0: gInitialized = false; michael@0: michael@0: gPrefBranch.removeObserver("", buildOverrides); michael@0: michael@0: Services.prefs.removeObserver(PREF_OVERRIDES_ENABLED, buildOverrides); michael@0: michael@0: Services.obs.removeObserver(HTTP_on_modify_request, "http-on-modify-request"); michael@0: }, michael@0: michael@0: receiveMessage: function(aMessage) { michael@0: let name = aMessage.name; michael@0: switch (name) { michael@0: case OVERRIDE_MESSAGE: michael@0: let uri = aMessage.data.uri; michael@0: return this.getOverrideForURI(uri); michael@0: default: michael@0: throw("Wrong Message in UserAgentOverride: " + name); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: function getUserAgentFromOverride(override) michael@0: { michael@0: let userAgent = gBuiltUAs.get(override); michael@0: if (userAgent !== undefined) { michael@0: return userAgent; michael@0: } michael@0: let [search, replace] = override.split("#", 2); michael@0: if (search && replace) { michael@0: userAgent = DEFAULT_UA.replace(new RegExp(search, "g"), replace); michael@0: } else { michael@0: userAgent = override; michael@0: } michael@0: gBuiltUAs.set(override, userAgent); michael@0: return userAgent; michael@0: } michael@0: michael@0: function buildOverrides() { michael@0: gOverrides.clear(); michael@0: gOverrideForHostCache.clear(); michael@0: michael@0: if (!Services.prefs.getBoolPref(PREF_OVERRIDES_ENABLED)) michael@0: return; michael@0: michael@0: let builtUAs = new Map; michael@0: let domains = gPrefBranch.getChildList(""); michael@0: michael@0: for (let domain of domains) { michael@0: let override = gPrefBranch.getCharPref(domain); michael@0: let userAgent = getUserAgentFromOverride(override); michael@0: michael@0: if (userAgent != DEFAULT_UA) { michael@0: gOverrides.set(domain, userAgent); michael@0: } michael@0: } michael@0: } michael@0: michael@0: function HTTP_on_modify_request(aSubject, aTopic, aData) { michael@0: let channel = aSubject.QueryInterface(Ci.nsIHttpChannel); michael@0: michael@0: for (let callback of gOverrideFunctions) { michael@0: let modifiedUA = callback(channel, DEFAULT_UA); michael@0: if (modifiedUA) { michael@0: channel.setRequestHeader("User-Agent", modifiedUA, false); michael@0: return; michael@0: } michael@0: } michael@0: }