1<?php 2 3/** 4 * Hoa 5 * 6 * 7 * @license 8 * 9 * New BSD License 10 * 11 * Copyright © 2007-2017, Hoa community. All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions are met: 15 * * Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * * Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * * Neither the name of the Hoa nor the names of its contributors may be 21 * used to endorse or promote products derived from this software without 22 * specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE 28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 * POSSIBILITY OF SUCH DAMAGE. 35 */ 36 37namespace Hoa\Protocol 38{ 39 40/** 41 * Class \Hoa\Protocol\Wrapper. 42 * 43 * Stream wrapper for the `hoa://` protocol. 44 * 45 * @copyright Copyright © 2007-2017 Hoa community 46 * @license New BSD License 47 */ 48class Wrapper 49{ 50 /** 51 * Opened stream. 52 * 53 * @var resource 54 */ 55 private $_stream = null; 56 57 /** 58 * Stream name (filename). 59 * 60 * @var string 61 */ 62 private $_streamName = null; 63 64 /** 65 * Stream context (given by the streamWrapper class). 66 * 67 * @var resource 68 */ 69 public $context = null; 70 71 72 73 /** 74 * Get the real path of the given URL. 75 * Could return false if the path cannot be reached. 76 * 77 * @param string $path Path (or URL). 78 * @param bool $exists If true, try to find the first that exists, 79 * @return mixed 80 */ 81 public static function realPath($path, $exists = true) 82 { 83 return Node::getRoot()->resolve($path, $exists); 84 } 85 86 /** 87 * Retrieve the underlying resource. 88 * 89 * @param int $castAs Can be STREAM_CAST_FOR_SELECT when 90 * stream_select() is calling stream_cast() or 91 * STREAM_CAST_AS_STREAM when stream_cast() is 92 * called for other uses. 93 * @return resource 94 */ 95 public function stream_cast($castAs) 96 { 97 return false; 98 } 99 100 /** 101 * Close a resource. 102 * This method is called in response to fclose(). 103 * All resources that were locked, or allocated, by the wrapper should be 104 * released. 105 * 106 * @return void 107 */ 108 public function stream_close() 109 { 110 if (true === @fclose($this->getStream())) { 111 $this->_stream = null; 112 $this->_streamName = null; 113 } 114 115 return; 116 } 117 118 /** 119 * Tests for end-of-file on a file pointer. 120 * This method is called in response to feof(). 121 * 122 * access public 123 * @return bool 124 */ 125 public function stream_eof() 126 { 127 return feof($this->getStream()); 128 } 129 130 /** 131 * Flush the output. 132 * This method is called in respond to fflush(). 133 * If we have cached data in our stream but not yet stored it into the 134 * underlying storage, we should do so now. 135 * 136 * @return bool 137 */ 138 public function stream_flush() 139 { 140 return fflush($this->getStream()); 141 } 142 143 /** 144 * Advisory file locking. 145 * This method is called in response to flock(), when file_put_contents() 146 * (when flags contains LOCK_EX), stream_set_blocking() and when closing the 147 * stream (LOCK_UN). 148 * 149 * @param int $operation Operation is one the following: 150 * * LOCK_SH to acquire a shared lock (reader) ; 151 * * LOCK_EX to acquire an exclusive lock (writer) ; 152 * * LOCK_UN to release a lock (shared or exclusive) ; 153 * * LOCK_NB if we don't want flock() to 154 * block while locking (not supported on 155 * Windows). 156 * @return bool 157 */ 158 public function stream_lock($operation) 159 { 160 return flock($this->getStream(), $operation); 161 } 162 163 /** 164 * Change stream options. 165 * This method is called to set metadata on the stream. It is called when 166 * one of the following functions is called on a stream URL: touch, chmod, 167 * chown or chgrp. 168 * 169 * @param string $path The file path or URL to set metadata. 170 * @param int $option One of the following constant: 171 * * STREAM_META_TOUCH, 172 * * STREAM_META_OWNER_NAME, 173 * * STREAM_META_OWNER, 174 * * STREAM_META_GROUP_NAME, 175 * * STREAM_META_GROUP, 176 * * STREAM_META_ACCESS. 177 * @param mixed $values Arguments of touch, chmod, chown and chgrp. 178 * @return bool 179 */ 180 public function stream_metadata($path, $option, $values) 181 { 182 $path = static::realPath($path, false); 183 184 switch ($option) { 185 case STREAM_META_TOUCH: 186 $arity = count($values); 187 188 if (0 === $arity) { 189 $out = touch($path); 190 } elseif (1 === $arity) { 191 $out = touch($path, $values[0]); 192 } else { 193 $out = touch($path, $values[0], $values[1]); 194 } 195 196 break; 197 198 case STREAM_META_OWNER_NAME: 199 case STREAM_META_OWNER: 200 $out = chown($path, $values); 201 202 break; 203 204 case STREAM_META_GROUP_NAME: 205 case STREAM_META_GROUP: 206 $out = chgrp($path, $values); 207 208 break; 209 210 case STREAM_META_ACCESS: 211 $out = chmod($path, $values); 212 213 break; 214 215 default: 216 $out = false; 217 } 218 219 return $out; 220 } 221 222 /** 223 * Open file or URL. 224 * This method is called immediately after the wrapper is initialized (f.e. 225 * by fopen() and file_get_contents()). 226 * 227 * @param string $path Specifies the URL that was passed to the 228 * original function. 229 * @param string $mode The mode used to open the file, as 230 * detailed for fopen(). 231 * @param int $options Holds additional flags set by the 232 * streams API. It can hold one or more of 233 * the following values OR'd together: 234 * * STREAM_USE_PATH, if path is relative, 235 * search for the resource using the 236 * include_path; 237 * * STREAM_REPORT_ERRORS, if this is 238 * set, you are responsible for raising 239 * errors using trigger_error during 240 * opening the stream. If this is not 241 * set, you should not raise any errors. 242 * @param string &$openedPath If the $path is opened successfully, and 243 * STREAM_USE_PATH is set in $options, 244 * $openedPath should be set to the full 245 * path of the file/resource that was 246 * actually opened. 247 * @return bool 248 */ 249 public function stream_open($path, $mode, $options, &$openedPath) 250 { 251 $path = static::realPath($path, 'r' === $mode[0]); 252 253 if (Protocol::NO_RESOLUTION === $path) { 254 return false; 255 } 256 257 if (null === $this->context) { 258 $openedPath = fopen($path, $mode, $options & STREAM_USE_PATH); 259 } else { 260 $openedPath = fopen( 261 $path, 262 $mode, 263 $options & STREAM_USE_PATH, 264 $this->context 265 ); 266 } 267 268 if (false === is_resource($openedPath)) { 269 return false; 270 } 271 272 $this->_stream = $openedPath; 273 $this->_streamName = $path; 274 275 return true; 276 } 277 278 /** 279 * Read from stream. 280 * This method is called in response to fread() and fgets(). 281 * 282 * @param int $count How many bytes of data from the current 283 * position should be returned. 284 * @return string 285 */ 286 public function stream_read($count) 287 { 288 return fread($this->getStream(), $count); 289 } 290 291 /** 292 * Seek to specific location in a stream. 293 * This method is called in response to fseek(). 294 * The read/write position of the stream should be updated according to the 295 * $offset and $whence. 296 * 297 * @param int $offset The stream offset to seek to. 298 * @param int $whence Possible values: 299 * * SEEK_SET to set position equal to $offset 300 * bytes ; 301 * * SEEK_CUR to set position to current 302 * location plus $offsete ; 303 * * SEEK_END to set position to end-of-file 304 * plus $offset. 305 * @return bool 306 */ 307 public function stream_seek($offset, $whence = SEEK_SET) 308 { 309 return 0 === fseek($this->getStream(), $offset, $whence); 310 } 311 312 /** 313 * Retrieve information about a file resource. 314 * This method is called in response to fstat(). 315 * 316 * @return array 317 */ 318 public function stream_stat() 319 { 320 return fstat($this->getStream()); 321 } 322 323 /** 324 * Retrieve the current position of a stream. 325 * This method is called in response to ftell(). 326 * 327 * @return int 328 */ 329 public function stream_tell() 330 { 331 return ftell($this->getStream()); 332 } 333 334 /** 335 * Truncate a stream to a given length. 336 * 337 * @param int $size Size. 338 * @return bool 339 */ 340 public function stream_truncate($size) 341 { 342 return ftruncate($this->getStream(), $size); 343 } 344 345 /** 346 * Write to stream. 347 * This method is called in response to fwrite(). 348 * 349 * @param string $data Should be stored into the underlying stream. 350 * @return int 351 */ 352 public function stream_write($data) 353 { 354 return fwrite($this->getStream(), $data); 355 } 356 357 /** 358 * Close directory handle. 359 * This method is called in to closedir(). 360 * Any resources which were locked, or allocated, during opening and use of 361 * the directory stream should be released. 362 * 363 * @return void 364 */ 365 public function dir_closedir() 366 { 367 closedir($this->getStream()); 368 $this->_stream = null; 369 $this->_streamName = null; 370 371 return; 372 } 373 374 /** 375 * Open directory handle. 376 * This method is called in response to opendir(). 377 * 378 * @param string $path Specifies the URL that was passed to opendir(). 379 * @param int $options Whether or not to enforce safe_mode (0x04). 380 * It is not used here. 381 * @return bool 382 */ 383 public function dir_opendir($path, $options) 384 { 385 $path = static::realPath($path); 386 $handle = null; 387 388 if (null === $this->context) { 389 $handle = @opendir($path); 390 } else { 391 $handle = @opendir($path, $this->context); 392 } 393 394 if (false === $handle) { 395 return false; 396 } 397 398 $this->_stream = $handle; 399 $this->_streamName = $path; 400 401 return true; 402 } 403 404 /** 405 * Read entry from directory handle. 406 * This method is called in response to readdir(). 407 * 408 * @return mixed 409 */ 410 public function dir_readdir() 411 { 412 return readdir($this->getStream()); 413 } 414 415 /** 416 * Rewind directory handle. 417 * This method is called in response to rewinddir(). 418 * Should reset the output generated by self::dir_readdir, i.e. the next 419 * call to self::dir_readdir should return the first entry in the location 420 * returned by self::dir_opendir. 421 * 422 * @return void 423 */ 424 public function dir_rewinddir() 425 { 426 return rewinddir($this->getStream()); 427 } 428 429 /** 430 * Create a directory. 431 * This method is called in response to mkdir(). 432 * 433 * @param string $path Directory which should be created. 434 * @param int $mode The value passed to mkdir(). 435 * @param int $options A bitwise mask of values. 436 * @return bool 437 */ 438 public function mkdir($path, $mode, $options) 439 { 440 if (null === $this->context) { 441 return mkdir( 442 static::realPath($path, false), 443 $mode, 444 $options | STREAM_MKDIR_RECURSIVE 445 ); 446 } 447 448 return mkdir( 449 static::realPath($path, false), 450 $mode, 451 $options | STREAM_MKDIR_RECURSIVE, 452 $this->context 453 ); 454 } 455 456 /** 457 * Rename a file or directory. 458 * This method is called in response to rename(). 459 * Should attempt to rename $from to $to. 460 * 461 * @param string $from The URL to current file. 462 * @param string $to The URL which $from should be renamed to. 463 * @return bool 464 */ 465 public function rename($from, $to) 466 { 467 if (null === $this->context) { 468 return rename(static::realPath($from), static::realPath($to, false)); 469 } 470 471 return rename( 472 static::realPath($from), 473 static::realPath($to, false), 474 $this->context 475 ); 476 } 477 478 /** 479 * Remove a directory. 480 * This method is called in response to rmdir(). 481 * 482 * @param string $path The directory URL which should be removed. 483 * @param int $options A bitwise mask of values. It is not used 484 * here. 485 * @return bool 486 */ 487 public function rmdir($path, $options) 488 { 489 if (null === $this->context) { 490 return rmdir(static::realPath($path)); 491 } 492 493 return rmdir(static::realPath($path), $this->context); 494 } 495 496 /** 497 * Delete a file. 498 * This method is called in response to unlink(). 499 * 500 * @param string $path The file URL which should be deleted. 501 * @return bool 502 */ 503 public function unlink($path) 504 { 505 if (null === $this->context) { 506 return unlink(static::realPath($path)); 507 } 508 509 return unlink(static::realPath($path), $this->context); 510 } 511 512 /** 513 * Retrieve information about a file. 514 * This method is called in response to all stat() related functions. 515 * 516 * @param string $path The file URL which should be retrieve 517 * information about. 518 * @param int $flags Holds additional flags set by the streams API. 519 * It can hold one or more of the following 520 * values OR'd together. 521 * STREAM_URL_STAT_LINK: for resource with the 522 * ability to link to other resource (such as an 523 * HTTP location: forward, or a filesystem 524 * symlink). This flag specified that only 525 * information about the link itself should be 526 * returned, not the resource pointed to by the 527 * link. This flag is set in response to calls to 528 * lstat(), is_link(), or filetype(). 529 * STREAM_URL_STAT_QUIET: if this flag is set, 530 * our wrapper should not raise any errors. If 531 * this flag is not set, we are responsible for 532 * reporting errors using the trigger_error() 533 * function during stating of the path. 534 * @return array 535 */ 536 public function url_stat($path, $flags) 537 { 538 $path = static::realPath($path); 539 540 if (Protocol::NO_RESOLUTION === $path) { 541 if ($flags & STREAM_URL_STAT_QUIET) { 542 return 0; 543 } else { 544 return trigger_error( 545 'Path ' . $path . ' cannot be resolved.', 546 E_WARNING 547 ); 548 } 549 } 550 551 if ($flags & STREAM_URL_STAT_LINK) { 552 return @lstat($path); 553 } 554 555 return @stat($path); 556 } 557 558 /** 559 * Get stream resource. 560 * 561 * @return resource 562 */ 563 public function getStream() 564 { 565 return $this->_stream; 566 } 567 568 /** 569 * Get stream name. 570 * 571 * @return resource 572 */ 573 public function getStreamName() 574 { 575 return $this->_streamName; 576 } 577} 578 579/** 580 * Register the `hoa://` protocol. 581 */ 582stream_wrapper_register('hoa', Wrapper::class); 583 584} 585 586namespace 587{ 588 589/** 590 * Alias of `Hoa\Protocol::resolve` method. 591 * 592 * @param string $path Path to resolve. 593 * @param bool $exists If `true`, try to find the first that exists, 594 * else return the first solution. 595 * @param bool $unfold Return all solutions instead of one. 596 * @return mixed 597 */ 598if (!function_exists('resolve')) { 599 function resolve($path, $exists = true, $unfold = false) 600 { 601 return Hoa\Protocol::getInstance()->resolve($path, $exists, $unfold); 602 } 603} 604 605} 606