The Problem
I'm having an issue with the PHP function is_file().
Some preliminaries: I'm developing on Ubuntu 12.04, 32-bit, using PHP 5.5.10 and Apache 2.4.9.
I'm currently rewriting some working code to convert it to a library in Laravel (completed with a Facade and a ServiceProvider). I'm doing this mainly to clean up some code I wrote when I was young and foolish (about 6 months ago) and to implement unit testing. The library I'm writing provides methods for taking a contract (of which there are two distinct types, with more to come) and finding the path to a PDF document (the scanned paper contract). My methods for finding the path work fine and the tests are all passing.
In my old code, I used to do this:
/**
* Get a scanned contract and return it to the client
*
* #param string $type
* The contract type. Must be either static::CONTRACT1 or static::CONTRACT2.
*
* #param string $contract_id
* The contract ID
*
* #return Response
*/
public static function get($type, $contract_id)
{
// get the file name
//
$results = static::getFileName($type, $contract_id);
// did we find a file? if not, throw a ScannedContractNotFoundException
//
if(!$results)
throw new \MyApplication\Exceptions\ScannedContractNotFoundException("No file found for $type contract $contract_id");
// get the path and file name
//
$path = $results['path'];
$name = $results['name'];
// get the full path
//
$file = $path.$name;
// get the file size
//
$contents = file_get_contents($file);
$fsize = strlen($contents);
// push the file to the client
//
header("Content-type: application/pdf");
header("Content-Disposition: inline; filename=\"".$name."\"");
header("Content-length: $fsize");
header("Cache-control: private");
echo $contents;
exit;
}
And it worked just fine.
Now I'm trying to rewrite it to get rid of the echo and move the code that actually does the work of sending the file to a controller. That code will look like this:
$x = \MyApplication\Models\Contract1::find($id);
$file = \ScannedContracts::getFileName($x);
$path = $file["path"].$file["name"];
return \Response::download($path, $file["name"]);
However, this code is throwing a FileNotFoundException. The code where the exception is being thrown looks like this:
public function __construct($path, $checkPath = true)
{
if ($checkPath && !is_file($path)) {
throw new FileNotFoundException($path);
}
...
Clearly the problem is with the if statement, and in particular the call to is_file().
I have written a little script to test this with a path which is known to be good and is_file() returns false.
When I copy a file to my "public" folder, it works fine.
In the documentation for the is_file() function there is a comment stating that the permissions for the parent folder must be +x. I've examined the permissions, and the folder is world-executable, as is the parent, and the grand-parent, and the great-grand-parent, etc.
There are two possible confounding factors: first, the files I'm working with are located on a CIFS/Samba share. I should mention that the paths in question are absolute paths to the mounted share.
The closest issue to this I've found on SO is PHP is_file returns false (incorrectly) for Windows share on Ubuntu, but that doesn't have a resolution. I've also searched for PHP bug reports, but there's nothing.
Second, some of the paths contain spaces. I've tried escaping them every way I can think of, but it doesn't help.
In the absence of a solution, I'll have to do it the old fashioned way, but I really wanted to do it using the functions Laravel provides.
Questions
Do I need to escape spaces in a path passed to is_file()?
Does anyone know of a fix or a workaround that doesn't a) require changing code in a 3rd party library, or b) require wholesale changes to permissions or other configuration on the CIFS/Samba server?
Thanks in advance!
I think you need to sanitize filenames when upload to directory
function sanitize_file_name( $str ) {
return preg_replace("/[^a-z0-9\.]/", "", strtolower($str));
}
Related
I am developing a PHP software that creates thumbnails from images.
Now I need to make sure that thumbnails are created successfully, in other words that the initial image has been resized/cropped correctly.
I think there's only one way to do this: I manually create the thumbnail to compare with the thumbnail created by the software.
But how to test?
If I use assertFileEquals() to compare the thumbnail created by me and the one created by the software, of course the test fails, even if the two images are identical.
I imagine that happening if only because the creation date of the two files is different, or for similar reasons.
So, how to do?
Storing a pair of source.png and expected_result.png (generated once by the software, verified as good and stored as the reference image) will suffice. Implementing a comparison function seems to be an overhead.
The main purpose of the unit tests is to signalize if system behavior changes, and that's what such test going to do if newly created thumbnail won't match with the reference one.
Yet, if for whichever reason software generates slightly different images every time, then, in case it's not a bug, use the suggested compare similar images approach.
What if image contents differ
In case of PNG files used in this example their contents might contain some auxiliary info such as EXIF.
So you might have to try creating a copy image without this additional info. Please verify if the following code works for you:
public function testThumbnails()
{
$this->assertPngImageContentsEquals(__DIR__ . '/test1.png', __DIR__ . '/test2.png');
}
public static function assertPngImageContentsEquals(
$expected,
$actual,
$message = 'Contents of PNG files differ'
)
{
self::assertFileExists($expected, $message);
self::assertFileExists($actual, $message);
$copy_expected = self::_makePngCopy($expected, __DIR__ . '/expected.png');
$copy_actual = self::_makePngCopy($actual, __DIR__ . '/actual.png');
var_dump($copy_expected);
var_dump($copy_actual);
self::assertFileEquals($copy_expected, $copy_actual, 'Thumbnails differ');
unlink($copy_expected);
unlink($copy_actual);
}
private static function _makePngCopy($sourceFile, $resultFile)
{
$image = imagecreatefrompng($sourceFile);
imagepng($image, $resultFile);
imagedestroy($image);
return $resultFile;
}
If assertFileEquals is failing, then there is something different between the two files. The internal code invokes file_get_contents on both files and asserts true if there are zero differences (so creation date is not part of the assertion).
Since you are manually creating the thumbnail, there must be slight differences. Instead you would need to do a "mostly the same" comparison, there are two questions related to coding this:
Compare 2 images in php
Similar images - how to compare them
Then decide how much difference is considered a pass. So you would do the "mostly the same comparison", then use asserts to determine if the answer of the "mostly the same" comparison falls within a range you can accept.
Update
I ran a quick test to ensure that assertFileEquals works properly on a binary file:
class FileEqualsTest extends PHPUnit_Framework_TestCase
{
public function test_yes_no_answer() {
file_put_contents("a.txt","\e[0m");
file_put_contents("b.txt","\e[0m");
file_put_contents("c.txt","\e[30m");
// straight get contents and comparisons
$contentsA = file_get_contents("a.txt");
$contentsB = file_get_contents("b.txt");
$this->assertEquals($contentsA, $contentsB);
$contentsC = file_get_contents("c.txt");
$this->assertNotEquals($contentsA, $contentsC);
// using file equals has same answer
$this->assertFileEquals("a.txt","b.txt");
$this->assertFileNotEquals("a.txt","c.txt");
}
..and it worked as expected on a very small scale. So it would seem there is some tiny difference in some way. You could try the options shown in the other questions above to see if there is a tiny difference, if that is important to your testing.
I am using the Tcpdf module and PHP to create dymanic PDF invoices from an ordering system.
The script should then save the invoice into a folder called "invoices". The folder exists, and there are full permissions for "everyone" (Windows).
The code I am using is this:
$pdf->Output('invoices/Delivery Note.pdf', 'F');
This uses fopen to save the file.
However the error I am getting is: Warning: fopen(): remote host file access not supported, file://invoices/Delivery Note.pdf
This is a local file, not a remote one.
I attempted adding a / prefix like this:
$pdf->Output('/invoices/Delivery Note.pdf', 'F');
but then I get this error instead: Warning: fopen(file:///invoices/Delivery Note.pdf): failed to open stream: No such file or directory
I created the file, and left it empty, but the same error as above.
Does anyone know why I am getting this error?
From php-Script you can use:
$pdf->Output(__DIR__ . '/invoices/Delivery Note.pdf', 'F');
After upgrading to the tcpdf 6.2.6 in vtiger 6.2 I've had the same problem, sending e-mail with pdf.
So I have changed the file:
libraries/tcpdf/include/tcpdf_static.php
I have commented the code in fopenLocal() and changed the line
fopen($_SERVER['DOCUMENT_ROOT'].$filename, $mode);
see:
/**
* Wrapper to use fopen only with local files
* #param filename (string) Name of the file to open
* #param $mode (string)
* #return Returns a file pointer resource on success, or FALSE on error.
* #public static
*/
public static function fopenLocal($filename, $mode) {
// if (strpos($filename, '://') === false) {
// $filename = 'file://'.$filename;
// } elseif (strpos($filename, 'file://') !== 0) {
// return false;
// }
return fopen($_SERVER['DOCUMENT_ROOT'].$filename, $mode);
}
After changing this, it worked.
similar to user1007017, but just comment the line like shown below (tcpdf 6.2.11)
public static function fopenLocal($filename, $mode) {
if (strpos($filename, '://') === false) {
//$filename = 'file://'.$filename;
} elseif (stream_is_local($filename) !== true) {
return false;
}
return fopen($filename, $mode);
}
I suggest using the following as Gerd has also suggested but make sure you use an absolute path:
$pdf->Output(__DIR__ . '/invoices/Delivery Note.pdf', 'F');
The path must be an absolute path & not a relative path. This PHP bug report explains why: https://bugs.php.net/bug.php?id=28820
The reason relative paths are not supported with the file:// wrapper comes down to a compromise in how UNC paths are dealt with (and more specifically how / are fuzzily interpreted as \ for windows installations).
For Example:
file://foo/bar
Could be interpreted as a relative URI: foo/bar from the current working directory, OR it could be interpreted as a UNC: \foo\bar (share bar on computer foo).
For this and a few internal reasons the file:// wrapper is limited to absolute paths when called explicitly. For relative paths either use realpath() {as you did in your report}, or omit the explicit naming of the file wrapper.
You can then avoid modifying the TCPDF code and worrying about any upgrades replacing your modified code.
I found the issue was that the path for fopen has to be from the document root, and not from the PHP script location.
C:\Website\www\script\invoice\invoice.pdf
For example if the PHP script is inside the "script" folder, and you want to create the pdf inside the "invoice" folder, the script needs to have "\script\invoice\invoice.pdf".
In prestashop you can do it in this way $pdf->Output(_PS_ROOT_DIR_.'/modules/xxx/ticket.pdf', 'F');
try this
$pdf->Output($_SERVER['DOCUMENT_ROOT'].'/invoices/Delivery Note.pdf', 'F');
currently i am working on zf2. Right now i have to give download option to download pdf files.i have stored all the pdf files in data directory.How can i specify link to that pdf files from .phtml file?
thanks in advance.
A user will never gain direct access to your /data directory. This would be just not that good. But you can easily write yourself a download-script.php or the likes that will hand out the content of this directory to your users.
If you take a look at the first six lines of public/index.php you'll see the following:
<?php
/**
* This makes our life easier when dealing with paths. Everything is relative
* to the application root now.
*/
chdir(dirname(__DIR__));
With this in mind, you know that from PHP's side of things the access to anything inside the data directory is as simple as data/file.pdf
You'd always want to write yourself some sort of download-logger. Write yourself a controller. Have an action inside of that controller probably called something like download or anything like that. That action should have one parameter filename.
All that this action does is to check if filename exists file_exists('data/'.$filename) and if it exists, you simply deliver this file to your users. An example mix or zf2 and native php could be:
public function downloadAction()
{
$filename = str_replace('..', '', $this->params('filename'));
$file = 'data/' . $filename;
if (false === file_exists($file)) {
return $this->redirect('routename-file-does-not-exist');
}
$filetype = finfo_file($file);
header("Content-Type: {$filetype}");
header("Content-Disposition: attachment; filename=\"{$filename}\"");
readfile($file);
// Do some DB or File-Increment on filename download counter
exit();
}
This is not clean ZF2 but i'm lazy right now. It may be much much more ideal to use a proper Response Object and do the File-Handling there!
Important Update this thing was actually quite insecure, too. You need to disallow parent-folders. You wouldn't wanna have this guy do something outside of the data/download directory like
`http://domain.com/download/../config/autoload/db.local.php`
If I'm not totally mistaken, simply replacing all occurences of double-dots should be enough...
I would create a symbolic link in public directory for PDF files in data folder.
For example:
ln -s /your/project/data/pdfdir /your/project/public/pdf
and create links something like
File.pdf
Borrowing Sam's code, here's what it looks like in ZF2 syntax.
public function downloadAction()
{
$filename = str_replace('..', '', $this->params('filename'));
$file = 'data/' . $filename;
if (false === file_exists($file)) {
return $this->redirect('routename-file-does-not-exist');
}
$filetype = finfo_file($file);
$response = new \Zend\Http\Response\Stream();
$response->getHeaders()->addHeaders(array(
'Content-Type' => $filetype,
'Content-Disposition' => "attachement; filename=\"$filename\""
));
$response->setStream(fopen($wpFilePath, 'r'));
return $response;
}
I'm having trouble when renaming a series of files to upload using Zend_File_Transfer_Adapter_Http. At some point in the code, I ran into a strange syntax. Say you have a series of validations on the multiple files that I'm trying to upload. I'm using a foreach loop to iterate over the files after calling the class method getFileInfo(). So basically I do a check to see if each file doesn't pass validation and afterwards, if they do pass validation, I'm trying to run the following code:
else {
$reconocido=TRUE;
$sin_espacios=str_replace(' ','_', $nombre_file, $renombrado);
$reconocido=FALSE;
$uploader->addValidator('Extension', FALSE, 'gif, jpg, png, pdf');
if($uploader->isValid($archivo)) {
$reconocido=TRUE;
} else {
$mime = $uploader->getMimeType($archivo);
$aceptados = array('jpg'=>'image/jpeg', 'png'=>'image/png', 'gif'=>'image/gif', 'pdf'=>'application/pdf');
$clave = array_search($mime, $aceptados);
if(!$clave) {
$messages[]="Tipo de archivo no reconocido.";
} else {
$sin_espacios = "$sin_espacios.$clave";
$reconocido=TRUE;
$renombrado=TRUE;
}
}
if($reconocido) {
$existe = scandir($destino);
if(in_array($sin_espacios, $existe)) {
$punto = strrpos($sin_espacios, '.');
$base = substr($sin_espacios, 0, $punto);
$extension = substr($sin_espacios, $punto);
$i = 1;
do {
$sin_espacios=$base.'_'.$i++.$extension;
} while (in_array($sin_espacios, $existe));
$renombrado=TRUE;
}
$uploader->clearValidators();
$uploader->addFilter('Rename', array('target'=>$sin_espacios, $info['tmp_name']));
$exito=$uploader->receive($archivo);
The problem I seem to have is with the line just before the last line, namely $uploader->addFilter('Rename', array('target'=>$sin_espacios, $info['tmp_name'])); because it seems an odd syntax (why would you include a comma inside a key value in an array?
And as it happens, running the code does upload the files, and renames them alright, but I keep having the following warnings:
Warning: Illegal string offset 'source' in C:\Zend_FW\ZendFramework-1.11.12-minimal\library\Zend\Filter\File\Rename.php on line 145
Warning: Illegal string offset 'target' in C:\Zend_FW\ZendFramework-1.11.12-minimal\library\Zend\Filter\File\Rename.php on line 145
And also:
File 'C:\Windows\Temp\php97CA.tmp' could not be renamed. It already exists.
Forgive me if I'm asking something that should be obvious. The fact is the documentation I have states that the syntax I'm talking about, is correct, but in the ZendFramework site I haven't found anything like it. So what would be the correct syntax for doing this?
I also realize that using the ZendFramework is quite complex, perhaps, for my present level, but it has been working for me until I tried to do multiple uploads. I read a very thorough explanation about file uploads with Zend here:
PHP: How to rename a file uploaded with Zend_Form_Element_File?
But I'm not too worried about files being overwritten because this is an administrative section of the webpage.
Please I'll be thankful for any idea.
In order to be more precise I suspect the problem is that the Filter cannot recognize the new name I'm trying to apply. I've used the 'target' option of the Rename filter before without trouble, and it being, according to the manual: target: The new directory, or filename of the file. I guess I'm doing it right. And though I wonder if I should use the complete path to the file here, I still cannot see why the syntax I mentioned before should be used, but then again what is the right syntax, huh?
I suspect your instinct is correct and the syntax is almost correct. The api for addFilter() is:
addFilter($filter, $options = null, $files = null): Adds the given
filter to the filter stack (optionally only to the file(s) specified).
$filter may be either an actual filter instance, or a short name
specifying the filter type (e.g., 'Rename').
I'm pretty sure the offending line should read:
$uploader->addFilter('Rename', array('target'=>$sin_espacios, 'source'=>$info['tmp_name'], 'overwrite' => true));
this according to the docblock for Zend_Filter_File_Rename
/**
* Class constructor
*
* Options argument may be either a string, a Zend_Config object, or an array.
* If an array or Zend_Config object, it accepts the following keys:
* 'source' => Source filename or directory which will be renamed
* 'target' => Target filename or directory, the new name of the sourcefile
* 'overwrite' => Shall existing files be overwritten ?
*
* #param string|array $options Target file or directory to be renamed
* #param string $target Source filename or directory (deprecated)
* #param bool $overwrite Should existing files be overwritten (deprecated)
* #return void
*/
I hope this will clear the warnings, I think it should.
Actually your original syntax was correct, except you missed the file name as a required parameter. Omitting the key as 'source' as well as overwrite option is OK, you just need the file in addFilter (requires only in case of multi-file upload).
I'm trying to upload some photos and handle this with the build in Laravel functions. But I can't for the life of me figure out how to do this properly. I have been able to actually upload something, but I've run into a few problems. This is the code I have right now:
If looked at the documentation, and found this function: $file = Input::file('photo'); I've used this function, and what the content of $file becomes is an instance of Symfony\Component\HttpFoundation\File\UploadedFile, which, as the documentation tells us, "extends the PHP SplFileInfo class and provides a variety of methods for interacting with the file." http://laravel.com/docs/4.2/requests#files
Then I used this function Input::file('photo')->move($destinationPath); which should but the file in the desired folder on the server. And it did. But now comes the problem. Now all uploaded files have a filename like phpgoJnLc, and without an extension.
I've looked at the functions available from SplFileInfo and tried getExtension which give me an empty string and getFilename which also gives me something like phpgoJnLc.
Then I looked around on the internet and found a few part of code from Laravel 3, where they did something like this:
$filename = Str::random(20) .'.'. File::extension(Input::file('photo.name'));
But the result of this is give me only the result from Str::random(20) followed by a dot. Again, no file extension.
So, what am I doing wrong? How to upload a file with Laravel 4?
Looking in that same class file I see a getClientOriginalName() function...
$file = Input::file('photo');
$file->move($destinationPath,$file->getClientOriginalName());
... which ais ssuming you want to keep the original name your client sets... which could be hazardous, do some safety checks on it would be my advice. Getting the extensionname only is done with ->getClientOriginalExtension(), so you could also only save that part & add a random string before that in the second argument of the move() function.
This worked for me, especially when you want to change the name of the uploaded image:
$filename = 'New_Name'.'.'.Input::file('photo')->getClientOriginalExtension();
You can also generate a name for the file with the original file extension like so:
$filename = Str::random(20) . '.' . Input::file('image')->guessExtension();
if you try to use the following in Laravel 4:
$filename = Str::random(20) .'.'. File::extension(Input::file('photo.name'));
you will get this error:
'Call to undefined method Illuminate\Filesystem\Filesystem::guessExtension()'