Get correct URL for uploaded files - laravel

I have a function in my platform for letting users upload their own icon images. Once they've uploaded them I save them using $request->icon->store('public/icons') and simply save the returned path, something like "public/icons/xa6y2am3e4cOdqQuLpvEhFSXDKwFMDOgggS2i67l.png".
I'm not really clear though on what's the correct way to show the icons. The URL for showing the icon in the above example is "/storage/icons/xa6y2am3e4cOdqQuLpvEhFSXDKwFMDOgggS2i67l.png", thus I need to replace "public" in the path with "storage", which makes me think I've done something wrong somewhere (I see no reason why the store() method should provide me with a path that's not correct).
I have done my symlinks as described in the documentation. Using the local storage. What am I missing?

This is how I handle my storage in one of my apps that includes blogs:
$storedImageName = $request->file('main_image')->store('blog_images', 'public');
The store method actually returns the stored file name along with the path. Here, I specify that my disk is public and that the folder inside public is blog_images. I store the result of that in $storedImageName.
Now, to save it in the database, I do not actually save the whole path, I save ONLY the image name. This is because, you may want to update your directory name in the future, or move it to another location, etc. You can get that like this:
$onlyImageName = basename($storedImageName);
basename() is PHP's function, has nothing to do with Laravel.
This way, I can render my images in my blade files like this:
<img ... src="{{ asset('/storage/blog_images/' . $blog->main_image) }}" />
asset() helper will provide you with path to your public directory, and then you just need to specify the rest of the path to your image.

Related

Render blade view from URL path

I've an unusual and interesting problem that I am trying to solve.
We had to put some of our blade views to S3 storage and render them from S3 url with passed variables.
For example:
view('page.user.show', ['user' => User::first()])->render();
Now, since we have our page.user.show blade file stored in S3 storage on the path "/views/pages/user/show.blade.php"
We somehow need to make Laravel get that view from s3 URL path instead from resource_path
So we need something like this:
view('s3url.com/views/pages/user/show.blade.php', ['user' => User::first()])->render();
Is there any way we can do this in Laravel ? Or, at least read content of blade file from S3 path and render that string content with variables?
To achieve something like this, you'll need to implement a custom version of the Laravel Illuminate\View\ViewFinderInterface. Your best bet is to create a new view finder that can load blade templates from S3 and move them to the local filesystem temporarily.
You can probably just extend the FileViewFinder class, and add some custom logic to the find() method. In there, try something like:
if (Str::startsWith($name, 's3::')) {
// This gets a temp file that PHP will clean up for you
$name = stream_get_meta_data(tmpfile())['uri'];
// Download the Blade template from S3 and store it temporarily
file_put_contents($name, Storage::disk('s3')->get(Str::after($name, 's3::')));
// Return the path
return $name;
}
Then you'll need to swap in your new finder using View::setFinder.
The major downside of this approach is that you'll need to download views from S3 every time you render them. You probably want to at least add some caching to the system so that you're not hitting S3 every time a page loads.

Laravel: How to prevent users loading files from outside the base path

I have a Laravel website and I have several routes that load the contents of images from Storage. I do this using the following code:
public function show_image($name) {
echo Storage::disk('images')->get($name);
}
I want to prevent users being able to set name to something like ../../../error.log. So I don't want users to escape the Storage directory. I have a few ideas on how to accomplish this however I want to know is there a best practice?
If you need just file name, not location, disallow them from inputting folder of any kind. Just cut the string on /.
end(preg_split("#/#", $name));
When you need to allow some folders and all of the contents, check the folder name, subfolder name, etc.
You could either keep a registry/index of the uploaded images, and only allow the user to show a image from that registry (e.g. an images database table).
Or you could do a scan of the directory, that you are allowing files from, and make sure, that the requested file is in that list.
public function show_image($name) {
$files = Storage::disk('images')->files();
if (! in_array($name, $files)) {
throw new \Exception('Requested file not found');
}
echo Storage::disk('images')->get($name);
}
(code untested)

Laravel: display file from cloud storage in the browser

This snippet from the Laravel docs, https://laravel.com/docs/5.5/responses#file-responses, seems to be just what I need:
return response()->file($pathToFile);
The problem is that my file is stored on an S3 disk, and I can't seem to reference it properly.
I tried using Storage::disk('s3')->getDriver()->getAdapter()->applyPathPrefix($myPath); to get the fully qualified file name. It just returned the value of $myPath though.
I then tried to get the url using Storage::disk('s3')->url($myPath); The URL looks fine however Symphony says it does not exist. When I check with Storage::disk('s3')->exists($myPath); however, it returns true.
How do I go about displaying a file from cloud storage directly in the user's browser?
EDIT:
More details below:
To save the item in the first instance I am using $map->storeAs('/my/path/maps/filename.pdf', ['disk' => 's3']);
The output of url() is "https://s3.ap-southeast-2.amazonaws.com/my.domain.com/my/path/maps/filename.pdf"
When I cut-and-past the url in a browser address bar, it loads no problem
It seems to me that the response()->file() method does not accept a URL parameter. Is that the case?
Question - does the file need to be publicly available? (It currently is, but I would prefer to make it private).
The following should be enough:
$path = Storage::disk('s3')->url($filename);
I don't have a working example set up, but from memory $filename should be the same as what you originally placed in the ->put() method. So when you say $mypath in your question, hopefully that isn't already prefixing stuff like your s3 instance.
If this isn't the case, can you edit the question to include the result of ->url() and an example of your put() call and $path.
Looking at the edit, I think I understand what you are trying to do, which has been solved here:
https://laracasts.com/discuss/channels/laravel/file-response-from-s3

Cakephp response (sending files) breaks when model functions are added

I have baked a File model and controller with default actions. Now I am trying to add an display function which can be used to show images in controlled manner.
I want to protect images so that display function can check does the user have an permissions to view image (image directory is not in a webroot).
I haven't been able to make it work, but when I started from the scratch I managed to find out that really minimal function did work.
Working function looks like this:
public function display($id) {
$this->response->file(ROOT.DS.'img'.DS.'noimage.jpg');
return $this->response;
}
When I add example:
$test=$this->File->findById($id);
to the starting of the function everything breaks.
--> http://www.example.com/files/display/1
The requested file /var/www/example.com/www/img/image.jpg was not found or not readable
Error: The requested address '/files/display/1' was not found on this server.
I have tried with debug zero, file can be found and is readable, obviously because the function without findById works.
Any ideas?
cakephp 2.4.3
You path is totally wrong.
Did you debug() what ROOT.DS.'img'.DS.'noimage.jpg' actually holds?
I bet all the money of the world that you would probably find the solution yourself if you did
The img folder is most likely in webroot
WWW_ROOT . 'img' . DS . 'noimage.jpg'
Note that paths usually end with a DS so no need to add it again.
So if it really is an image folder in ROOT:
ROOT . 'img' . DS . 'noimage.jpg'
Also note that you can easily check if a path is valid using
file_exists()
If the file has the correct file permissions this should return true.
EDIT:
$this->File->...: File is not a good choice for a model name as it collides with the existing core class in Utility. You need to be a little bit more creative with your model naming scheme.

How to use storage_path() to view an image in laravel 4

I'm using storage_path() for storing uploaded images, but when I use it is pointing wrong on my page.
I use it like this {{ $data->thumbnail }} where $data came from the database and thumbnail comes as the string which used storage_path
Let us take a look at the default L4 application structure:
app // contains restricted server-side application data
app/storage // a writeable directory used by L4 and custom functions to store data ( i.e. log files, ... )
public // this directory is accessible for clients
If I were you, I would upload the file to the public directory directly:
Store image here: public_path() . 'img/filename.jpg'
Save the 'img/filename.jpg' in database
Generate the image URL with url('img/filename.jpg') => http://www.your-domain.com/img/filename.jpg
Hope this helps.
The storage_path function returns the path to the storage folder, which is inside the app folder --and outside the public folder-- so it's not directly accessible from the client, that's why your images are not being displayed. You can move them to the public folder path, or you could use a custom controller to handle the image requests, read the image from the storage folder and return the value.

Resources