/** * Handles a POST request * @param DAV_Resource $resource * @return void * @throws DAV_Status */ public function handle($resource) { $resource->assertLock(); $headers = array(); try { ob_start(); $entity = $resource->method_POST($headers); } catch (DAV_Status $e) { ob_end_clean(); throw $e; } if ($length = ob_get_length()) { $headers['Content-Length'] = $length; DAV::header($headers); ob_end_flush(); return; } else { ob_end_clean(); } if (is_string($entity)) { $headers['Content-Length'] = strlen($entity); DAV::header($headers); echo $entity; return; } DAV::header($headers); }
/** * Returns the HTTP 405 Method Not Allowed status code * * This will only be called when an unknown/unsupported HTTP method is used. So * We'll return the correct status code and explain which methods are allowed. * * @param DAV_Resource $resource * @return void * @throws DAV_Status */ public function handle($resource) { $allow = implode(', ', self::$ALLOWED_METHODS); DAV::header("Allow: {$allow}"); $status = new DAV_Status(DAV::HTTP_METHOD_NOT_ALLOWED, "Allowed methods: {$allow}"); $status->output(); }
/** * Handles the UNLOCK request * * @param DAV_Resource $resource * @return void * @throws DAV_Status */ protected function handle($resource) { if (!DAV::$LOCKPROVIDER) { throw new DAV_Status(DAV::HTTP_FORBIDDEN); } $lock = DAV::$LOCKPROVIDER->getlock(DAV::getPath()); if (!$lock || $this->locktoken !== $lock->locktoken) { throw new DAV_Status(DAV::HTTP_CONFLICT, DAV::COND_LOCK_TOKEN_MATCHES_REQUEST_URI); } DAV::$LOCKPROVIDER->unlock($lock->lockroot); DAV::header(array('status' => DAV::HTTP_NO_CONTENT)); }
/** * Checks that the Depth header is correct and then handles a DELETE request * @param DAV_Resource $resource * @return void * @throws DAV_Status */ protected function handle($resource) { if (DAV::DEPTH_INF !== $this->depth()) { throw new DAV_Status(DAV::HTTP_BAD_REQUEST, 'Only Depth: infinity is allowed for DELETE requests.'); } self::delete($resource); if (DAV_Multistatus::active()) { DAV_Multistatus::inst()->close(); } else { DAV::header(array('status' => DAV::HTTP_NO_CONTENT)); } }
/** * Handles the OPTIONS request * @param DAV_Resource $resource * @return void * @throws DAV_Status */ protected function handle($resource) { $headers = array('DAV' => array('1' . (DAV::$LOCKPROVIDER ? ', 2' : '') . ', 3', 'access-control', '<http://apache.org/dav/propset/fs/1>'), 'MS-Author-Via' => 'DAV', 'Allow' => implode(', ', self::$ALLOWED_METHODS), 'Content-Length' => 0); if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) { $headers['Access-Control-Allow-Methods'] = $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']; } if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) { $headers['Access-Control-Allow-Headers'] = $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']; } if ($resource instanceof DAV_Resource) { DAV::header($resource->method_OPTIONS($headers)); } else { DAV::header($headers); } }
/** * This should be a identical copy of DAV_Multistatus::__construct() */ private function __construct() { DAV::header(array('Content-Type' => 'application/xml; charset="utf-8"', 'status' => DAV::HTTP_MULTI_STATUS)); echo DAV::xml_header() . '<D:multistatus xmlns:D="DAV:">'; }
public function testHeader() { ob_start(); DAV::header(array('status' => DAV::HTTP_EXPECTATION_FAILED, 'x-test-header' => 'with a test value')); $returnedValue = ob_get_contents(); ob_end_clean(); $this->assertSame("x-test-header: with a test value\nHTTP/1.1 417 Expectation Failed\n", $returnedValue, 'DAV::header() should print the correct headers (print them, not sent them, because we\'re in testing mode'); }
/** * Handle the HEAD request * * @param DAV_Resource $resource * @return void * @throws DAV_Status */ protected function handle($resource) { $headers = self::common($resource); DAV::header($headers); return; }
/** * Serve WebDAV HTTP request. * @param DAV_Registry $registry */ public function handleRequest() { // We want to catch every exception thrown in this code, and report about it // to the user appropriately. $shallow_lock = false; try { $shallow_lock = $this->check_if_headers(); $resource = DAV::$REGISTRY->resource(DAV::getPath()); if (!$resource || !$resource->isVisible() and in_array($_SERVER['REQUEST_METHOD'], array('ACL', 'COPY', 'DELETE', 'GET', 'HEAD', 'MOVE', 'OPTIONS', 'POST', 'PROPFIND', 'PROPPATCH', 'REPORT', 'UNLOCK'))) { throw new DAV_Status(DAV::HTTP_NOT_FOUND); } if ('/' !== substr(DAV::getPath(), -1) && ($resource && $resource instanceof DAV_Collection || 'MKCOL' === $_SERVER['REQUEST_METHOD'])) { $newPath = DAV::getPath() . '/'; DAV::setPath(DAV::encodeURIFullPath($newPath)); DAV::header(array('Content-Location' => DAV::path2uri($newPath))); } $this->handle($resource); } catch (Exception $e) { if (!$e instanceof DAV_Status) { $e = new DAV_Status(DAV::HTTP_INTERNAL_SERVER_ERROR, "{$e}"); } $e->output(); } if ($shallow_lock) { DAV::$REGISTRY->shallowUnlock(); } if (DAV_Multistatus::active()) { DAV_Multistatus::inst()->close(); } }
/** * Shows a decent HTML page with an error for the end-user * @param type $message The message to show. Could contain HTML. * @param type $status The HTTP status code to return * @return void */ public static function htmlError($message, $status = DAV::HTTP_OK) { DAV::header(array('status' => $status)); require 'views/' . 'html_error.php'; exit; }
/** * Checks and handles the GET request * * @param DAV_Resource $resource * @return void * @throws DAV_Status */ protected function handle($resource) { $headers = self::common($resource); try { ob_start(); $entity = $resource->method_GET(); } catch (DAV_Status $e) { ob_end_clean(); throw $e; } if ($length = ob_get_length()) { $headers['Content-Length'] = $length; DAV::header($headers); ob_end_flush(); return; } else { ob_end_clean(); } if (!is_resource($entity)) { $entity = "{$entity}"; $headers['Content-Length'] = strlen($entity); DAV::header($headers); echo "{$entity}"; return; } // GET handler returned a stream // Try to find out the length of the total entity: if (isset($headers['Content-Length'])) { $entity_length = (int) $headers['Content-Length']; } else { $stat = @fstat($entity); $entity_length = !is_array($stat) || !isset($stat['size']) ? '*' : $stat['size']; } // process Range: header if present $ranges = self::range_header($entity_length); if (isset($headers['status']) && substr($header['status'], 0, 3) !== '200' || empty($ranges)) { // No byte ranges, or unexpected status code. // We just relay everything as-is. DAV::header($headers); $time = time() + 60; while (!feof($entity)) { if (time() > $time) { set_time_limit(120); $time = time() + 60; } echo fread($entity, DAV::$CHUNK_SIZE); } fclose($entity); return; } // One or more Ranges! $headers['status'] = DAV::HTTP_PARTIAL_CONTENT; if (1 === count($ranges)) { $range = $ranges[0]; $content_length = $range['end'] - $range['start'] + 1; $headers['Content-Length'] = $content_length; $headers['Content-Range'] = "bytes {$range['start']}-{$range['end']}/{$entity_length}"; DAV::header($headers); if (0 !== fseek($entity, $range['start'], SEEK_SET)) { // The stream is not seekable $size = $range['start']; while ($size && !feof($entity)) { $buffer = fread($entity, $size < DAV::$CHUNK_SIZE ? $size : DAV::$CHUNK_SIZE); $size -= strlen($buffer); } } // fpassthru() seems to be buggy when the stream handle points to the start (byte 0) of a stream, so let's not use it (but do some testing some place else) // You can uncomment this if statement and test if it works by applying the Range header to a GET request for a file larger than 2 or 2.5 GB: Range: bytes=0- // if ( $entity_length === $range['end'] + 1 ) { // fpassthru($entity); // }else{ $time = time() + 60; $size = $content_length; while ($size && !feof($entity)) { $buffer = fread($entity, $size < DAV::$CHUNK_SIZE ? $size : DAV::$CHUNK_SIZE); $size -= strlen($buffer); echo $buffer; if (time() > $time) { set_time_limit(120); $time = time() + 60; } } if ($size) { trigger_error(var_export(debug_backtrace(), true), E_USER_WARNING); } // } fclose($entity); return; } // Multiple ranges! $multipart_separator = 'SDisk_' . strtr(microtime(), '. ', '__'); // Remove all Content-* headers from the HTTP response headers. // They are moved to the body parts. $partheaders = array(); foreach (array_keys($headers) as $header) { if (substr(strtolower($header), 0, 8) === 'content-') { $partheaders[$header] = $headers[$header]; unset($headers[$header]); } } $headers['Content-Type'] = "multipart/byteranges; boundary={$multipart_separator}"; DAV::header($headers); echo "This is a message in multipart MIME format.\r\n"; $current_position = 0; foreach ($ranges as $range) { if (0 === fseek($entity, $range['start'], SEEK_SET)) { $current_position = $range['start']; } elseif ($range['start'] >= $current_position) { $skip = $range['start'] - $current_position; while ($skip && !feof($entity)) { $buffer = fread($entity, $skip < DAV::$CHUNK_SIZE ? $skip : DAV::$CHUNK_SIZE); $skip -= strlen($buffer); echo $buffer; } $current_position = $range['start'] - $skip; if ($skip) { $current_position = $range['start'] - $skip; trigger_error(var_export(debug_backtrace(), true), E_USER_WARNING); continue; } } else { trigger_error(var_export(debug_backtrace(), true), E_USER_WARNING); continue; } echo "\r\n--{$multipart_separator}\r\n"; $partheaders['Content-Range'] = "{$range['start']}-{$range['end']}/{$entity_length}"; $partheaders['Content-Length'] = $range['end'] - $range['start'] + 1; foreach ($partheaders as $header => $value) { echo "{$header}: {$value}\r\n"; } echo "\r\n"; if ($entity_length === $range['end'] + 1) { fpassthru($entity); } else { $size = $range['end'] - $range['start'] + 1; while ($size && !feof($entity)) { $buffer = fread($entity, $size < DAV::$CHUNK_SIZE ? $size : DAV::$CHUNK_SIZE); $size -= strlen($buffer); echo $buffer; } $current_position = $range['end'] + 1 - $size; if ($size) { trigger_error(var_export(debug_backtrace(), true), E_USER_WARNING); } } } echo "\r\n--{$multipart_separator}--\r\n"; fclose($entity); }
/** * This method is called when DAV receives an 401 Unauthenticated exception. * @return bool true if a response has been sent to the user. */ public function unauthorized() { DAV::header(array('WWW-Authenticate' => 'Basic realm="' . BeeHub::$CONFIG['authentication']['realm'] . '"', 'Content-Type' => BeeHub::best_xhtml_type())); BeeHub::htmlError(file_get_contents(dirname(dirname(__FILE__)) . '/views/error_unauthorized.html'), DAV::HTTP_UNAUTHORIZED); }
/** * Refreshes an already existing lock * * @param DAV_Resource $resource * @return void * @throws DAV_Statuss */ private function handleRefreshLock($resource) { $if_header = $this->if_header; if (!isset($if_header[DAV::getPath()]) || !$if_header[DAV::getPath()]['lock']) { throw new DAV_Status(DAV::HTTP_BAD_REQUEST, array(DAV::COND_LOCK_TOKEN_SUBMITTED => new DAV_Element_href(DAV::getPath()))); } // I think this can never evaluate to true, because DAV_Request already checks // whether the 'If' header matches the lock token of the resource. So if the // resource doesn't have a lock, this is already detected before this method // is called! (However, I don't dare to delete this yet and it doesn't hurt to // keep it) if (!($lock = DAV::$LOCKPROVIDER->getlock(DAV::getPath()))) { throw new DAV_Status(DAV::HTTP_PRECONDITION_FAILED, array(DAV::COND_LOCK_TOKEN_MATCHES_REQUEST_URI)); } DAV::$LOCKPROVIDER->refresh($lock->lockroot, $lock->locktoken, $this->timeout); if (!($lockdiscovery = $resource->prop_lockdiscovery())) { throw new DAV_Status(DAV::HTTP_INTERNAL_SERVER_ERROR); } // Generate output: DAV::header('application/xml; charset="utf-8"'); echo DAV::xml_header() . '<D:prop xmlns:D="DAV:"><D:lockdiscovery>' . $lockdiscovery . '</D:lockdiscovery></D:prop>'; }
/** * Handles the PUT request * * This method checks whether the PUT request is valid and, if so, writes the * request body to the resource. * * @param DAV_Resource $resource */ protected function handle($resource) { # This variable also flags if a new resource was created. $parent = null; if (!$resource) { if (!is_null($this->range_start)) { throw new DAV_Status(DAV::HTTP_NOT_FOUND); } $parent = DAV::$REGISTRY->resource(dirname(DAV::getPath())); if (!$parent || !$parent instanceof DAV_Collection) { throw new DAV_Status(DAV::HTTP_CONFLICT, 'Unable to PUT file in non-existing collection.'); } $parent->assertLock(); $parent->create_member(basename(DAV::getPath())); $resource = DAV::$REGISTRY->resource(DAV::getPath()); } elseif ($resource instanceof DAV_Collection) { throw new DAV_Status(DAV::HTTP_METHOD_NOT_ALLOWED, 'Method PUT not supported on collections.'); } else { $resource->assertLock(); } if (is_null($this->range_start)) { try { if (isset($_SERVER['CONTENT_TYPE']) && 'application/octet-stream' !== $_SERVER['CONTENT_TYPE']) { $resource->set_getcontenttype($_SERVER['CONTENT_TYPE']); } else { $resource->set_getcontenttype(null); } } catch (DAV_Status $e) { } if (isset($_SERVER['HTTP_CONTENT_LANGUAGE'])) { try { $resource->set_getcontentlanguage($_SERVER['HTTP_CONTENT_LANGUAGE']); } catch (DAV_Status $e) { } } $resource->storeProperties(); $input = fopen('php://input', 'r'); try { $resource->method_PUT($input); fclose($input); } catch (DAV_Status $e) { fclose($input); if ($parent) { $parent->method_DELETE(basename(DAV::getPath())); } throw $e; } } else { $cl = $resource->user_prop_getcontentlength(); if (!is_null($cl) && ($this->range_start > $cl or !is_null($this->range_total) && $this->range_total !== $cl)) { throw new DAV_Status(DAV::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE); } $input = fopen('php://input', 'r'); try { $resource->method_PUT_range($input, $this->range_start, $this->range_end, $this->range_total); fclose($input); } catch (DAV_Status $e) { fclose($input); throw $e; } } if ($etag = $resource->prop_getetag()) { header('ETag: ' . htmlspecialchars_decode($etag)); } if ($parent) { DAV::redirect(DAV::HTTP_CREATED, DAV::getPath()); } else { DAV::header(array('status' => DAV::HTTP_NO_CONTENT)); } }
/** * Sends this status to client. * @return void */ public function output() { $status = $this->getCode(); if ($status < 300) { throw new DAV_Status(DAV::HTTP_INTERNAL_SERVER_ERROR, "DAV_Status object with status {$status} " . var_export($this->getMessage(), true)); } if (DAV::HTTP_UNAUTHORIZED === $status && DAV::$UNAUTHORIZED) { call_user_func(DAV::$UNAUTHORIZED); return; } elseif (!empty($this->conditions)) { $headers = array('status' => $status, 'Content-Type' => 'application/xml; charset="UTF-8"'); if ($this->location) { $headers['Location'] = DAV::encodeURIFullPath($this->location); } DAV::header($headers); echo DAV::xml_header() . '<D:error xmlns:D="DAV:">'; foreach ($this->conditions as $condition => $xml) { echo "\n<D:" . $condition; echo $xml ? ">{$xml}</D:{$condition}>" : "/>"; } echo "\n</D:error>"; } elseif ($this->location) { DAV::redirect($status, $this->location); } else { if (self::$RESPONSE_GENERATOR && in_array($_SERVER['REQUEST_METHOD'], array('GET', 'POST'))) { DAV::header(array('status' => $status)); call_user_func(self::$RESPONSE_GENERATOR, $status, $this->getMessage()); } else { DAV::header(array('status' => $status, 'Content-Type' => 'text/plain; charset="UTF-8"')); echo "HTTP/1.1 " . DAV::status_code($status) . "\n" . $this->getMessage(); } } }
/** * Determines whether the copy request is valid and if so, copies the resources * * @param DAV_Resource $resource * @return void * @throws DAV_Status */ protected function handle($resource) { $destination = $this->destination(); if ($resource instanceof DAV_Collection) { $destination = DAV::slashify($destination); } else { // The next line is here to make the litmus test succeed. The author of // litmus had eir own doubts wether this is actually desirable behaviour, // but chose to require this behaviour anyway: $destination = DAV::unslashify($destination); } // Can't move the root collection: if ($this instanceof DAV_Request_MOVE && '/' === DAV::getPath()) { throw new DAV_Status(DAV::HTTP_FORBIDDEN); } // Assert proper Depth: header value: if (DAV::DEPTH_1 === $this->depth() or $this instanceof DAV_Request_MOVE && DAV::DEPTH_INF !== $this->depth()) { throw new DAV_Status(DAV::HTTP_BAD_REQUEST, 'Illegal value for Depth: header.'); } // Check: Can't move a collection to one of its members. if ($this instanceof DAV_Request_MOVE && '/' === substr(DAV::getPath(), -1) && 0 === strpos($destination, DAV::getPath())) { throw new DAV_Status(DAV::HTTP_FORBIDDEN, "Can't move a collection to itself or one of its members."); } $resourceCollection = $resource->collection(); if ($this instanceof DAV_Request_MOVE) { $resourceCollection->assertLock(); $resource->assertLock(); $resource->assertMemberLocks(); } if ('/' !== $destination[0]) { // Copy to an external URI? $isCreated = $resource->method_COPY_external($destination, $this->overwrite()); if ($this instanceof DAV_Request_MOVE && !DAV_Multistatus::active()) { DAV_Request_DELETE::delete($resource); } if (DAV_Multistatus::active()) { DAV_Multistatus::inst()->close(); } elseif ($isCreated) { DAV::redirect(DAV::HTTP_CREATED, $destination); } else { DAV::header(array('status' => DAV::HTTP_NO_CONTENT)); } return; } // Check: Won't move a resource to one of its parents. if (0 === strpos(DAV::slashify(DAV::getPath()), DAV::slashify($destination))) { throw new DAV_Status(DAV::HTTP_NOT_IMPLEMENTED, "Won't move or copy a resource to one of its parents."); } $destinationResource = DAV::$REGISTRY->resource($destination); $destinationCollection = DAV::$REGISTRY->resource(dirname($destination)); if (!$destinationCollection) { throw new DAV_Status(DAV::HTTP_CONFLICT, 'Unable to COPY to unexisting destination collection'); } if ($destinationResource) { if (!$this->overwrite()) { throw new DAV_Status(DAV::HTTP_PRECONDITION_FAILED); } else { $destinationResource->assertLock(); } } else { $destinationCollection->assertLock(); } if ($this instanceof DAV_Request_MOVE) { if (DAV::$LOCKPROVIDER) { foreach (DAV::$LOCKPROVIDER->memberLocks(DAV::getPath()) as $lock) { DAV::$LOCKPROVIDER->unlock($lock->lockroot); } if ($lock = DAV::$LOCKPROVIDER->getlock(DAV::getPath())) { DAV::$LOCKPROVIDER->unlock($lock->lockroot); } } $resourceCollection->method_MOVE(basename($resource->path), $destination); } else { $this->copy_recursively($resource, $destination); } #<<<<<<<< #// This version always returns a 207 Multistatus wrapper: #if (!DAV_Multistatus::active()) # if ( $destinationResource ) # DAV_Multistatus::inst()->addStatus( # $resource->path, # new DAV_Status( DAV::HTTP_NO_CONTENT ) # ); # else # DAV_Multistatus::inst()->addStatus( # $resource->path, # new DAV_Status( # DAV::HTTP_CREATED, DAV::path2uri($destination) # ) # ); #DAV_Multistatus::inst()->close(); #======== if (DAV_Multistatus::active()) { DAV_Multistatus::inst()->close(); } elseif ($destinationResource) { DAV::header(array('status' => DAV::HTTP_NO_CONTENT)); } else { DAV::redirect(DAV::HTTP_CREATED, $destination); } #>>>>>>>> }