In Moodle 1.9.7, is it possible to somehow specify a white-list of the allowed extensions for the user uploaded files?
Looking at the source, there is no built in way to limit filetypes when modules use the upload_manager to process the uploaded files. There also is no use of any mimetype detection based on content of the files. The filelib libraries in moodle base their mimetype on file extension.
A neat way to do this for a module while is using the moodle upload manager object, would be to write a new class which extends the existing upload_manager class, and add some validatation based on file content.
Something like the following - you'll need to tidy this up a bit, and complete it with your own validation code.
class upload_manager_strict extends upload_manager {
var $allowed_types
function upload_manager_strict($inputname='', $deleteothers=false, $handlecollisions=false, $course=null, $recoverifmultiple=false, $modbytes=0, $silent=false, $allownull=false, $allownullmultiple=true, $allowed_types=null) {
$this->allowed_types = $allowed_types;
parent::upload_manager_strict($inputname, $deleteothers, $handlecollisions, $course, $recoverifmultiple, $modbytes, $silent, $allownull, $allownullmultiple)
}
function validate_file(&$file) {
$status = parent::validate_file(&$file);
if ($status) {
// do some mimetype checking and validation on the file $file['tmp_name']
// could use $this->allowedtypes
}
return $status;
}
}
Related
Is there a way to mock a file using Laravels Storage::fake() method?
I have used https://laravel.com/docs/5.7/mocking#storage-fake as a base for my tests, which works fine for uploads. But my download tests are ugly as I have to run my upload route first every time with a mock upload UploadedFile::fake()->image('avatar.jpg'). Is there a way to skip that part and mock the file to exist directly in the fake storage system?
public function testAvatarUpload()
{
Storage::fake('avatars');
// This is the call I would like to change into a mocked existing uploaded file
$uploadResponse = $this->json('POST', '/avatar', [
'avatar' => UploadedFile::fake()->image('avatar.jpg')
]);
// Download the first avatar
$response = $this->get('/download/avatar/1');
$response->assertStatus(200);
}
I might be late here. but wanted to help others visiting this question to give an idea of implementing it.
Here is a sample with some assertions.
<?php
namespace Tests\Feature\Upload;
use Illuminate\Http\File;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class SampleDownloadTest extends TestCase
{
/**
* #test
*/
public function uploaded_file_downloads_correctly()
{
//keep a sample file inside projectroot/resources/files folder
//create a file from it
$exampleFile = new File(resource_path('files/test-file.png'))
//copy that file to projectroot/storage/app/uploads folder
Storage::putFileAs('/uploads', $exampleFile, 'test-file.png');
//make request to file download url to get file
$response = $this->get("/files/file/download/url");
//check whethe response was ok
$response->assertOk();
$response->assertHeader('Content-Type', 'image/png')
//check whether file exists in path
Storage::assertExists('/uploads/test-file.png');
//do some more assertions.....
//after test delete the file from storage path
Storage::delete('uploads/test-file.png');
//check whether file was deleted
Storage::assertMissing('/uploads/test-file.png');
}
}
Yes, you can use fake file storage feature of Laravel (mocking):
use Illuminate\Http\UploadedFile;
$file = UploadedFile::fake()->create('filename.ext', $sizeInKb)->store('filename.ext');
If you want to create a text/csv file with a specific content you can use this:
use Illuminate\Http\UploadedFile;
$header = 'a,b,c';
$row1 = 'x,y,z';
$row2 = 's,f,t';
$row3 = 'r,i,o';
$content = implode("\n", [$header, $row1, $row2, $row3]);
$file = UploadedFile::fake()->createWithContent('filename.ext', $content)->store('filename.ext');
You can find this methods definitions in Illuminate\Http\Testing\FileFactory
You could just create a new file directly or copy a specific test file for example:
use Illuminate\Http\File;
use Illuminate\Support\Facades\Storage;
// for simple text files or if the content doesn't matter
Storage::disk('avatars')->put('avatar.jpg', 'some non-jpg content');
// if you need a specific file for your test
$file = new File(base_path('tests/resources/avatar.jpg'));
Storage::disk('avatars')->putFileAs('/', $file, 'avatar.jpg');
The latter function will take the $file and copy it under the given name avatar.jpg to the given directory / on the disk avatars. You can read more about it in the official documentation.
What you could use to solve that problem is fixtures. Laravel's testing framework is essentially PHPUnit, so I see no reason why it would not work.
define your test like so:
use Tests\TestCase;
class ExampleTest extends TestCase {
protected function setUp() {
parent::setUp();
Storage::fake('avatars');
$uploadResponse = $this->json('POST', '/avatar', [
'avatar' => UploadedFile::fake()->image('avatar.jpg')
]);
}
protected function tearDown() {
parent::tearDown();
}
public function testAvatarUpload() {
// Download the first avatar
$response = $this->get('/download/avatar/1');
$response->assertStatus(200);
}
}
setUp and tearDown get called, respectively, before and after each test in the class. So, before each test method, setUp will wipe the avatars fake disk and run the request. As there is nothing to do after a test (since Storage::fake() replaces the disk if it already exists), the method is empty; I left it here purely to make the example complete.
There's some pretty good documentation on here about this feature of PHPunit.
Regarding putting the file on there, once you have your setUp working correctly, there's nothing stopping you from throwing the file on it.
Using Symfony2.0 and jQuery
My application generates and stores PDF outside the ./www folder as it is private information (comercial invoices for the purchases within my shop).
I am trying and not getting to allow the customer to download their invoices at any time.
I found this article, but at the beginning it talks about how to get them from the "public" ./www folder. At the end, it offers a piece of code, but I think it is not supported by my version of Symfony:
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class OfflineToolController extends Controller
{
/**
* #return BinaryFileResponse
*/
public function downloadAction()
{
$path = $this->get('kernel')->getRootDir(). "/../downloads/";
$file = $path.'my_file.zip'; // Path to the file on the server
$response = new BinaryFileResponse($file);
// Give the file a name:
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT,'my_file_name.zip');
return $response;
}
}
This is the case: How to create a link to download generated documents in symfony2?
Any idea of how to do this?
I'm trying to write a controller to accept file uploads from the Plupload plugin. As an added bit of fun, the uploads are coming from a different URL so I have to set the Access-Control-Allow-Origin header myself. So far I've done that like so:
/**
* #Route("/frontEnd/file/upload.{_format}")
*/
public function upload(Request $request) {
$response = new Response();
$response->setContent(json_encode(array('hello' => 'world')));
$response->setStatusCode(200);
$response->headers->set('Access-Control-Allow-Origin', '*');
$response->send();
}
which seems to work. When I submit the uploads using plupload I see the XHR requests hit Symfony and the JSON is returned. However, I have no idea how to handle the actual file and move it into a directory.
I did a var_dump() on $_POST and it only returned the following:
array(1) {
["name"]=>
string(21) "wallpaper-2873928.jpg"
}
The upload is definitely being sent as I can see the file's bytes being part of the request payload with developer tools. Do I need to use Symfony's own components to handle the upload? If so, how? The Symfony documentation only seems to cover uploading from a file upload form.
First of all, try to use the Symfony2 way of accessing request parameters. You can get more information in the book.
When uploading a file, Symfony2 automatically creates an instance of UploadedFile for you and puts it in a FileBag in the request object.
You can access the files in your controller like this:
$files = $request->files;
Like said previously, these are temporary files. To upload them in a user defined directory, use the move method on the object.
$directory = //...
foreach ($files as $uploadedFile) {
$name = //...
$file = $uploadedFile->move($directory, $name);
}
The variable $files now contains an instance of File.
On the other hand, you can also use a bundle that supports the Plupload uploader. I'd recommend the OneupUploaderBundle. (Note: I'm the main developer of this bundle, I guess this needs to be added).
Anyone tried using TinyButStrong together with CakePHP?
I have no prior knowledge of TinyButStrong but seems to be a good way to generate Word documents from templates. But I am not sure how to integrate this with a CakePHP application.
Thank you for any ideas / suggestions.
Best regards,
Tony.
I presume you mean TinyButStrong with the OpenTBS plug-in which can merge DOCX (and other Ms Office and OpenOffice documents) using templates.
Here is a way to add an export action in a CakePHP Controller which is destined to generate a Docx to be downloaded.
The following code is available for CakePHP version 1.3, it is not tested with version 2.0.
Steps :
1) Add the TBS and OpenTBS classes in the vendor directory, under a subdirectory:
vendors/tbs/tbs_class.php
vendors/tbs/tbs_plugin_opentbs.php
2) Create a CakePHP helper that will simplify the preparation of TBS + OpenTBS:
app/views/helpers/tbs.php
<?php
class TbsHelper extends AppHelper {
function getOpenTbs() {
App::import('Vendor', 'tbs/tbs_class');
App::import('Vendor', 'tbs/tbs_plugin_opentbs');
$tbs = new clsTinyButStrong; // new instance of TBS
$tbs->Plugin(TBS_INSTALL, OPENTBS_PLUGIN); // load OpenTBS plugin
return $tbs;
}
}
3) Now add a new "export" action in the controller that should generate the Docx:
app/controllers/example_controller.php
<?php
class ExamplesController extends AppController {
var $name = 'Examples';
function export() {
// Stop Cake from displaying action's execution time, this can corrupt the exported file
// Re-ativate in order to see bugs
Configure::write('debug',0);
// Make the Tbs helper available in the view
$this->helpers[] = 'Tbs';
// Set available data in the view
$this->set('records', $this->Example->find('all'));
}
}
4) The last thing is to create the corresponding view. Don't forget to place your DOCX template in the same folder as the view.
app/views/examples/export.ctp (below)
app/views/examples/export_template1.docx (to build with Ms Office)
<?php
ob_end_clean(); // Just in case, to be sure
// Get a new instance of TBS with the OpenTBS plug-in
$otbs = $tbs->getOpenTbs();
// Load the DOCX template which is supposed to be placed in the same folder
$otbs->LoadTemplate(dirname(__FILE__).'/export_template1.docx');
// Merge data in the template
$otbs->MergeBlock('r', $records);
// End the merge and export
$file_name = 'export.docx';
$otbs->Show(OPENTBS_DOWNLOAD, $file_name);
exit; // Just in case, to be sure
TinyButStrong gives facilities to merge PHP global variables, but it is recommended to not use such feature within CakePHP. Instead, you should use MergeBlock() and MergeField() with the data set by the Controller for the View.
If you met bugs, don't forget to disable the line
Configure::write('debug', 0);
and this will show you the CakePHP errors. Otherwise CakePHP will hide all errors including PHP errors.
Don't forget that OpenTBS has also a debug mode. See the manual if needed.
You can also make this a lib (to be used anywhere in your application).
Currently if I supply no extensions to the class it allows no extensions. I would like to allow all extensions. Is there any way to do this without hacking the core?
In Codeigniter 2, you simply need to define allowed types like this :
$config['allowed_types'] = '*';
What I do is:
$ext=preg_replace("/.*\.([^.]+)$/","\\1", $_FILES['userfile']['name']);
$fileType=$_FILES['userfile']['type'];
$config['allowed_types'] = $ext.'|'.$fileType;
That makes all files in every function call automatically allowed.
The answer to your direct question: No, there's no way to do this without overriding the core
To good news is you can avoid hacking the core, per the manual
As an added bonus, CodeIgniter permits your libraries to extend
native classes if you simply need to add some functionality to
an existing library. Or you can even replace native libraries just
by placing identically named versions in your application/libraries folder.
So, to have a drop in replacement for your library, you could copy Upload.php to your
application/libraries
folder, and then add your custom logic to that Upload.php file. Code Igniter will include this file instead whenever you load the upload library.
Alternately, you could create your OWN custom uploader class that extends the original, and only refines the is_allowed_filetype function.
application/libraries/MY_Upload.php
class MY_Upload Extends CI_Upload{
function is_allowed_filetype(){
//my custom code here
}
}
You'll want to read over the changelog whenever you're upgrading, but this will allow you to keep your code and the core code in separate universes.
So far it looks like it would only be possible via a hack.
I inserted a return true on line 556 in system/libraries/Upload.php.
$config['allowed_types'] = '*';
Which Will Upload Any File Formats Like .exe or .jpegs extra...
if all not works then rearrange the allowed type order like that in first video format
$config['allowed_types'] = 'mp4|jpg|png|'
It will works in my case so I shared if possible try it.
You simply need to replace this condition:
if (! $this->is_allowed_filetype())
{
$this->set_error('upload_invalid_filetype');
return false;
}
With:
if (count($this->allowed_types) && !$this->is_allowed_filetype())
{
$this->set_error('upload_invalid_filetype');
return false;
}
$config['allowed_types'] = '*'; is possible solution, but IMHO it is not very safe. The better way is to add a file types you need to $config['allowed_types']. In case the CodeIgniter doesn't have the MIME types you need, you can add them by editing file mimes.php in "application/config" folder. Just include your mime types in array.
Example of adding epub and fb2 file types:
return array(
'epub' => 'application/epub+zip',
'fb2' => 'application/x-fictionbook+xml',
'hqx' => array('application/mac-binhex40', 'application/mac-binhex', 'application/x-binhex40', 'application/x-mac-binhex40'),
Then you'll be able to use the extensions you added in $config['allowed_types'] variable.