Get full path while opening a directory - php

I'm creating a script to run through a given folder and list all files inside it.
The problem is: The user may or may not give the full path or just the folder name (if the target folder is inside the script folder).
Is there a way to get the full/absolute path of that folder even if the user gives only its name ?
//Check if the folder path was given as an argument
if( $argc >= 2) {
$folderPath = $argv[1]; //Read the folder path argument
if( !is_dir($folderPath) ) {
echo "Folder does NOT exists !";
}
else {
if( $handle = opendir($folderPath) ) {
//Find the $folderPath absolute path here
$folderPath may be:
- C:\wamp64\www\myfolder\documents
- Or just: documents
either cases the script will find the folder, open it and list it's files. But I need to write the fullPath later on the code.

You may want realpath, which cuts through all the issues - relative paths, symbolic links, etc.

Related

php file_exists() only works once in the same function

I have a php function that renames two separate image files from a temporary to permanent path after first confirming that the temporary path exists.
When it checks for the fist file it works fine but, for some reason, the second file never passes the if(file_exists()) even though I can confirm with 100% certainty that the file path being checked does, in fact, exist.
The image files have different names but the codes are otherwise structured exactly the same so I can't see why one would work and the other wouldn't.
if(file_exists('temp/'.strtolower($option['image1']))){
$path1 = 'images/'.strtolower($option['image1']); // upload directory
$tmp1 = 'temp/'.strtolower($option['image1']);
if(rename($tmp1, $path1)){
$error = 0;
}else{
$error = 4;
}
}
if(file_exists('temp/'.strtolower($option['image2']))){
$path2 = 'images/'.strtolower($option['image2']); // upload directory
$tmp2 = 'temp/'.strtolower($option['image2']);
if(rename($tmp2, $path2)){
$error = 0;
}else{
$error = 5;
}
}
Is there an issue with calling file_exists() twice? How else can I check for both paths?
Edit
As per Marco-A's suggestion, I added clearstatcache(); between the two if/then blocks and it worked like a charm.
The only two possibilities (if you're absolutely sure the file path exists) I'm seeing are either 1.) a stat cache problem (you can clear the cache with clearstatcache) or 2.) a permission issue. Consider this:
$ touch /tmp/locked/file
$ php is_file_test.php
$ bool(true)
$ chmod -x /tmp/locked
$ php is_file_test.php
$ bool(false)
So it might be, that the parent directory of that file doesn't have the x (executable) permission bit set. This prevents any process from iterating and accessing the directory's content.
The uploaded file names can have uppercase characters. If you use strtolower in the file_exists function, you probably wouldn't be looking for the original file path.
if(file_exists('temp/' . strtolower($option['image']))){
// ...
}
Should be changed to:
if(file_exists('temp/' . $option['image'])){
// ...
}

Why does mkdir not work without recursion set to true?

I was fiddling with my code, trying to write some code that will take data(including pictures) uploaded from a form via POST which will then create a directory complete with its associated subdirectories to store the image.
While writing the code, i kept getting the error
Warning: mkdir(): No such file or directory in C:\Users\Admin\Desktop\UniServer\www\AddItem.php on line 94
HOWEVER, when i set mkdir's resursion to true, mkdir suddenly works and the directory is created without any problems.
My Code:
if(isset($_FILES['upload']['tmp_name']))
{
$numfile=count($_FILES['upload']['tmp_name']);
{
for($i=0;$i<$numfile;$i++)
{
if(is_uploaded_file($_FILES['upload']['tmp_name'][$i]))
{
//Conditionals for uploaded file
$foldername=$_SESSION['UserId'];
$cat=$_POST['category'];
$sub=$_POST['subcat'];
$itemname=$_POST['itemname'];
$allowed_filetypes=array('.jpg','.gif','.bmp','.png');
$max_filesize = 2097152; // Maximum filesize in BYTES (currently 2.0MB).
$upload_path = 'C:\Users\Admin\Desktop\UniServer\www\images\\'.$foldername.'\\'.$cat.'\\'.$sub.'\\'.$itemname.'\\'; // The place the files will be uploaded to.
//Checks if Folder for User exists
//If not, A folder for the user is created to store the user's images
if(!file_exists($upload_path))
{
$upload_path=mkdir($upload_path,0644,true);<-- This is the line
}
$filename = $_FILES['upload']['name'][$i]; // Get the name of the file (including file extension).
$ext = substr($filename, strpos($filename,'.'), strlen($filename)-1); // Get the extension from the filename.
// Check if the filetype is allowed, if not DIE and inform the user.
if(!in_array($ext,$allowed_filetypes))
{
die('The file you attempted to upload is not allowed.');
}
// Now check the filesize, if it is too large then DIE and inform the user.
if(filesize($_FILES['upload']['tmp_name'][$i]) > $max_filesize)
{
die('The file you attempted to upload is too large.');
}
// Check if we can upload to the specified path, if not DIE and inform the user.
if(!is_writable($upload_path))
{
$errormsg="Image Upload Failed.";
}
if(!move_uploaded_file($_FILES['upload']['tmp_name'][$i],"$upload_path" . $filename))
{
$errormsg= 'Your file upload was successful, view the file here'; // It worked.
}
}
}
}
}
else{echo"Upload failed";}
While my code is working now that i've set recursion to true, i still DON't understand exactly WHY it is working, so i would really appreciate it if someone could explain why exactly my code is working.
The closest i've come is with Why mkdir fails with recursive option set true?
Though i couldnt understand any of what was said in the link.
Thanks!
The mkdir() needs the recursive set to true since you ask it to create nested directories that do not exist, i.e.:
$upload_path = 'C:\Users\Admin\Desktop\UniServer\www\images\\'.$foldername.'\\'.$cat.'\\'.$sub.'\\'.$itemname.'\\';
So since the variable $foldername gets its value from the user session, if the user session change it changes. The same goes for the rest of the $upload_path parts, if something of them changes you have to create the whole path. Only the last part of the path ($itemname) can change without using the recursive option.
It fails because it parses the path provided as argument and "changes" path to parent of new directory.
Try this (in a folder test with a subfolder s):
mkdir s/s2/s3 <- will fail because s2 does not exist in s
mkdir s/s2 <- ok
mkdir s/s2/s3 <- ok
When calling with recursive set to TRUE, it does something different: splits the path as usual, but checks existence of each prefix.
Again in folder test:
mkdir -p s/s1/s2/s3/s4 will yield the following prefixes:
s
s/s1
s/s1
s/s1/s2
s/s1/s2/s3
s/s1/s2/s3/s4
Note: I've used mkdir under linux and p argument tells it to create parent directories if they do not exist (same as recursive).

Forcing file-creation in public directory?

Here's an idea. I'm trying to create file from PHP script. File may be created in any public place on server (in public root or in its subdirectories). Bottom of line, I want to write function like this (it must work on both unix and windows servers).
function create_file($path, $filename, $content, $overwrite=false) {
# body
...
}
Parameters:
$path - file-path (empty or ends with /)
$filename - any valid file-name (requires dot+extension)
$content - file-content (anything)
$overwrite - if set true, existing file will be overwritten (default. false).
Result:
Function returns TRUE and creates file with content, or FALSE if file-creation was not possible for any reason.
Description of problem:
I expect this function to return true and create file $filename on path $path, or false if it wasn't possible for any reason. Also if file was successfully opened for writing, need to put $content in it.
Argument $path is relative path to public directory, and $filename is any valid filename. Of course, I want to avoid creating files if path points outside of public directory. Function may be called from subdirectory-scripts like this example
# php script: /scripts/test.php
create_file('../data/', 'test.info', 'some contents');
# result file: /data/test.info
What have I tried so far?
I’ve tried doing this with fopen() and fwrite() functions and that works on some servers, and doesn’t work on some. I guess there’s problem with writing privileges and chmod() but to be honest I’m not very familiar with chmod attributes. Also I couldn't check if $path points outside of server's public directory.
In short, I want this function to create file and return TRUE if file doesn't exist, or file exists and $owerwrite=true. Otherwise, nothing happens and function returns FALSE.
Additionally, I would like to know reason why file can't be created on some path (just in theory). Incorrect path/filename is only thing I have on my mind and I'm sure there's more about this problem.
Thanks in advance for any sample/example/suggestion.
update code
So far I have this code...
function create_file($path, $filename, $content, $overwrite=false) {
$reldir = explode('/', trim(str_replace('\\', '/', dirname($_SERVER['PHP_SELF'])), '/'));
$dirdepth = sizeof($reldir);
$rpadd = $dirdepth > 0 ? '(\.\./){0,' . $dirdepth . '}' : '';
$ptpath = '#^/?' . $rpadd . '([a-z0-9]\w*/)*$#i';
$ptname = '/^[a-z]\w*\.[a-z0-9]+$/i';
if ($res = preg_match($ptpath, $path) && preg_match($ptname, $filename)) {
$res = false;
if ($overwrite === true || !file_exists($path.$filename)) {
if ($f = #fopen($path.$filename, 'w')) {
fwrite($f, $content);
fclose($f);
$res = true;
}
}
}
return $res;
}
Some suggestions:
set the owner of the web server document root: chown -R apache:apache /var/www (i suppose /var/www is your document root and that the web server apache runs with user apache). Set the privilegies of the document root like this in order to have all directories under document look with privilegies 755 (only owner which is user apache can write in folders /var/www and sub folders)
Block paths that point out of your /var/www document root: you are under the issue known as http://en.wikipedia.org/wiki/Directory_traversal_attack. What about if the $path is something like: /var/www/../../../etc/passwd?
Basename php function can help you identifying this kind of malignous paths. Look this post: php directory traversal issue
To check wheter a file already exists or not: http://php.net/manual/en/function.file-exists.php
All file functions in php will not work properly in two circumstances
If you don't have enough user privileges for applications, then
You may not used/passed the arguements/parameters correctly .

How to build a file path correctly by using php?

I have a directory and a dynamic file name. Are there any functions I can use to make sure it is impossible to break out from the specified directory?
for example:
secure_path('/tmp/', 'file.txt') -> /tmp/file.txt
secure_path('/tmp/', '../file.txt') -> /tmp/file.txt
If you're only working in the single directory and not subdirectories below you could do
$file = "/tmp/".basename($input);
Which should give you the filename at the end of any path given in input, and append it to the directory you wanted.
Use realpath() to get a canonic form of the path, and eliminate any ../-s. Then verify that it contains the full path of the directory you want to contain it to.
Such as:
$path = "/tmp/file.txt";
$directory = "{full path of your files directory}";
if (strpos(realpath($path), $directory) === 0) {
// path OK
}
else {
// path not ok
}
I don't really know how ../file.txt is supposed to turn into /tmp/file.txt in your example, but to check whether a path is a subpath of another, use realpath with a simple comparison:
$path = '../foo';
$allowedPath = '/tmp/';
$path = realpath($path);
if (substr($path, 0, strlen($allowedPath)) !== $allowedPath) {
// path is not within allowed path!
}

directory listing with restrictions

I am trying to make a script that lists only folders within a folder. The thing is that I have users who can create folders within their folders so basically what I'm trying to do is to have a way for users to manage their folders and storing files... My problem is that I want to make php "think" that the root folder is their home directory and they cannot go upper than their home directory. Currently my php function doesn't do that, it only shows the content of the directory...and if the user goes one level up and again one level up ...and so on....he could browse the entire hard drive.
function directoryList($path) {
$dirStruct = array();
if(is_dir($path)) {
$handle = opendir($path);
while(($file = readdir($handle)) !== false) {
if(#opendir($path.$file)) {
chdir($path.$file);
$absolutepath = getcwd();
$dirStruct[] = array('path' => $absolutepath.'\\', 'name'=>$file);
}
}
}
return $dirStruct;
}
Instead of giving the user an absolute path, only allow them to specify paths which are relative to a given base path. Next, write a function which removes any "/../" for the relative path and you're safe (as long as users can't create links on the server ...).
If you want to be nice, you can match the ".." with the element before that (so "a/../b" would become "b", i.e. the ".." remove the "a") or ignore the ".." if there is no path element before it.
Here is a little something to expand on:
function listFolders($folderPath, $homeFolder)
{
$folderPath = realpath($folderPath);
$homeFolder = realpath($homeFolder);
if(strpos($folderPath, $homeFolder) === 0) {
return glob("$folderPath/*", GLOB_ONLYDIR);
}
}
$dirs = listFolders('/home/gordon/code/php', '/home/gordon');
print_r($dirs);
For $folderPath you pass in the folder you want to list the directories from. For $homeFolder pass in the folder you want to be the top most folder. By realpathing both paths you make sure they are resolved to absolute paths. If the $folderPath is below the $homeFolder, then the $folderPath string will start with and contain the entire $homeFolder string. If this is the case, we just glob all directories in the $folderPath and return their absolute pathes in an array.
To get the relative path of the $folderPath from the $homeFolder, just do
ltrim(str_replace('/home/gordon/', './', '/home/gordon/code/php/'), '/');
which would return ./code/php/.
If you want to do this with OOP, you might be interested in the SPL DirectoryIterator.

Categories