/** * This is the default implementation for the GET method. * * @param RequestInterface $request * @param ResponseInterface $response * @return bool */ function httpGet(RequestInterface $request, ResponseInterface $response) { $path = $request->getPath(); $node = $this->server->tree->getNodeForPath($path, 0); if (!$node instanceof IFile) { return; } $body = $node->get(); // Converting string into stream, if needed. if (is_string($body)) { $stream = fopen('php://temp', 'r+'); fwrite($stream, $body); rewind($stream); $body = $stream; } /* * TODO: getetag, getlastmodified, getsize should also be used using * this method */ $httpHeaders = $this->server->getHTTPHeaders($path); /* ContentType needs to get a default, because many webservers will otherwise * default to text/html, and we don't want this for security reasons. */ if (!isset($httpHeaders['Content-Type'])) { $httpHeaders['Content-Type'] = 'application/octet-stream'; } if (isset($httpHeaders['Content-Length'])) { $nodeSize = $httpHeaders['Content-Length']; // Need to unset Content-Length, because we'll handle that during figuring out the range unset($httpHeaders['Content-Length']); } else { $nodeSize = null; } $response->addHeaders($httpHeaders); $range = $this->server->getHTTPRange(); $ifRange = $request->getHeader('If-Range'); $ignoreRangeHeader = false; // If ifRange is set, and range is specified, we first need to check // the precondition. if ($nodeSize && $range && $ifRange) { // if IfRange is parsable as a date we'll treat it as a DateTime // otherwise, we must treat it as an etag. try { $ifRangeDate = new \DateTime($ifRange); // It's a date. We must check if the entity is modified since // the specified date. if (!isset($httpHeaders['Last-Modified'])) { $ignoreRangeHeader = true; } else { $modified = new \DateTime($httpHeaders['Last-Modified']); if ($modified > $ifRangeDate) { $ignoreRangeHeader = true; } } } catch (\Exception $e) { // It's an entity. We can do a simple comparison. if (!isset($httpHeaders['ETag'])) { $ignoreRangeHeader = true; } elseif ($httpHeaders['ETag'] !== $ifRange) { $ignoreRangeHeader = true; } } } // We're only going to support HTTP ranges if the backend provided a filesize if (!$ignoreRangeHeader && $nodeSize && $range) { // Determining the exact byte offsets if (!is_null($range[0])) { $start = $range[0]; $end = $range[1] ? $range[1] : $nodeSize - 1; if ($start >= $nodeSize) { throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $range[0] . ') exceeded the size of the entity (' . $nodeSize . ')'); } if ($end < $start) { throw new Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[1] . ') is lower than the start offset (' . $range[0] . ')'); } if ($end >= $nodeSize) { $end = $nodeSize - 1; } } else { $start = $nodeSize - $range[1]; $end = $nodeSize - 1; if ($start < 0) { $start = 0; } } // New read/write stream $newStream = fopen('php://temp', 'r+'); // fseek will return 0 only if $streem is seekable (and -1 otherwise) // for a seekable $body stream we set the pointer write before copying it // for a non-seekable $body stream we set the pointer on the copy if (fseek($body, $start, SEEK_SET) === 0) { stream_copy_to_stream($body, $newStream, $end - $start + 1, $start); rewind($newStream); } else { stream_copy_to_stream($body, $newStream, $end + 1); fseek($newStream, $start, SEEK_SET); } $response->setHeader('Content-Length', $end - $start + 1); $response->setHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $nodeSize); $response->setStatus(206); $response->setBody($newStream); } else { if ($nodeSize) { $response->setHeader('Content-Length', $nodeSize); } $response->setStatus(200); $response->setBody($body); } // Sending back false will interupt the event chain and tell the server // we've handled this method. return false; }