Working with encrypted files in Laravel (how to download decrypted file) - php

In my webapp, users can upload files. Before being saved and stored, the contents of the file are encrypted using something like this:
Crypt::encrypt(file_get_contents($file->getRealPath()));
I then use the file system that comes with Laravel to move the file
Storage::put($filePath, $encryptedFile);
I have a table to store information about each file with columns such as:
id
file_path
file_name
original_name (includes the extension)
Now I want the user to be able to download this encrypted file. However, I'm having trouble decrypting the file and returning it to the user. In the file downloads response section of the Laravel documentation, it suggests to do this:
return response()->download($pathToFile, $name, $headers);
It wants a file path which is fine, but at which point can I decrypt the file contents so that it is actually readable?
I do seem to be able to do this:
$encryptedContents = Storage::get($fileRecord->file_path);
$decryptedContents = Crypt::decrypt($encryptedContents);
... but I don't know how to return it as a download with a specified file name.

You could manually create the response like so:
$encryptedContents = Storage::get($fileRecord->file_path);
$decryptedContents = Crypt::decrypt($encryptedContents);
return response()->make($decryptedContents, 200, array(
'Content-Type' => (new finfo(FILEINFO_MIME))->buffer($decryptedContents),
'Content-Disposition' => 'attachment; filename="' . pathinfo($fileRecord->file_path, PATHINFO_BASENAME) . '"'
));
You can check out the Laravel API for more info on what the parameters of the make method are. The pathinfo function is also used to extract the filename from the path so it sends the correct filename with the response.

Laravel 5.6 allows you to use streams for downloads: https://laravel.com/docs/5.6/responses#file-downloads
So in your case:
return $response()->streamDownload(function() use $decryptedContents {
echo $decryptedContents;
}, $fileName);

Related

(Laravel/PHP) How to save file in memory or delete file before return without causing error?

(It's API only) I'm using JasperPHP to generate reports, the user can choose the extension (xls or pdf). After I process the data, then I save the file/report locally, it's in "storage/app/jasper/example1234.pdf".
So at the end of the function I do this:
// more code
$file = $path . $filename . '.' . $extension; // file path
// more code
return response()->file($file);
And it works, I can get the file using ReactJS. OK! But I wanted to delete the file before the "return", but if I do that the file will not be read on the "return". That's why I wanted to save it in a variable/memory first, delete the file locally and then return.
Note I tried to get the file using several methods, such as "Storage::get()", but they all generate errors on "return", stating that "string file" is expected but "resource" was given.
Response facade has deleteFileAfterSend() method. You can use it like the following:
return response()->file($file)->deleteFileAfterSend();

Prevent users to download other files by changing the path in a url query

i have a download function receiving the filename by $_GET and i want to prevent users of downloading other files changing the path and accessing other files in the system.
method:
function actionDownload($arquivo) {
try {
$filepath = \Yii::getAlias('#webroot') . '/files/coordenadas/'. $arquivo;
if (file_exists($filepath)){
return \Yii::$app->getResponse()->sendFile(\Yii::getAlias('#webroot') . '/files/coordenadas/'. $arquivo, $arquivo);
}
}
catch (\Exception $exception) {
throw new NotFoundHttpException("Arquivo não encontrado");
}
}
the route to download the method:
http://example.com/converter-coordenadas/download?arquivo=geografica-utm-20200830171051.xlsx
if someone change the arquivo variable to another valid path it will be able to download other files. How prevent that, but keeping the function receiving the file name in a url param?
the situation that i have is:
the user upload a file through ajax
i convert this file and return the filename
create a download button with the link to the new file.
I don't have any other information to make a relation with the file, like an user id.
As #GetSet explained in the comments, the biggest problem is procedural. One way to do this correctly is as follows:
Upload the file to your server and save the reference in database (you already doing) and generate an unique ID for this file (or for this download). This ID will be saved in a database field, for example with the name: "donwload_id"
Then in the view (when you are creating the link for the download):
Html::a('Download', [Url::to('donwload-action'), 'download_id' => $model- >download_id]);
In your controller, You will know how to find the file by its unique identifier (download_id).
No one knows how you have generated this ID and therefore it is more difficult for anyone to be able to generate it again. Also you can limit the time available to download the file by setting an expiration date to the link.

Laravel - Display preview of file stored in Storage directory

I have to upload some files for each users, and the files should not be accessible publicly.
When a user a created, I'm creating a folder in storage directory using-
Storage::makeDirectory($user->ref_id);
Now I've a files table, which stores the file details. Here is the code for uploading file and saving the path to the database.
$this->validate($request, [
'file' => 'required|mimetypes:image/png,image/jpeg,application/pdf',
]);
$user = Auth::user();
$filename = time() . '.' . $request->file->getClientOriginalExtension();
$path = $request->file('file')->storeAs($user->ref_id, $filename);
$user->files()->create(['file_name' => $path]);
The file is being stored successfully. Now when a user logs in, I want to display a preview of that file in the view.
Any help, how can I do that??
This is how you serve a file from a controller. Instead of returning a view you return the file like this. In case of an image you can't echo it directly to the view unless you base64 encode it (which increases the filesize).
return response()
->download($file_path, "file_name",
[
'Content-Type' => 'application/octet-stream'
]);
Make a function in your controller containing the code above and some logic to retrieve the right file path and make a route for it.
After you've done that you can (for example) add in your view
<img src="{{route('ROUTE_NAME')}}">
You'll have to fill in all the variables yourself ofcourse.
Using this method the files will always stay private and will only be echoed once you allowed the user access. Note that this WILL use more recourses as you let PHP handle serving the file instead of apache. Hope this helps!
This solved my problem...
https://stackoverflow.com/a/41322072/6792282
I used this in my code and preview is working fine for pdf files..
<embed name="plugin" src="{{url('/')}}/{{ Storage::disk('local')->url($file->file_name)}}" type="application/pdf">

Ensuring random key names in S3 while maintaining original file names by user

Previously, I stored client files locally, on a server using PHP (and running Apache). They would upload files, and each one would be given a randomized string ending in a pdf / jpg file extension. The original file name would be kept in a database along with the randomized name to link them back together when the user wanted the file.
I wanted to transition to storing files on a private bucket in S3. The first thing I'm seeing is this article which says to give Object keys a unique name, but all the examples I'm seeing just put the user's file name in there.
This is an issue because if a user stores test.pdf and another, entirely different user uploads test.pdf, then it won't be uploaded. Another issue is if I use the random file names like I have previously been doing, and then the user gets the file from a pre-signed request, then they will be accessing a file named via some random string and not the file they thought they uploaded.
What should I be doing to separate out a user's files, while keeping the original file name on s3?
Personally, I do exactly what you describe in your first example. The S3 file gets a UUID generated for the file name in the bucket and all the metadata including the original file name goes in the database.
I don't even bother giving the S3 file an extension.
To expand on my comments and the question about how to read the files back;
I'm using Laravel with Intervention\Image (site).
My GET endpoint for the attachment controller returns this function in my model:
/**
* Gets an image from Amazon and returns it
* #param boolean $thumb
* #return null|Image
*/
public function output($thumb = false)
{
if ($this->s3_filename === null) {
return null;
}
// Grab the image from S3
$this->image = $this->s3->get('/' . $this->getPath() . '/' . ($thumb ? 'thumb/' : '') . $this->s3_filename);
if ($this->image === null) {
return null;
}
return Image::make($this->image)->response()->withHeaders([
'content-disposition' => 'inline; filename="' . ($thumb ? 'thumb_' : '') . $this->filename . '"',
]);
}
How about considering using buckets/folders?
Buckets need to have unique names (across ALL of AWS... not sure if that has changed). But the folders within them are fine.
But otherwise:
myBucket/
user1/
test.pdf
user2/
test.pdf
There's not an additional cost to having directories within buckets AFAIK so you should be good.
You can also use a UUID instead of user1, and have a table somewhere that maps usernames to UUID to generate the bucket/folder path.

Laravel 5.5 read a file from the local disk

I am trying to import a csv file with Laravel 5.5 from the local file location. For some reason it can't find the file on my computer however the path is correct.
$fileLocation = $request->file('file')->store('csv');
$importFile = File::file(storage_path('app/' . $fileLocation));
You can use the Storage facade for this. Here is an example:
if (Storage::disk($disk)->exists($filename)) {
return Storage::disk($disk)->get($filename);
}
throw new FileNotFoundException(sprintf('File not found: %s', $filename), 404);
If you don't wanna use Storage facade, you can use below code to get the file
$importFile = Illuminate\Support\Facades\File::get(storage_path('app/' . $fileLocation));
but when you store a file with csv as store argument, it will save in storage/csv folder, and you just need call storage_path($fileLocation).
be sure that your storage path is correct and for more information read here

Categories