|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 this.EXPORTED_SYMBOLS = [ "UserAgentOverrides" ]; |
|
8 |
|
9 const Ci = Components.interfaces; |
|
10 const Cc = Components.classes; |
|
11 |
|
12 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
13 Components.utils.import("resource://gre/modules/Services.jsm"); |
|
14 Components.utils.import("resource://gre/modules/UserAgentUpdates.jsm"); |
|
15 |
|
16 const OVERRIDE_MESSAGE = "Useragent:GetOverride"; |
|
17 const PREF_OVERRIDES_ENABLED = "general.useragent.site_specific_overrides"; |
|
18 const DEFAULT_UA = Cc["@mozilla.org/network/protocol;1?name=http"] |
|
19 .getService(Ci.nsIHttpProtocolHandler) |
|
20 .userAgent; |
|
21 const MAX_OVERRIDE_FOR_HOST_CACHE_SIZE = 250; |
|
22 |
|
23 XPCOMUtils.defineLazyServiceGetter(this, "ppmm", |
|
24 "@mozilla.org/parentprocessmessagemanager;1", |
|
25 "nsIMessageListenerManager"); // Might have to make this broadcast? |
|
26 |
|
27 var gPrefBranch; |
|
28 var gOverrides = new Map; |
|
29 var gUpdatedOverrides; |
|
30 var gOverrideForHostCache = new Map; |
|
31 var gInitialized = false; |
|
32 var gOverrideFunctions = [ |
|
33 function (aHttpChannel) UserAgentOverrides.getOverrideForURI(aHttpChannel.URI) |
|
34 ]; |
|
35 var gBuiltUAs = new Map; |
|
36 |
|
37 this.UserAgentOverrides = { |
|
38 init: function uao_init() { |
|
39 if (gInitialized) |
|
40 return; |
|
41 |
|
42 gPrefBranch = Services.prefs.getBranch("general.useragent.override."); |
|
43 gPrefBranch.addObserver("", buildOverrides, false); |
|
44 |
|
45 ppmm.addMessageListener(OVERRIDE_MESSAGE, this); |
|
46 Services.prefs.addObserver(PREF_OVERRIDES_ENABLED, buildOverrides, false); |
|
47 |
|
48 try { |
|
49 Services.obs.addObserver(HTTP_on_modify_request, "http-on-modify-request", false); |
|
50 } catch (x) { |
|
51 // The http-on-modify-request notification is disallowed in content processes. |
|
52 } |
|
53 |
|
54 UserAgentUpdates.init(function(overrides) { |
|
55 gOverrideForHostCache.clear(); |
|
56 if (overrides) { |
|
57 for (let domain in overrides) { |
|
58 overrides[domain] = getUserAgentFromOverride(overrides[domain]); |
|
59 } |
|
60 overrides.get = function(key) this[key]; |
|
61 } |
|
62 gUpdatedOverrides = overrides; |
|
63 }); |
|
64 |
|
65 buildOverrides(); |
|
66 gInitialized = true; |
|
67 }, |
|
68 |
|
69 addComplexOverride: function uao_addComplexOverride(callback) { |
|
70 // Add to front of array so complex overrides have precedence |
|
71 gOverrideFunctions.unshift(callback); |
|
72 }, |
|
73 |
|
74 getOverrideForURI: function uao_getOverrideForURI(aURI) { |
|
75 let host = aURI.asciiHost; |
|
76 if (!gInitialized || |
|
77 (!gOverrides.size && !gUpdatedOverrides) || |
|
78 !(host)) { |
|
79 return null; |
|
80 } |
|
81 |
|
82 let override = gOverrideForHostCache.get(host); |
|
83 if (override !== undefined) |
|
84 return override; |
|
85 |
|
86 function findOverride(overrides) { |
|
87 let searchHost = host; |
|
88 let userAgent = overrides.get(searchHost); |
|
89 |
|
90 while (!userAgent) { |
|
91 let dot = searchHost.indexOf('.'); |
|
92 if (dot === -1) { |
|
93 return null; |
|
94 } |
|
95 searchHost = searchHost.slice(dot + 1); |
|
96 userAgent = overrides.get(searchHost); |
|
97 } |
|
98 return userAgent; |
|
99 } |
|
100 |
|
101 override = (gOverrides.size && findOverride(gOverrides)) |
|
102 || (gUpdatedOverrides && findOverride(gUpdatedOverrides)); |
|
103 |
|
104 if (gOverrideForHostCache.size >= MAX_OVERRIDE_FOR_HOST_CACHE_SIZE) { |
|
105 gOverrideForHostCache.clear(); |
|
106 } |
|
107 gOverrideForHostCache.set(host, override); |
|
108 |
|
109 return override; |
|
110 }, |
|
111 |
|
112 uninit: function uao_uninit() { |
|
113 if (!gInitialized) |
|
114 return; |
|
115 gInitialized = false; |
|
116 |
|
117 gPrefBranch.removeObserver("", buildOverrides); |
|
118 |
|
119 Services.prefs.removeObserver(PREF_OVERRIDES_ENABLED, buildOverrides); |
|
120 |
|
121 Services.obs.removeObserver(HTTP_on_modify_request, "http-on-modify-request"); |
|
122 }, |
|
123 |
|
124 receiveMessage: function(aMessage) { |
|
125 let name = aMessage.name; |
|
126 switch (name) { |
|
127 case OVERRIDE_MESSAGE: |
|
128 let uri = aMessage.data.uri; |
|
129 return this.getOverrideForURI(uri); |
|
130 default: |
|
131 throw("Wrong Message in UserAgentOverride: " + name); |
|
132 } |
|
133 } |
|
134 }; |
|
135 |
|
136 function getUserAgentFromOverride(override) |
|
137 { |
|
138 let userAgent = gBuiltUAs.get(override); |
|
139 if (userAgent !== undefined) { |
|
140 return userAgent; |
|
141 } |
|
142 let [search, replace] = override.split("#", 2); |
|
143 if (search && replace) { |
|
144 userAgent = DEFAULT_UA.replace(new RegExp(search, "g"), replace); |
|
145 } else { |
|
146 userAgent = override; |
|
147 } |
|
148 gBuiltUAs.set(override, userAgent); |
|
149 return userAgent; |
|
150 } |
|
151 |
|
152 function buildOverrides() { |
|
153 gOverrides.clear(); |
|
154 gOverrideForHostCache.clear(); |
|
155 |
|
156 if (!Services.prefs.getBoolPref(PREF_OVERRIDES_ENABLED)) |
|
157 return; |
|
158 |
|
159 let builtUAs = new Map; |
|
160 let domains = gPrefBranch.getChildList(""); |
|
161 |
|
162 for (let domain of domains) { |
|
163 let override = gPrefBranch.getCharPref(domain); |
|
164 let userAgent = getUserAgentFromOverride(override); |
|
165 |
|
166 if (userAgent != DEFAULT_UA) { |
|
167 gOverrides.set(domain, userAgent); |
|
168 } |
|
169 } |
|
170 } |
|
171 |
|
172 function HTTP_on_modify_request(aSubject, aTopic, aData) { |
|
173 let channel = aSubject.QueryInterface(Ci.nsIHttpChannel); |
|
174 |
|
175 for (let callback of gOverrideFunctions) { |
|
176 let modifiedUA = callback(channel, DEFAULT_UA); |
|
177 if (modifiedUA) { |
|
178 channel.setRequestHeader("User-Agent", modifiedUA, false); |
|
179 return; |
|
180 } |
|
181 } |
|
182 } |