Overview

Namespaces

  • Jyxo
    • Beholder
      • TestCase
    • Gettext
      • Parser
    • Input
      • Chain
      • Filter
      • Validator
    • Mail
      • Email
        • Attachment
      • Parser
      • Sender
    • Rpc
      • Json
      • Xml
    • Shell
    • Spl
    • Svn
    • Time
    • Webdav
  • PHP

Classes

  • Client

Exceptions

  • Exception
  • FileNotExistException
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  * Jyxo PHP Library
  5:  *
  6:  * LICENSE
  7:  *
  8:  * This source file is subject to the new BSD license that is bundled
  9:  * with this package in the file license.txt.
 10:  * It is also available through the world-wide-web at this URL:
 11:  * https://github.com/jyxo/php/blob/master/license.txt
 12:  */
 13: 
 14: namespace Jyxo\Webdav;
 15: 
 16: /**
 17:  * Client for work with WebDav. Uses the http PHP extension.
 18:  *
 19:  * @category Jyxo
 20:  * @package Jyxo\Webdav
 21:  * @copyright Copyright (c) 2005-2011 Jyxo, s.r.o.
 22:  * @license https://github.com/jyxo/php/blob/master/license.txt
 23:  * @author Jaroslav HanslĂ­k
 24:  */
 25: class Client
 26: {
 27:     /**
 28:      * Servers list.
 29:      *
 30:      * @var array
 31:      */
 32:     private $servers = array();
 33: 
 34:     /**
 35:      * Connection options.
 36:      *
 37:      * @var array
 38:      */
 39:     private $options = array(
 40:         'connecttimeout' => 1,
 41:         'timeout' => 30
 42:     );
 43: 
 44:     /**
 45:      * Request pool.
 46:      *
 47:      * @var \HttpRequestPool
 48:      */
 49:     private $pool = null;
 50: 
 51:     /**
 52:      * Constructor.
 53:      *
 54:      * @param array $servers
 55:      */
 56:     public function __construct(array $servers)
 57:     {
 58:         $this->servers = $servers;
 59: 
 60:         $this->pool = new \HttpRequestPool();
 61:     }
 62: 
 63:     /**
 64:      * Sets an option.
 65:      *
 66:      * @param string $name Option name
 67:      * @param mixed $value Option value
 68:      */
 69:     public function setOption($name, $value)
 70:     {
 71:         $this->options[(string) $name] = $value;
 72:     }
 73: 
 74:     /**
 75:      * Checks if a file exists.
 76:      *
 77:      * @param string $path File path
 78:      * @return boolean
 79:      * @throws \Jyxo\Webdav\Exception On error
 80:      */
 81:     public function exists($path)
 82:     {
 83:         $response = $this->sendRequest($this->getFilePath($path), \HttpRequest::METH_HEAD);
 84:         return (200 === $response->getResponseCode());
 85:     }
 86: 
 87:     /**
 88:      * Returns file contents.
 89:      *
 90:      * @param string $path File path
 91:      * @return string
 92:      * @throws \Jyxo\Webdav\FileNotExistException If the file does not exist
 93:      * @throws \Jyxo\Webdav\Exception On error
 94:      */
 95:     public function get($path)
 96:     {
 97:         // Asking random server
 98:         $path = $this->getFilePath($path);
 99:         $response = $this->sendRequest($path, \HttpRequest::METH_GET);
100: 
101:         if (200 !== $response->getResponseCode()) {
102:             throw new FileNotExistException(sprintf('File %s does not exist.', $path));
103:         }
104: 
105:         return $response->getBody();
106:     }
107: 
108:     /**
109:      * Returns a file property.
110:      * If no particular property is set, all properties are returned.
111:      *
112:      * @param string $path File path
113:      * @param string $property Property name
114:      * @return mixed
115:      * @throws \Jyxo\Webdav\FileNotExistException If the file does not exist
116:      * @throws \Jyxo\Webdav\Exception On error
117:      */
118:     public function getProperty($path, $property = '')
119:     {
120:         // Asking random server
121:         $path = $this->getFilePath($path);
122:         $response = $this->sendRequest($path, \HttpRequest::METH_PROPFIND, array('Depth' => '0'));
123: 
124:         if (207 !== $response->getResponseCode()) {
125:             throw new FileNotExistException(sprintf('File %s does not exist.', $path));
126:         }
127: 
128:         // Fetches file properties from the server
129:         $properties = $this->getProperties($response);
130: 
131:         // Returns the requested property value
132:         if (isset($properties[$property])) {
133:             return $properties[$property];
134:         }
135: 
136:         // Returns all properties
137:         return $properties;
138:     }
139: 
140:     /**
141:      * Saves data to a remote file.
142:      *
143:      * @param string $path File path
144:      * @param string $data Data
145:      * @return boolean
146:      * @throws \Jyxo\Webdav\Exception On error
147:      */
148:     public function put($path, $data)
149:     {
150:         return $this->processPut($this->getFilePath($path), $data, false);
151:     }
152: 
153:     /**
154:      * Saves file contents to a remote file.
155:      *
156:      * @param string $path File path
157:      * @param string $file Local file path
158:      * @return boolean
159:      * @throws \Jyxo\Webdav\Exception On error
160:      */
161:     public function putFile($path, $file)
162:     {
163:         return $this->processPut($this->getFilePath($path), $file, true);
164:     }
165: 
166:     /**
167:      * Copies a file.
168:      * Does not work on Lighttpd.
169:      *
170:      * @param string $pathFrom Source file path
171:      * @param string $pathTo Target file path
172:      * @return boolean
173:      * @throws \Jyxo\Webdav\Exception On error
174:      */
175:     public function copy($pathFrom, $pathTo)
176:     {
177:         $requestList = $this->getRequestList($this->getFilePath($pathFrom), \HttpRequest::METH_COPY);
178:         foreach ($requestList as $server => $request) {
179:             $request->addHeaders(array('Destination' => $server . $this->getFilePath($pathTo)));
180:         }
181: 
182:         foreach ($this->sendPool($requestList) as $request) {
183:             // 201 means copied
184:             if (201 !== $request->getResponseCode()) {
185:                 return false;
186:             }
187:         }
188: 
189:         return true;
190:     }
191: 
192:     /**
193:      * Renames a file.
194:      * Does not work on Lighttpd.
195:      *
196:      * @param string $pathFrom Original file name
197:      * @param string $pathTo New file name
198:      * @return boolean
199:      * @throws \Jyxo\Webdav\Exception On error
200:      */
201:     public function rename($pathFrom, $pathTo)
202:     {
203:         $requestList = $this->getRequestList($this->getFilePath($pathFrom), \HttpRequest::METH_MOVE);
204:         foreach ($requestList as $server => $request) {
205:             $request->addHeaders(array('Destination' => $server . $this->getFilePath($pathTo)));
206:         }
207: 
208:         foreach ($this->sendPool($requestList) as $request) {
209:             // 201 means renamed
210:             if (201 !== $request->getResponseCode()) {
211:                 return false;
212:             }
213:         }
214: 
215:         return true;
216:     }
217: 
218:     /**
219:      * Deletes a file.
220:      * Contains a check preventing from deleting directories.
221:      *
222:      * @param string $path Directory path
223:      * @return boolean
224:      * @throws \Jyxo\Webdav\Exception On error
225:      */
226:     public function unlink($path)
227:     {
228:         // We do not delete directories
229:         if ($this->isDir($path)) {
230:             return false;
231:         }
232: 
233:         foreach ($this->send($this->getFilePath($path), \HttpRequest::METH_DELETE) as $request) {
234:             // 204 means deleted
235:             if (204 !== $request->getResponseCode()) {
236:                 return false;
237:             }
238:         }
239: 
240:         return true;
241:     }
242: 
243:     /**
244:      * Checks if a directory exists.
245:      *
246:      * @param string $dir Directory path
247:      * @return boolean
248:      * @throws \Jyxo\Webdav\Exception On error
249:      */
250:     public function isDir($dir)
251:     {
252:         // Asking random server
253:         $response = $this->sendRequest($this->getDirPath($dir), \HttpRequest::METH_PROPFIND, array('Depth' => '0'));
254: 
255:         // The directory does not exist
256:         if (207 !== $response->getResponseCode()) {
257:             return false;
258:         }
259: 
260:         // Fetches properties from the server
261:         $properties = $this->getProperties($response);
262: 
263:         // Checks if it is a directory
264:         return isset($properties['getcontenttype']) && ('httpd/unix-directory' === $properties['getcontenttype']);
265:     }
266: 
267:     /**
268:      * Creates a directory.
269:      *
270:      * @param string $dir Directory path
271:      * @param boolean $recursive Create directories recursively?
272:      * @return boolean
273:      * @throws \Jyxo\Webdav\Exception On error
274:      */
275:     public function mkdir($dir, $recursive = true)
276:     {
277:         // If creating directories recursively, create the parent directory first
278:         $dir = trim($dir, '/');
279:         if ($recursive) {
280:             $dirs = explode('/', $dir);
281:         } else {
282:             $dirs = array($dir);
283:         }
284: 
285:         $path = '';
286:         foreach ($dirs as $dir) {
287:             $path .= rtrim($dir);
288:             $path = $this->getDirPath($path);
289: 
290:             foreach ($this->send($path, \HttpRequest::METH_MKCOL) as $request) {
291:                 switch ($request->getResponseCode()) {
292:                     // The directory was created
293:                     case 201:
294:                         break;
295:                     // The directory already exists
296:                     case 405:
297:                         break;
298:                     // The directory could not be created
299:                     default:
300:                         return false;
301:                 }
302:             }
303:         }
304: 
305:         // The directory was created
306:         return true;
307:     }
308: 
309:     /**
310:      * Deletes a directory.
311:      *
312:      * @param string $dir Directory path
313:      * @return boolean
314:      * @throws \Jyxo\Webdav\Exception On error
315:      */
316:     public function rmdir($dir)
317:     {
318:         foreach ($this->send($this->getDirPath($dir), \HttpRequest::METH_DELETE) as $request) {
319:             // 204 means deleted
320:             if (204 !== $request->getResponseCode()) {
321:                 return false;
322:             }
323:         }
324: 
325:         return true;
326:     }
327: 
328:     /**
329:      * Processes a PUT request.
330:      *
331:      * @param string $path File path
332:      * @param string $data Data
333:      * @param boolean $isFile Determines if $data is a file name or actual data
334:      * @return boolean
335:      * @throws \Jyxo\Webdav\Exception On error
336:      */
337:     private function processPut($path, $data, $isFile)
338:     {
339:         $success = true;
340:         foreach ($this->sendPut($path, $data, $isFile) as $request) {
341:             switch ($request->getResponseCode()) {
342:                 // Saved
343:                 case 200:
344:                 case 201:
345:                     break;
346:                 // An existing file was modified
347:                 case 204:
348:                     break;
349:                 // The directory might not exist
350:                 case 403:
351:                 case 404:
352:                 case 409:
353:                     $success = false;
354:                     break;
355:                 // Could not save
356:                 default:
357:                     return false;
358:             }
359:         }
360: 
361:         // Saved
362:         if ($success) {
363:             return true;
364:         }
365: 
366:         // Not saved, try creating the directory first
367:         if (!$this->mkdir(dirname($path))) {
368:             return false;
369:         }
370: 
371:         // Try again
372:         foreach ($this->sendPut($path, $data, $isFile) as $request) {
373:             // 201 means saved
374:             if (201 !== $request->getResponseCode()) {
375:                 return false;
376:             }
377:         }
378: 
379:         return true;
380:     }
381: 
382:     /**
383:      * Sends a PUT request.
384:      *
385:      * @param string $path File path
386:      * @param string $data Data
387:      * @param boolean $isFile Determines if $data is a file name or actual data
388:      * @return \HttpRequestPool
389:      * @throws \Jyxo\Webdav\Exception On error
390:      */
391:     private function sendPut($path, $data, $isFile)
392:     {
393:         $requestList = $this->getRequestList($path, \HttpRequest::METH_PUT);
394:         foreach ($requestList as $request) {
395:             if ($isFile) {
396:                 $request->setPutFile($data);
397:             } else {
398:                 $request->setPutData($data);
399:             }
400:         }
401: 
402:         return $this->sendPool($requestList);
403:     }
404: 
405:     /**
406:      * Creates a request pool and sends it.
407:      *
408:      * @param string $path Request path
409:      * @param integer $method Request method
410:      * @param array $headers Array of headers
411:      * @return \HttpRequestPool
412:      */
413:     private function send($path, $method, array $headers = array())
414:     {
415:         return $this->sendPool($this->getRequestList($path, $method, $headers));
416:     }
417: 
418:     /**
419:      * Sends a request pool.
420:      *
421:      * @param \ArrayObject $requestList Request list
422:      * @return \HttpRequestPool
423:      * @throws \Jyxo\Webdav\Exception On error
424:      */
425:     private function sendPool(\ArrayObject $requestList)
426:     {
427:         try {
428:             // Clean the pool
429:             $this->pool->reset();
430: 
431:             // Attach requests
432:             foreach ($requestList as $request) {
433:                 $this->pool->attach($request);
434:             }
435: 
436:             // Send
437:             $this->pool->send();
438: 
439:             return $this->pool;
440:         } catch (\HttpException $e) {
441:             // Find the innermost exception
442:             $inner = $e;
443:             while (null !== $inner->innerException) {
444:                 $inner = $inner->innerException;
445:             }
446:             throw new Exception($inner->getMessage(), 0, $inner);
447:         }
448:     }
449: 
450:     /**
451:      * Sends a request.
452:      *
453:      * @param string $path Request path
454:      * @param integer $method Request method
455:      * @param array $headers Array of headers
456:      * @return \HttpMessage
457:      * @throws \Jyxo\Webdav\Exception On error
458:      */
459:     private function sendRequest($path, $method, array $headers = array())
460:     {
461:         try {
462:             // Send request to a random server
463:             $request = $this->getRequest($this->servers[array_rand($this->servers)] . $path, $method, $headers);
464:             return $request->send();
465:         } catch (\HttpException $e) {
466:             throw new Exception($e->getMessage(), 0, $e);
467:         }
468:     }
469: 
470:     /**
471:      * Returns a list of requests; one for each server.
472:      *
473:      * @param string $path Request path
474:      * @param integer $method Request method
475:      * @param array $headers Array of headers
476:      * @return \ArrayObject
477:      */
478:     private function getRequestList($path, $method, array $headers = array())
479:     {
480:         $requestList = new \ArrayObject();
481:         foreach ($this->servers as $server) {
482:             $requestList->offsetSet($server, $this->getRequest($server . $path, $method, $headers));
483:         }
484:         return $requestList;
485:     }
486: 
487:     /**
488:      * Creates a request.
489:      *
490:      * @param string $url Request URL
491:      * @param integer $method Request method
492:      * @param array $headers Array of headers
493:      * @return \HttpRequest
494:      */
495:     private function getRequest($url, $method, array $headers = array())
496:     {
497:         $request = new \HttpRequest($url, $method, $this->options);
498:         $request->setHeaders(array('Expect' => ''));
499:         $request->addHeaders($headers);
500:         return $request;
501:     }
502: 
503:     /**
504:      * Creates a file path without the trailing slash.
505:      *
506:      * @param string $path File path
507:      * @return string
508:      */
509:     private function getFilePath($path)
510:     {
511:         return '/' . trim($path, '/');
512:     }
513: 
514:     /**
515:      * Creates a directory path with the trailing slash.
516:      *
517:      * @param string $path Directory path
518:      * @return string
519:      */
520:     private function getDirPath($path)
521:     {
522:         return '/' . trim($path, '/') . '/';
523:     }
524: 
525:     /**
526:      * Fetches properties from the response.
527:      *
528:      * @param \HttpMessage $response Response
529:      * @return array
530:      */
531:     private function getProperties(\HttpMessage $response)
532:     {
533:         // Process the XML with properties
534:         $properties = array();
535:         $reader = new \Jyxo\XmlReader();
536:         $reader->XML($response->getBody());
537: 
538:         // Ignore warnings
539:         while (@$reader->read()) {
540:             if ((\XMLReader::ELEMENT === $reader->nodeType) && ('D:prop' === $reader->name)) {
541:                 while (@$reader->read()) {
542:                     // Element must not be empty and has to look something like <lp1:getcontentlength>13744</lp1:getcontentlength>
543:                     if ((\XMLReader::ELEMENT === $reader->nodeType) && (!$reader->isEmptyElement)) {
544:                         if (preg_match('~^lp\d+:(.+)$~', $reader->name, $matches)) {
545:                             // Apache
546:                             $properties[$matches[1]] = $reader->getTextValue();
547:                         } elseif (preg_match('~^D:(.+)$~', $reader->name, $matches)) {
548:                             // Lighttpd
549:                             $properties[$matches[1]] = $reader->getTextValue();
550:                         }
551:                     } elseif ((\XMLReader::END_ELEMENT === $reader->nodeType) && ('D:prop' === $reader->name)) {
552:                         break;
553:                     }
554:                 }
555:             }
556:         }
557: 
558:         return $properties;
559:     }
560: }
561: 
Jyxo PHP Library API documentation generated by ApiGen 2.3.0