/** * Download file! * @param BaseFileDownload $file */ function download(BaseFileDownload $transfer) { $this->currentTransfer = $transfer; $this->sendStandardFileHeaders($transfer, $this); @ignore_user_abort(true); // For onAbort event $req = \Nette\Environment::getHttpRequest(); $res = \Nette\Environment::getHttpResponse(); $filesize = $this->size = $transfer->sourceFileSize; $this->length = $this->size; // Content-length $this->start = 0; $this->end = $this->size - 1; /* ### Headers ### */ // Now that we've gotten so far without errors we send the accept range header /* At the moment we only support single ranges. * Multiple ranges requires some more work to ensure it works correctly * and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2 * * Multirange support annouces itself with: * header('Accept-Ranges: bytes'); * * Multirange content must be sent with multipart/byteranges mediatype, * (mediatype = mimetype) * as well as a boundry header to indicate the various chunks of data. */ //$res->setHeader("Accept-Ranges", "0-".$this->end); // single-part - now not accepted by mozilla $res->setHeader("Accept-Ranges", "bytes"); // multi-part (through Mozilla) // http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2 if ($req->getHeader("Range", false)) { try { $range_start = $this->start; $range_end = $this->end; // Extract the range string $rangeArray = explode('=', $req->getHeader("Range"), 2); $range = $rangeArray[1]; // Make sure the client hasn't sent us a multibyte range if (strpos($range, ',') !== false) { // (?) Shoud this be issued here, or should the first // range be used? Or should the header be ignored and // we output the whole content? throw new FileDownloaderException("HTTP 416", 416); } // If the range starts with an '-' we start from the beginning // If not, we forward the file pointer // And make sure to get the end byte if spesified if ($range[0] == '-') { // The n-number of the last bytes is requested $range_start = $this->size - (double) substr($range, 1); } else { $range = explode('-', $range); $range_start = $range[0]; $range_end = isset($range[1]) && is_numeric($range[1]) ? $range[1] : $this->size; } /** * Check the range and make sure it's treated according to the specs. * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html */ // End bytes can not be larger than $end. $range_end = $range_end > $this->end ? $this->end : $range_end; // Validate the requested range and return an error if it's not correct. if ($range_start > $range_end || $range_start > $this->size - 1 || $range_end >= $this->size) { throw new FileDownloaderException("HTTP 416", 416); } // All is ok - so assign variables back $this->start = $range_start; $this->end = $range_end; $this->length = $this->end - $this->start + 1; // Calculate new content length } catch (FileDownloaderException $e) { if ($e->getCode() == 416) { $res->setHeader("Content-Range", "bytes {$this->start}-{$this->end}/{$this->size}"); FDTools::_HTTPError(416); } else { throw $e; } } $res->setCode(206); // Partial content } // End of if partial download // Notify the client the byte range we'll be outputting $res->setHeader("Content-Range", "bytes {$this->start}-{$this->end}/{$this->size}"); $res->setHeader("Content-Length", $this->length); /* ### Call callbacks ### */ $transfer->onBeforeOutputStarts($transfer, $this); if ($this->start > 0) { $transfer->onTransferContinue($transfer, $this); } else { $transfer->onNewTransferStart($transfer, $this); } /* ### Send file to browser - document body ### */ $buffer = FDTools::$readFileBuffer; $sleep = false; if (is_int($transfer->speedLimit) and $transfer->speedLimit > 0) { $sleep = true; $buffer = (int) round($transfer->speedLimit); } $this->sleep = $sleep; if ($buffer < 1) { throw new Nette\InvalidArgumentException("Buffer must be bigger than zero!"); } if ($buffer > FDTools::getAvailableMemory() - memory_get_usage()) { throw new Nette\InvalidArgumentException("Buffer is too big! (bigger than available memory)"); } $this->buffer = $buffer; $fp = fopen($transfer->sourceFile, "rb"); // TODO: Add flock() READ if (!$fp) { throw new \Nette\InvalidStateException("Can't open file for reading!"); } if ($this->end === null) { $this->end = $filesize - 1; } if (fseek($fp, $this->start, SEEK_SET) === -1) { // Move file pointer to the start of the download // Can not move pointer to begining of the filetransfer if ($this->processByCUrl() === true) { // Request was hadled by curl, clean, exit $this->cleanAfterTransfer(); return; } // Use this hack (fread file to start position) $destPos = $this->position = PHP_INT_MAX - 1; if (fseek($fp, $this->position, SEEK_SET) === -1) { rewind($fp); $this->position = 0; throw new InvalidStateException("Can not move pointer to position ({$destPos})"); } $maxBuffer = 1024 * 1024; while ($this->position < $this->start) { $this->position += strlen(fread($fp, min($maxBuffer, $this->start - $this->position))); } } else { // We are at the begining $this->position = $this->start; } $this->processNative($fp, $sleep); $this->cleanAfterTransfer(); }
/** * Download the file! * @param IDownloader $downloader */ function download(IDownloader $downloader = null) { $req = \Nette\Environment::getHttpRequest(); $res = \Nette\Environment::getHttpResponse(); if (self::$closeSession) { $ses = \Nette\Environment::getSession(); if ($ses->isStarted()) { $ses->close(); } } if ($this->getContentDisposition() == "inline" and is_null($this->enableBrowserCache)) { $this->enableBrowserCache = true; } else { $this->enableBrowserCache = false; } if ($downloader === null) { $downloaders = self::getFileDownloaders(); } else { $downloaders = array($downloader); } if (count($downloaders) <= 0) { throw new \Nette\InvalidStateException("There is no registred downloader!"); } krsort($downloaders); $lastException = null; foreach ($downloaders as $downloader) { if ($downloader instanceof IDownloader and $downloader->isCompatible($this)) { try { FDTools::clearHeaders($res); // Delete all headers $this->transferredBytes = 0; $this->onBeforeDownloaderStarts($this, $downloader); $downloader->download($this); // Start download $this->onComplete($this, $downloader); die; // If all gone ok -> die } catch (FDSkypeMeException $e) { if ($res->isSent()) { throw new \Nette\InvalidStateException("Headers are already sent! Can't skip downloader."); } else { continue; } } catch (Exception $e) { if (!$res->isSent()) { FDTools::clearHeaders($res); } throw $e; } } } // Pokud se soubor nějakým způsobem odešle - toto už se nespustí if ($lastException instanceof Exception) { FDTools::clearHeaders(\Nette\Environment::getHttpResponse(), TRUE); throw $lastException; } if ($req->getHeader("Range")) { FDTools::_HTTPError(416); } else { $res->setCode(500); } throw new \Nette\InvalidStateException("There is no compatible downloader (all downloader returns downloader->isComplatible()=false or was skipped)!"); }