I'm creating files based on user data (taking from the database table, writing to a csv file, storing on the server, and then printing out a link for the user (logged in) to download).
how can I ensure that only the user can download that file? For instance, if the file is stored at http://mysite.com/username/file, how can I make sure only that user, when signed in, is the only one who can download the file? Using php.
thanks for the help
The PHP manual has a user submitted entry that does what you're looking for:
function query_to_csv($db_conn, $query, $filename, $attachment = false, $headers = true) {
if($attachment) {
// send response headers to the browser
header( 'Content-Type: text/csv' );
header( 'Content-Disposition: attachment;filename='.$filename);
$fp = fopen('php://output', 'w');
} else {
$fp = fopen($filename, 'w');
}
$result = mysql_query($query, $db_conn) or die( mysql_error( $db_conn ) );
if($headers) {
// output header row (if at least one row exists)
$row = mysql_fetch_assoc($result);
if($row) {
fputcsv($fp, array_keys($row));
// reset pointer back to beginning
mysql_data_seek($result, 0);
}
}
while($row = mysql_fetch_assoc($result)) {
fputcsv($fp, $row);
}
fclose($fp);
}
// Using the function
$sql = "SELECT * FROM table";
// $db_conn should be a valid db handle
// output as an attachment
query_to_csv($db_conn, $sql, "test.csv", true);
// output to file system
query_to_csv($db_conn, $sql, "test.csv", false);
I was about to edit your question and remove the sql and csv tags because, as asked, it doesn't really have to do with those topics. However, my answer does.
Instead of saving a file to the file system, just generate the csv file for output directly to the response. Your code would only generate the correct csv for the user (or no content for non users), thereby solving the problem.
This assumes generation of the file is modestly lightweight and would not be done very frequently.
You could make a file called download.php, for example:
if($logged_in && $username == $_GET['download_username']) {
header("Content-Type: application/force-download");
header('Content-Disposition: attachment; filename=nameofdownload.zip');
readfile('downloads/' . $_GET['download_username'] . '/file.zip');
}
Then in the downloads folder, you could create a .htaccess file, and put the following:
Deny from all
If the user is not logged in, or if it's a wrong user, just show a message or redirect or whatever you want instead of giving access to the file. And that url example you've shown should be only a page with a link or redirection to a real file. You can even generate a file name hashes for even better security.
Unfortunatley you will need to wrap the download in some code. You can use a technique similar to this:
if ($user->is_correct_user_with_access())
{
header('Content-Type: application/octet-stream'); // this can be replaced with whatever file format you want;
echo file_get_contents($local_path_to_file);
die;
}
Also ensure that the file you are generating is not in a path that is publicly accessible (ie, it should be below your document root)
Sounds like a trivial question... there's any number of ways, if the user is logged in then simply check the file they're requesting belongs to them.
The best way to do this would probably be to use a pass-through file. For example the request to /username/file would actually be calling /download.php?id=username&dl=file in which you can then checked the logged in user is equal to id or the username of the file trying to be downloaded from and if they match then use php header location to redirect the request to another location although that can be exploited by looking at headers; so to make it seemless, you'd store the files outside the web directory and read in the file using php and output it directly to the browser.
Related
When user want to download file from my web site, user have to click link like below
https://www.example.com/download.php?aaa=111&bbb=222
download.php
<?PHP
session_start();
include("connect.php");
$aaa = mysql_real_escape_string($_GET[aaa]);
$bbb = mysql_real_escape_string($_GET[bbb]);
if(($aaa = '111')&($bbb = '222')) // this line is example for ask stackoverflow //
{
$filePath_try_to_download = 'attachments_files/test.pdf';
if(file_exists($filePath_try_to_download))
{
$fileSize = filesize($filePath_try_to_download);
$fileName = "test.pdf";
header("Cache-Control: private");
header("Content-Type: application/stream");
header("Content-Length: ".$fileSize);
header("Content-Disposition: attachment; filename=".$fileName);
// Output file.
readfile ($filePath_try_to_download);
exit();
}
}
?>
I want to know when user download file from this link https://www.example.com/download.php?aaa=111&bbb=222 user can get my file path on server or not (attachments_files/test.pdf). If user can get my file path, how can i hide it's ? (file in this dir is very importance)
Since I was posting comments from my Phone, they couldn't really explain much, so here goes your answer.
I want to know when user download file from this link https://www.example.com/download.php?aaa=111&bbb=222 user can get my file path on server or not (attachments_files/test.pdf).
No, Users can not see that file path which you are reading via readfile(). They will not be able to find out that file's location at all.
And if you want to eliminate any chances of people guessing the file path simply put those files outside of your web root folder and then readfile() them from there.
$filePath_try_to_download = 'attachments_files/test.pdf';
That path is only known to your PHP code, which is not visible to users hence they have no idea from where did you read the file they are downloading, just eliminate the guesswork chances though :)
And Obviously you have to secure access to this url https://www.example.com/download.php?aaa=111&bbb=222 otherwise what's the point!
No. The user cannot get the file path. He only get the content outputed by PHP script.
Your can do this and the user only get the "Hello" string. So it's your PHP script's role determining which contents the user can get.
<?php
echo "Hello";
?>
I'm working on a small piece of code that takes a specific record from an SQL table and places certain columns from that record in a txt file. This works fine, however if I try to echo something to screen, after or before where I fopen the file or fwrite it places that echo in the file as well, even text outside the is placed in the file.
Here is my current code in its' entirety:
Even this outside the PHP is placed in the file.
<?php
echo "Banana";
$orderNo = 3;
//DB Connect
$conn = mysql_connect('localhost', 'root', '');
mysql_select_db('amazondb', $conn);
//Open file, select Table
$file = fopen('upload/Order.txt', 'w');
$result = mysql_query("SELECT ID, PurchaseDate, BuyerName, BuyerPhoneNumber,
BuyerEmail, ShipAddress1, ShipAddress2, ShipAddress3
FROM imported_orders");
//Loop, input to file
while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
{ if($row['ID']==($orderNo-1))
{fwrite($file, $row['PurchaseDate'].PHP_EOL .$row['BuyerName']);}
}
fclose($file);
//Download File
$filename = "upload/Order.txt";
header("Content-Length: " . filesize($filename));
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=Order.txt');
readfile($filename);
?>
The above code outputs the following to file:
Even this outside the PHP is placed in the file.
Banana2013-03-14
name
This is my first time using headers and doing any form of download with PHP, so unsure if that is causing an issue. (I'll be having the download triggered via a HTML form using if isset and the "$orderNo" in the code will be input by the user, not sure if having that done now will solve this issue, though the fact it writes stuff even outside of the PHP worries me)
I open the file, write to the file, then close it, yet the echo before I even open the file is being written to it, same goes for having an echo after I close it.
This is really weird to me and makes no sense, though there is probably a simple
explanation.
Edit: Only the file downloaded has the extra stuff in it, the file saved on the server is just the data from the table as intended.
The way you use your headers now, all output will be put in the download. If you want a webpage with a link to a download, then create a second page with the link, linking to this page. Then on this page, only echo the data that you want in your file.
Basically what you say with the header is, everything that is sent to the browser for this request will be a file that you want to download.
I want to protect a pdf file from being directly linked but instead have my logged in users be able to access it. I have a link which currently goes to a javascript function which posts a form:
$('nameofdoc').setProperty('value',doc);
document.getElementById('sendme').submit();
where sendme is the name of the form and nameof doc the index of the document I want to display.
This then goes to a php file:
$docpath = $holdingArray[0].$holdingArray[1];
$file = $holdingArray[0]; //file name
$filename = $holdingArray[1]; //path to the file]
header( 'Location:'.$docpath ) ;
header('Content-type: application/pdf');
header('Content-Disposition: attachment; filename="'.$filename . '"');
readfile($filename)
This all works fine it loads up the file and outputs the pdf. What I can't do is protect the directory from direct linking - ie www.mydomain.com/pathToPdf/pdfname.pdf
I've thought of using .htaccess to protect the directory but it's on a shared host so I'm not sure about the security and anyway when I've tried I can't get it to work.
Any help would be great since this is my fourth day of trying to fix this.
thanks
Update
I've had a lot of help thank you but I'm not quite there yet.
I've got an .htaccess file that now launches another php file when a pdf is requested from the directory:
RewriteEngine on
RewriteRule ^(.*).(pdf)$ fileopen.php
When the fileopen.php file lauches it fails to open the pdf
$path = $_SERVER['REQUEST_URI'];
$paths = explode('/', $path);
$lastIndex = count($paths) - 1;
$fileName = $paths[$lastIndex];
$file = basename($path);
$filepath = $path;
if (file_exists($file)) {
header( 'Location: http://www.mydomain.com'.$path ) ;
header("Content-type: application/pdf");
header("Content-Disposition: attachment; filename=".$file);
readfile($filepath);
}else{
echo "file not found using path ".$path." and file is ".$file;
}
The output is
file not found using path /documents/6/Doc1.pdf and file is Doc1.pdf
but the file does exist and is in that direcotry - any ideas??
OKAY I'm happy to report that Jaroslav really helped me sort out the issue. His method works well but it is tricky to get all the directory stuff lined up. In the end I spent a few hours playing about with combinations to get it working but the principle he gave works well. Thanks
The best way would be to protect that folder with htaccess, as you have mentioned. So you put all PDFs in pdf/ folder, and in the same pdf folder you out .htaccess file:
RewriteEngine on
RewriteRule .* your-php-script.php
Now no files can be accessed by url in this folder. Every request to every file in this folder will return what your-php-script.php script returns. In your-php-script.php you do something like this:
//Check if user has right to access the file. If no, show access denied and exit the script.
$path = $_SERVER['REQUEST_URI'];
$paths = explode('/', path);
$lastIndex = count($paths) - 1;
$fileName = $paths[$lastIndex]; // Maybe add some code to detect subfolder if you have them
// Check if that file exists, if no show some error message
// Output headers here
readfile($filename);
Now if user opens domain.com/pdf/nsa-secrets.pdf Apache will run your-php-script.php. Script will have variable $_SERVER['REQUEST_URI'] set to "domain.com/pdf/nsa-secrets.pdf". You take the last part (filename) and output it to a user (or not).
This will stop anyone from accessing files directly from the internet by knowing URL. If someone has direct access to files on your server, that will not stop them. On the other hand, I think any shared hosting stops users from getting files of other clients. Only way to do it is to hack the server in some way. But then we are getting very paranoid and if that may be a case for you, you shouldn't use shared hosting in the first place.
If you cannot make htaccess work, you can try to obfuscate files, so it would be difficult to spot them for someone outside. For example change file from mySecretData.pdf to djjsdmdkjeksm.pdf. This may help a little bit.
I want to protect a pdf file from being directly linked but instead have my logged in users be able to access it.
Check to ensure there is an authenticated user before streaming the PDF's content.
This is kinda sloppy but it could work assuming you can setup a MYSQL DB. It lets you pass the "password" in the URL as an MD5 string or as a clear text if you want to. Trying to setup some kind of security without using htaccess or an existing frame work is kinda clunky. This however won't even attach the file to the stream until it knows you've been "Authenticated" I think you could maybe make this a little better if you setup a login page that saved a cookie locally then you wouldn't need to pass the "passphrase" in the URL.
$file = $_GET['file'];
$pass = $_GET['pass'];
$download_folder = '../Protected';
$file = basename($file);
$filepath = "$download_folder/$file";
if (file_exists($filepath)) {
if(CheckUser($pass)){
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=$file");
session_write_close();
readfile($filepath);
} else {
echo 'Not Authenticated!';
}
} else {
echo 'No File!';
}
function CheckUser($value){
$con = mysqli_connect("test.com","test","123456","my_db");
// Check connection
if (mysqli_connect_errno()){
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
$result = mysqli_query($con,"SELECT user FROM pass_table WHERE password =".md5($value).";");
while($row = mysqli_fetch_array($result)){
mysqli_close($con);
//return $row['user'];
if($row['user']){
return true;
}
}
mysqli_close($con);
return false;
}
I am curious how I could make a PHP script where you pass your username and password to my webserver and it downloads a file (or multiple) that is specific to that one user only. I am not sure where to start. Could someone please help? thanks!
Here it is:
http://www.php.net/manual/en/function.readfile.php
You shouldn't put a password directly in the URL. Instead, use a random generated token, for example using MD5 ($token = md5($username.time());). So, your user table should look like this :
id
email
password
token
The URLs you'll provide to your user will be like this :
http://www.example.com/download.php?token=$userToken&file=$file
And download.php:
<?php
if ( ! isset($_GET['token']) || ! isset($_GET['file'])) {
//throw 404
}
//check token exists in DB else throw 403
//use readfile function :)
First you could create a mysql table for all users. Then you could make a second one where the filenames and usernames are saved like "file:test.png user:xyz" that every file has an owner. If somebody wants to download a file you have to check if this user is the file owner in the database. The list of all files then shows all files of this user.
Here's a simple example:
download.php
<?php
if (isset($_GET['user_info']) && check_validity($_GET['user_info'])) {
$file_info = determine_file_info($_GET['user_info']);
header("Content-type: " . $file_info['type']);
header('Content-disposition: attachment; filename="' . $file_info['name'] . '"');
readfile($file_info['absolute_path']);
exit;
}
?>
That mock-up imagines you have defined a function check_validity that determines if the given user info is valid, and that you have defined a function determine_file_info that uses the user info to build an array of file type, file name, and file path (where the file is on the server).
I am working on a function for a PostgreSQL database, that when client issues a database dump, the dump is offered as a download. This snapshot could then later be used to restore the database with. However, I can't seem to figure out how to do it. When the user presses the button, an AJAX call to the server is made, as to which the server executes the following code:
if($_POST['command'] == 'dump'){
$dump = $table->Dump();
header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename=/"'.$dump.'/"');
}
Where $table->Dump() looks like this:
public function Dump(){
$filename = dirname(__FILE__)."/db_Dump.out";
exec("pg_dump ".$this->name." > $filename");
return $filename;
}
The dump isn't made though. Any tips on this?
This approach however, doesn't work. I thought that setting the headers would be enough to cause a download, but apparently I was wrong. So what would be the correct way of creating a download?
Edit 1, #stevevls:
if($_POST['command'] == 'dump'){
$dump = $table->Dump();
$fh = fopen($dump, 'r') or die("Can't open file");
header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename=/"'.$dump.'/"');
$dumpData = fread($fh, filesize($fh));
fclose($fh);
echo $dumpData;
}
I still don't get anything as a download though.
Edit 2, #myself
I have been able to get a return value, it seemed that the the check if the command given was 'dump' was never reached. I fixed that, and now I get an error on the pg_dump command. I now get
sh: cannot create ../database/db_Dump.sql: Permission denied
I bet this is due to php not being allowed to run pg_dump, but how could I get the system to allow it to be able to run it?
Edit 3, #myself
After resolving the issue with the pg__dump (I added www-data, Apaches user on my system, to the sudoers list, which resoved the issue. Also setting the correct permissions on the directory to write to is handy aswell.) I now get the db_Dump.sql as plain text instead of a save as dialog. Any ideas on that?
first of all check if dump file was created on disc.
Second, check if your PHP script has not reached time limit, because making dump can last long.
Third, you want to read whole dump into memory? You can easly reach memory limit, so do it part-by-part. On php.net you have example in fread manual:
$handle = fopen("http://www.example.com/", "rb");
$contents = '';
while (!feof($handle)) {
$contents .= fread($handle, 8192);
}
fclose($handle);
Turns out, it was all due to the fact of how I requested the download. It seems that it is impossible to get a download when you request it via Ajax, as the returned file get's accepted in the success method of the call. After Changing this to a direct link to the file, I was able to get a download.