View files via links in Zend MVC - model-view-controller

In my app I have a module named "client". I wish to create a sub directory "statements" and below that "accounts" then "account_123" and within this directory the pdfs representing statements for clint 123.
Structure client then statements then accounts then acount_XXX and the pdf files.
The problem
statement_1.pdf?
Being MVC I am making little progess.
I moved the structure to public and it almost wotk except I could not retrieve dynamically the hostname i.r. www.hostnam.com/client/statement....
I know this is a security issue so I have moved the directory back yo below the client module.
Any help will be appreciayed.
TIA Ephraim

As far as I know you can't place a direct link to a file placed under 'application' director (because of security issues as you already wrote), so one of possible solution is to make an action which read the content of a file and return it in a response object.
When user click a link to this action user get a window to open/save provided file.
Example:
public function exampleAction() {
$this->_helper->layout->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
/* ... */
$file = "FILENAME WITH_PATH";
$response = $this->getResponse();
$response->setHeader('Cache-Control', 'public', true);
$response->setHeader('Content-Description', 'public', true);
$response->setHeader('Content-Disposition', 'attachment; filename=' . FILENAME, true);
$response->setHeader('Content-Type', FILETYPE, true);
$response->setHeader('Content-Transfer-Encoding', 'binary', true);
$response->setHeader('Content-Length', filesize($file), true);
$response->setBody(file_get_contents($file));
}
I put in above code some constants because its values depends on your application.

Related

Downloading files which were stored using the storage facade in laravel 5.4

I've noticed that the method Storage::download($path) is not available until Laravel 5.6. I would rather not create symbolic links since this appears to me to defeat the purpose of using the storage facade, might as well save the file in the assets folder (not sure how correct I am in saying this).
My question is, how can I download a file that I have saved using the storage facade and not make the file publicly visible through a URL link?
I use this:
$path = "/path/to/file/";
$filename = "my_file.pdf"
return response(Storage::disk('local')->get($path.$filename), 200)
->header('Content-Type', Storage::disk('local')
->mimeType($path.$filename)
);
Edit:
If to store you use:
$path=$request->file('file')->store('file');
that's going to store the file in the storage/app/file folder, with a name created by laravel. For example:
storage/app/file/7K5gIZvRVWbdBedRXEyVm5X1Ubz61vJZguFmERlT.jpeg
(Make sure the file has been stored).
And in DB you must save (I do not know how you're doing this, but it's the 'file' folder and the file name created by laravel):
file/7K5gIZvRVWbdBedRXEyVm5X1Ubz61vJZguFmERlT.jpeg
So, to download you have to make a route:
Route::get('/download', 'DownloadController#downloadFile')->name('download-file');
And a Controller method:
public function downloadFile()
{
     $path = // get the DB field
     return response(Storage::get($path), 200)
         ->header( 'Content-Type', Storage::mimeType($path) );
}
And you can add a link in a view:
<a href="{!! route('download-file') !!}" download>Download the file</a>

Checking folder already existed and if not create a new folder by id in laravel

i want to store my image to a specific folder and the folder will named by page id. For example if Page id=1, the folder location should be public/pages/page_id/image_here.
If the folder are not existed yet the system will generate and named with their page id.
$id=1;
$directoryPath=public_path('pages/' . $id);
if(File::isDirectory($directoryPath)){
//Perform storing
} else {
Storage::makeDirectory($directoryPath);
//Perform storing
}
But i having error that "mkdir(): Invalid argument".
Can i know why it happen?
And after my research some people say the folder name should based with id+token so intruder cannot search image based on id, is there possible to achieve?
I had the same problem, but when I use File instead of Storage it works!
$id=1;
$directoryPath=public_path('pages/'.$id);
//check if the directory exists
if(!File::isDirectory($directoryPath)){
//make the directory because it doesn't exists
File::makeDirectory($directoryPath);
}
//Perform store of the file
Hope this works!
When you use Storage facade, it uses local disk as default which is configured to work with storage_path().
So, if you want to create directory in public directory, use File::makeDirectory which is just simple wrapper for mkdir() with additional options or use mkdir() directly:
File::makeDirectory($directoryPath);
mkdir($directoryPath);
For basic Laravel file system the syntax is :
At very top under namespace write:
use File;
Then in your method use:
$id=1;
$directoryPath=public_path('pages/' . $id);
if(File::isDirectory($directoryPath)){
//Perform storing
} else {
File::makeDirectory($directoryPath, 0777, true, true);
//Perform storing
}

How to protect image from public view in Laravel 5?

I have installed Laravel 5.0 and have made Authentication. Everything is working just fine.
My web site is only open for Authenticated members. The content inside is protected to Authenticated members only, but the images inside the site is not protected for public view.
Any one writes the image URL directly can see the image, even if the person is not logged in to the system.
http://www.somedomainname.net/images/users/userImage.jpg
My Question: is it possible to protect images (the above URL example) from public view, in other Word if a URL of the image send to any person, the individual must be member and login to be able to see the image.
Is that possible and how?
It is possible to protect images from public view in Laravel 5.x folder.
Create images folder under storage folder (I have chosen storage folder because it has write permission already that I can use when I upload images to it) in Laravel like storage/app/images.
Move the images you want to protect from public folder to the new created images folder. You could also chose other location to create images folder but not inside the public folder, but with in Laravel folder structure but still a logical location example not inside controller folder. Next you need to create a route and image controller.
Create Route
Route::get('images/users/{user_id}/{slug}', [
'as' => 'images.show',
'uses' => 'ImagesController#show',
'middleware' => 'auth',
]);
The route will forward all image request access to Authentication page if person is not logged in.
Create ImagesController
class ImagesController extends Controller {
public function show($user_id, $slug)
{
$storagePath = storage_path('app/images/users/' . $user_id . '/' . $slug);
return Image::make($storagePath)->response();
}
}
EDIT (NOTE)
For those who use Laravel 5.2 and newer. Laravel introduces new and better way to serve files that has less overhead (This way does not regenerate the file as mentioned in the answer):
File Responses
The file method can be used to display a file, such as an image or
PDF, directly in the user's browser instead of initiating a download.
This method accepts the path to the file as its first argument and an
array of headers as its second argument:
return response()->file($pathToFile);
return response()->file($pathToFile, $headers);
You can modify your storage path and file/folder structure as you wish to fit your requirement, this is just to demonstrate how I did it and how it works.
You can also added condition to show the images only for specific members in the controller.
It is also possible to hash the file name with file name, time stamp and other variables in addition.
Addition: some asked if this method can be used as alternative to public folder upload, YES it is possible but it is not recommended practice as explained in this answer. So the same method can be also used to upload images in storage path even if you do not intend to protect them, just follow the same process but remove 'middleware' => 'auth',. That way you won't give 777 permission in your public folder and still have a safe uploading environment. The same mentioned answer also explain how to use this method with out authentication in case some one would use it or giving alternative solution as well.
In a previous project I protected the uploads by doing the following:
Created Storage Disk:
config/filesystems.php
'myDisk' => [
'driver' => 'local',
'root' => storage_path('app/uploads'),
'url' => env('APP_URL') . '/storage',
'visibility' => 'private',
],
This will upload the files to \storage\app\uploads\ which is not available to public viewing.
To save files on your controller:
Storage::disk('myDisk')->put('/ANY FOLDER NAME/' . $file, $data);
In order for users to view the files and to protect the uploads from unauthorized access. First check if the file exist on the disk:
public function returnFile($file)
{
//This method will look for the file and get it from drive
$path = storage_path('app/uploads/ANY FOLDER NAME/' . $file);
try {
$file = File::get($path);
$type = File::mimeType($path);
$response = Response::make($file, 200);
$response->header("Content-Type", $type);
return $response;
} catch (FileNotFoundException $exception) {
abort(404);
}
}
Serve the file if the user have the right access:
public function licenceFileShow($file)
{
/**
*Make sure the #param $file has a dot
* Then check if the user has Admin Role. If true serve else
*/
if (strpos($file, '.') !== false) {
if (Auth::user()->hasAnyRole(['Admin'])) {
/** Serve the file for the Admin*/
return $this->returnFile($file);
} else {
/**Logic to check if the request is from file owner**/
return $this->returnFile($file);
}
} else {
//Invalid file name given
return redirect()->route('home');
}
}
Finally on Web.php routes:
Route::get('uploads/user-files/{filename}', 'MiscController#licenceFileShow');
I haven't actually tried this but I found Nginx auth_request module that allows you to check the authentication from Laravel, but still send the file using Nginx.
It sends an internal request to given url and checks the http code for success (2xx) or failure (4xx) and on success, lets the user download the file.
Edit: Another option is something I've tried and it seemed to work fine. You can use X-Accel-Redirect -header to serve the file from Nginx. The request goes through PHP, but instead of sending the whole file through, it just sends the file location to Nginx which then serves it to the client.
if I am understanding you it's like !
Route::post('/download/{id}', function(Request $request , $id){
{
if(\Auth::user()->id == $id) {
return \Storage::download($request->f);
}
else {
\Session::flash('error' , 'Access deny');
return back();
}
}
})->name('download')->middleware('auth:owner,admin,web');
Every file inside the public folder is accessible in the browser. Anyone easily gets that file if they find out the file name and storage path.
So better option is to store the file outside the public folder eg: /storage/app/private
Now do following steps:
create a route (eg: private/{file_name})
Route::get('/private/{file_name}', [App\Http\Controllers\FileController::class, 'view'])->middleware(['auth'])->name('view.file');
create a function in a controller that returns a file path. to create a controller run the command php artisan make:controller FileController
and paste the view function in FileController
public function view($file)
{
$filePath = "notes/{$file}";
if(Storage::exists($filePath)){
return Storage::response($filePath);
}
abort(404);
}
then, paste use Illuminate\Support\Facades\Storage; in FileController for Storage
And don't forget to assign middleware (in route or controller) as your requirement(eg: auth)
And now, only those who have access to that middleware can access that file through a route name called view.file

codeigniter output cache doesn't work?

I'm using $this->output->cache(n) to cache webpage, but i cannot figure out how does it work.. I didn't find any cache files under system/cache folder...and also after I edit the page and show it again, the content changes, so it seems that the page is not really cached. Can anyone give a help? (i'm using phil's template lib)
my code:
function show(){
$this->output->cache(5);
$this->load->model('page_model');
$var = $this->uri->segment(3, 0); //get About page
$row = $this->page_model->getPage($var);
$this->template->title('about')
->set_layout('default')
->set_partial('styles', 'css')
->set('data', $row->body)
->build('about');
}
THANKS!
Two things, as outlined in the documentation:
Warning: Because of the way CodeIgniter stores content for output, caching will only work if you are generating display for your controller with a view.
Perhaps not using the "native" views is an issue?
Additionally:
Note: Before the cache files can be written you must set the file permissions on your application/cache folder such that it is writable.
Are you sure your application/cache directory has the correct permissions?
Debug this file and check it's actually writing the cache:
system/core/Output.php
// Do we need to write a cache file? Only if the controller does not have its
// own _output() method and we are not dealing with a cache file, which we
// can determine by the existence of the $CI object above
if ($this->cache_expiration > 0 && isset($CI) && ! method_exists($CI, '_output'))
{
$this->_write_cache($output);
}
This works well for me:
function _output($content) {
// Load the base template with output content available as $content
$data['content'] = &$content;
$this->output->cache(600);
$output = $this->load->view('base', $data, true);
$this->output->_write_cache($output);
echo $output;
}
Are you sure your application/cache directory has the correct permissions?
you you to directories application/cache in cpanel , permissions become 777 is OK

Codeigniter dynamic configuration

How do I let users set the database configuration for a new CI install?
My intention is to have a form that allows them to enter the data then either generate a new config file or to set the options in the database.php file.
Is this possible?
Let's say you want to create a database config file. Here's how I do it:
Create a "dummy" config file by copying a real one, and use values that look something like this:
$db['default']['hostname'] = '__HOSTNAME__';
$db['default']['username'] = '__USERNAME__';
$db['default']['password'] = '__PASSWORD__';
$db['default']['database'] = '__DATABASE__';
Have the user complete the installation form, and verify all the data by testing a database connection. Next, use file_get_contents() to grab the content of the "dummy" config file, use a simple str_replace() to rewrite the values with the ones the user provided, then write the new content to your real config file overwriting the entire thing.
Here's part of the actual code I'm using for creating the file, just to get an idea. Bear in mind that I'm running this on a totally separate CI installation (the "installer"), and this is not a copy/paste example:
// Write the database configuration file
$config = array(
'hostname',
'username',
'password',
'database',
);
$data = file_get_contents(APPPATH.'config/dummy_database.php');
$file = '../private/config/database.php';
// Oops! Just caught this after posting: str_replace() accepts arrays.
// TODO: Use the array method instead of a loop!
foreach ($config as $item)
{
$data = str_replace('____'.strtoupper($item).'____', mysql_escape_string($this->data[$item]), $data);
}
if (write_file($file, $data))
{
$this->message('Database configuration file was written!', 'success');
#chmod($file, 0644);
{
if (is_really_writable($file))
{
$this->message('The database configuration is still writable, make sure to set permissions to <code>0644</code>!', 'notice');
}
}
}
else
{
$this->message('Could not write the database configuration file!');
redirect('step_4');
}
This retains the original copy of the "dummy" config file. You could use the same logic for editing, just read the current config values for anything the user has not changed and do the same string replacement technique.

Resources