dan3460's avatar

Retrieving files from /storage/app/documents

We are doing a rewrite of our document management system, the system currently stores the document in a blob on the database. I'm exploring the possibility of storing the documents outside the database and Laravel makes that super easy. The files are stored in the path given above, now i'm playing with retrieving the document for display. if I use storage_path("nameOfDocument") get an error "Not allowed to load local resource", which makes sense. For what i read the solution is to make a sim-link from the public folder to the storage folder, my problem is that exposes the documents to the web. Is there a way to do what i need to do?

0 likes
8 replies
arukomp's avatar

Don't use a symlink then. Just use:

Storage::disk('local')->put('filename.txt', 'Contents bla bla bla');

to store the file in the storage/app directory, and later:

Storage::disk('local')->get('filename.txt')

to retrieve it. This approach would later allow you to change "disks" and store/fetch your files from other sources, like AWS S3 storage, or FTP server, etc.

Here's more about it: https://laravel.com/docs/5.5/filesystem#storing-files

2 likes
dan3460's avatar

I'm still learning Laravel, so please bear with me. To upload the file the user selects it in a form, then i have the following to store the file: $doc->document_location=$request->file('file')->store('documents'); which gives me the location and the name (hash) to store in the database. Then to display i have a blade with a iframe to show the document using a winddow.open(), i tried the following but got an empty string window.open('{{Storage::disk('local')->get('documents/KxfAL72tsX18kzZyfwLRjLjHyWILXHGWnvBRvze3.pdf')}}','docFrame'); For sure i'm doing something stupid.

arukomp's avatar

well, if the file was not found, you would've gotten an exception:

    /**
     * Get the contents of a file.
     *
     * @param  string  $path
     * @param  bool  $lock
     * @return string
     *
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
     */
    public function get($path, $lock = false)
    {
        if ($this->isFile($path)) {
            return $lock ? $this->sharedGet($path) : file_get_contents($path);
        }

        throw new FileNotFoundException("File does not exist at path {$path}");
    }

If you look at your uploaded file manually, just using Finder/Explorer, does it have any content?

arthurvillar's avatar

You cannot access any files outside of the /public folder from the browser and any file in there is available on the web if the visitor knows the name of the file. The hash name already helps preventing that, but if you need total security you can create a small Controller to handle the download of the file when the user types, for example, http://your-site.com/docs/the-doc-here.pdf. You then use the Response class to download the file from storage and show it in the browser. As long as you have a middleware in that controller you will be safe from unauthorized visitors.

class FileController extends Controller {
    public function __construct()
    {
        // This will block anyone who is not registered from continuing with this request
        $this->middleware('auth');
    }

    public function getFile($filename)
    {
        // The Response class has the helper function response() that you can use
        return response()->download(storage_path($filename), null, [], null);
    }
}

The last argument in download() being null prevents the Content-Disposition header being set to attachment. In other words, the browser won't ask you save the file, but just show it.

Then in the route you can have

Route::get('/docs/{filename}', 'FileController@getFile');

If you want to be specific about the characters used naming the file you can add this to the route

Route::get('/docs/{filename}', 'FileController@getFile')->where('filename', '^[^/]+$');;

Now the file is located only on the storage folder. When an authenticated user makes the request on the browser, it will be displayed but not downloaded or permanently saved on the public folder.

1 like
dan3460's avatar

@arthruvillar i was writting this when i saw your post:

Yes it does, i can open the file if i navigate to the folder. Did not get an exception. I went for a cup of tea and done some thinking, please correct me if I'm wrong. The `window.open()` command is looking for an url that can attach to the iframe, therefore it will never going to be able to open a non public folder. ```

So using Arth idea and that i have to make sure that even authorized users do not have access to each other's files, i will make a temporary copy of the file that can be uploaded by the `window.open()` command. This is probably not as efficient as Arth solution but i feel that is more secure.

Thanks everyone for the help
 
1 like
dan3460's avatar

In case anyone follows this, my solution was to basically make a temporary copy of the file within the public folder. I still have to resolve the delete of the temporary file, right now is being deleted after one hour by a crontab process. I tried to check when the w=window.open() command was done, but i think that because is handled on a iframe the test $(w).ready() returns true right away thus the iframe doesn't display the document.

Spiral's avatar

Following code should work.

$file = $request->file->storeAs('importFiles', $request->file->getClientOriginalName());
$getFile = Storage::disk('local')->get($file);

Please or to participate in this conversation.