webdev
webdev

Reputation: 339

PHP Stop Remote File Download if it Exceeds 5mb

How do I stop a remote file from downloading a file if it exceeds 5mb? If I stop it while transferring, will the file be held somewhere in some other temp dir or memory? How would I know? Here is my current code:

$url = 'http://www.spacetelescope.org/static/archives/images/large/heic0601a.jpg';
$file = '../temp/test.jpg';
file_put_contents($file, file_get_contents($url));

Upvotes: 7

Views: 2137

Answers (4)

DaveRandom
DaveRandom

Reputation: 88707

There are a number of ways in which you could do this, but because you are currently using file_get_contents(), here is what I would do:

  • Open the remote file using fopen()
  • Read the file and save it in increments using fread() - this allows you to track the current size and throw and error when you pass the threshold.

Something like this:

$url = 'http://www.spacetelescope.org/static/archives/images/large/heic0601a.jpg';
$file = '../temp/test.jpg';
$limit = 5 * 1024 * 1024; // 5MB

if (!$rfp = fopen($url, 'r')) {
  // error, could not open remote file
}
if (!$lfp = fopen($file, 'w')) {
  // error, could not open local file
}

// Check the content-length for exceeding the limit
foreach ($http_response_header as $header) {
  if (preg_match('/^\s*content-length\s*:\s*(\d+)\s*$/', $header, $matches)) {
    if ($matches[1] > $limit) {
      // error, file too large
    }
  }
}

$downloaded = 0;

while (!feof($rfp) && $downloaded < $limit) {
  $chunk = fread($rfp, 8192);
  fwrite($lfp, $chunk);
  $downloaded += strlen($chunk);
}

if ($downloaded > $limit) {
  // error, file too large
  unlink($file); // delete local data
} else {
  // success
}

NB: it is tempting to inspect the Content-Length: header before you receive any of the file to check whether the file is too large - you can still do this if you like, but do not trust the value! The header is essentially an arbitrary value, and while it would be a protocol violation to use a content length that does not match the size of the file, it could be used to fool the system into downloading a file that breaks the rules. You'd need count the bytes even if you do check this value.

You could also do this using curl via the data callback, but I would regard this as a less satisfactory solution, mostly because of fact that curl requires a string function name and not the standard generic callable type, which means you would either need to use a global variable or a static variable in order to keep track of the downloaded content length, neither of which are acceptable (IMHO) for this.

Upvotes: 8

leepowers
leepowers

Reputation: 38318

This isn't possible with file_get_contents - you'll need to use the PHP cURL extension to grab the file size.

<?php
$max_length = 5 * 1024 * 1024;
$url = 'http://www.spacetelescope.org/static/archives/images/large/heic0601a.jpg';
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$data = curl_exec($ch);
curl_close($ch);
if ($data === false) {
  echo 'cURL failed';
  exit;
}
$contentLength = 'unknown';
if (preg_match('/Content-Length: (\d+)/', $data, $matches)) {
  $contentLength = (int)$matches[1];
}
echo "Content-Length: $contentLength\n";

if ($contentLength > $max_length) {
    echo "File is too large\n";
} else {
    echo "File size is ok\n";
}

Wrap this up in a function and use it to check a URL - anything too large can be skipped.

Downside:

This method only works if the Content-Length header is returned by the server - not all servers are well-behaved and not all will return a correct length.

Upside:

There's no need to download and write up to $max_length bytes. This method will be faster and less resource intensive.

Modified from:

http://php.net/filesize#92462

Upvotes: 1

gview
gview

Reputation: 15381

Here's a variation on DaveRandom's solution along the same lines but without writing the file until it's completely loaded.

<?php
define('MAXMB', 5);
define('BYTESPERMB', 1048576);

$maxsize = MAXMB * BYTESPERMB;

$handle = fopen("http://www.example.com/", "rb");
$contents = '';
$completed = true;
while (!feof($handle)) {
  $chunk = fread($handle, 8192);

  $size += strlen($chunk);
  if ($size > $maxsize) {
      $completed = false;
      break;
  }
  $contents .= $chunk;
}
fclose($handle);

if ($completed) {
  // File appears to be completely downloaded so write it --
  file_put_contents('path/to/localfile', $contents);

} else {
  // It was too big
}

Upvotes: 1

cHao
cHao

Reputation: 86545

As for taking a URL and limiting the size, it's not too hard...but file_get_contents won't do it. You could fopen the url, and use stream_get_meta_data on the stream to get an array of info about the stream. If i'm reading the example right (and if the transfer encoding isn't "chunked"), you'll find the Content-Length header in there, which will tell you the file size. If the value is set, and is over your limit, bail.

If the stream metadata doesn't have that info, or the info says the file's small enough, you still have the stream itself; you can still fread from the remote file and fwrite to the local one in chunks til you read it all or hit the limit. Of course, if you get to that point, the file will still exist once you hit the limit; you'll have to delete it yourself.

Upvotes: 2

Related Questions