Here is a simple script I have written to limit downloads for users to one at a time (IE if they are downloading a file then they cannot download another one until they cancel the current download or it finishes).
ignore_user_abort(true);
$local_file = $_GET['filename'];
$download_file = explode("/", $local_file);
$download_file = $download_file[count($download_file) -1];
// set the download rate limit (value is in kilobytes per second
$download_rate = 100;
if(file_exists($local_file) && is_file($local_file)) {
$ip = visitor_ip();
if(!are_downloading($ip)) {
header('Cache-control: private');
header('Content-Type: application/octet-stream');
header('Content-Length: '.filesize($local_file));
header('Content-Disposition: filename='.$download_file);
flush();
$file = fopen($local_file, "r");
log_downloader($ip);
while(!feof($file)) {
if (!connection_aborted()) {
// send the current file part to the browser
print fread($file, round($download_rate * 1024));
// flush the content to the browser
flush();
// sleep one second
sleep(1);
} else {
break;
}
}
clear_downloader($ip);
fclose($file);
} else {
die('<span style="color:#DDDDDD">Due to server limitations you may only download one file at a time. Please cancel or wait for your current download to finish before trying again. Click here to return.</span>');
}
} else {
die('Error: The file '.$local_file.' does not exist!');
}
function visitor_ip() {
if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
$TheIp=$_SERVER['HTTP_X_FORWARDED_FOR'];
else $TheIp=$_SERVER['REMOTE_ADDR'];
return trim($TheIp);
}
function are_downloading($ip) {
$query = "select * from downloaders where ip_addr='$ip'";
$result = mysql_query($query);
$num_rows = mysql_num_rows($result);
return $num_rows > 0;
}
function log_downloader($ip) {
$query = "insert into downloaders (ip_addr) values ('$ip')";
$result = mysql_query($query);
}
function clear_downloader($ip) {
$query = "delete from downloaders where ip_addr='$ip'";
$result = mysql_query($query);
}
When I test it out, it works fine, but for a lot of people, their IP never gets cleared out of the database - even when they have finished downloading/cancelled a file. Why don't the IPs get deleted?
The problem was that with big downloads the MySQL connection went away, I simply had to reconnect in the clear_downloader function and now it works fine.
Related
Im using server sent events in php. Here is my code in php.
<?php
header("Cache-Control: no-cache");
header("Content-Type: text/event-stream");
$lastMod = 0;
$filename = "tmp.txt";
$filetext = '';
while (true) {
$time = filemtime("tmp.txt");
if($time != $lastMod){
$lastMod = $time;
$file = fopen($filename, "r");
if ($file == false) {
echo ("Error in opening file");
exit();
}
$counter = $counter +1;
$filesize = filesize($filename);
$filetext = fread($file, $filesize);
fclose($file);
echo 'data: This is a message at time '. $filetext . $time. "\n\n";
}
ob_end_flush();
flush();
if (connection_aborted()) break;
sleep(2);
}
Even if the file was modified the value returned by the filemtime() doesn't change. So data in the file doesn't go to the client. What is the solution for this. Any help is appreciated.
As stated in comments, just add clearstatcache like this:
clearstatcache();
$time = filemtime("tmp.txt");
From PHP documentation:
Note: The results of this function (filemtime) are cached. See clearstatcache() for more details.
I'm trying to force a zip-file's download after some checks ( hash , expiration date, download's number ) .
The link of the page contains the id ( hash ) to validate the download.
For example:
www.example.com/download.php?id=505fbeabfd97c0ab15a016d68512d8df5acdedd6e6cc6
Everything works, but the file is corrupted.
If I try to eliminate all checks and the connection to the database, the zip downloaded is ok.
Here's the code:
<?php
$maxdownloads = "80";
// Set the key's viable duration in seconds (86400 seconds = 24 hours)
$maxtime = "2592000";
require ('connect.php');
if(get_magic_quotes_gpc()) {
$id = stripslashes($_GET['id']);
}else{
$id = $_GET['id'];
}
echo $id;
// Get the key, timestamp, and number of downloads from the database
$query = sprintf("SELECT * FROM digital_orders WHERE hash= '%s'",
mysqli_real_escape_string($conn,$id));
$result = $conn->query($query);
$row = $result->fetch_assoc();
if (!$row) {
echo "The download key you are using is invalid.";
}else{
$timecheck = date('U') - $row['date'];
echo $timecheck;
if ($timecheck >= $maxtime) {
echo "This key has expired (exceeded time allotted).<br />";
}else{
$downloads = $row['download'];
echo $downloads;
$downloads += 1;
if ($downloads > $maxdownloads) {
echo "This key has expired (exceeded allowed downloads<br/>";
}else{
$sql = sprintf("UPDATE digital_orders SET download .$downloads."' WHERE hash= '%s'",
mysqli_real_escape_string($conn,$id));
$incrementdownloads = $conn->query($sql);
$result->close();
// chiusura della connessione
$conn->close();
// Debug echo "Key validated.";
// Force the browser to start the download automatically
/*
Variables:
$file = real name of actual download file on the server
$filename = new name of local download file - this is what the visitor's file will actually be called when he/she saves it
*/
// Get parameters
$file = "dante.zip"; // Decode URL-encoded string
$filename = "foto.zip";
$filepath = "digital_orders/Dante/" . $file;
// Process download
if(file_exists($filepath)) {
ob_start();
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.$filename.'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($filepath));
flush(); // Flush system output buffer
ob_end_clean();
readfile($filepath);
exit;
}
}
}
}
?>
If I try to open the zip file that works with note-pad there is :
PK
ûe‹L dante.txtPK
ûe‹L $ dante.txt
CdÔ‹‚ÑÓCdÔ‹‚ÑÓÆ‹‚ÑÓPK [
'
If I try to open the corrupted zip file there is:
505fbeabfd97c0ab15a016d68512d8df5acdedd6e6cc61226822PK
ûe‹L dante.txtPK
ûe‹L $ dante.txt
CdÔ‹‚ÑÓCdÔ‹‚ÑÓÆ‹‚ÑÓPK [
Any suggestion? '
I am writing a basic license-validated PHP download script. The target file is about 50MB, and it works for some. Others can't finish it, sometimes retrying it works.
Here is the script:
$method = $_GET['method'];
if($method == "webdownload") {
$airlineid = $_GET['airline'];
$sql = "SELECT * FROM airlines WHERE airlineid='$airlineid'";
$result = mysql_query($sql) or die(mysql_error());
$row = mysql_fetch_array($result);
if($row['licensekey'] == "")
die("Invalid airline id");
$filename = $row['code'].'_installer.exe';
$file_path = '../resources/application/files/'.$row['airlineid'].'/'.$row['clientversion'].'/application_installer.exe';
if($row['licensestate'] != "OK")
die("The license associated with this downloaded has been deauthorized.");
if(!is_file($file_path))
die("The file associated with this version for this airline appears to be invalid.");
//download code here - it runs once only, if refreshed it will not allow it.
header('Content-type: application/exe');
header("Content-Disposition: attachment; filename=".$filename);
header("Content-Length: ".filesize($file_path));
header("Content-Transfer-Encoding: binary");
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
//header('X-Sendfile: '.$file_path); I tried this - it had no effect and I want the portability.
$file = #fopen($file_path,"rb");
while(!feof($file)) {
$buffer = fread($file, 1024 * 8);
print($buffer);
flush();
}
close($file);
}
EDIT: Upon advice, I discovered that the script, among others, is highly vulnerable to SQL injection. I have replaced direct variable-SQL expressions with the use of this function:
function secure_string($raw) {
$sid = strtolower($raw);
$sid = str_replace("'","_SINGLE_QUOTE", $sid);
$sid = str_replace('"','_DOUBLE_QUOTE', $sid);
$cmd[0] = "insert";
$cmd[1] = "select";
$cmd[2] = "union";
$cmd[3] = "delete";
$cmd[4] = "modify";
$cmd[5] = "replace";
$cmd[6] = "update";
$cmd[7] = "create";
$cmd[8] = "alter";
for($index = 0; $index <= 8; $index++) {
$sid = str_replace($cmd[$index],"_SQL_COMMAND", $sid);
}
return $sid;
}
Is that sufficient to block SQL-injection?
EDIT2: I have used this function in conjunction with a PDO prepare functions to eliminate this exploit. Thanks 100x for letting me learn this lesson without disastrous results.
readfile() is a function that puts the entire file into the buffer in one go. Will probably prevent PHP from timing out. Use it instead of the fopen() and print() loop you have at the bottom.
Another solution is to see if you server has mod_x_sendfile as this takes the downloading out of PHP and into apache internals.
Edit: I notice you say you've tried sendfile. Might be a better option if you can get it working.
Sorry to bother you. I have download script. Its is downloading from remote server to local server then send to user for download. The main site grabs the file from the file server then provides it to the user. I want main site to pass direct link from remote server.
I have no idea How can i send it to user directly. Now it is creating load and bandwidth problems on my site. Any idea, how to do this.
public function download()
{
// remove session
if (isset($_SESSION['showDownload']))
{
// reset session variable for next time
$_SESSION['showDownload'] = null;
unset($_SESSION['showDownload']);
session_write_close();
}
// php script timeout for long downloads (2 days!)
set_time_limit(60 * 60 * 24 * 2);
// load the server the file is on
$storageType = 'local';
$storageLocation = _CONFIG_FILE_STORAGE_PATH;
$uploadServerDetails = $this->loadServer();
if ($uploadServerDetails != false)
{
$storageLocation = $uploadServerDetails['storagePath'];
$storageType = $uploadServerDetails['serverType'];
// if no storage path set & local, use system default
if ((strlen($storageLocation) == 0) && ($storageType == 'local'))
{
$storageLocation = _CONFIG_FILE_STORAGE_PATH;
}
}
// get file path
$fullPath = $this->getFullFilePath($storageLocation);
// open file - via ftp
if ($storageType == 'remote')
{
// connect via ftp
$conn_id = ftp_connect($uploadServerDetails['ipAddress'], $uploadServerDetails['ftpPort'], 30);
if ($conn_id === false)
{
$this->errorMsg = 'Could not connect to ' . $uploadServerDetails['ipAddress'] . ' to upload file.';
return false;
}
// authenticate
$login_result = ftp_login($conn_id, $uploadServerDetails['ftpUsername'], $uploadServerDetails['ftpPassword']);
if ($login_result === false)
{
$this->errorMsg = 'Could not login to ' . $uploadServerDetails['ipAddress'] . ' with supplied credentials.';
return false;
}
// prepare the stream of data
$pipes = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
if ($pipes === false)
{
$this->errorMsg = 'Could not create stream to download file on ' . $uploadServerDetails['ipAddress'];
return false;
}
stream_set_write_buffer($pipes[0], 10000);
stream_set_timeout($pipes[1], 10);
stream_set_blocking($pipes[1], 0);
$fail = false;
$ret = ftp_nb_fget($conn_id, $pipes[0], $fullPath, FTP_BINARY, FTP_AUTORESUME);
}
// open file - locally
else
{
$handle = #fopen($fullPath, "r");
if (!$handle)
{
$this->errorMsg = 'Could not open file for reading.';
return false;
}
}
// download speed
$speed = 0;
// if free/non user
$Auth = Auth::getAuth();
if (($Auth->loggedIn == false) || ($Auth->level == 'free user'))
{
$speed = (int) SITE_CONFIG_FREE_USER_MAX_DOWNLOAD_SPEED;
}
else
{
$speed = (int) SITE_CONFIG_PREMIUM_USER_MAX_DOWNLOAD_SPEED;
}
// do we need to throttle the speed?
if ($speed > 0)
{
// create new throttle config
$config = new ThrottleConfig();
// set standard transfer rate (in bytes/second)
$config->burstLimit = $speed;
$config->rateLimit = $speed;
// enable module (this is a default value)
$config->enabled = true;
// start throttling
$x = new Throttle($config);
}
// output some headers
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Content-type: " . $this->fileType);
header("Pragma: public");
header("Content-Disposition: attachment; filename=\"" . str_replace("\"", "", $this->originalFilename) . "\"");
header("Content-Description: File Transfer");
header("Content-Length: " . $this->fileSize);
// output file - via ftp
if ($storageType == 'remote')
{
while ($ret == FTP_MOREDATA)
{
$contents = stream_get_contents($pipes[1]);
if ($contents !== false)
{
echo $contents;
flush();
}
$ret = ftp_nb_continue($conn_id);
}
/*
$contents = stream_get_contents($pipes[1]);
if($contents !== false)
{
echo $contents;
flush();
}
*/
fclose($pipes[0]);
fclose($pipes[1]);
}
// output file - local
else
{
while (($buffer = fgets($handle, 4096)) !== false)
{
echo $buffer;
}
fclose($handle);
}
exit();
}
public function loadServer()
{
// load the server the file is on
if ((int) $this->serverId)
{
// load from the db
$db = Database::getDatabase(true);
$uploadServerDetails = $db->getRow('SELECT * FROM file_server WHERE id = ' . $db->quote((int) $this->serverId));
$db->close();
if (!$uploadServerDetails)
{
return false;
}
return $uploadServerDetails;
}
return false;
}
Thank you in advance for your time.
Explanations:
I don't have direct link for any files. The function up is generating the links and the file name on remote server is different (Handel by the function mentioned above).
If the FTP server is accessible to the general public - via the Internet - and doesn't need a username or password (looks like it may not), then you can provide the following link:
Download via FTP
I'm developing a quick rapidshare-like site where the user can download files. First, I created a quick test setting headers and using readfile() but then I found in the comments section there's a way to limit the speed of the download, which is great, here's the code:
$local_file = 'file.zip';
$download_file = 'name.zip';
// set the download rate limit (=> 20,5 kb/s)
$download_rate = 20.5;
if(file_exists($local_file) && is_file($local_file))
{
header('Cache-control: private');
header('Content-Type: application/octet-stream');
header('Content-Length: '.filesize($local_file));
header('Content-Disposition: filename='.$download_file);
flush();
$file = fopen($local_file, "r");
while(!feof($file))
{
// send the current file part to the browser
print fread($file, round($download_rate * 1024));
// flush the content to the browser
flush();
// sleep one second
sleep(1);
}
fclose($file);}
else {
die('Error: The file '.$local_file.' does not exist!');
}
But now my question is, how to limit the number of downloads at the same time? How can I check there's still a connection with some user's IP?
Thanks.
Does a user have a login? if not just use sessions, or even better track on their ip-address.
Here's a sessions example:
$_SESSION['file_downloading']==true;
$file = fopen($local_file, "r");
while(!feof($file))
{
// send the current file part to the browser
print fread($file, round($download_rate * 1024));
// flush the content to the browser
flush();
// sleep one second
sleep(1);
}
$_SESSION['file_downloading']=null;
fclose($file);}
Then above all this code,
if(!empty($_SESSION['file_downloading']))
//perform a redirect or reduce their download rate or something.
Next option is via ip address.
//http://wiki.jumba.com.au/wiki/PHP_Get_user_IP_Address
function VisitorIP()
{
if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
$TheIp=$_SERVER['HTTP_X_FORWARDED_FOR'];
else $TheIp=$_SERVER['REMOTE_ADDR'];
return trim($TheIp);
}
get the visitor ip address, store this in the database along with the datetime stamp. Then simply remove that ip address when the file is finished downloading. Are you using a database system?