view-as-mime-type: fix bug in interactive declaration
[conkeror.git] / modules / permission-manager.js
blob04dc70464aaba3b40fd50cf021864641b981cde6
1 /**
2  * (C) Copyright 2008 Jeremy Maitin-Shepard
3  *
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.
11  */
14 let permission_manager = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
16 function get_spaces (n) {
17     var x = "";
18     while (x.length < n) x += " ";
19     return x;
22 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);
27     }
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 = "";
44         }
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);
50         }
51         cur_prefix = line_prefix;
52     }
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."] ]
67             },
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."
84                   ],
85                   [ "network.cookie.lifetime.days" ] ]
86              },
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." ] ]
99             },
101     install : {desc: "specifies a whitelist of sites from which XPI files may be opened",
102               values: [
103                   ["allow", Ci.nsIPermissionManager.ALLOW_ACTION] ]
104               }
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.
120  */
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";
135                 {
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);
151                     }
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;
164                                 }
165                             }
166                         }
167                         file_buf += host + max_host.substr(host.length) + type + max_type.substr(type.length) + cap + "\n";
168                     }
170                     if (arr.length == 0)
171                         file_buf += "\n";
172                 }
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";
192                     }
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, "#         ", "#");
200                             }
201                             file_buf += "\n";
202                         }
203                     }
204                 }
205                 var file = null;
206                 try {
207                     file = get_temporary_file("permissions.txt");
208                     let line = 4;
210                     outer: while (1) {
212                         write_text_file(file, file_buf);
213                         yield open_file_with_external_editor(file, $line = line);
215                         let new_buf = read_text_file(file);
216                         if (new_buf == file_buf) {
217                             I.minibuffer.message("No permission changes made.");
218                             break;
219                         }
221                         // Parse
222                         let lines = new_buf.split("\n");
223                         if (lines[lines.length - 1].length == 0) // Remove extra line at end
224                             lines.length = lines.length - 1;
225                         let arr = [];
226                         let prev_entries = new string_hashset();
227                         for (let i = 0; i < lines.length; ++i) {
228                             // Parse each line, checking for syntax errors
229                             let x = lines[i];
230                             let idx = x.indexOf('#');
231                             if (idx != -1)
232                                 x = x.substr(0, idx);
233                             let parts = x.split(/\s+/);
234                             if (parts.length == 1 && parts[0].length == 0)
235                                 continue; // ignore blank line
236                             try {
237                                 let host = parts[0];
238                                 if (!/[a-zA-Z0-9-_]+(\.[a-zA-Z0-9-_]+)*/.test(host))
239                                     throw "invalid host name: " + host;
240                                 if (parts.length < 2)
241                                     throw "missing permission type";
242                                 let type = parts[1];
243                                 if (parts.length < 3)
244                                     throw "missing permission value";
245                                 let cap = parts[2];
246                                 if (permission_types.hasOwnProperty(type)) {
247                                     let values = permission_types[type].values;
248                                     for (let i = 0; i < values.length; ++i) {
249                                         if (cap == values[i][0]) {
250                                             cap = values[i][1];
251                                             break;
252                                         }
253                                     }
254                                 }
255                                 if (!/([0-9]+)/.test(cap))
256                                     throw "invalid permission value: " + cap;
257                                 cap = parseInt(cap);
258                                 if (parts.length > 3)
259                                     throw "too many terms";
260                                 if (prev_entries.contains([host,type]))
261                                     throw "duplicate entry";
262                                 prev_entries.add([host,type]);
263                                 arr.push([host,type,cap]);
264                             } catch (syntax_err) {
265                                 line = i + 1;
266                                 lines.splice(i+1, 0, "# ERROR on previous line: " + syntax_err, "");
267                                 file_buf = lines.join("\n") + "\n";
268                                 I.minibuffer.message("Correct the syntax error in the permissions list, " +
269                                                      "or close the editor to abort.");
270                                 continue outer;
271                             }
272                         }
273                         let num_added = 0;
274                         let num_changed = 0;
275                         for (let i = 0; i < arr.length; ++i) {
276                             let [host,type,cap] = arr[i];
277                             let x = existing_perms.get([host,type]);
278                             let add = false;
279                             if (x === undefined) {
280                                 ++num_added;
281                                 add = true;
282                             } else {
283                                 if (x != cap) {
284                                     ++num_changed;
285                                     add = true;
286                                 }
287                                 existing_perms.remove([host,type]);
288                             }
289                             if (add)
290                                 permission_manager.add(make_uri("http://" + host), type, cap);
291                         }
292                         let num_removed = 0;
293                         for (let k in existing_perms.iterator(true)) {
294                             let [host,type] = k.split(",",2);
295                             ++num_removed;
296                             permission_manager.remove(host,type);
297                         }
298                         let msg;
299                         if (num_added == 0 && num_changed == 0 && num_removed == 0)
300                             msg = "No permission changes made.";
301                         else {
302                             msg = "Updated permissions list: " +
303                                 [["added", num_added],
304                                  ["changed", num_changed],
305                                  ["removed", num_removed]].
306                                 filter(function ([caption, count]) count > 0).
307                                 map(function ([caption, count]) {
308                                         if (count == 1)
309                                              return "1 entry " + caption;
310                                         return count + " entries " + caption;
311                                     }).join(", ") +
312                                 ".";
313                         }
314                         I.minibuffer.message(msg);
315                         break;
316                     }
317                 } catch (e) {
318                     dump_error(e);
319                     throw interactive_error("Failed to edit permissions list in external editor.");
320                 } finally {
321                     file.remove(false);
322                 }
323             });