/** * Setts speed limit * @param int $speed Speed limit * @return BaseFileDownload */ function setSpeedLimit($speed) { if (!is_int($speed)) { throw new InvalidArgumentException("Max download speed must be intiger!"); } if ($speed < 0) { throw new InvalidArgumentException("Max download speed can't be smaller than zero!"); } $availableMem = FDTools::getAvailableMemory(); $availableMemWithReserve = $availableMem - 100 * 1024; if ($availableMem !== null and $speed > $availableMemWithReserve) { throw new InvalidArgumentException("Max download speed can't be a bigger than available memory " . $availableMemWithReserve . "b!"); } $this->vSpeedLimit = (int) round($speed); return $this; }
/** * Download file! * @param BaseFileDownload $file */ function download(BaseFileDownload $transfer) { $this->currentTransfer = $transfer; $this->sendStandardFileHeaders($transfer, $this); @ignore_user_abort(true); // For onAbort event $req = Environment::getHttpRequest(); $res = 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 InvalidArgumentException("Buffer must be bigger than zero!"); } if ($buffer > FDTools::getAvailableMemory() - memory_get_usage()) { throw new 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 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(); }