I have a question. I got a PHP script (PHP 5) which is saving a URL-Parameter $_GET['file'] to the variable $file. Is there now a way to check if this variable is a valid filename (for example: hello.txt and not /../otherdir/secret.txt). Because without checking the $file variable a hacker would be able to use the /../ to get to my parent folder.
You may have a look in php's basename function, it will return with filename, see example below:
$file = '../../abc.txt';
echo basename($file); //output: abc.txt
Note: basename gets you the file name from path string irrespective of file physically exists or not. file_exists function can be used to verify that the file physically exists.
POSIX "Fully portable filenames" lists these: A-Z a-z 0-9 . _ -
Use this code to validate the filename against POSIX rules using regex:
/ - forward slash (if you need to validate a path rather than a filename)
\w - equivalent of [0-9a-zA-Z_]
- . - dash dot space
$filename = '../../test.jpg';
if (preg_match('/^[\/\w\-. ]+$/', $filename))
echo 'VALID FILENAME';
else
echo 'INVALID FILENAME';
If you want to ensure it has no path (no forwardslash) then change the regex to '/^[\w\-. ]+$/'.
Instead of checking valid characters why not looking for character you don't want. Also filenames are limited to 255 characters:
function valid_filename(string $filename)
{
if (strlen($filename) > 255) { // no mb_* since we check bytes
return false;
}
$invalidCharacters = '|\'\\?*&<";:>+[]=/';
if (false !== strpbrk($filename, $invalidCharacters)) {
return false;
}
return true;
}
valid_filename('hello'); // true
valid_filename('hello.php'); // true
valid_filename('foo:bar.php'); // false
valid_filename('foo/bar'); // false
Adapt $invalidCharacters according to your needs/OS.
Source: https://www.cyberciti.biz/faq/linuxunix-rules-for-naming-file-and-directory-names/
Would that work?
http://php.net/manual/en/function.file-exists.php
E.g, in your case,
if(file_exists(str_replace("../", "", $file))){
// valid
}
else{
// invalid
}
Files can be in subfolders but not in parent folders.
However, if you're just interested in the filename,
if(file_exists(pathinfo($file, PATHINFO_BASENAME))){
// valid
}
else{
// invalid
}
should work.
i will like to combine kamal pal's and Pancake_M0nster's answers to create simple:
if(file_exists(basename($file))){
// valid
}
else{
// invalid
}
Related
I am trying to unlink files in a Directory which have no extensions.
I did following, but did not work:
$fileInfo = pathinfo($this->outputPath);
if($fileInfo['extension'] == NULL)
{
unlink($fileInfo['dirname'].$fileInfo['basename']);
}
according to the pathinfo manual :
Note:
If the path does not have an extension, no extension element
will be returned (see second example below).
so you need to check if the returned value has the extension element using isset , note that using empty will pass the dot directories in unix systems, for example if you are iterating some directory empty will consider the . and .. directories as an empty extension elements
// check if not isset
if(isset($fileInfo['extension']) === false) {
// perform some action
}
or if in the future you want to perform some complex search [eg: recursive search for files that does not have extensions] you may use FilesystemIterator
foreach (new FilesystemIterator($dir) as $fileInfo) {
// check if some file extension is null
if ($fileInfo->getExtension() == null) {
// perform some action
}
}
As #CBroe noticed in comments:
Your comparison with NULL is just wrong here.
Check for empty'ness instead:
$fileInfo = pathinfo($this->outputPath);
if(empty($fileInfo['extension']))
{
unlink($fileInfo['dirname'] . DIRECTORY_SEPARATOR . $fileInfo['basename']);
}
Also, you missed a DIRECTORY_SEPARATOR between the dirname and the basename.
Update: As #hassan pointed out, empty is not the proper way to check for this either. That's because of directories . and .. on unix-like systems will pass this test, which is not desired.
So, the proper way to check for files without extension would be:
if(isset($fileInfo['extension']) === false)
{
unlink($fileInfo['dirname'] . DIRECTORY_SEPARATOR . $fileInfo['basename']);
}
I want to download the file after login check so wrote a function in my controller like
// Function to check login and download News PDF file
public function download(){
if($this->Auth->user()){
// Get the news file path from newsId
$pNewsObj = ClassRegistry::init('PublicNews');
$news = $pNewsObj->findById($newsId);
$filePath = ROOT.DS.APP_DIR.DS.'webroot/upload_news'.DS.$news['PublicNews']['reference'];
// Check if file exists
if(!file_exists($filePath)){
return $this->redirect('/404/index.php');
}
$this->response->charset('UTF-8');
//$this->response->type('pdf');
$this->response->file('webroot/upload_news'.DS.$news['PublicNews']['reference'], array('download' => true, 'name' => $news['PublicNews']['reference']));
//$this->response->download($news['PublicNews']['reference']);
return $this->response;
}else{
return $this->redirect(array('controller'=> 'users', 'action' => 'login'));
}
}
Now, everything works fine as required.
PROBLEM : when the file name is in UTF-8 eg. テスト.pdf (its Test.pdf in japanese) cakephp throws error like this.
For English filename it works perfectly fine but my client wants the filename should be the same as uploaded, so I can't change the filename to English.
If you want to know character encoding, you can use mb_detect_encoding() function if input text has enough length to detect encoding.
But I am guessing your client would upload SJIS file. Because most Japanese people are using SJIS, as Windows has adopted SJIS for Japanese language.
I confirmed your code in my local environment. As cake's File class seems to be not able to handle SJIS correctly, you cannot use Response::file(). So I wrote alternative code.
public function download(){
if($this->Auth->user()){
// Get the news file path from newsId
$pNewsObj = ClassRegistry::init('PublicNews');
$news = $pNewsObj->findById($newsId);
if (!$news) {
throw new NotFoundException();
}
$fileName = mb_convert_encoding($news['PublicNews']['reference'], 'SJIS-win', 'UTF8');
// Directory traversal protection
if (strpos($fileName, '..') !== false) {
throw new ForbiddenException();
}
$filePath = WWW_ROOT . 'upload_news' . DS . $fileName;
if (!is_readable($filePath)) {
throw new NotFoundException();
}
if (function_exists('mime_content_type')) {
$type = mime_content_type($filePath);
$this->response->type( $type );
} else {
// TODO: If Finfo extension is not loaded, you need to detect content type here;
}
$this->response->download( $fileName );
$this->response->body( file_get_contents($filePath) );
return $this->response;
}else{
return $this->redirect(array('controller'=> 'users', 'action' => 'login'));
}
}
However, I recommend you to convert SJIS to UTF8 before save it into your database and your disk. It is difficult to handle SJIS characters without enough knowledge about it. Because SJIS characters may contain ascii characters in the second byte. Especially backslash (\) is most dangerous. For example, 表 (955C) contains a backslash (5C = backslash). Note that I am not talking about rare cases. 表 means table or appearance in Japanese. 十 also contains a backslash and it means 10 in Japanese. 能 also contains a backslash and it means skill.
Unlike UTF-8 byte sequence, if you handle SJIS characters, almost all string functions don't work correctly. explode() would break SJIS byte sequence. strpos() would return wrong result.
Does your client connect to your server by using FTP or SCP directly? If not, it would be better to convert SJIS to UTF-8 before save, and re-convert UTF-8 to SJIS before return to your client.
If you like you can change the file name before uploading the file so at time of downloading this error will not happen.
public function change_file_name($fileName= '') {
$ext = pathinfo($fileName, PATHINFO_EXTENSION);
$fileName = 'file_'.time().".".$ext;
$exFileName = strtolower(substr($fileName,strrpos($fileName,".") + 1));
$sampleFileName = str_replace('.'.$exFileName,'', $fileName);
$name = Sanitize::paranoid($sampleFileName,array('_'));
$fileRename = $name.'.'.$exFileName;
return $fileRename;
}
Call this function before uploading the file
$return_file_name = $this->change_file_name($file_name);
if($this->moveUploadedFile($tmp_name,WEBSITE_PROFILE_ROOT_PATH.$return_file_name)){
$saveData['profile_image'] = $return_file_name;
}
I know this is not proper answer for your case.For this you can make a function like this which will fetch data from database and automatic rename all your save file and update it in your database
Some more information about your client's specifications would help greatly, but Tom Scott found base64 to be the simplest method of making Unicode characters work correctly in PHP.
Depending on how crucial the preservation of filenames in storage is, a solution could be to encode the filenames in base64 when files are uploaded, and reverse the encoding on download. You can then know that you are dealing with ASCII, which should be much more likely to work correctly.
You may need to replace / characters with %2F to make it work.
Hope this helps,
Issa Chanzi
I want to amend a PHP script I'm using in wordPress (Auto Featured Image plugin).
The problem is that this script creates filenames for thumbnails based on the URLs of the image.
That sounds great until you get a filename with spaces and the thumbnail is something like this%20Thumbnail.jpg and when the browser goes to http://www.whatever.com/this%20Thumbnail.jpg it converts the %20 to a space and there is no filename on the server by that name (with spaces).
To fix this, I think I need to change the following line in such a way that $imageURL is filtered to convert %20 to spaces. Sound right?
Here is the code. Perhaps you can tell me if I'm barking up the wrong tree.
Thank you!
<?php
static function create_post_attachment_from_url($imageUrl = null)
{
if(is_null($imageUrl)) return null;
// get file name
$filename = substr($imageUrl, (strrpos($imageUrl, '/'))+1);
if (!(($uploads = wp_upload_dir(current_time('mysql')) ) && false === $uploads['error'])) {
return null;
}
// Generate unique file name
$filename = wp_unique_filename( $uploads['path'], $filename );
?>
Edited to a more appropriate and complete answer:
static function create_post_attachment_from_url($imageUrl = null)
{
if(is_null($imageUrl)) return null;
// get the original filename from the URL
$filename = substr($imageUrl, (strrpos($imageUrl, '/'))+1);
// this bit is not relevant to the question, but we'll leave it in
if (!(($uploads = wp_upload_dir(current_time('mysql')) ) && false === $uploads['error'])) {
return null;
}
// Sanitize the filename we extracted from the URL
// Replace any %-escaped character with a dash
$filename = preg_replace('/%[a-fA-F0-9]{2}/', '-', $filename);
// Let Wordpress further modify the filename if it may clash with
// an existing one in the same directory
$filename = wp_unique_filename( $uploads['path'], $filename );
// ...
}
You better to replace the spaces in image name with underscores or hypens using regexp.
$string = "Google%20%20%20Search%20Amit%20Singhal"
preg_replace('/%20+/g', ' ', $string);
This regex will replace multiple spaces (%20) with a single space(' ').
I have almost 10,000 images in a Folder with image name like
Abies_koreana_Blauer_Pfiff_05-06-10_1.jpg
Abies_koreana_Prostrate_Beauty_05-05-10_2.jpg
Chamaecyparis_obtusa_Limerick 06-10-10_3.jpg
Fagus_sylvatica_Dawyck_Gold_05-02-10_1.jpg
What i want do is rename the images using PHP so that only the characters remain in the image name want to delete the Numeric part so for example the above images would look like
Abies_koreana_Blauer_Pfiff.jpg
Abies_koreana_Prostrate_Beauty.jpg
Chamaecyparis_obtusa_Limerick.jpg
Fagus_sylvatica_Dawyck_Gold.jpg
Is this possible ? Or i have to do it manually ?
foreach file name do this
$new_filename = preg_replace("/(\w\d{0,2}[\W]{1}.+\.)/",".",$current_file_name);
so final function may look like this
function renameFiles($directory)
{
$handler = opendir($directory);
while ($file = readdir($handler)) {
if ($file != "." && $file != "..") {
if(preg_match("/(\w\d{0,2}[\W]{1}.+\.)/",$file)) {
echo $file."<br/>";
}
rename($directory."/".$file,$directory."/".preg_replace("/(\w\d{0,2}[\W]{1}.+\.)/",".",$file));
}
}
closedir($handler);
}
renameFiles("c:/wserver");
Updated
You can do this with PHP (or bash).
Your friends are RecursiveDirectoryIterator to walk through directories, preg_replace() to modify the file names, rename() to reflect changed filename on disk.
What you're trying to do can be done in ~10 lines of code. Using the ingredients above, you should be able to write a little script to change filenames yourself.
Update
throwing out the numeric parts (according to the examples given) can be done with a rather simple regular expression. Note that this will remove any numbers (-_ ) between the [a-z] filename and the suffix (".jpq"). So you won't get "foo3.png" but "foo.png". If this is a problem, the regex can be adjusted to meet that criteria…
<?php
$files = array(
'Abies_koreana_Blauer_Pfiff_05-06-10_1.jpg',
'Abies_koreana_Prostrate_Beauty_05-05-10_2.jpg',
'Chamaecyparis_obtusa_Limerick 06-10-10_3.jpg',
'Fagus_sylvatica_Dawyck_Gold_05-02-10_1.jpg',
);
foreach ($files as $source) {
// strip all numeric (date, counts, whatever)
// characters before the file's suffix
// (?= …) is a non-capturing look-ahead assertion
// see http://php.net/manual/en/regexp.reference.assertions.php for more info
$destination = preg_replace('#[ _0-9-]+(?=\.[a-z]+$)#i', '', $source);
echo "'$source' to '$destination'\n";
}
Can someone please help me with this preg_match
if (preg_match('~[^A-Za-z0-9_\./\]~', $filepath))
// Show Error message.
I need to match a possible filepath. So I need to check for double slashes, etc. Valid file path strings should look like this only:
mydir/aFile.php
or
mydir/another_dir/anyfile.js
So a slash at the beginning of this string should be checked also. Please help.
Thanks :)
EDIT:
Also, guys, this path is being read from within a text file. It is not a filepath on the system. So hopefully it should be able to support all systems in this case.
RE-EDIT:
Sorry, but the string can also look like this too:
myfile.php, or myfile.js, or myfile.anything
How do I allow strings like this as well?? I apologize for not being too specific on this before...
Please notice that there are many types of possible file paths.
For example:
"./"
"../"
"........" (yes this can be a file's name)
"file/file.txt"
"file/file"
"file.txt"
"file/.././/file/file/file"
"/file/.././/file/file/.file" (UNIX)
"C:\Windows\" (Windows)
"C:\Windows\asd/asd" (Windows, php accepts this)
"file/.././/file/file/file!##$"
"file/.././/file/file/file!##.php.php.php.pdf.php"
All these file paths are valid. I can't think of a simple regex that can make it perfect.
Let's assume it's just a UNIX path for now, this is what I think should work for most cases:
preg_match('/^[^*?"<>|:]*$/',$path)
It checks all string for ^, *, ?, ", <, >, |, :(remove this for windows). These are all character that windows does not allow for file name, along with / and .
If it's windows, you should replace the path's \ with / and then explode it and check if it's absolute. Here is one example that working in both unix and windows.
function is_filepath($path)
{
$path = trim($path);
if(preg_match('/^[^*?"<>|:]*$/',$path)) return true; // good to go
if(!defined('WINDOWS_SERVER'))
{
$tmp = dirname(__FILE__);
if (strpos($tmp, '/', 0)!==false) define('WINDOWS_SERVER', false);
else define('WINDOWS_SERVER', true);
}
/*first, we need to check if the system is windows*/
if(WINDOWS_SERVER)
{
if(strpos($path, ":") == 1 && preg_match('/[a-zA-Z]/', $path[0])) // check if it's something like C:\
{
$tmp = substr($path,2);
$bool = preg_match('/^[^*?"<>|:]*$/',$tmp);
return ($bool == 1); // so that it will return only true and false
}
return false;
}
//else // else is not needed
return false; // that t
}
You can do:
if(preg_match('#^(\w+/){1,2}\w+\.\w+$#',$path)) {
// valid path.
}else{
// invalid path
}