Getting an extension of an uploaded file in yii2 - php

I'm trying to get an extension of a file during upload but I get an error that path info requires a string:
I have tried:
$path_parts = pathinfo($_FILES['evidence']['name']);
echo $path_parts['extension'];
How can extract file extension, for example jpeg, doc, pdf, etc.

If you are using yii2 kartik file input you can get the instance of yii\web\uploadedFile this way to:
$file = UploadedFile::getInstanceByName('evidence'); // Get File Object byName
// Then you can get extension by this:
$file->getExtension()
If you want to validate file as well then you can use FileValidator using adhoc role:
$validator = new FileValidator(['extensions' => ['png','jpg']]);
if( $validator->validate($file, $errors) ) {
// Validation success now you can save file using $file->saveAs method
} else {
// ToDO with error: print_r($errors);
}

It's better not use $_FILES directly in Yii2 since framework provides abstraction with a class yii\web\UploadedFile. There is also separate page in guide describing working with uploaded files.
There is an example with model.
namespace app\models;
use yii\base\Model;
use yii\web\UploadedFile;
class UploadForm extends Model
{
/**
* #var UploadedFile
*/
public $imageFile;
public function rules()
{
return [
[['imageFile'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg'],
];
}
public function upload()
{
if ($this->validate()) {
$this->imageFile->saveAs('uploads/' . $this->imageFile->baseName . '.' . $this->imageFile->extension);
return true;
} else {
return false;
}
}
}
As you can see, extension is extracted using extension property ($this->imageFile->extension).
There are more info about form settings, handling in controller, uploading multiple files. All this can be found by the link mentioned above.

Related

Laravel 5 safe upload document files

in this case I want to safe upload pdf, doc, docx, ppt, pptx, xls, xlsx, rar, zip prevent from arbitrary file upload especially web shell or any evil script.
The problem is how I can validate file, is safe to upload? prevent from bypass like change mime type with tamper data, rename file with multiple extension, using ; and space in file name, lowercase and uppercase file extension and etc.
my controller code look like this
public function fileUpload(){
$ext = ['pdf', 'doc', 'ppt', 'xls', 'docx', 'pptx', 'xlsx', 'rar', 'zip'];
$data = Request::all();
$name = $data['file']->getClientOriginalName();
$rules = [
'file' => 'required'
];
$v = Validator::make($data, $rules);
if($v->passes()){
// Check safe file validation
// should here or something? and how to prevent bypass
// arbitrary file upload especially evil script.
$data['file']->move(public_path('assets/uploads'), $name);
return 'file uploaded';
}else{
return 'file upload failed';
}
}
I would suggest looking at Laravel Middleware for the validation. This will reduce the code in your controllers and allow them to be reused.
I personally change the name of any file upload to something random. I can always save the original file name somewhere in the system if needs be.
I would also look at using a htaccess command which prevents file execution from that folder.
Controller method below
Note: it uses App\Http\Requests\CreateUploadRequest;
public function store(CreateUploadRequest $request)
{
$file = Input::file('file');
$destinationPath = 'assets/uploads'; // upload path
$name = $file->getClientOriginalName(); // getting original name
$fileName = time().rand(11111, 99999) . '.' . $extension; // renaming image
$extension = $file->getClientOriginalExtension(); // getting fileextension
$file->save($destinationPath.'/'.$fileName); // uploading file to given path
}
Middleware
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
class CreateUploadRequest extends Request {
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'file' => 'required_if:update,false|mimes:pdf,doc,ppt,xls,docx,pptx,xlsx,rar,zip|max:1000'
];
}
}
I think this idea was taken from a laracast video. I'll have a look around to see if i can find it.

Input class not found in laravel 5

I want to save file uploaded through form into a json file for this I need to get post data which is easily get through Request or Input class methods.
The problem is whenever I use Request or Input I can't get methods such as getClientOriginalName to get name of file and other parameters of file.
My FileController code is as below:
<?php namespace App\Http\Controllers;
use App\Http\Requests;
use Illuminate\Http\Request; // this handles both for Input and Request as in laravel 5.1 documentation
use Illuminate\Support\Facades\Input; // though added some classes to get work
use Illuminate\Support\Facades\File; // though added some classes to get work
use Illuminate\Filesystem\Filesystem; // though added some classes to get work
class FileController extends Controller
{
public function index()
{
$files = $this->getAllData();
return view('document.index', compact('files'));
}
public function create()
{
return view('document.create');
}
public function store(Request $request)
{
$name = $request->input('title');
echo $name;
$file = $request->file('afile');
if($request->hasFile('afile')) {
$file = $request->file('afile');
print_r($file); // return array of uploaded as expected
$filename = $file->getClientOriginalName(); // not working
// or
$filename = Input::file('afile')->getClientOriginalName(); // not working
echo $filename;
}
// print_r($file);
// $data= array('title'=>$name, 'afile'=>$file);
// $this->create_entry($data);
// return redirect('document');
}
}
FYI my file upload is sucessful and has got file array as
Array ( [0] => Symfony\Component\HttpFoundation\File\UploadedFile Object ( [test:Symfony\Component\HttpFoundation\File\UploadedFile:private] => [originalName:Symfony\Component\HttpFoundation\File\UploadedFile:private] => new_file_1.txt [mimeType:Symfony\Component\HttpFoundation\File\UploadedFile:private] => text/plain [size:Symfony\Component\HttpFoundation\File\UploadedFile:private] => 0 [error:Symfony\Component\HttpFoundation\File\UploadedFile:private] => 0 [pathName:SplFileInfo:private] => E:\xampp\tmp\php5680.tmp [fileName:SplFileInfo:private] => php5680.tmp ) )
The only problem is I can't get methods of Symphon2 API though i used
use Input;
or
use Illuminate\Support\Facades\Input;
Every methods of Input are not working either to check its valid or not.
Every tutorial I refer or documentation from laravel 5 uses same as I have used in my code.
So any Kind of suggestion or solution is really appreciated.
the functions as used in this documentation are working but no other methods except than that.
Your missing the use statement for the Input facade. Add the following to your use statements.
use Illuminate\Support\Facades\Input;
You may try this:
$filename = $file->getClientOriginalName();
Since you have already used the following:
$file = $request->file('afile');
The file method returns an instance of Symfony\Component\HttpFoundation\File\UploadedFile and in this case, the instance is already cached in the $file variable.
Also to make sure the upload was successful you may check it using something like this:
if($request->hasFile('afile')) {
$file = $request->file('afile');
$filename = $file->getClientOriginalName() .'.'. $file->getExtension();
}
this works for me
\Input::file('file')->getClientOriginalName();
In your form open tag add 'files' => true
{!! Form::open(array('files' => true, ....)) !!}
In the controller check first if the file has been uploaded correctly
if (!Input::file('afile')->isValid())
{
// return error 50x
}
$filename = Input::file('afile')->getClientOriginalName();
can't apply method to non-object means that Input::file(...) returned null and therefore the file wasn't uploaded or it doesn't exists. Then, when you call ->getClientOriginalName() from a null value php throws an exception.

validating and save an uploaded ajax image in Yii

I need to send an image to server via an ajax request and it gets through just fine
and in my controller I can just use $_FILES["image"] to do stuff to it.
But I need to validate the image before I save it.
And in the Yii this can be achieved by doing something like this
$file = CUploadedFile::getInstance($model,'image');
if($model->validated(array('image'))){
$model->image->saveAs(Yii::getPathOfAlias('webroot') . '/upload/user_thumb/' . $model->username.'.'.$model->photo->extensionName);
}
But the problem is I don't have a $model, all I have is $_FILES["image"], now what should I put instead of the $model???
is there any other way where I can validate and save files without creating a model and just by Using $_FILES["image"]?
thanks for this awesome community... :)
Exists many ways how you can do upload. I want offer to you one of them.
1.You need to create model for your images.
class Image extends CActiveRecord {
//method where need to specify validation rules
public function rules()
{
return [
['filename', 'length', 'max' => 40],
//other rules
];
}
//this function allow to upload file
public function doUpload($insName)
{
$file = CUploadedFile::getInstanceByName($insName);
if ($file) {
$file->saveAs(Yii::getPathOfAlias('webroot').'/upload/user_thumb/'.$this->filename.$file->getExtensionName());
} else {
$this->addError('Please, select at least one file'); // for example
}
}
}
2.Now, need to create controller, where you will do all actions.
class ImageController extends CController {
public function actionUpload()
{
$model = new Image();
if (Yii::app()->request->getPost('upload')) {
$model->filename = 'set filename';
$insName = 'image'; //if you try to upload from $_FILES['image']
if ($model->validate() && $model->doUpload($insName)) {
//upload is successful
} else {
//do something with errors
$errors = $model->getErrors();
}
}
}
}
Creating a model might be overkill in some instances.
The $_FILE supervariable is part of the HTTP mechanism.
You can handle the copy by using the native PHP function move_uploaded_file()
$fileName = "/uploads/".myimage.jpg";
unlink($fileName);
move_uploaded_file($_FILES['Filedata']['tmp_name'], $fileName);
However, you lose the niceties of using a library that provides additional functionality and checks (eg file type and file size limitations).

Test move_uploaded_file and is_uploaded_file with vfsStream

I've tried to test move_uploaded_file and is_uploaded_file with PHPUnit and vfsStream. They always return false.
public function testShouldUploadAZipFileAndMoveIt()
{
$_FILES = array('fieldName' => array(
'name' => 'file.zip',
'type' => 'application/zip',
'tmp_name' => 'vfs://root/file.zip',
'error' => 0,
'size' => 0,
));
vfsStream::setup();
$vfsStreamFile = vfsStream::newFile('file.zip');
vfsStreamWrapper::getRoot()
->addChild($vfsStreamFile);
$vfsStreamDirectory = vfsStream::newDirectory('/destination');
vfsStreamWrapper::getRoot()
->addChild($vfsStreamDirectory);
$fileUpload = new File_Upload();
$fileUpload->upload(
vfsStream::url('root/file.zip'),
vfsStream::url('root/destination/file.zip')
);
$this->assertFileExists(vfsStream::url('root/destination/file.zip'));
}
Is it possible? How do I do that?
Can I post a vfsStreamFile (or any data) without a form, just using PHP code?
Thank you.
No. move_uploaded_file and is_uploaded_file are specifically designed to handle uploaded files. They include extra security checks to ensure that the file's not been tampered with in the time between the upload completing and the controlling script accessing the file.
Note: changing the file from within the script counts as tampering.
Assuming you're using classes you can create a parent class.
// this is the class you want to test
class File {
public function verify($file) {
return $this->isUploadedFile($file);
}
public function isUploadedFile($file) {
return is_uploaded_file($file);
}
}
// for the unit test create a wrapper that overrides the isUploadedFile method
class FileWrapper extends File {
public function isUploadedFile($file) {
return true;
}
}
// write your unit test using the wrapper class
class FileTest extends PHPUnit_Framework_TestCase {
public function setup() {
$this->fileObj = new FileWrapper;
}
public function testFile() {
$result = $this->fileObj->verify('/some/random/path/to/file');
$this->assertTrue($result);
}
}

How do I retain the Original Filename After upload in Symfony

Users from the backend application can upload files and publish them to the frontend. Using sfWidgetFormInputFile and sfValidatorFile, I would like to keep the original filename instead of the default functionality of a random string (i.e. Meaningful_filename.docx instead of a4b25e9f48cfb6268f34b691fc18cd76fefe96b5.docx - numbers can be appended onto duplicate names). This can be useful in scenarios where the user downloads several files and would not be able to tell them apart from the file name.
$this->widgetSchema['file_name'] = new sfWidgetFormInputFile(array('label' => 'File'));
$this->validatorSchema['file_name'] = new sfValidatorFile(array(
'required' => true,
'path' => sfConfig::get('sf_upload_dir').DIRECTORY_SEPARATOR.sfConfig::get('app_dir_file_sharing').DIRECTORY_SEPARATOR,
'mime_types' => array('application/msword',
'application/vnd.ms-word',
'application/msword',
'application/msword; charset=binary')
), array(
'invalid' => 'Invalid file.',
'required' => 'Select a file to upload.',
'mime_types' => 'The file must be a supported type.'
));
Is there native functionality in the sfWidgetFormInputFile widget or is there another solution to this?
You get the file by calling $form["file_name"]->getValue(). This gives you an object of class sfValidatedFile where you can call the method getOriginalName().
To define how the file should be save you can do this:
The sfValidatorFile class accepts an option which sfValidatedFile class to use:
validated_file_class: Name of the class that manages the cleaned uploaded file (optional)
The sfValidatedFile class has a method save that calls a method generateFileName. Subclass this class and overwrite this method:
class CustomValidatedFile extends sfValidatedFile {
/**
* Generates a random filename for the current file.
*
* #return string A random name to represent the current file
*/
public function generateFilename()
{
return 'foo bar'// your custom generated file name;
}
}
Here is the function from the original class:
public function generateFilename()
{
return sha1($this->getOriginalName().rand(11111, 99999)).$this->getExtension($this->getOriginalExtension());
}
Then you set up the validator this way:
$this->validatorSchema['file_name'] = new sfValidatorFile(array(
'required' => true,
'path' => 'yourpath',
'validated_file_class' => 'CustomValidatedFile',
'mime_types' => array('application/msword',
'application/vnd.ms-word',
'application/msword',
'application/msword; charset=binary')
),
array('invalid' => 'Invalid file.',
'required' => 'Select a file to upload.',
'mime_types' => 'The file must be a supported type.')
);
Hope that helps!
After some research:
While you can extend sfValidatedFile and override generateFilename I found out that sfFormPropel checks for the existence of a method based on the column name for the model to name the file.
From symfony/plugins/sfPropelPlugin/lib/form line 292:
$method = sprintf('generate%sFilename', $column);
if (null !== $filename)
{
return $file->save($filename);
}
else if (method_exists($this, $method))
{
return $file->save($this->$method($file));
}
Therefore, if your column is called file_name, the method looks for the existence of generateFileNameFilename in the form class. This way you only have to add one method to your form class, rather than extending the sfValidatedFile widget. For instance, my function uses the original name if it is not taken, otherwise appends a sequential number (one method is to recursively check the generated filename):
public function generateFileNameFilename($file = null)
{
if (null === $file) {
// use a random filename instead
return null;
}
if (file_exists($file->getpath().$file->getOriginalName())) {
return $this->appendToName($file);
}
return $file->getOriginalName();
}
public function appendToName($file, $index = 0)
{
$newname = pathinfo($file->getOriginalName(), PATHINFO_FILENAME).$index.$file->getExtension();
if (file_exists($file->getpath().$newname)) {
return $this->appendToName($file, ++$index);
} else {
return $newname;
}
}
I can't find this documented in the symfony API anywhere which is why it took some searching the code base to find. If you are using this method in many places, extending sfValidatedFile might be a good option too.
According to the Symfony documentation "The sfValidatorFile validator validates an uploaded file. The validator converts the uploaded file to an instance of the sfValidatedFile class, or of the validated_file_class option if it is set." (Source: http://www.symfony-project.org/forms/1_4/en/B-Validators#chapter_b_sub_sfvalidatorfile)
Although the sfValidatedFile class renames files right out of the box, you can override this function by setting the validated_file_class to a custom class, and extending sfValidatedFile.
In your custom validated file class, pass your custom filename to the save() method. "If you don't pass a file name, it will be generated by the generateFilename method." (Source:
http://www.symfony-project.org/api/1_4/sfValidatedFile#method_save)
Here's one way you could do it (Source: http://forum.symfony-project.org/index.php/m/90887/#msg_90887)...
A custom validated file class:
// lib/validator/myValidatedFile.php
class myValidatedFile extends sfValidatedFile {
private $savedFilename = null;
// Override sfValidatedFile's save method
public function save($file = null, $fileMode = 0666, $create = true, $dirMode = 0777) {
// This makes sure we use only one savedFilename (it will be the first)
if ($this->savedFilename === null) $this->savedFilename = $file;
// Let the original save method do its magic :)
return parent::save($this->savedFilename, $fileMode, $create, $dirMode);
}
}
Make sure to set 'validated_file_class' => 'myValidatedFile' for the sfWidgetFormInputFile. And to set the logic for what the file name is going to be in Form's save method.

Categories