7c22e6e746a81e6d733218b4005bc4fb085f765e
[atutor.git] / mods / wiki / plugins / feature / xpi.php
1 <?php
2
3 /*
4    If you load this plugin, you'll get WikiFeatures:AutomaticFeatureInstall
5    with ewiki unique ".xpi" plugins, that can simply be uploaded through a
6    web interface. The name xpi was borought from the Mozilla project, the
7    actual files are very different.
8
9    To install plugins, one must provide a correct password. Most types of
10    plugins can come in .xpi form. The .xpi must be generated using the
11    tools/mkxpi utility.
12
13    In conjunction with phpjs and the WikiApi plugin, this also allows for
14    installation of .jpi plugins, which contain JavaScript code, that is
15    compiled into sandboxed PHP script - such plugins could therefore be
16    uploaded and installed by anybody, because they're guaranteed to be
17    harmless to server security.
18 */
19
20
21 define("XPI_DB", "system/xpi/registry");
22 define("XPI_EVERYBODY_JPI", 1);
23
24 $ewiki_config["xpi_pw"] = array(     // plain password or md5 hash
25 #  "3389dae361af79b04c9c8e7057f60cc6",
26 #  "password",
27    "*",
28    defined("EWIKI_ADMIN_PW") ? EWIKI_ADMIN_PW : "*",
29 );
30 $ewiki_config["xpi_dirs"] = array(
31    "http://erfurtwiki.sourceforge.net/downloads/contrib-add-ons/xpi/",
32    "http://erfurtwiki.sourceforge.net/?XpiPlugins",
33 #  "http://erfurtwiki.sourceforge.net/?JpiPlugins",
34 #  "http://erfurtwiki.sf.net/xpi/",
35 );
36
37 $ewiki_plugins["page"]["PlugInstall"] = "ewiki_page_pluginstall";
38 $ewiki_plugins["handler"][] = "ewiki_xpi_exec";
39 $ewiki_plugins["init"][] = "ewiki_xpi_init_plugins";
40
41
42 #-- executes pages with the _EXEC flag set
43 function ewiki_xpi_exec($id, $data, $action) {
44
45    global $ewiki_id, $ewiki_title, $ewiki_action, $ewiki_data,
46       $ewiki_config, $ewiki_t, $ewiki_plugins, $_EWIKI;
47
48    if ($data["flags"] & EWIKI_DB_F_EXEC) {
49       eval($data["content"]);
50       return($o);
51    }
52 }
53
54
55 #-- runs plugins at init time
56 function ewiki_xpi_init_plugins() {
57
58    global $ewiki_id, $ewiki_title, $ewiki_action, $ewiki_data,
59       $ewiki_config, $ewiki_t, $ewiki_plugins, $_EWIKI;
60
61    #-- load xpi registry
62    $conf = ewiki_db::GET(XPI_DB);
63    if ($conf && ($conf["flags"] & EWIKI_DB_F_SYSTEM)
64    && ($conf = unserialize($conf["content"]))) {
65
66       $eval_this = "";
67
68       #-- collect xpi code, execute it
69       foreach ($conf as $xpi) {
70          if ($xpi["state"] && ($xpi["type"] != "page")) {
71             $d = ewiki_db::GET($xpi["id"]);
72             if ($d && ($d["flags"] & EWIKI_DB_F_EXEC)) {
73                $eval_this .= $d["content"];
74             }
75          }
76       }
77       eval($eval_this);
78    }
79 }
80
81
82
83 #-- provides the upload <form> and installation procedures for .xpi
84 function ewiki_page_pluginstall($id, $data, $action) {
85
86    global $ewiki_config, $ewiki_plugins;
87    $jpi_support = function_exists("js_compile") && function_exists("jsa_generate");
88    $jpi_access = XPI_EVERYBODY_JPI && $jsi_support;
89
90    #-- title
91    $o .= ewiki_make_title($id, $id, 2);
92    $o .= '<form action="'.$_SERVER["REQUEST_URI"].'" method="POST" enctype="multipart/form-data">'
93       . '<input type="hidden" name="id" value="'.htmlentities($id).'">';
94
95    #-- pw, access
96    $access = 0;
97    $o .= ewiki_xpi_password($access, $jpi_access);
98
99
100    #-- upload & install
101    if ($access || $jpi_access) {
102       $o .= '<div class="xpi-upload"><h4>.xpi plugin upload</h4>';
103
104       #-- if filename received => upload+install
105       if (($xpi_install_fn = $_REQUEST["install_remote_xpi"])
106       or ($_REQUEST["install_xpi"])
107          && ($xpi_install_fn = $_FILES["xpi_file"]["tmp_name"]) )
108       {
109          $o .= ewiki_xpi_install($xpi_install_fn, $access, $jsi_access, $jsi_support);
110       }
111       #-- or upload <form>
112       else {
113          $o .= ewiki_xpi_show_remote_repository();
114          $o .= ewiki_xpi_upload_form();
115       }
116
117       $o .= '</div><br />';
118    }
119
120
121    #-- plugin control
122    $o .= ewiki_xpi_plugin_control_centre();
123    
124    return($o);
125 }
126
127
128
129 #-- install given file as .xpi plugin
130 function ewiki_xpi_install($xpi_install_fn, $access, $jsi_access, $jsi_support) {
131    ewiki_xpi_load_registry($registry, $registry_hash);
132
133    #-- load (possibly remote) .xpi file
134    $xpi = ewiki_xpi_read($xpi_install_fn, "rb");
135    if (!$xpi) {
136       return "not a valid .xpi plugin (or wrong/inacceptable xpi plugin type/version)";
137    }
138    if (strlen($xpi["id"]) < 3) {
139       return "missing .xpi header";
140    }
141
142    #-- it's a .jpi plugin
143    if (($access || $jsi_access) && ($xpi["type"] == "jpi")) {
144    
145       #-- compile from JS (WikiScript) into sandboxed PHP
146       if ($jsi_support) {
147          $xpi["type"] = "page";
148          js_compile($xpi["code"]);
149          $xpi["code"] = NULL;
150          $xpi["code"] = jsa_generate();
151       }
152       else {
153          return "<b>ERROR</b>: cannot handle .jpi plugins without installed JavaScript interpreter";
154       }
155       if ($GLOBALS["js_err"]) {
156          ewiki_log("failed compiling .jpi plugin '$xpi[id]'", 0);
157          return "<b>ERROR</b>: broken .jpi plugin!";
158       }
159       
160    }
161    #-- else no permission to upload anything
162    elseif (!$access) {
163       return "<b>ERROR</b>: You don't have permission to install this type of plugin.<br /><br />";
164    }
165
166
167    #-- proceed with setup
168    $xpi["state"] = 1;
169    if (function_exists("php_check_syntax")) {
170       if (!php_check_syntax($xpi["code"])) {
171          return "<b>ERROR</b>: plugin code is broken";
172       }
173    }
174
175    #-- create new database entry
176    $new = ewiki_new_data($xpi["id"], EWIKI_DB_F_SYSTEM|EWIKI_DB_F_EXEC);
177    $new["content"] = $xpi["code"];
178    unset($xpi["code"]);
179
180
181    #-- check for old version
182    if ($access) {
183       $old = ewiki_db::GET($new["id"]);
184       if ($old["version"]) {
185          $new["version"] = $old["version"]+1;
186          $o .= "(overwriting plugin [version {$old[version]}])<br />";
187       }
188    }
189
190    #-- store plugin into database
191    if (ewiki_db::WRITE($new)) {
192       ewiki_log("successfully installed .xpi plugin '$xpi[id]'", 0);
193       $o .= ($xpi["type"] == "page") ? ewiki_link($xpi[id]) : "<b>{$xpi[id]}</b>";
194       $o .= " plugin stored. ";
195
196       #-- update .xpi registry
197       $registry[$xpi["id"]] = $xpi;
198       $registry_hash["content"] = serialize($registry);
199       ewiki_data_update($registry_hash);
200       $registry_hash["version"]++;
201       ewiki_db::WRITE($registry_hash);
202    }
203    else {
204       $o .= "<b>error</b> saving";
205       ewiki_log("error installing .xpi/.jpi plugin '$xpi[id]'", 0);
206    }
207
208    return($o);
209 }
210
211
212
213 #-- check pw, set cookie
214 function ewiki_xpi_password(&$access, &$jpi_access) {
215
216    #-- check
217    $o = '<div class="xpi-login">';
218    $pw = $_REQUEST["xpi_pw"];
219    $access = (strlen($pw) >= 3) && (in_array($pw, $ewiki_config["xpi_pw"]) || in_array(md5($pw), $ewiki_config["xpi_pw"]));
220    if (isset($_POST["xpi_pw"]) && ($_COOKIE["xpi_pw"] != $pw) || ($_REQUEST["xpi_logout"])) {
221       $pw = $_POST["xpi_pw"];
222       if ($access || !$pw) {
223          setcookie("xpi_pw", $pw);
224          $access = $access && $pw;
225       }
226    }
227
228    #-- login form
229    if (!$access) {
230       if ($jpi_access) {
231          $o .= "On this Wiki everybody is allowed to install safe .jpi (phpjs) plugins. ";
232       }
233       $o .= "To install click-and-run .xpi plugins you must be administrator and provide the correct password. ";
234       $o .= '<p><b>access password</b><br /><input type="password" name="xpi_pw" size="16"><br /><input type="submit" value="log in"></p>';
235    }
236    else {
237       $o .= '<input type="submit" name="xpi_logout" value="log out">';
238    }
239    $o .= "</div>\n";
240
241    return($o);
242 }
243
244
245
246 #-- load .xpi plugin registry
247 function ewiki_xpi_load_registry(&$registry, &$registry_hash) {
248    $registry_hash = ewiki_db::GET(XPI_DB);
249    if (!$registry_hash || !($registry_hash["flags"] & EWIKI_DB_F_SYSTEM)) {
250       $registry_hash = ewiki_new_data(XPI_DB, EWIKI_DB_F_SYSTEM);
251       $registry_hash["version"] = 0;
252       $registry = array();
253    }
254    else {
255       $registry = unserialize($registry_hash["content"]);
256    }
257 }
258
259
260
261 #-- delete + disable plugins
262 function ewiki_xpi_plugin_control_centre() {
263    ewiki_xpi_load_registry($registry, $registry_hash);
264
265    #-- title
266    $o = '<div class="xpi-settings"><h4>plugin control</h4>';
267
268    #-- delete plugins
269    if ($access && ($uu = $_REQUEST["xpi_rm"])) {
270       foreach ($uu as $id=>$del) {
271          if ($del) {
272             $id = rawurldecode($id);
273             $dat = ewiki_db::GET($id);
274             $vZ = $dat["version"];
275             for ($v=1; $v<=$vZ; $v++) {
276                ewiki_db::DELETE($id, $v);
277             }
278             unset($registry[$id]);
279             $vZ += 0;
280             $o .= "<b>i</b>: Purged $vZ versions of '$id' and removed xpi registry entry.<br /><br />";
281             ewiki_log("uninstalled .xpi/.jpi plugin '$id'", 0);
282          }
283       }
284       $_REQUEST["setup_xpi"]=1;
285    }
286
287    #-- update config settings
288    if ($_REQUEST["setup_xpi"]) {
289
290       if ($access) {
291          foreach ($registry as $id=>$uu) {
292             $registry[$id]["state"] = $_REQUEST["xpi_set"][rawurlencode($id)] ?1:0;
293          }
294
295          $registry_hash["content"] = serialize($registry);
296          ewiki_data_update($registry_hash);
297          $registry_hash["version"]++;
298          ewiki_db::WRITE($registry_hash);
299       }
300       else {
301          $o .= "You have no privileges to change the status of installed .xpi plugins.<br />\n";
302       }
303    }
304
305    #-- enable/disable checkboxes
306    $o .= '<table border="0" cellspacing="1" cellpadding="2">';
307    foreach ($registry as $dat) {
308       $enabled = ($dat["state"]==1);
309       $hard = ($dat["type"]=="page");
310       $title = $hard ? ewiki_link($dat["id"]) : $dat["id"];
311       $o .= '<tr>'
312          . '<td><tt>' . $dat["type"] . '</tt></td>'
313          . '<td class="xs-check"><input type="checkbox" name="xpi_set['.rawurlencode($dat["id"])
314          . ']" value="1"' . ($enabled?" checked":"")
315          . ($hard?" disabled":"") . '></td>'
316          . '<td class="xs-id">' . $title . '</td>'
317          . '<td><small>' . htmlentities($dat["description"]) . '</small></td>'
318          . '<td>' . $dat["author"] . ", " . $dat["license"] . '</td>'
319          . '<td class="xs-check"><input type="submit" name="xpi_rm['.rawurlencode($dat["id"]).']" value="rm" title="uninstall plugin"'.($access?"":" disabled").'></td>'
320          . '</tr>';
321    }
322    $o .= '</table>';
323    $o .= '<br /><input type="submit" name="setup_xpi" value="configure"'.($access?"":" disabled").'>';
324    $o .= '</form></div>';
325    
326    return($o);
327 }
328
329
330
331 #-- plugin upload <form>
332 function ewiki_xpi_upload_form() {
333    $o = '<b>Warning</b>: before uploading an extension plugin, you should check its source, because you\'ll otherwise may open big security leaks in your installation. <br /><br /> <input type="file" name="xpi_file"> <br /> <input type="submit" name="install_xpi" value="install"> <br /><br />';
334    $o .= 'Or install a plugin from one of the registered plugin directories:<br />';
335    foreach ($ewiki_config["xpi_dirs"] as $s) {
336       $o .= '<input type="submit" name="xpidir" value="'.htmlentities($s).'"><br />';
337    }
338    return($o);
339 }
340
341
342
343 #-- show remote .xpi directory
344 function ewiki_xpi_show_remote_repository() {
345   if ($url = $_REQUEST["xpidir"]) {
346      $r = array();
347      if ($urls = ewiki_util_getlinks($url, '[^\"\'\>\s]+?\.[jx]pi')) {
348         foreach ($urls as $fn) {
349            if ($xpi = ewiki_xpi_read($fn)) {
350               $xpi["XPI"] = $xpi["code"] = NULL;
351               if (!$access && $xpi["JPI"]) { 
352                  continue;
353               }
354               $r[$fn] = $xpi;
355            }
356         }
357      }
358      $o .= "install .xpi plugins from remote directory<br /><a href=\"$url\">$url</a>:<br />"
359          . "<small>don't do this, if you don't know the operator of the provided extension plugins (could contain malicious code)</small><br />\n";
360      $o .= '<table border="0" cellspacing="1" cellpadding="2">';
361      foreach ($r as $fn=>$xpi) {
362         $o .= "\n".'<tr><td colspan="3">'
363            . '<input type="submit" name="install_remote_xpi" value="'
364              .htmlentities($fn).'" title="'.$xpi["id"].'">'
365            . '</td></tr><tr>'
366            . "<td class=\"xs-id\">[{$xpi[type]}] {$xpi[id]} {$xpi[version]}</td>"
367            . "<td>{$xpi[description]}<br /></td>"
368            . "<td>{$xpi[author]}, {$xpi[license]}</td>"
369            . '</tr>';
370      }
371      $o .= '</table><br />';
372   }
373
374   return($o);
375 }
376
377
378
379 #-- open binary .xpi file
380 function ewiki_xpi_read($fn, $maxsize=0x020000) {
381    if ($f = gzopen($fn, "rb")) {
382       $xpi = gzread($f, $maxsize);
383       gzclose($f);
384    }
385    if ($xpi) {
386       $xpi = unserialize($xpi);
387       if (($xpi["XPI"]=="0.1")
388       and (($xpi["engine"]=="ewiki") || ($xpi["type"]=="jpi"))
389       and $xpi["id"] && $xpi["type"] && $xpi["code"]) {
390          return($xpi);
391       }
392    }
393 }
394
395
396
397 #-- read out file names from directory listing in .html format
398 function ewiki_util_getlinks($url, $regex='.+?') {
399    $r = array();
400
401    if ($html = @file($url)) {
402       $html = implode("", $html);
403
404       $url_b = substr("$url", 0, strrpos($url, "/"));
405       $url_s = substr("$url", 0, strpos($url, "/", 10));
406
407       preg_match_all('#<a[^>]+href=["\']?('.$regex.')["\'\s>]#i', $html, $uu);
408       foreach ($uu[1] as $fn) {
409          if ($fn[0] == "/") {
410             $fn = $url_s . $fn;
411          }
412          elseif (strpos($fn, "://")) {
413          }
414          else {
415             $fn = $url_b . "/" . $fn;
416          }
417          $r[] = $fn;
418       }
419    }
420    return($r);
421 }
422
423
424 ?>