8cf8bdd7246bad46c96d04e407b3b338101254a8
3 * Copyright (C) 2003-2004 Polytechnique.org
4 * http://opensource.polytechnique.org/
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 require_once 'HTTP/WebDAV/Server.php';
23 require_once 'diogenes/diogenes.misc.inc.php';
24 require_once 'diogenes.webdav.logger.inc.php';
25 require_once 'Barrel.php';
26 require_once 'Barrel/Page.php';
29 * Filesystem access using WebDAV
32 class DiogenesWebDAV
extends HTTP_WebDAV_Server
34 /** The Diogenes_Barrel representing the barrel */
37 /** Debugging log file handle */
40 /** Capture out to log ? */
46 function DiogenesWebDAV()
50 // call parent constructor
51 $this->HTTP_WebDAV_Server();
53 // construct new session each time, there is no practical way
54 // of tracking sessions with WebDAV clients
55 $_SESSION['session'] = new $globals->session
;
57 $this->debug_capture
= $globals->debugwebdav_capture
;
59 // init debug log if necessary
60 if ($globals->debugwebdav
)
62 $this->debugInit($globals->debugwebdav
);
63 register_shutdown_function(array(&$this, 'debugClose'));
70 * Hack to support barrels on virtualhosts.
75 function _urlencode($path, $for_html=false
) {
76 if ($this->barrel
->vhost
) {
77 $path = preg_replace("/^(.*)\/site\/{$this->barrel->alias}\/(.*)/", "/\$2",$path);
79 return parent
::_urlencode($path, $for_html);
84 * Perform authentication
86 * @param type HTTP Authentication type (Basic, Digest, ...)
87 * @param user Username
88 * @param pass Password
90 function check_auth($type, $user, $pass)
94 // WebDAV access is only granted for users who log in
95 if (!$_SESSION['session']->doAuthWebDAV($user,$pass))
98 // we retrieve the user's permissions on the current barrel
99 $_SESSION['session']->setBarrelPerms($this->barrel
->alias
);
107 * Handle a request to copy a file or directory.
111 function copy($options)
113 // we do not allow copying files
114 return "403 Forbidden";
121 * Open the debugging log file
123 function debugInit($debugfile)
125 if (empty($debugfile))
129 if ($fp = fopen($debugfile, "a"))
131 $this->debug_handle
= $fp;
137 * Log a debugging message
139 function debug($func, $msg)
141 if (isset($this->debug_handle
))
143 $out = sprintf("[%s] %s : %s\n",date("H:i:s"), $func, $msg);
144 fputs($this->debug_handle
, $out);
150 * Close the debugging log file
152 function debugClose()
154 if (isset($this->debug_handle
))
156 fclose($this->debug_handle
);
162 * Handle a request to delete a file or directory.
166 function delete($options)
169 $pathinfo = $this->parsePathInfo($options["path"]);
171 // get the page ID and write permissions for the current path
172 $pid = $this->barrel
->getPID($pathinfo['dir']);
173 if (!$pid ||
!$bpage = Diogenes_Barrel_Page
::fromDb($this->barrel
, $pid))
175 $this->debug('delete', "Could not find directory {$pathinfo['dir']}");
180 if (!$_SESSION['session']->hasPerms($bpage->props
['wperms']))
182 $this->debug('delete', "Insufficient privileges (needed : {$bpage->props['wperms']})");
183 return "403 Forbidden";
186 // create an RCS handle
187 $rcs = $this->getRcs();
188 $rcs->del($pid,$pathinfo['file']);
189 return "204 No content";
194 * Return information about a file or directory.
196 * @param fspath path of the filesystem entry
197 * @param uri the address at which the item is visible
199 function fileinfo($fspath, $uri)
206 $file["props"][] = $this->mkprop("displayname", strtoupper($uri));
208 $file["props"][] = $this->mkprop("creationdate", filectime($fspath));
209 $file["props"][] = $this->mkprop("getlastmodified", filemtime($fspath));
211 if (is_dir($fspath)) {
212 $file["props"][] = $this->mkprop("getcontentlength", 0);
213 $file["props"][] = $this->mkprop("resourcetype", "collection");
214 $file["props"][] = $this->mkprop("getcontenttype", "httpd/unix-directory");
216 $file["props"][] = $this->mkprop("resourcetype", "");
217 $file["props"][] = $this->mkprop("getcontentlength", filesize($fspath));
218 if (is_readable($fspath)) {
219 $file["props"][] = $this->mkprop("getcontenttype", get_mime_type($fspath));
221 $file["props"][] = $this->mkprop("getcontenttype", "application/x-non-readable");
229 * Handle a GET request.
233 function GET(&$options)
236 $pathinfo = $this->parsePathInfo($options["path"]);
238 // get the page ID and read permissions for the current path
239 $pid = $this->barrel
->getPID($pathinfo['dir']);
240 if (!$pid ||
!$bpage = Diogenes_Barrel_Page
::fromDb($this->barrel
, $pid))
242 $this->debug('PROPFIND', "Could not find directory {$pathinfo['dir']}");
247 if (!$_SESSION['session']->hasPerms($bpage->props
['perms']))
249 $this->debug('PROPFIND', "Insufficient privileges (needed : {$bpage->props['perms']})");
250 return "403 Forbidden";
254 $fspath = $this->barrel
->spool
->spoolPath($pid,$pathinfo['file']);
255 if (file_exists($fspath)) {
256 $options['mimetype'] = get_mime_type($fspath);
258 // see rfc2518, section 13.7
259 // some clients seem to treat this as a reverse rule
260 // requiering a Last-Modified header if the getlastmodified header was set
261 $options['mtime'] = filemtime($fspath);
262 $options['size'] = filesize($fspath);
264 // TODO check permissions/result
265 $options['stream'] = fopen($fspath, "r");
275 * Return an RCS handle.
280 return new $globals->rcs($this,$this->barrel
->alias
,$_SESSION['session']->username
);
285 * PROPFIND method handler
289 function http_PROPFIND()
291 $this->debug('http_PROPFIND', "called");
292 return parent
::http_PROPFIND();
296 /** Report an information. Needed for RCS operations.
300 * @see Diogenes_VCS_RCS
302 function info($msg) {
308 * Die with a given error message. Needed for RCS operations.
312 * @see Diogenes_VCS_RCS
314 function kill($msg) {
315 $this->http_status("400 Error");
321 * Record an action to the log. Needed for RCS operations.
326 * @see Diogenes_VCS_RCS
328 function log($action,$data) {
329 if (isset($_SESSION['log']) && is_object($_SESSION['log']))
330 $_SESSION['log']->log($action,$data);
335 * Handle an MKCOL (directory creation) request.
339 function MKCOL($options)
341 // we do not allow directory creations
342 return "403 Forbidden";
347 * Handle a request to move/rename a file or directory.
351 function move($options)
353 // we do not allow moving files
354 return "403 Forbidden";
359 * Break down a PATH_INFO into site, page id and file for WebDAV
360 * We accept a missing trailing slash after the directory name.
362 * @param path the path to parse
364 function parsePathInfo($path) {
367 $this->debug('parsePathInfo', "path : $path");
368 if (empty($path) ||
!preg_match("/^\/([^\/]+)\/webdav(\/((.+)\/)?([^\/]*))?$/",$path,$asplit))
371 $split['alias'] = $asplit[1];
372 $split['dir'] = isset($asplit[4]) ?
$asplit[4] : "";
373 $split['file'] = isset($asplit[5]) ?
$asplit[5] : "";
375 // check that what we considered as a file is not in fact a directory
376 // with a missing trailing slash
378 if ( empty($split['dir']) and
379 !empty($split['file']) and
380 (mysql_num_rows($globals->db->query("select location from {$split['alias']}_page where location='{$split['file']}'"))>0))
382 $split['dir'] = $split['file'];
390 * Handle a PROPFIND request.
395 function PROPFIND($options, &$files)
398 $pathinfo = $this->parsePathInfo($options["path"]);
400 // get the page ID and read permissions for the current path
401 $pid = $this->barrel
->getPID($pathinfo['dir']);
402 if (!$pid ||
!$bpage = Diogenes_Barrel_Page
::fromDb($this->barrel
, $pid))
404 $this->debug('PROPFIND', "Could not find directory {$pathinfo['dir']}");
409 if (!$_SESSION['session']->hasPerms($bpage->props
['perms']))
411 $this->debug('PROPFIND', "Insufficient privileges (needed : {$bpage->props['perms']})");
412 return "403 Forbidden";
415 // get absolute fs path to requested resource
416 $fspath = $this->barrel
->spool
->spoolPath($pid,$pathinfo['file']);
419 if (!file_exists($fspath)) {
423 // prepare property array
424 $files["files"] = array();
426 // store information for the requested path itself
427 $files["files"][] = $this->fileinfo($fspath, $options["path"]);
429 // information for contained resources requested?
430 if (!$pathinfo['file'] && !empty($options["depth"])) {
432 // make sure path ends with '/'
433 if (substr($options["path"],-1) != "/") {
434 $options["path"] .= "/";
437 // list the sub-directories
438 $res = $globals->db
->query("select PID,location from {$this->barrel->table_page} where parent='$pid'");
439 while (list($dpid,$dloc) = mysql_fetch_row($res)) {
440 $dpath = $this->barrel
->spool
->spoolPath($dpid);
441 $duri = $options["path"].$dloc;
442 $files["files"][] = $this->fileinfo($dpath, $duri);
444 mysql_free_result($res);
446 // now list the files in the current directory
447 $handle = @opendir
($fspath);
449 // ok, now get all its contents
450 while ($filename = readdir($handle)) {
451 if ($filename != "." && $filename != "..") {
452 $fpath = $this->barrel
->spool
->spoolPath($pid,$filename);
453 $furi = $options["path"].$filename;
455 $files["files"][] = $this->fileinfo ($fpath, $furi);
466 * Handle a PROPPATCH request.
470 function proppatch(&$options)
476 $path = $options["path"];
478 $dir = dirname($path)."/";
479 $base = basename($path);
481 foreach($options["props"] as $key => $prop) {
483 $options["props"][$key][$status] = "403 Forbidden";
491 * Handle a PUT request.
495 function PUT(&$options)
498 $pathinfo = $this->parsePathInfo($options["path"]);
500 // we do not support multipart
501 if (!empty($options["ranges"]))
504 // get the page ID and write permissions for the current path
505 $pid = $this->barrel
->getPID($pathinfo['dir']);
506 if (!$pid ||
!$bpage = Diogenes_Barrel_Page
::fromDb($this->barrel
, $pid))
508 $this->debug('PROPFIND', "Could not find directory {$pathinfo['dir']}");
513 if (!$_SESSION['session']->hasPerms($bpage->props
['wperms']))
515 $this->debug('PROPFIND', "Insufficient privileges (needed : {$bpage->props['wperms']})");
516 return "403 Forbidden";
519 // create an RCS handle
520 $rcs = $this->getRcs();
521 $options["new"] = !file_exists($rcs->rcsFile($pid,$pathinfo['file']));
525 while (!feof($options["stream"])) {
526 $content .= fread($options["stream"], 4096);
529 // if this is a barrel page, strip extraneous tags
530 if ($pathinfo['file'] == $globals->htmlfile
)
531 $content = $rcs->importHtmlString($content);
534 if (!$rcs->commit($pid,$pathinfo['file'],$content,"WebDAV PUT of {$pathinfo['file']}"))
537 // if this is Word master document, do the HTML conversion
538 if ($globals->word_import
&& $pathinfo['file'] == $globals->wordfile
) {
539 $myfile = $this->barrel
->spool
->spoolPath($pid,$globals->wordfile
);
540 $rcs->importWordFile($pid, $globals->htmlfile
, $myfile);
543 return $options["new"] ?
"201 Created" : "204 No Content";
548 * Serve a webdav request
550 function ServeRequest()
554 // break down path into site and location components
555 if (!($pathinfo = $this->parsePathInfo($_SERVER['PATH_INFO'])))
557 $this->http_status("404 not found");
561 // Retrieve site-wide info from database
562 $this->barrel
= new Diogenes_Barrel($pathinfo['alias'], $this);
563 if (!$this->barrel
->alias
)
565 $this->debug("Could not find barrel '{$pathinfo['alias']}'");
566 $this->http_status("404 not found");
569 $this->debug('ServeRequest', "barrel : ".$this->barrel
->alias
);
572 $props = array( 'REQUEST_METHOD', 'REQUEST_URI', 'SCRIPT_NAME', 'PATH_INFO');
573 foreach ($props as $prop) {
574 $this->debug('ServeRequest', "$prop : ". $_SERVER[$prop]);
577 // Here we perform some magic on the script name (on PHP 4.3.10)
578 // If the script is addressed by REQUEST_URI /site/foo/bar/ :
579 // - Apache 1.x returns /site as the SCRIPT_NAME
580 // - Apache 2.x returns /site.php as the SCRIPT_NAME
582 // We set SCRIPT_NAME to match the REQUEST_URI minus the PATH_INFO
584 $_SERVER['SCRIPT_NAME'] = substr($_SERVER['REQUEST_URI'], 0, - strlen($_SERVER['PATH_INFO']));
585 $this->debug('ServeRequest', "SCRIPT_NAME(mod) : ". $_SERVER['SCRIPT_NAME']);
587 // turn on output buffering
590 // let the base class do all the work
591 parent
::ServeRequest();
593 // stop output buffering
594 $out = ob_get_contents();
597 // to support barrels on virtualhosts, we rewrite the URLs
598 if ($this->barrel
->vhost
)
600 $ohref = (@$_SERVER["HTTPS"] === "on" ?
"https:" : "http:");
601 $ohref.= "//".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'];
602 $ohref.= "/" . $this->barrel
->alias
. "/webdav/";
603 $this->debug('ServeRequest', "ohref : ".$ohref);
605 $vhref = (@$_SERVER['HTTPS'] === "on" ?
"https:" : "http:");
606 $vhref.= "//".$this->barrel
->vhost
. "/webdav/";
607 $this->debug('ServeRequest', "vhref : ".$vhref);
609 $out = str_replace($ohref, $vhref, $out);
610 $out = str_replace('<D:displayname>/'.strtoupper($this->barrel
->alias
).'/WEBDAV/', '<D:displayname>/WEBDAV/', $out);
616 // if requested, log the output that is sent to the client
617 if ($this->debug_capture
)
619 $this->debug('ServeRequest', "out\n--- begin --\n$out--- end ---\n");