Project

General

Profile

Fehler #332 ยป Filesystem.php

Daniel Roche, 11/08/2016 11:06 AM

 
1
<?php // $Id: Filesystem.php 246165 2007-11-14 13:43:51Z hholzgra $
2
/*
3
   +----------------------------------------------------------------------+
4
   | Copyright (c) 2002-2007 Christian Stocker, Hartmut Holzgraefe        |
5
   | All rights reserved                                                  |
6
   |                                                                      |
7
   | Redistribution and use in source and binary forms, with or without   |
8
   | modification, are permitted provided that the following conditions   |
9
   | are met:                                                             |
10
   |                                                                      |
11
   | 1. Redistributions of source code must retain the above copyright    |
12
   |    notice, this list of conditions and the following disclaimer.     |
13
   | 2. Redistributions in binary form must reproduce the above copyright |
14
   |    notice, this list of conditions and the following disclaimer in   |
15
   |    the documentation and/or other materials provided with the        |
16
   |    distribution.                                                     |
17
   | 3. The names of the authors may not be used to endorse or promote    |
18
   |    products derived from this software without specific prior        |
19
   |    written permission.                                               |
20
   |                                                                      |
21
   | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
22
   | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
23
   | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
24
   | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE       |
25
   | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,  |
26
   | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
27
   | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;     |
28
   | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER     |
29
   | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT   |
30
   | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN    |
31
   | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE      |
32
   | POSSIBILITY OF SUCH DAMAGE.                                          |
33
   +----------------------------------------------------------------------+
34
*/
35

    
36
require_once (dirname(__FILE__).'/../Server.php');
37
require_once (dirname(__FILE__).'/../../../System.php');
38
    
39
/**
40
 * Filesystem access using WebDAV
41
 *
42
 * @access  public
43
 * @author  Hartmut Holzgraefe <hartmut@php.net>
44
 * @version @package-version@
45
 */
46
class HTTP_WebDAV_Server_Filesystem extends HTTP_WebDAV_Server 
47
{
48
    /**
49
     * Root directory for WebDAV access
50
     *
51
     * Defaults to webserver document root (set by ServeRequest)
52
     *
53
     * @access private
54
     * @var    string
55
     */
56
    var $base = "";
57

    
58
    /** 
59
     * MySQL Host where property and locking information is stored
60
     *
61
     * @access private
62
     * @var    string
63
     */
64
    var $db_host = "localhost";
65

    
66
    /**
67
     * MySQL database for property/locking information storage
68
     *
69
     * @access private
70
     * @var    string
71
     */
72
    var $db_name = "webdav";
73

    
74
    /**
75
     * MySQL table name prefix 
76
     *
77
     * @access private
78
     * @var    string
79
     */
80
    var $db_prefix = "";
81

    
82
    /**
83
     * MySQL user for property/locking db access
84
     *
85
     * @access private
86
     * @var    string
87
     */
88
    var $db_user = "root";
89

    
90
    /**
91
     * MySQL password for property/locking db access
92
     *
93
     * @access private
94
     * @var    string
95
     */
96
    var $db_passwd = "";
97

    
98
    /**
99
     * MySQLI link 
100
     */
101
    var $db_link = NULL;
102

    
103

    
104
    /**
105
     * Serve a webdav request
106
     *
107
     * @access public
108
     * @param  string  
109
     */
110
    function ServeRequest($base = false) 
111
    {
112
        // special treatment for litmus compliance test
113
        // reply on its identifier header
114
        // not needed for the test itself but eases debugging
115
        if (isset($this->_SERVER['HTTP_X_LITMUS'])) {
116
            error_log("Litmus test ".$this->_SERVER['HTTP_X_LITMUS']);
117
            header("X-Litmus-reply: ".$this->_SERVER['HTTP_X_LITMUS']);
118
        }
119

    
120
        // set root directory, defaults to webserver document root if not set
121
        if ($base) {
122
            $this->base = realpath($base); // TODO throw if not a directory
123
        } else if (!$this->base) {
124
            $this->base = $this->_SERVER['DOCUMENT_ROOT'];
125
        }
126
                
127
        // establish connection to property/locking db
128
        $this->db_link = mysqli_connect($this->db_host, $this->db_user, $this->db_passwd) or die(mysqli_error($this->db_link));
129
        mysqli_select_db($this->db_link, $this->db_name) or die(mysqli_error($this->db_link));
130
        // TODO throw on connection problems
131

    
132
        // let the base class do all the work
133
        parent::ServeRequest();
134
    }
135

    
136
    /**
137
     * No authentication is needed here
138
     *
139
     * @access private
140
     * @param  string  HTTP Authentication type (Basic, Digest, ...)
141
     * @param  string  Username
142
     * @param  string  Password
143
     * @return bool    true on successful authentication
144
     */
145
    function check_auth($type, $user, $pass) 
146
    {
147
        return true;
148
    }
149

    
150

    
151
    /**
152
     * PROPFIND method handler
153
     *
154
     * @param  array  general parameter passing array
155
     * @param  array  return array for file properties
156
     * @return bool   true on success
157
     */
158
    function PROPFIND(&$options, &$files) 
159
    {
160
        // get absolute fs path to requested resource
161
        $fspath = $this->base . $options["path"];
162
            
163
        // sanity check
164
        if (!file_exists($fspath)) {
165
            return false;
166
        }
167

    
168
        // prepare property array
169
        $files["files"] = array();
170

    
171
        // store information for the requested path itself
172
        $files["files"][] = $this->fileinfo($options["path"]);
173

    
174
        // information for contained resources requested?
175
        if (!empty($options["depth"]) && is_dir($fspath) && is_readable($fspath)) {                
176
            // make sure path ends with '/'
177
            $options["path"] = $this->_slashify($options["path"]);
178

    
179
            // try to open directory
180
            $handle = opendir($fspath);
181
                
182
            if ($handle) {
183
                // ok, now get all its contents
184
                while ($filename = readdir($handle)) {
185
                    if ($filename != "." && $filename != "..") {
186
                        $files["files"][] = $this->fileinfo($options["path"].$filename);
187
                    }
188
                }
189
                // TODO recursion needed if "Depth: infinite"
190
            }
191
        }
192

    
193
        // ok, all done
194
        return true;
195
    } 
196
        
197
    /**
198
     * Get properties for a single file/resource
199
     *
200
     * @param  string  resource path
201
     * @return array   resource properties
202
     */
203
    function fileinfo($path) 
204
    {
205
        // map URI path to filesystem path
206
        $fspath = $this->base . $path;
207

    
208
        // create result array
209
        $info = array();
210
        // TODO remove slash append code when base clase is able to do it itself
211
        $info["path"]  = is_dir($fspath) ? $this->_slashify($path) : $path; 
212
        $info["props"] = array();
213
            
214
        // no special beautified displayname here ...
215
        $info["props"][] = $this->mkprop("displayname", strtoupper($path));
216
            
217
        // creation and modification time
218
        $info["props"][] = $this->mkprop("creationdate",    filectime($fspath));
219
        $info["props"][] = $this->mkprop("getlastmodified", filemtime($fspath));
220

    
221
        // Microsoft extensions: last access time and 'hidden' status
222
        $info["props"][] = $this->mkprop("lastaccessed",    fileatime($fspath));
223
        $info["props"][] = $this->mkprop("ishidden", ('.' === substr(basename($fspath), 0, 1)));
224

    
225
        // type and size (caller already made sure that path exists)
226
        if (is_dir($fspath)) {
227
            // directory (WebDAV collection)
228
            $info["props"][] = $this->mkprop("resourcetype", "collection");
229
            $info["props"][] = $this->mkprop("getcontenttype", "httpd/unix-directory");             
230
        } else {
231
            // plain file (WebDAV resource)
232
            $info["props"][] = $this->mkprop("resourcetype", "");
233
            if (is_readable($fspath)) {
234
                $info["props"][] = $this->mkprop("getcontenttype", $this->_mimetype($fspath));
235
            } else {
236
                $info["props"][] = $this->mkprop("getcontenttype", "application/x-non-readable");
237
            }               
238
            $info["props"][] = $this->mkprop("getcontentlength", filesize($fspath));
239
        }
240

    
241
        // get additional properties from database
242
        $query = "SELECT ns, name, value 
243
                        FROM {$this->db_prefix}properties 
244
                       WHERE path = '$path'";
245
        $res = mysqli_query($this->db_link, $query);
246
        while ($row = mysqli_fetch_assoc($res)) {
247
            $info["props"][] = $this->mkprop($row["ns"], $row["name"], $row["value"]);
248
        }
249
        mysqli_free_result($res);
250

    
251
        return $info;
252
    }
253

    
254
    /**
255
     * detect if a given program is found in the search PATH
256
     *
257
     * helper function used by _mimetype() to detect if the 
258
     * external 'file' utility is available
259
     *
260
     * @param  string  program name
261
     * @param  string  optional search path, defaults to $PATH
262
     * @return bool    true if executable program found in path
263
     */
264
    function _can_execute($name, $path = false) 
265
    {
266
        // path defaults to PATH from environment if not set
267
        if ($path === false) {
268
            $path = getenv("PATH");
269
        }
270
            
271
        // check method depends on operating system
272
        if (!strncmp(PHP_OS, "WIN", 3)) {
273
            // on Windows an appropriate COM or EXE file needs to exist
274
            $exts     = array(".exe", ".com");
275
            $check_fn = "file_exists";
276
        } else {
277
            // anywhere else we look for an executable file of that name
278
            $exts     = array("");
279
            $check_fn = "is_executable";
280
        }
281
            
282
        // now check the directories in the path for the program
283
        foreach (explode(PATH_SEPARATOR, $path) as $dir) {
284
            // skip invalid path entries
285
            if (!file_exists($dir)) continue;
286
            if (!is_dir($dir)) continue;
287

    
288
            // and now look for the file
289
            foreach ($exts as $ext) {
290
                if ($check_fn("$dir/$name".$ext)) return true;
291
            }
292
        }
293

    
294
        return false;
295
    }
296

    
297
        
298
    /**
299
     * try to detect the mime type of a file
300
     *
301
     * @param  string  file path
302
     * @return string  guessed mime type
303
     */
304
    function _mimetype($fspath) 
305
    {
306
        if (is_dir($fspath)) {
307
            // directories are easy
308
            return "httpd/unix-directory"; 
309
        } else if (function_exists("mime_content_type")) {
310
            // use mime magic extension if available
311
            $mime_type = mime_content_type($fspath);
312
        } else if ($this->_can_execute("file")) {
313
            // it looks like we have a 'file' command, 
314
            // lets see it it does have mime support
315
            $fp    = popen("file -i '$fspath' 2>/dev/null", "r");
316
            $reply = fgets($fp);
317
            pclose($fp);
318
                
319
            // popen will not return an error if the binary was not found
320
            // and find may not have mime support using "-i"
321
            // so we test the format of the returned string 
322
                
323
            // the reply begins with the requested filename
324
            if (!strncmp($reply, "$fspath: ", strlen($fspath)+2)) {                     
325
                $reply = substr($reply, strlen($fspath)+2);
326
                // followed by the mime type (maybe including options)
327
                if (preg_match('|^[[:alnum:]_-]+/[[:alnum:]_-]+;?.*|', $reply, $matches)) {
328
                    $mime_type = $matches[0];
329
                }
330
            }
331
        } 
332
            
333
        if (empty($mime_type)) {
334
            // Fallback solution: try to guess the type by the file extension
335
            // TODO: add more ...
336
            // TODO: it has been suggested to delegate mimetype detection 
337
            //       to apache but this has at least three issues:
338
            //       - works only with apache
339
            //       - needs file to be within the document tree
340
            //       - requires apache mod_magic 
341
            // TODO: can we use the registry for this on Windows?
342
            //       OTOH if the server is Windos the clients are likely to 
343
            //       be Windows, too, and tend do ignore the Content-Type
344
            //       anyway (overriding it with information taken from
345
            //       the registry)
346
            // TODO: have a seperate PEAR class for mimetype detection?
347
            switch (strtolower(strrchr(basename($fspath), "."))) {
348
            case ".html":
349
                $mime_type = "text/html";
350
                break;
351
            case ".gif":
352
                $mime_type = "image/gif";
353
                break;
354
            case ".jpg":
355
                $mime_type = "image/jpeg";
356
                break;
357
            default: 
358
                $mime_type = "application/octet-stream";
359
                break;
360
            }
361
        }
362
            
363
        return $mime_type;
364
    }
365

    
366
    /**
367
     * HEAD method handler
368
     * 
369
     * @param  array  parameter passing array
370
     * @return bool   true on success
371
     */
372
    function HEAD(&$options) 
373
    {
374
        // get absolute fs path to requested resource
375
        $fspath = $this->base . $options["path"];
376

    
377
        // sanity check
378
        if (!file_exists($fspath)) return false;
379
            
380
        // detect resource type
381
        $options['mimetype'] = $this->_mimetype($fspath); 
382
                
383
        // detect modification time
384
        // see rfc2518, section 13.7
385
        // some clients seem to treat this as a reverse rule
386
        // requiering a Last-Modified header if the getlastmodified header was set
387
        $options['mtime'] = filemtime($fspath);
388
            
389
        // detect resource size
390
        $options['size'] = filesize($fspath);
391
            
392
        return true;
393
    }
394

    
395
    /**
396
     * GET method handler
397
     * 
398
     * @param  array  parameter passing array
399
     * @return bool   true on success
400
     */
401
    function GET(&$options) 
402
    {
403
        // get absolute fs path to requested resource
404
        $fspath = $this->base . $options["path"];
405

    
406
        // is this a collection?
407
        if (is_dir($fspath)) {
408
            return $this->GetDir($fspath, $options);
409
        }
410

    
411
        // the header output is the same as for HEAD
412
        if (!$this->HEAD($options)) {
413
            return false;
414
        }
415

    
416
        // no need to check result here, it is handled by the base class
417
        $options['stream'] = fopen($fspath, "r");
418
            
419
        return true;
420
    }
421

    
422
    /**
423
     * GET method handler for directories
424
     *
425
     * This is a very simple mod_index lookalike.
426
     * See RFC 2518, Section 8.4 on GET/HEAD for collections
427
     *
428
     * @param  string  directory path
429
     * @return void    function has to handle HTTP response itself
430
     */
431
    function GetDir($fspath, &$options) 
432
    {
433
        $path = $this->_slashify($options["path"]);
434
        if ($path != $options["path"]) {
435
            header("Location: ".$this->base_uri.$path);
436
            exit;
437
        }
438

    
439
        // fixed width directory column format
440
        $format = "%15s  %-19s  %-s\n";
441

    
442
        if (!is_readable($fspath)) {
443
            return false;
444
        }
445

    
446
        $handle = opendir($fspath);
447
        if (!$handle) {
448
            return false;
449
        }
450

    
451
        echo "<html><head><title>Index of ".htmlspecialchars($options['path'])."</title></head>\n";
452
            
453
        echo "<h1>Index of ".htmlspecialchars($options['path'])."</h1>\n";
454
            
455
        echo "<pre>";
456
        printf($format, "Size", "Last modified", "Filename");
457
        echo "<hr>";
458

    
459
        while ($filename = readdir($handle)) {
460
            if ($filename != "." && $filename != "..") {
461
                $fullpath = $fspath."/".$filename;
462
                $name     = htmlspecialchars($filename);
463
                printf($format, 
464
                       number_format(filesize($fullpath)),
465
                       strftime("%Y-%m-%d %H:%M:%S", filemtime($fullpath)), 
466
                       "<a href='$name'>$name</a>");
467
            }
468
        }
469

    
470
        echo "</pre>";
471

    
472
        closedir($handle);
473

    
474
        echo "</html>\n";
475

    
476
        exit;
477
    }
478

    
479
    /**
480
     * PUT method handler
481
     * 
482
     * @param  array  parameter passing array
483
     * @return bool   true on success
484
     */
485
    function PUT(&$options) 
486
    {
487
        $fspath = $this->base . $options["path"];
488

    
489
        $dir = dirname($fspath);
490
        if (!file_exists($dir) || !is_dir($dir)) {
491
            return "409 Conflict"; // TODO right status code for both?
492
        }
493

    
494
        $options["new"] = ! file_exists($fspath);
495

    
496
        if ($options["new"] && !is_writeable($dir)) {
497
            return "403 Forbidden";
498
        }
499
        if (!$options["new"] && !is_writeable($fspath)) {
500
            return "403 Forbidden";
501
        }
502
        if (!$options["new"] && is_dir($fspath)) {
503
            return "403 Forbidden";
504
        }
505

    
506
        $fp = fopen($fspath, "w");
507

    
508
        return $fp;
509
    }
510

    
511

    
512
    /**
513
     * MKCOL method handler
514
     *
515
     * @param  array  general parameter passing array
516
     * @return bool   true on success
517
     */
518
    function MKCOL($options) 
519
    {           
520
        $path   = $this->base .$options["path"];
521
        $parent = dirname($path);
522
        $name   = basename($path);
523

    
524
        if (!file_exists($parent)) {
525
            return "409 Conflict";
526
        }
527

    
528
        if (!is_dir($parent)) {
529
            return "403 Forbidden";
530
        }
531

    
532
        if ( file_exists($parent."/".$name) ) {
533
            return "405 Method not allowed";
534
        }
535

    
536
        if (!empty($this->_SERVER["CONTENT_LENGTH"])) { // no body parsing yet
537
            return "415 Unsupported media type";
538
        }
539
            
540
        $stat = mkdir($parent."/".$name, 0777);
541
        if (!$stat) {
542
            return "403 Forbidden";                 
543
        }
544

    
545
        return ("201 Created");
546
    }
547
        
548
        
549
    /**
550
     * DELETE method handler
551
     *
552
     * @param  array  general parameter passing array
553
     * @return bool   true on success
554
     */
555
    function DELETE($options) 
556
    {
557
        $path = $this->base . "/" .$options["path"];
558

    
559
        if (!file_exists($path)) {
560
            return "404 Not found";
561
        }
562

    
563
        if (is_dir($path)) {
564
            $query = "DELETE FROM {$this->db_prefix}properties 
565
                           WHERE path LIKE '".$this->_slashify($options["path"])."%'";
566
            mysqli_query($this->db_link,$query);
567
            System::rm(array("-rf", $path));
568
        } else {
569
            unlink($path);
570
        }
571
        $query = "DELETE FROM {$this->db_prefix}properties 
572
                       WHERE path = '$options[path]'";
573
        mysqli_query($this->db_link,$query);
574

    
575
        return "204 No Content";
576
    }
577

    
578

    
579
    /**
580
     * MOVE method handler
581
     *
582
     * @param  array  general parameter passing array
583
     * @return bool   true on success
584
     */
585
    function MOVE($options) 
586
    {
587
        return $this->COPY($options, true);
588
    }
589

    
590
    /**
591
     * COPY method handler
592
     *
593
     * @param  array  general parameter passing array
594
     * @return bool   true on success
595
     */
596
    function COPY($options, $del=false) 
597
    {
598
        // TODO Property updates still broken (Litmus should detect this?)
599

    
600
        if (!empty($this->_SERVER["CONTENT_LENGTH"])) { // no body parsing yet
601
            return "415 Unsupported media type";
602
        }
603

    
604
        // no copying to different WebDAV Servers yet
605
        if (isset($options["dest_url"])) {
606
            return "502 bad gateway";
607
        }
608

    
609
        $source = $this->base . $options["path"];
610
        if (!file_exists($source)) {
611
            return "404 Not found";
612
        }
613

    
614
        if (is_dir($source)) { // resource is a collection
615
            switch ($options["depth"]) {
616
            case "infinity": // valid 
617
                break;
618
            case "0": // valid for COPY only
619
                if ($del) { // MOVE?
620
                    return "400 Bad request";
621
                }
622
                break;
623
            case "1": // invalid for both COPY and MOVE
624
            default: 
625
                return "400 Bad request";
626
            }
627
        }
628

    
629
        $dest         = $this->base . $options["dest"];
630
        $destdir      = dirname($dest);
631
        
632
        if (!file_exists($destdir) || !is_dir($destdir)) {
633
            return "409 Conflict";
634
        }
635

    
636

    
637
        $new          = !file_exists($dest);
638
        $existing_col = false;
639

    
640
        if (!$new) {
641
            if ($del && is_dir($dest)) {
642
                if (!$options["overwrite"]) {
643
                    return "412 precondition failed";
644
                }
645
                $dest .= basename($source);
646
                if (file_exists($dest)) {
647
                    $options["dest"] .= basename($source);
648
                } else {
649
                    $new          = true;
650
                    $existing_col = true;
651
                }
652
            }
653
        }
654

    
655
        if (!$new) {
656
            if ($options["overwrite"]) {
657
                $stat = $this->DELETE(array("path" => $options["dest"]));
658
                if (($stat{0} != "2") && (substr($stat, 0, 3) != "404")) {
659
                    return $stat; 
660
                }
661
            } else {
662
                return "412 precondition failed";
663
            }
664
        }
665

    
666
        if ($del) {
667
            if (!rename($source, $dest)) {
668
                return "500 Internal server error";
669
            }
670
            $destpath = $this->_unslashify($options["dest"]);
671
            if (is_dir($source)) {
672
                $query = "UPDATE {$this->db_prefix}properties 
673
                                 SET path = REPLACE(path, '".$options["path"]."', '".$destpath."') 
674
                               WHERE path LIKE '".$this->_slashify($options["path"])."%'";
675
                mysqli_query($this->db_link,$query);
676
            }
677

    
678
            $query = "UPDATE {$this->db_prefix}properties 
679
                             SET path = '".$destpath."'
680
                           WHERE path = '".$options["path"]."'";
681
            mysqli_query($this->db_link,$query);
682
        } else {
683
            if (is_dir($source)) {
684
                $files = System::find($source);
685
                $files = array_reverse($files);
686
            } else {
687
                $files = array($source);
688
            }
689

    
690
            if (!is_array($files) || empty($files)) {
691
                return "500 Internal server error";
692
            }
693
                    
694
                
695
            foreach ($files as $file) {
696
                if (is_dir($file)) {
697
                    $file = $this->_slashify($file);
698
                }
699

    
700
                $destfile = str_replace($source, $dest, $file);
701
                    
702
                if (is_dir($file)) {
703
                    if (!file_exists($destfile)) {
704
                        if (!is_writeable(dirname($destfile))) {
705
                            return "403 Forbidden";
706
                        }
707
                        if (!mkdir($destfile)) {
708
                            return "409 Conflict";
709
                        }
710
                    } else if (!is_dir($destfile)) {
711
                        return "409 Conflict";
712
                    }
713
                } else {
714
                    
715
                    if (!copy($file, $destfile)) {
716
                        return "409 Conflict";
717
                    }
718
                }
719
            }
720

    
721
            $query = "INSERT INTO {$this->db_prefix}properties 
722
                               SELECT *
723
                                 FROM {$this->db_prefix}properties 
724
                                WHERE path = '".$options['path']."'";
725
        }
726

    
727
        return ($new && !$existing_col) ? "201 Created" : "204 No Content";         
728
    }
729

    
730
    /**
731
     * PROPPATCH method handler
732
     *
733
     * @param  array  general parameter passing array
734
     * @return bool   true on success
735
     */
736
    function PROPPATCH(&$options) 
737
    {
738
        global $prefs, $tab;
739

    
740
        $msg  = "";
741
        $path = $options["path"];
742
        $dir  = dirname($path)."/";
743
        $base = basename($path);
744
            
745
        foreach ($options["props"] as $key => $prop) {
746
            if ($prop["ns"] == "DAV:") {
747
                $options["props"][$key]['status'] = "403 Forbidden";
748
            } else {
749
                if (isset($prop["val"])) {
750
                    $query = "REPLACE INTO {$this->db_prefix}properties 
751
                                           SET path = '$options[path]'
752
                                             , name = '$prop[name]'
753
                                             , ns= '$prop[ns]'
754
                                             , value = '$prop[val]'";
755
                } else {
756
                    $query = "DELETE FROM {$this->db_prefix}properties 
757
                                        WHERE path = '$options[path]' 
758
                                          AND name = '$prop[name]' 
759
                                          AND ns = '$prop[ns]'";
760
                }       
761
                mysqli_query($this->db_link,$query);
762
            }
763
        }
764
                        
765
        return "";
766
    }
767

    
768

    
769
    /**
770
     * LOCK method handler
771
     *
772
     * @param  array  general parameter passing array
773
     * @return bool   true on success
774
     */
775
    function LOCK(&$options) 
776
    {
777
        // get absolute fs path to requested resource
778
        $fspath = $this->base . $options["path"];
779

    
780
        // TODO recursive locks on directories not supported yet
781
        // makes litmus test "32. lock_collection" fail
782
        if (is_dir($fspath) && !empty($options["depth"])) {
783
            return "409 Conflict";
784
        }
785

    
786
        $options["timeout"] = time()+300; // 5min. hardcoded
787

    
788
        if (isset($options["update"])) { // Lock Update
789
            $where = "WHERE path = '$options[path]' AND token = '$options[update]'";
790

    
791
            $query = "SELECT owner, exclusivelock FROM {$this->db_prefix}locks $where";
792
            $res   = mysqli_query($this->db_link,$query);
793
            $row   = mysqli_fetch_assoc($res);
794
            mysqli_free_result($res);
795

    
796
            if (is_array($row)) {
797
                $query = "UPDATE {$this->db_prefix}locks 
798
                                 SET expires = '$options[timeout]' 
799
                                   , modified = ".time()."
800
                              $where";
801
                mysqli_query($this->db_link,$query);
802

    
803
                $options['owner'] = $row['owner'];
804
                $options['scope'] = $row["exclusivelock"] ? "exclusive" : "shared";
805
                $options['type']  = $row["exclusivelock"] ? "write"     : "read";
806

    
807
                return true;
808
            } else {
809
                return false;
810
            }
811
        }
812
            
813
        $query = "INSERT INTO {$this->db_prefix}locks
814
                        SET token   = '$options[locktoken]'
815
                          , path    = '$options[path]'
816
                          , created = ".time()."
817
                          , modified = ".time()."
818
                          , owner   = '$options[owner]'
819
                          , expires = '$options[timeout]'
820
                          , exclusivelock  = " .($options['scope'] === "exclusive" ? "1" : "0")
821
            ;
822
        mysqli_query($this->db_link,$query);
823

    
824
        return mysqli_affected_rows() ? "200 OK" : "409 Conflict";
825
    }
826

    
827
    /**
828
     * UNLOCK method handler
829
     *
830
     * @param  array  general parameter passing array
831
     * @return bool   true on success
832
     */
833
    function UNLOCK(&$options) 
834
    {
835
        $query = "DELETE FROM {$this->db_prefix}locks
836
                      WHERE path = '$options[path]'
837
                        AND token = '$options[token]'";
838
        mysqli_query($this->db_link,$query);
839

    
840
        return mysqli_affected_rows() ? "204 No Content" : "409 Conflict";
841
    }
842

    
843
    /**
844
     * checkLock() helper
845
     *
846
     * @param  string resource path to check for locks
847
     * @return bool   true on success
848
     */
849
    function checkLock($path) 
850
    {
851
        $result = false;
852
            
853
        $query = "SELECT owner, token, created, modified, expires, exclusivelock
854
                  FROM {$this->db_prefix}locks
855
                 WHERE path = '$path'
856
               ";
857
        $res = mysqli_query($this->db_link,$query);
858

    
859
        if ($res) {
860
            $row = mysqli_fetch_array($res);
861
            mysqli_free_result($res);
862

    
863
            if ($row) {
864
                $result = array( "type"    => "write",
865
                                 "scope"   => $row["exclusivelock"] ? "exclusive" : "shared",
866
                                 "depth"   => 0,
867
                                 "owner"   => $row['owner'],
868
                                 "token"   => $row['token'],
869
                                 "created" => $row['created'],   
870
                                 "modified" => $row['modified'],   
871
                                 "expires" => $row['expires']
872
                                 );
873
            }
874
        }
875

    
876
        return $result;
877
    }
878

    
879

    
880
    /**
881
     * create database tables for property and lock storage
882
     *
883
     * @param  void
884
     * @return bool   true on success
885
     */
886
    function create_database() 
887
    {
888
        // TODO
889
        return false;
890
    }
891
}
892

    
893

    
894
/*
895
 * Local variables:
896
 * tab-width: 4
897
 * c-basic-offset: 4
898
 * indent-tabs-mode:nil
899
 * End:
900
 */
    (1-1/1)