/** * @static * @throws \Exception * @return User */ public static function authenticateHttpBasic() { // we're using Sabre\HTTP for basic auth $request = \Sabre\HTTP\Sapi::getRequest(); $response = new \Sabre\HTTP\Response(); $auth = new \Sabre\HTTP\Auth\Basic(Tool::getHostname(), $request, $response); $result = $auth->getCredentials(); if (is_array($result)) { list($username, $password) = $result; $user = self::authenticatePlaintext($username, $password); if ($user) { return $user; } } $auth->requireLogin(); $response->setBody("Authentication required"); \Logger::error("Authentication Basic (WebDAV) required"); \Sabre\HTTP\Sapi::sendResponse($response); die; }
/** * Start the server, trigger an exception and see if the logger captured * it. */ function testLogException() { $server = new Server(); $logger = new MockLogger(); $server->setLogger($logger); // Creating a fake environment to execute http requests in. $request = new \Sabre\HTTP\Request('GET', '/not-found', []); $response = new \Sabre\HTTP\Response(); $server->httpRequest = $request; $server->httpResponse = $response; $server->sapi = new \Sabre\HTTP\SapiMock(); // Executing the request. $server->exec(); // The request should have triggered a 404 status. $this->assertEquals(404, $response->getStatus()); // We should also see this in the PSR-3 log. $this->assertEquals(1, count($logger->logs)); $logItem = $logger->logs[0]; $this->assertEquals(\Psr\Log\LogLevel::INFO, $logItem[0]); $this->assertInstanceOf('Exception', $logItem[2]['exception']); }
function handleRequest(RequestInterface $request) { $sabreRequest = \Sabre\HTTP\Sapi::createFromServerArray($request->getParams()); $sabreRequest->setBody($request->getStdin()); if ($sabreRequest->getMethod() === 'POST' && $sabreRequest->getHeader('Content-Type') === 'application/x-www-form-urlencoded') { parse_str($sabreRequest->getBodyAsString(), $postData); $sabreRequest->setPostData($postData); } $sabreResponse = new \Sabre\HTTP\Response(); $this->server->httpRequest = $sabreRequest; $this->server->httpResponse = $sabreResponse; $this->server->exec(); $body = $sabreResponse->getBody(); if (is_scalar($body) || is_null($body)) { $newBody = fopen('php://memory', 'r+'); fwrite($newBody, (string) $body); rewind($newBody); $body = $newBody; } // Turning sabre into psr-7 response. $psr7Response = new ZendResponse($body, $sabreResponse->getStatus(), $sabreResponse->getHeaders()); return $psr7Response; }
/** * */ public function process() { $this->emit('process:before', [['request' => $this->httpRequest]]); // set Content Security Policy and CORS headers $this->httpResponse->addHeader('Content-Security-Policy', "default-src *"); $this->httpResponse->addHeader('X-Content-Security-Policy', "default-src *"); if ($this->httpRequest->hasHeader('Origin')) { // TODO: allow to configure allowed origins $this->httpResponse->addHeader('Access-Control-Allow-Origin', "*"); } // FIXME: respond to OPTIONS requests directly and without validation if ($this->httpRequest->getMethod() == 'OPTIONS') { $this->httpResponse->addHeader('Access-Control-Request-Method', 'GET, POST, OPTIONS'); $this->httpResponse->addHeader('Access-Control-Allow-Headers', $this->httpRequest->getHeader('Access-Control-Request-Headers')); $this->httpResponse->setStatus(204); $this->sapi->sendResponse($this->httpResponse); return; } // extract route from request (jmap, auth|.well-known/jmap, upload) if ($route = $this->getRouteMatch($this->httpRequest->getPath())) { try { call_user_func($this->routes[$route], $this->httpRequest, $this->httpResponse); } catch (\RuntimeException $e) { if ($e instanceof Exception\ProcessorException) { $this->httpResponse->setStatus($e->getStatusCode()); } else { $this->httpResponse->setStatus(500); } $this->logger->err(strval($e)); $this->emit('process:error', [['request' => $this->httpRequest, 'exception' => $e]]); } } else { // TODO: throw invalid route error $this->httpResponse->setStatus(404); } $this->emit('process:after', [['response' => $this->httpResponse]]); $this->sapi->sendResponse($this->httpResponse); }
/** * Starts the DAV Server * * @return void */ function exec() { try { // If nginx (pre-1.2) is used as a proxy server, and SabreDAV as an // origin, we must make sure we send back HTTP/1.0 if this was // requested. // This is mainly because nginx doesn't support Chunked Transfer // Encoding, and this forces the webserver SabreDAV is running on, // to buffer entire responses to calculate Content-Length. $this->httpResponse->setHTTPVersion($this->httpRequest->getHTTPVersion()); // Setting the base url $this->httpRequest->setBaseUrl($this->getBaseUri()); $this->invokeMethod($this->httpRequest, $this->httpResponse); } catch (\Exception $e) { try { $this->emit('exception', [$e]); } catch (\Exception $ignore) { } $DOM = new \DOMDocument('1.0', 'utf-8'); $DOM->formatOutput = true; $error = $DOM->createElementNS('DAV:', 'd:error'); $error->setAttribute('xmlns:s', self::NS_SABREDAV); $DOM->appendChild($error); $h = function($v) { return htmlspecialchars($v, ENT_NOQUOTES, 'UTF-8'); }; if (self::$exposeVersion) { $error->appendChild($DOM->createElement('s:sabredav-version', $h(Version::VERSION))); } $error->appendChild($DOM->createElement('s:exception', $h(get_class($e)))); $error->appendChild($DOM->createElement('s:message', $h($e->getMessage()))); if ($this->debugExceptions) { $error->appendChild($DOM->createElement('s:file', $h($e->getFile()))); $error->appendChild($DOM->createElement('s:line', $h($e->getLine()))); $error->appendChild($DOM->createElement('s:code', $h($e->getCode()))); $error->appendChild($DOM->createElement('s:stacktrace', $h($e->getTraceAsString()))); } if ($this->debugExceptions) { $previous = $e; while ($previous = $previous->getPrevious()) { $xPrevious = $DOM->createElement('s:previous-exception'); $xPrevious->appendChild($DOM->createElement('s:exception', $h(get_class($previous)))); $xPrevious->appendChild($DOM->createElement('s:message', $h($previous->getMessage()))); $xPrevious->appendChild($DOM->createElement('s:file', $h($previous->getFile()))); $xPrevious->appendChild($DOM->createElement('s:line', $h($previous->getLine()))); $xPrevious->appendChild($DOM->createElement('s:code', $h($previous->getCode()))); $xPrevious->appendChild($DOM->createElement('s:stacktrace', $h($previous->getTraceAsString()))); $error->appendChild($xPrevious); } } if ($e instanceof Exception) { $httpCode = $e->getHTTPCode(); $e->serialize($this, $error); $headers = $e->getHTTPHeaders($this); } else { $httpCode = 500; $headers = []; } $headers['Content-Type'] = 'application/xml; charset=utf-8'; $this->httpResponse->setStatus($httpCode); $this->httpResponse->setHeaders($headers); $this->httpResponse->setBody($DOM->saveXML()); $this->sapi->sendResponse($this->httpResponse); } }
/** * This method checks the main HTTP preconditions. * * Currently these are: * * If-Match * * If-None-Match * * If-Modified-Since * * If-Unmodified-Since * * The method will return true if all preconditions are met * The method will return false, or throw an exception if preconditions * failed. If false is returned the operation should be aborted, and * the appropriate HTTP response headers are already set. * * Normally this method will throw 412 Precondition Failed for failures * related to If-None-Match, If-Match and If-Unmodified Since. It will * set the status to 304 Not Modified for If-Modified_since. * * If the $handleAsGET argument is set to true, it will also return 304 * Not Modified for failure of the If-None-Match precondition. This is the * desired behaviour for HTTP GET and HTTP HEAD requests. * * @param bool $handleAsGET * @return bool */ public function checkPreconditions($handleAsGET = false) { $uri = $this->getRequestUri(); $node = null; $lastMod = null; $etag = null; if ($ifMatch = $this->httpRequest->getHeader('If-Match')) { // If-Match contains an entity tag. Only if the entity-tag // matches we are allowed to make the request succeed. // If the entity-tag is '*' we are only allowed to make the // request succeed if a resource exists at that url. try { $node = $this->tree->getNodeForPath($uri); } catch (Exception\NotFound $e) { throw new Exception\PreconditionFailed('An If-Match header was specified and the resource did not exist', 'If-Match'); } // Only need to check entity tags if they are not * if ($ifMatch !== '*') { // There can be multiple etags $ifMatch = explode(',', $ifMatch); $haveMatch = false; foreach ($ifMatch as $ifMatchItem) { // Stripping any extra spaces $ifMatchItem = trim($ifMatchItem, ' '); $etag = $node->getETag(); if ($etag === $ifMatchItem) { $haveMatch = true; } else { // Evolution has a bug where it sometimes prepends the " // with a \. This is our workaround. if (str_replace('\\"', '"', $ifMatchItem) === $etag) { $haveMatch = true; } } } if (!$haveMatch) { throw new Exception\PreconditionFailed('An If-Match header was specified, but none of the specified the ETags matched.', 'If-Match'); } } } if ($ifNoneMatch = $this->httpRequest->getHeader('If-None-Match')) { // The If-None-Match header contains an etag. // Only if the ETag does not match the current ETag, the request will succeed // The header can also contain *, in which case the request // will only succeed if the entity does not exist at all. $nodeExists = true; if (!$node) { try { $node = $this->tree->getNodeForPath($uri); } catch (Exception\NotFound $e) { $nodeExists = false; } } if ($nodeExists) { $haveMatch = false; if ($ifNoneMatch === '*') { $haveMatch = true; } else { // There might be multiple etags $ifNoneMatch = explode(',', $ifNoneMatch); $etag = $node->getETag(); foreach ($ifNoneMatch as $ifNoneMatchItem) { // Stripping any extra spaces $ifNoneMatchItem = trim($ifNoneMatchItem, ' '); if ($etag === $ifNoneMatchItem) { $haveMatch = true; } } } if ($haveMatch) { if ($handleAsGET) { $this->httpResponse->sendStatus(304); return false; } else { throw new Exception\PreconditionFailed('An If-None-Match header was specified, but the ETag matched (or * was specified).', 'If-None-Match'); } } } } if (!$ifNoneMatch && ($ifModifiedSince = $this->httpRequest->getHeader('If-Modified-Since'))) { // The If-Modified-Since header contains a date. We // will only return the entity if it has been changed since // that date. If it hasn't been changed, we return a 304 // header // Note that this header only has to be checked if there was no If-None-Match header // as per the HTTP spec. $date = HTTP\Util::parseHTTPDate($ifModifiedSince); if ($date) { if (is_null($node)) { $node = $this->tree->getNodeForPath($uri); } $lastMod = $node->getLastModified(); if ($lastMod) { $lastMod = new \DateTime('@' . $lastMod); if ($lastMod <= $date) { $this->httpResponse->sendStatus(304); $this->httpResponse->setHeader('Last-Modified', HTTP\Util::toHTTPDate($lastMod)); return false; } } } } if ($ifUnmodifiedSince = $this->httpRequest->getHeader('If-Unmodified-Since')) { // The If-Unmodified-Since will allow allow the request if the // entity has not changed since the specified date. $date = HTTP\Util::parseHTTPDate($ifUnmodifiedSince); // We must only check the date if it's valid if ($date) { if (is_null($node)) { $node = $this->tree->getNodeForPath($uri); } $lastMod = $node->getLastModified(); if ($lastMod) { $lastMod = new \DateTime('@' . $lastMod); if ($lastMod > $date) { throw new Exception\PreconditionFailed('An If-Unmodified-Since header was specified, but the entity has been changed since the specified date.', 'If-Unmodified-Since'); } } } } return true; }