Add generic label mechanism
[conkeror.git] / modules / permission-manager.js
blob7abf2bf27fe0a84484d31cc79122a8057d19c02d
1 /**
2 * (C) Copyright 2008 Jeremy Maitin-Shepard
4 * Use, modification, and distribution are subject to the terms specified in the
5 * COPYING file.
6 **/
8 /**
9 * Interface to nsIPermissionManager, which controls the popup
10 * blocking whitelist, among other things.
14 let permission_manager = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
16 let get_spaces = function get_spaces(n) {
17 var x = "";
18 while (x.length < n) x += " ";
19 return x;
22 let word_wrap = function word_wrap(str, line_length, line_prefix_first, line_prefix) {
23 if (line_prefix === undefined)
24 line_prefix = line_prefix_first;
25 else if (line_prefix.length < line_prefix_first.length) {
26 line_prefix += get_spaces(line_prefix_first.length - line_prefix.length);
29 line_length -= line_prefix_first.length;
31 if (line_length < 1)
32 line_length = 1;
34 let cur_prefix = line_prefix_first;
36 var out = "";
37 while (line_length < str.length) {
38 let i = str.lastIndexOf(" ", line_length);
39 if (i == -1)
40 i = str.indexOf(" ", line_length);
41 if (i == -1) {
42 out += cur_prefix + str + "\n";
43 str = "";
45 else {
46 out += cur_prefix + str.substr(0, i) + "\n";
47 while (i < str.length && str.charAt(i) == " ")
48 ++i;
49 str = str.substr(i + 1);
51 cur_prefix = line_prefix;
53 if (str.length > 0)
54 out += cur_prefix + str + "\n";
55 return out;
58 let permission_types = {
59 popup : {desc: "specifies a whitelist and blacklist for the popup blocker",
60 values: [ ["allow", Ci.nsIPermissionManager.ALLOW_ACTION],
61 ["deny", Ci.nsIPermissionManager.DENY_ACTION], ],
62 related_prefs: [
63 [ "dom.disable_open_during_load",
64 "This preference defines the default behavior of whether " +
65 "unrequested popups are allowed for sites not listed in the permission list." ],
66 [ "dom.popup_maximum", "The number of pop-ups to allow from a single non-click event."] ]
69 cookie : {desc: "specifies per-host cookie policies",
70 values: [
71 ["allow", Ci.nsIPermissionManager.ALLOW_ACTION],
72 ["session", Ci.nsICookiePermission.ACCESS_SESSION, "expire matching cookies when the browser exits"],
73 ["deny", Ci.nsIPermissionManager.DENY_ACTION], ],
74 related_prefs: [
75 [ "network.cookie.lifetime.behavior.enabled" ],
76 [ "network.cookie.cookieBehavior",
77 "This preference defines the default cookie behavior for sites not listed in the " +
78 "permission list. The value 0 means to enable all cookies, 1 means to reject " +
79 "only third-party cookies, and 2 means to reject all cookies." ],
80 [ "network.cookie.lifetime.behavior",
81 "If network.cookie.lifetime.behavior.enabled is set to true, a value of 0 means all " +
82 "cookies expire at the end of the current session, while a value of 1 means that " +
83 "cookies expire after the number of days specified by network.cookie.lifetime.days."
85 [ "network.cookie.lifetime.days" ] ]
89 image : {desc: "specifies per-host image automatic loading policies",
90 values: [
91 ["allow", Ci.nsIPermissionManager.ALLOW_ACTION],
92 ["deny", Ci.nsIPermissionManager.DENY_ACTION], ],
93 related_prefs: [
94 [ "permissions.default.image", "This prefreence defines the default image loading policy "
95 + "for sites not listed in the permission list. The value "
96 + "1 means all images should be loaded, 2 means no images "
97 + "should be loaded, and 3 means only third-party images "
98 + "are blocked." ] ]
101 install : {desc: "specifies a whitelist of sites from which XPI files may be opened",
102 values: [
103 ["allow", Ci.nsIPermissionManager.ALLOW_ACTION] ]
108 * cookie
112 * popup
113 * dom.popup_maximum - The number of pop-ups to allow from a single non-click event
115 * dom.popup_allowed_events
117 * dom.disable_open_during_load - This preference defines the default behavior of whether unrequested popups are allowed for sites not listed in the permission list.
122 interactive("permission-manager", "View or edit the host-specific "
123 + "permission list.\nThis list is used for the popup"
124 + "blocker whitelist, among other things.",
125 function (I) {
126 I.minibuffer.message("Save the file and close the editor when done editing permissions.");
128 let existing_perms = new string_hashmap();
130 var file_buf =
131 "# -*- conf -*-\n" +
132 "# Permission list\n\n";
136 let e = permission_manager.enumerator;
137 let arr = [];
138 let max_host_len = 0;
139 let max_type_len = 0;
140 while (e.hasMoreElements()) {
141 let p = e.getNext().QueryInterface(Ci.nsIPermission);
142 let host = p.host;
143 let type = p.type;
144 let cap = p.capability;
145 if (max_host_len < host.length)
146 max_host_len = host.length;
147 if (max_type_len < type.length)
148 max_type_len = type.length;
149 arr.push([host, type, cap]);
150 existing_perms.put([host, type], cap);
152 ++max_host_len;
153 ++max_type_len;
154 let max_host = get_spaces(max_host_len);
155 let max_type = get_spaces(max_type_len);
156 for (let i = 0; i < arr.length; ++i) {
157 let [host, type, cap] = arr[i];
158 if (permission_types.hasOwnProperty(type)) {
159 let values = permission_types[type].values;
160 for (let j = 0; j < values.length; ++j) {
161 if (cap == values[j][1]) {
162 cap = values[j][0];
163 break;
167 file_buf += host + max_host.substr(host.length) + type + max_type.substr(type.length) + cap + "\n";
170 if (arr.length == 0)
171 file_buf += "\n";
174 file_buf += "\n" +
175 "# entry syntax (one per line): <domain> <type> <permission>\n\n" +
176 "# example: google.com popup allow\n\n" +
178 word_wrap("The <domain> must be a valid domain name. Depending on the <type>, only exact " +
179 "matches may be used, or alternatively it may match any sub-domain if a more " +
180 "specific entry is not found.", 80, "# ") + "\n" +
181 "# The possible values for the permission <type> include:\n";
182 for (let type in permission_types) {
183 let data = permission_types[type];
184 file_buf += "# " + type + " - " + data.desc + "\n\n";
185 file_buf += "# Supported <permission> values:\n";
186 for (let i = 0; i < data.values.length; ++i) {
187 let x = data.values[i];
188 file_buf += "# " + x[0] + " (" + x[1] + ")";
189 if (x[3])
190 file_buf += " - " + x[3];
191 file_buf += "\n";
193 if (data.related_prefs && data.related_prefs.length > 0) {
194 file_buf += "\n# Related Mozilla preferences:\n"
195 for (let i = 0; i < data.related_prefs.length; ++i) {
196 let x = data.related_prefs[i];
197 file_buf += "# " + x[0] + " = " + get_pref(x[0]) + "\n";
198 if (x.length > 1) {
199 file_buf += word_wrap(x[1], 80, "# ", "#") + "\n";
204 var file = null;
205 try {
206 file = get_temporary_file("permissions.txt");
207 let line = 4;
209 outer: while (1) {
211 write_text_file(file, file_buf);
212 yield open_file_with_external_editor(file, $line = line);
214 let new_buf = read_text_file(file);
215 if (new_buf == file_buf) {
216 I.minibuffer.message("No permission changes made.");
217 break;
220 // Parse
221 let lines = new_buf.split("\n");
222 if (lines[lines.length - 1].length == 0) // Remove extra line at end
223 lines.length = lines.length - 1;
224 let arr = [];
225 let prev_entries = new string_hashset();
226 for (let i = 0; i < lines.length; ++i) {
227 // Parse each line, checking for syntax errors
228 let x = lines[i];
229 let idx = x.indexOf('#');
230 if (idx != -1)
231 x = x.substr(0, idx);
232 let parts = x.split(/\s+/);
233 if (parts.length == 1 && parts[0].length == 0)
234 continue; // ignore blank line
235 try {
236 let host = parts[0];
237 if (!/[a-zA-Z0-9-_]+(\.[a-zA-Z0-9-_]+)*/.test(host))
238 throw "invalid host name: " + host;
239 if (parts.length < 2)
240 throw "missing permission type";
241 let type = parts[1];
242 if (parts.length < 3)
243 throw "missing permission value";
244 let cap = parts[2];
245 if (permission_types.hasOwnProperty(type)) {
246 let values = permission_types[type].values;
247 for (let i = 0; i < values.length; ++i) {
248 if (cap == values[i][0]) {
249 cap = values[i][1];
250 break;
254 if (!/([0-9]+)/.test(cap))
255 throw "invalid permission value: " + cap;
256 cap = parseInt(cap);
257 if (parts.length > 3)
258 throw "too many terms";
259 if (prev_entries.contains([host,type]))
260 throw "duplicate entry";
261 prev_entries.add([host,type]);
262 arr.push([host,type,cap]);
263 } catch (syntax_err) {
264 line = i + 1;
265 lines.splice(i+1, 0, "# ERROR on previous line: " + syntax_err, "");
266 file_buf = lines.join("\n") + "\n";
267 I.minibuffer.message("Correct the syntax error in the permissions list, " +
268 "or close the editor to abort.");
269 continue outer;
272 let num_added = 0;
273 let num_changed = 0;
274 for (let i = 0; i < arr.length; ++i) {
275 let [host,type,cap] = arr[i];
276 let x = existing_perms.get([host,type]);
277 let add = false;
278 if (x === undefined) {
279 ++num_added;
280 add = true;
281 } else {
282 if (x != cap) {
283 ++num_changed;
284 add = true;
286 existing_perms.remove([host,type]);
288 if (add)
289 permission_manager.add(make_uri("http://" + host), type, cap);
291 let num_removed = 0;
292 for (let k in existing_perms.iterator(true)) {
293 let [host,type] = k.split(",",2);
294 ++num_removed;
295 permission_manager.remove(host,type);
297 let msg;
298 if (num_added == 0 && num_changed == 0 && num_removed == 0)
299 msg = "No permission changes made.";
300 else {
301 msg = "Updated permissions list: " +
302 [["added", num_added],
303 ["changed", num_changed],
304 ["removed", num_removed]].
305 filter(function ([caption, count]) count > 0).
306 map(function ([caption, count]) {
307 if (count == 1)
308 return "1 entry " + caption;
309 return count + " entries " + caption;
310 }).join(", ") +
311 ".";
313 I.minibuffer.message(msg);
314 break;
316 } catch (e) {
317 dump_error(e);
318 throw interactive_error("Failed to edit permissions list in external editor.");
319 } finally {
320 file.remove(false);