Restrict File type in File Upload - drupal

I have created a Webform in Drupal 9 and I am using File Upload Field of type document field. We have added condition in setting to accept only pdf, doc, docx files. However user is able to upload php files using the exploit throught script. However on the server we checked the uploaded php files are getting saved with extension txt.
E.g user uploaded test.php file, it is saved as test.php.txt file on server.
Is there a way to restrict user to upload php files?
Thanks,
Akshay Sharma

You can try to create a validation of your form in a custom module.
Example:
In your custom.module:
function custom_module_form_alter(&$form, FormStateInterface $form_state, $form_id) {
if ($form_id == 'webform_submission_yourformid_add_form') {
$form['#validate'][] = 'yourcustommodule_form_validate';
}
}
public function yourcustommodule_form_validate(array &$form, FormStateInterface $form_state) {
$extensions= array('doc','docx','pdf');
$files = $form_state->getValue('yourfilefield');
foreach($files["uploaded_files"] as $item => $val){
$filename= $val["filename"];
$ext = pathinfo($filename, PATHINFO_EXTENSION);
if (!in_array($ext, $extensions)) {
$form_state->setErrorByName('yourfilefield', t('Error') );
}
}
}

Related

liip imagine_filter in Symfony Controller on non public path

Is there any way to use liip imagine_filter without copying the image source to a public path?
I can not see how resolvers/loaders have to be set up to load images from a non public file location and store them likewise.
I defined a watermark filter with a watermark image placed outside public path - which works without problems. But ONLY applied on images placed IN public path.
I am on Symfony 5 and "liip/imagine-bundle": "^2.6"
I have the same problem as you and i found this solution, of course is a little workaround but it works very well.
So I copy the file from $remoteWatermak into the server. if the $localWatermark is setted I check if the file exist.
$arrContextOptions=array(
"ssl"=>array(
"verify_peer"=>false,
"verify_peer_name"=>false,
),
);
if($localWatermark){
$filesystem = new Filesystem();
if(!$filesystem->exists($localWatermark)){
$contents = file_get_contents($remoteWatermark,false,
stream_context_create($arrContextOptions)); // get file
file_put_contents($localWatermark , $contents);
}
}else{
$contents = file_get_contents($remoteWatermark,false,
stream_context_create($arrContextOptions)); // get file
$file = "uploads/watermarks/" . uniqid();
file_put_contents($file , $contents);
$localWatermark = $file;
}

Where do you store user uploaded content within a Symfony 4 application?

I have a section within my site where the user can upload their own profile pictures which is stored in the output directory and tracked in the database like so:
$form = $this->createForm(ProfileUpdateForm::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
$user = $this->getUser();
$firstname = $form->get('firstname')->getData();
$lastname = $form->get('lastname')->getData();
$picture = $form->get('profilepicture')->getData();
if($picture == null)
{
$user
->setFirstName($firstname)
->setLastName($lastname);
}
else
{
$originalFilename = pathinfo($picture->getClientOriginalName(), PATHINFO_FILENAME);
// this is needed to safely include the file name as part of the URL
$safeFilename = strtolower(str_replace(' ', '', $originalFilename));
$newFilename = $safeFilename.'-'.uniqid().'.'.$picture->guessExtension();
try {
$picture->move(
'build/images/user_profiles/',
$newFilename
);
} catch (FileException $e) {
$this->addFlash("error", "Something happened with the file upload, try again.");
return $this->redirect($request->getUri());
}
// updates the 'picture' property to store the image file name
// instead of its contents
$user
->setProfilePicture($newFilename)
->setFirstName($firstname)
->setLastName($lastname);
}
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
$this->addFlash("success", "Your profile was updated!");
return $this->redirectToRoute('account');
}
return $this->render('account/profile.html.twig', [
'profileform' => $form->createView()
]);
That issue I've found is that every time I compile my (local) project, the image is then deleted (because the public/build directory gets built by deleting and creating again).
If I'm not mistaken, isn't that how deployments work too? And if so, is that the right way to upload an image? What's the right way of going about this?
I'm not sure why, but your public/ directory shouldn't be deleted.
If you're using Webpack Encore, then public/build/ content is deleted and created again when you compile assets. But not public/ itself.
For uploads, we create public/upload/ directory.
Then, most of the time, we set some globals, which allow us to save the file name only.
Globals for Twig in config/packages/twig.yaml which "root" will be in your public/ directory
twig:
globals:
app_ul_avatar: '/upload/avatar/'
app_ul_document: '/upload/document/'
And globals for your controllers, repositories, etc in config/services.yaml
parameters:
app_ul_avatar: '%kernel.root_dir%/../public/upload/avatar/'
app_ul_document: '%kernel.root_dir%/../public/upload/document/'
It's handy because, as I just said, you only get to save the file name in the database.
Which mean that, if you got a public/upload/img/ folder, and want to also generates thumbnails, you can then create public/upload/img/thumbnail/ and nothing will change in your database, nor do you have to save an extra path.
Just create a new global app_ul_img_thumbnail, and you're set.
Then all you have to do is call your globals when you need them, and contact with the file name:
In Twig:
{{ app_ul_avatar~dbResult.filename }}
Or in Controller:
$this->getParameter('app_ul_avatar').$dbResult->getFilename();

How to limit file uploads to Amazon S3 with Slingshot.createDirective

Am trying to limit my uploads to amazon s3 to 10 pictures, i came across this link
https://github.com/themeteorchef/uploading-files-to-amazon-s3/blob/master/code/server/slingshot.js
for some reason it doesn't work with me
this is my code
Slingshot.createDirective( "uploadToAmazonS3Cg2", Slingshot.S3Storage, {
bucket: "bucket-name",
region: 'ap-southeast-1',
acl: "public-read",
authorize: function () {
return true;
},
key: function ( file ) {
var user = Meteor.users.findOne( this.userId );
return user.emails[0].address + "/screenshots" + "/" + file.name;
}
});
here is my uploader in the html file
Application ScreenShots:
{{> uploader config="2"}}
and this is how am calling my slingshot method based on the link i attached above
var uploader
if (config === '1') {
uploader = new Slingshot.Upload( "uploadToAmazonS3Cg1" );
}
if (config === '2') {
uploader = new Slingshot.Upload( "uploadToAmazonS3Cg2" );
} else {
uploader = new Slingshot.Upload( "uploadToAmazonS3Cg3" );
}
i returned true, from my understanding, it should allow me to upload as many as i want but i only can upload one file.
am i missing something here? are there any alternatives to set the limitation?
Taking a look at the source-code for slingshot, you need to upload the files one at a time. To limit the number of files uploaded in total by a user, you'd need to use the authorize function and store how many files they've uploaded in Mongo. I'm not sure if you mean 10 files total or 10 at a time based on your question, but if it was 10 files at a time you'd simply validate that using jQuery validation on your file input element.

How to integrate Dropzonejs with wordpress media handler in frontend?

How can I integrate Dropzonejs file uploader library in wordpress front end just like the built in one and have the uploaded one available in my media library?
Dropzonejs is a very extensive javascript library that provides a lot of options to handle media uploading.
To integrate dropzonejs with wordpress the process is pretty straight forward. Assume the following piece of code is where you want to appear your uploader.
<div id="media-uploader" class="dropzone"></div>
<input type="hidden" name="media-ids" value="">
Having a class dropzone will automatically attach the dropzone event with the element. That will stop us from overriding default parameters. So we would like to disable the auto discover feature of the library.
// Disabling autoDiscover, otherwise Dropzone will try to attach twice.
Dropzone.autoDiscover = false;
Now we will use jQuery to bind our configuration with the element.
jQuery("#media-uploader").dropzone({
url: dropParam.upload,
acceptedFiles: 'image/*',
success: function (file, response) {
file.previewElement.classList.add("dz-success");
file['attachment_id'] = response; // push the id for future reference
var ids = jQuery('#media-ids').val() + ',' + response;
jQuery('#media-ids').val(ids);
},
error: function (file, response) {
file.previewElement.classList.add("dz-error");
},
// update the following section is for removing image from library
addRemoveLinks: true,
removedfile: function(file) {
var attachment_id = file.attachment_id;
jQuery.ajax({
type: 'POST',
url: dropParam.delete,
data: {
media_id : attachment_id
}
});
var _ref;
return (_ref = file.previewElement) != null ? _ref.parentNode.removeChild(file.previewElement) : void 0;
}
});
In the code above what we have done is we attached dropzone with our element with some parameters-
url - location where we want to send our files to upload. I'll initialize the variable later.
acceptedFiles - since we are only interested in uploading images, we will limit the files to be attached only to images. You can find about more in the website of this library.
success - a callback that is fired when the file/image is uploaded successfully. It accepts two parameter the reference of the uploaded file itself and the response from the server. This is very important, here we stored the attachment id in our form. You can perform a validation here prior to store the id.
error - if the file failed to upload then you can perform any task here.
addRemoveLinks - add the remove file link below the preview panel, you can style it with your css.
removedfile - handles the operation while you click on the remove file link for an image in the preview panel. In this function we sent an ajax call to our server to remove the image from the library
Of course there are a lot of option available, but I found these are the most basic parameters I required to setup my drag-n-drop media uploader.
Now the most important thing is to decide about the file uploader url. You can have a custom file where you would want to process the operation. But I found another way.
From this question and the answer I found using admin-post.php file is pretty amazing.
Many people complained about this admin-post.php, so think sticking to the wp_ajax.php is the best option.
So I initialized the drophandler variable prior to my dropzone initialization as follows-
wp_enqueue_script('dropzone','path/to/dropzone', array('jquery'));
wp_enqueue_script('my-script','path/to/script',array('jquery','dropzone'));
$drop_param = array(
'upload'=>admin_url( 'admin-ajax.php?action=handle_dropped_media' ),
'delete'=>admin_url( 'admin-ajax.php?action=handle_deleted_media' ),
)
wp_localize_script('my-script','dropParam', $drop_param);
Now we are ready to send our images to the server. Here we will add some php code whether in the theme's function.php file or in our plugin file, but we need to be assured that it is loaded.
The following function will take care of the uploading the image and saving as an attachment in the library.
add_action( 'wp_ajax_handle_dropped_media', 'handle_dropped_media' );
// if you want to allow your visitors of your website to upload files, be cautious.
add_action( 'wp_ajax_nopriv_handle_dropped_media', 'handle_dropped_media' );
function handle_dropped_media() {
status_header(200);
$upload_dir = wp_upload_dir();
$upload_path = $upload_dir['path'] . DIRECTORY_SEPARATOR;
$num_files = count($_FILES['file']['tmp_name']);
$newupload = 0;
if ( !empty($_FILES) ) {
$files = $_FILES;
foreach($files as $file) {
$newfile = array (
'name' => $file['name'],
'type' => $file['type'],
'tmp_name' => $file['tmp_name'],
'error' => $file['error'],
'size' => $file['size']
);
$_FILES = array('upload'=>$newfile);
foreach($_FILES as $file => $array) {
$newupload = media_handle_upload( $file, 0 );
}
}
}
echo $newupload;
die();
}
The following action take care of the deletion of the media element. Second parameter of wp_delete_attachment() function allows us to decide whether we want to trash the image or completely delete it. I wanted to delete it completely so passed true.
add_action( 'wp_ajax_handle_deleted_media', 'handle_deleted_media' );
function handle_deleted_media(){
if( isset($_REQUEST['media_id']) ){
$post_id = absint( $_REQUEST['media_id'] );
$status = wp_delete_attachment($post_id, true);
if( $status )
echo json_encode(array('status' => 'OK'));
else
echo json_encode(array('status' => 'FAILED'));
}
die();
}
This will return the attachment_id in the response and we'll get it in the success function. In the media_handle_upload( $file, 0 ); I passed the reference of the file and a 0 because I didn't wanted to assign the media with any post yet (0 for no post, but if you want to assign then pass the post ID here. More reference in the codex.)
This is all for uploading media in wordpress.
Note: I haven't completed the removing uploaded file part. I'll complete this in a moment.
UPDATE
The post is updated. Now we can remove uploaded media elements from the uploader container. Thanks to this question and the answer I could figure out the actual process.
Those who are having problems getting this to work for non-admin users; please use admin-ajax.php instead of admin-post.php.
I had faced a strange issue that admin-post.php would work for non-admin users on my local server; but my live server refused to let non-admins upload files. php would echo entire page instead of the echoed value.
I replaced admin-post.php with admin-ajax.php and uploads work super cool.
I hope this helps.
The solution added to this post is incorrect unless I've misunderstood the question. Basically the solution won't work for anyone who isn't logged in as an admin. It took me 30 minutes to work it out plus the solution for removing images doesn't delete it from the media library.

Serve a download of an uploaded file in Symfony2

My Symfony2 app allows users to upload files. I'd like to users to also be able to download their files.
If I were doing straight PHP, I'd just output the appropriate headers, then output the contents of the file. How would I do this within a Symfony2 controller?
(If you use a hard-coded filename in your answer, that's good enough for me.)
I ended up doing this:
/**
* Serves an uploaded file.
*
* #Route("/{id}/file", name="event_file")
* #Template()
*/
public function fileAction($id)
{
$em = $this->getDoctrine()->getEntityManager();
$entity = $em->getRepository('VNNPressboxBundle:Event')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find Event entity.');
}
$headers = array(
'Content-Type' => $entity->getDocument()->getMimeType(),
'Content-Disposition' => 'attachment; filename="'.$entity->getDocument()->getName().'"'
);
$filename = $entity->getDocument()->getUploadRootDir().'/'.$entity->getDocument()->getName();
return new Response(file_get_contents($filename), 200, $headers);
}
Any reason why you do not want to bypass Symfony entirely and just serve the file via your HTTP server (Apache, Nginx, etc)?
Just have the uploaded files dropped somewhere in the document root and let your HTTP server do what it does best.
Update: While the Symfony2 code posted by #Jason Swett will work for 99% of cases - I just wanted to make sure to document the alternative(s). Another way of securing downloads would be to use the mod_secdownload module of Lighttpd. This would be the ideal solution for larger files or files that need to be served quickly with little-as-possible memory usage.
Have a look at the VichUploaderBundle
It will allow you to do this:
/**
* #param integer $assetId
*
* #return Response
*/
public function downloadAssetAction($assetId)
{
if (!$courseAsset = $this->get('crmpicco.repository.course_asset')->findOneById($assetId)) {
throw new NotFoundHttpException('Requested asset (' . $assetId . ') does not exist.');
}
$downloadHandler = $this->get('vich_uploader.download_handler');
return $downloadHandler->downloadObject($courseAsset->getFile(), 'assetFile', null, $courseAsset->getName());
}

Resources