Detect broken symlink in PHP on Windows 7 + Apache environment - php

I have a Windows 7 dev environment and I am using symlinks (not junctions) with php's symlink() function. Everything works fine until a target of a symlink gets deleted. When that happens all the PHP's file functions (file_exists(), is_file(), is_dir(), is_link(),...) return false although the symlink is still there.
This gets even more troubling if the broken symlink was originally targetting a directory. I can do file_put_contents() on the symlink path, which creates a file on the original target directory path. That's quite unfortunate and unpredictable.
So to my question: is there a way to detect a broken symlink in PHP on Windows 7 environment? I need whatever decent solution possible (an exec() or something like that). The production server is running standard LAMP configuration which works fine as expected.
I am using XAMPP with PHP 5.5.3.
A sample script:
$dir_path = __DIR__ . '/temporary_directory';
$link_path = __DIR__ . '/broken_symlink';
mkdir($dir_path);
symlink($dir_path, $link_path);
rmdir($dir_path);
echo 'file_exists(): ';
var_dump(file_exists($link_path));// false
echo "<br>\n";
echo 'is_file(): ';
var_dump(is_file($link_path));// false
echo "<br>\n";
echo 'is_dir(): ';
var_dump(is_dir($link_path));// false
echo "<br>\n";
echo 'is_link(): ';
var_dump(is_link($link_path));// false
echo "<br>\n";
echo 'readlink(): ';
var_dump(readlink($link_path));// false
echo "<br>\n";
// Now it is possible to create file:
file_put_contents($link_path, '');
// which creates a new file on the $dir_path. That makes the symlink somewhat hybrid as Windows Explorer thinks it is a directory symlink but it points to a file (so it's unable to resolve it).

I have come up with a solution. I am not completely happy about it but it should work.
function is_link_on_windows($path) {
$parent_dir_path = dirname($path);
if (false === file_exists($parent_dir_path)) return false;
$filename_regex = preg_quote(substr($path, strlen($parent_dir_path) + 1), '#');
// DOS' dir command is unlike PHP sensitive about the path format.
$parent_dir_path = realpath($parent_dir_path);
exec('dir ' . $parent_dir_path, $dir_output);
foreach ($dir_output as $line) {
// Symlink's entry should look like this:
// broken_symlink [C:\xampp\htdocs\symlink_test\temporary_directory]
// where the part within square brackets is the symlink's target.
if (preg_match('#\\s' . $filename_regex . '\\s\\[[^\\]]+\\]#', $line)) {
return true;
}
}
return false;
}

Related

"No such file or directory" on localhost copy

EDIT: I'm pretty sure the issue has to do with the firewall, which I can't access. Marking Canis' answer as correct and I will figure something else out, possibly wget or just manually scraping the files and hoping no major updates are needed.
EDIT: Here's the latest version of the builder and here's the output. The build directory has the proper structure and most of the files, but only their name and extension - no data inside them.
I am coding a php script that searches the local directory for files, then scrapes my localhost (xampp) for the same files to copy into a build folder (the goal is to build php on the localhost and then put it on a server as html).
Unfortunately I am getting the error: Warning: copy(https:\\localhost\intranet\builder.php): failed to open stream: No such file or directory in C:\xampp\htdocs\intranet\builder.php on line 73.
That's one example - every file in the local directory is spitting the same error back. The source addresses are correct (I can get to the file on localhost from the address in the error log) and the local directory is properly constructed - just moving the files into it doesn't work. The full code is here, the most relevant section is:
// output build files
foreach($paths as $path)
{
echo "<br>";
$path = str_replace($localroot, "", $path);
$source = $hosted . $path;
$dest = $localbuild . $path;
if (is_dir_path($dest))
{
mkdir($dest, 0755, true);
echo "Make folder $source at $dest. <br>";
}
else
{
copy($source, $dest);
echo "Copy $source to $dest. <br>";
}
}
You are trying to use URLs to travers local filesystem directories. URLs are only for webserver to understand web requests.
You will have more luck if you change this:
copy(https:\\localhost\intranet\builder.php)
to this:
copy(C:\xampp\htdocs\intranet\builder.php)
EDIT
Based on your additional info in the comments I understand that you need to generate static HTML-files for hosting on a static only webserver. This is not an issue of copying files really. It's accessing the HMTL that the script generates when run through a webserver.
You can do this in a few different ways actually. I'm not sure exactly how the generator script works, but it seems like that script is trying to copy the supposed output from loads of PHP-files.
To get the generated content from a PHP-file you can either use the command line php command to execute the script like so c:\some\path>php some_php_file.php > my_html_file.html, or use the power of the webserver to do it for you:
<?php
$hosted = "https://localhost/intranet/"; <--- UPDATED
foreach($paths as $path)
{
echo "<br>";
$path = str_replace($localroot, "", $path);
$path = str_replace("\\","/",$path); <--- ADDED
$source = $hosted . $path;
$dest = $localbuild . $path;
if (is_dir_path($dest))
{
mkdir($dest, 0755, true);
echo "Make folder $source at $dest. <br>";
}
else
{
$content = file_get_contents(urlencode($source));
file_put_contents(str_replace(".php", ".html", $dest), $content);
echo "Copy $source to $dest. <br>";
}
}
In the code above I use file_get_contents() to read the html from the URL you are using https://..., which in this case, unlike with copy(), will call up the webserver, triggering the PHP engine to produce the output.
Then I write the pure HTML to a file in the $dest folder, replacing the .php with .htmlin the filename.
EDIT
Added and revised the code a bit above.

PHP function 'scandir' not working on WordPress hosted on windows

When i am using scandir function i get different results on linux and windows.
Windows i see only folder name
Linux it does the job under
I run a check if WordPress files has been changed ?
function find_all_files($dir, $deep)
{
if(strpos($dir,"wp-content") == false){
$root = scandir($dir);
foreach($root as $value)
{
if($value === '.' || $value === '..') {continue;}
if(is_file("$dir/$value")) {
$tmpFile = "$dir/$value";
if(5000000>#filesize($tmpFile)){
$result[]="$tmpFile";
}
continue;
}
$tmp = "$dir/$value";
if(strpos($tmp,"wp-content") == false){
if($deep == true){
foreach(#$this->find_all_files("$dir/$value",true) as $value)
{
$tmpFile = $value;
if(5000000>#filesize($tmpFile)){
$result[]="$tmpFile";
}
}
}
}
}
//var_dump($result);
return $result;
}
}
I had a similar issue and it all came down to:
1) if you mean "running on windows" OS you need to include, and start from the drive directory (where server is installed) in $dir to scan.
2) Windows might be picky about separators, compare your $dir with Wordpress ABSPATH and try to use PHP constant DIRECTORY_SEPARATOR instead of your own '/' separators.
3) And I asume you working with paths, not urls (http://localhost/wordpress/...plugin/dir/). This is working for me on Windows 7, running Uniserver (LAMP):
$rootfolder = 'startfolder';
$dir = plugin_dir_path(dirname( __FILE__ )).DIRECTORY_SEPARATOR.$rootfolder;
$files = array_slice(scandir($dir), 2);
However, you need to cut down the path end begin in the install dir with ABSPATH or something. But I recommend to test where your filescript is, like plugins folder, to not consume a huge stall on errors while working with this kind of stuff.
Hint!
The PHP docs about scandir has a lot off contributed functions about reading, retriving, scanning functions and issues. Take a look at: http://php.net/manual/en/function.scandir.php
Important!
Do not start messing with windows folder access, and if you do, take a RAW copy of whole www directory first, otherwise exported Worpdress installs could get messy on a Apache server live.
Remember, did you manage to install a new plugin from the Wordpress
admin, you propably dont have a folder access issue.
Hop this helps!

Get directory of current php file, which might have changed since the script started to run

I hope you can help. I need to find to way to get the current directory of the currently running PHP script.
The location of the script itself might have changed since the script was started.
I cannot use any magic constants such as __FILE__ or __DIR__ as a basis as they appear to be defined by the PHP processor at the beginning of execution and so continue to point to the original location of the script.
To test out ideas, I'm doing the following (from the command line on linux):
Have a file named test.php in directory test_1 i.e. test_1/test.php
Run this file on the command line with: php test_1/test.php
Open another terminal session and rename the directory with mv test_1 test_2
Keep an eye on the original terminal and hope you can find that it is reporting the new test_2 dir not test_1
The script used for this test was as follows:
<?php
while (true) {
echo "__FILE__: " . __FILE__ . "\n";
echo "__DIR__: " . __DIR__ . "\n";
echo "realpath(__FILE__): " . realpath(__FILE__) . "\n";
echo "realpath(__DIR__): " . realpath(__DIR__) . "\n";
echo '-----'. "\n";
sleep(5);
}
So, the question: How do you find out the current directory of the script running now (not where it was when the script started)?
The inode of a script will not change, even if it is moved to a different location in the same filesystem.
You could get the inode and pathname of the file at the beginning of the script, using the built-in function getmyinode:
<?php
$myinode = getmyinode();
$mypath = realpath(__FILE__);
?>
Then whenever you want to get the file path, call a function which compares the two and updates the path if necessary:
<?php
function getMyPath() {
global $myinode, $mypath;
if (fileinode($mypath) != $myinode) {
// File moved!
$searchfrom = '/'; // Maybe you can change this to $_SERVER['DOCUMENT_ROOT']
$mypath = shell_exec("find " . $searchfrom . " -inum " . $myinode . " -print -quit 2>/dev/null");
if (fileinode($mypath) != $myinode) {
// error; not found
}
}
return $mypath;
}
?>
It will be very slow to search for the inode if/when the file is moved but I don't see any way around that. Also note that if there are hardlinks then there could be multiple paths to the same file; the -print -quit above simply stops after the first one.
This will only work on Linux due to the use of inodes and the find shell command.
dirname(__FILE__);
Maybe this will be helpful?

clamdscan can't read from tmp directory

I was wondering what's wrong with my code, if I use clamscan, it works fine both reading from /tmp, or manually specified the path. but if I use clamdscan, any path from /tmp will result in error (the int result is 2). This is the code.
$command = 'clamdscan ' . escapeshellarg($_FILES['file']['tmp_name']);
$out = '';
$int = -1;
exec($command, $out, $int);
echo "\n" . $command;
echo "\n" . $out;
echo "\n This is int = " . $int;
if ($int == 0) {
// all good, code goes here uploads file as normal IE move to
//echo "File path : ".$file."Return code : ".cl_pretcode($retcode);
//echo "\n";
move_uploaded_file($_FILES["file"]["tmp_name"], "filesave/" . $_FILES["file"]["name"]);
echo "Stored in: " . "filesave/" . $_FILES["file"]["name"];
} else {
echo "\n FAILED";
}
based on above code, it will failed because $int = 2. But, if I change the command to
//some file that is saved already in the directory
$command = 'clamdscan ' . '/abc/abc.txt';
It works perfectly fine.
It only failed if the command is clamdscan. if I use clamscan, temp directory is fine
any idea?
You should really just use one of the many clamd clients out there instead of relying on exec'ing a command and parsing its output, that's super fragile and will bring you nothing but headache in the future. For example:
http://torquecp.sourceforge.net/phpclamcli.html
If you are the do-it-yourself type, the clamd wire protocol is super simple (http://linux.die.net/man/8/clamd) and you could potentially write up a simple client in a couple of hours. Again, the benefit here is that it's a well defined protocol and has some nice features like a streaming call that allows you to operate the clamd service and your webapp with completely difference security credentials (heck they can even run on different boxes).
I hope this helps.
Just a quick remark on using http://torquecp.sourceforge.net/phpclamcli.html as a supposedly better alternative to a DIY cli exec. The aforementioned php script does entirely rely on the syntax of the clamav response text as well.
Old question but I've just had the same issue. clamdscan runs as user clamav which doesn't have permission to files in /tmp. There is an additional parameter --fdpass which runs the command as the user running the script.
Using $command = 'clamdscan --fdpass' . escapeshellarg($_FILES['file']['tmp_name']); should run the command as the www user which will have access to the temporary file.

file_exists() returns false, but the file DOES exist

I'm having a very weird issue with file_exists(). I'm using this function to check if 2 different files in the same folders do exist. I've double-checked, they BOTH do exist.
echo $relative . $url['path'] . '/' . $path['filename'] . '.jpg';
Result: ../../images/example/001-001.jpg
echo $relative . $url['path'] . '/' . $path['filename'] . '.' . $path['extension'];
Result: ../../images/example/001-001.PNG
Now let's use file_exists() on these:
var_dump(file_exists($relative . $url['path'] . '/' . $path['filename'] . '.jpg'));
Result: bool(false)
var_dump(file_exists($relative . $url['path'] . '/' . $path['filename'] . '.' . $path['extension']));
Result: bool(true)
I don't get it - both of these files do exist. I'm running Windows, so it's not related to a case-sensitive issue. Safe Mode is off.
What might be worth mentioning though is that the .png one is uploaded by a user via FTP, while the .jpg one is created using a script. But as far as I know, that shouldn't make a difference.
Any tips?
Thanks
file_exists() just doesn't work with HTTP addresses.
It only supports filesystem paths (and FTP, if you're using PHP5.)
Please note:
Works :
if (file_exists($_SERVER['DOCUMENT_ROOT']."/folder/test.txt")
echo "file exists";
Does not work:
if (file_exists("www.mysite.com/folder/test.txt")
echo "file exists";
Results of the file_exists() are cached, so try using clearstatcache(). If that not helped, recheck names - they might be similar, but not same.
I found that what works for me to check if a file exists (relative to the current php file it is being executed from) is this piece of code:
$filename = 'myfile.jpg';
$file_path_and_name = dirname(__FILE__) . DIRECTORY_SEPARATOR . "{$filename}";
if ( file_exists($file_path_and_name) ){
// file exists. Do some magic...
} else {
// file does not exists...
}
Just my $.02: I just had this problem and it was due to a space at the end of the file name. It's not always a path problem - although that is the first thing I check - always. I could cut and paste the file name into a shell window using the ls -l command and of course that locates the file because the command line will ignore the space where as file_exists does not. Very frustrating indeed and nearly impossible to locate were it not for StackOverflow.
HINT: When outputting debug statements enclose values with delimiters () or [] and that will show a space pretty clearly. And always remember to trim your input.
It's because of safe mode. You can turn it off or include the directory in safe_mode_include_dir. Or change file ownership / permissions for those files.
php.net: file_exists()
php.net: safe mode
Try using DIRECTORY_SEPARATOR instead of '/' as separator. Windows uses a different separator for file system paths (backslash) than Linux and Unix systems.
A very simple trick is here that worked for me.
When I write following line, than it returns false.
if(file_exists('/my-dreams-files/'.$_GET['article'].'.html'))
And when I write with removing URL starting slash, then it returns true.
if(file_exists('my-dreams-files/'.$_GET['article'].'.html'))
I have a new reason this happens - I am using PHP inside a Docker container with a mounted volume for the codebase which resides on my local host machine.
I was getting file_exists == FALSE (inside Composer autoload), but if I copied the filepath into terminal - it did exist! I tried the clearstatche(), checked safe-mode was OFF.
Then I remembered the Docker volume mapping: the absolute path on my local host machine certainly doesn't exist inside the Docker container - which is PHP's perspective on the world.
(I keep forgetting I'm using Docker, because I've made shell functions which wrap the docker run commands so nicely...)
It can also be a permission problem on one of the parent folders or the file itself.
Try to open a session as the user running your webserver and cd into it. The folder must be accessible by this user and the file must be readable.
If not, php will return that the file doesn't exist.
have you tried manual entry. also your two extensions seem to be in different case
var_dump(file_exists('../../images/example/001-001.jpg'));
var_dump(file_exists('../../images/example/001-001.PNG'));
A custom_file_exists() function inspired by #Timur, #Brian, #Doug and #Shahar previous answers:
function custom_file_exists($file_path=''){
$file_exists=false;
//clear cached results
//clearstatcache();
//trim path
$file_dir=trim(dirname($file_path));
//normalize path separator
$file_dir=str_replace('/',DIRECTORY_SEPARATOR,$file_dir).DIRECTORY_SEPARATOR;
//trim file name
$file_name=trim(basename($file_path));
//rebuild path
$file_path=$file_dir."{$file_name}";
//If you simply want to check that some file (not directory) exists,
//and concerned about performance, try is_file() instead.
//It seems like is_file() is almost 2x faster when a file exists
//and about the same when it doesn't.
$file_exists=is_file($file_path);
//$file_exists=file_exists($file_path);
return $file_exists;
}
This answer may be a bit hacky, but its been working for me -
$file = 'path/to/file.jpg';
$file = $_SERVER['REQUEST_SCHEME'].'://'.$_SERVER['HTTP_HOST'].'/'.$file;
$file_headers = #get_headers($file);
if($file_headers[0] == 'HTTP/1.1 404 Not Found') {
$exists = false;
}else{
$exists = true;
}
apparently $_SERVER['REQUEST_SCHEME'] is a bit dicey to use with IIS 7.0 + PHP 5.3 so you could probably look for a better way to add in the protocol.
I found this answer here http://php.net/manual/en/function.file-exists.php#75064
I spent the last two hours wondering what was wrong with my if statement: file_exists($file) was returning false, however I could call include($file) with no problem.
It turns out that I didn't realize that the php include_path value I had set in the .htaccess file didn't carry over to file_exists, is_file, etc.
Thus:
<?PHP
// .htaccess php_value include_path '/home/user/public_html/';
// includes lies in /home/user/public_html/includes/
//doesn't work, file_exists returns false
if ( file_exists('includes/config.php') )
{
include('includes/config.php');
}
//does work, file_exists returns true
if ( file_exists('/home/user/public_html/includes/config.php') )
{
include('includes/config.php');
}
?>
Just goes to show that "shortcuts for simplicity" like setting the include_path in .htaccess can just cause more grief in the long run.
In my case, the problem was a misconception of how file_exists() behaves with symbolic links and .. ("dotdot" or double period) parent dir references. In that regard, it differs from functions like require, include or even mkdir().
Given this directory structure:
/home/me/work/example/
www/
/var/www/example.local/
tmp/
public_html -> /home/me/work/example/www/
file_exists('/var/www/example.local/public_html/../tmp/'); would return FALSE even though the subdir exists as we see, because the function traversed up into /home/me/work/example/ which does not have that subdir.
For this reason, I have created this function:
/**
* Resolve any ".." ("dotdots" or double periods) in a given path.
*
* This is especially useful for avoiding the confusing behavior `file_exists()`
* shows with symbolic links.
*
* #param string $path
*
* #return string
*/
function resolve_dotdots( string $path ) {
if (empty($path)) {
return $path;
}
$source = array_reverse(explode(DIRECTORY_SEPARATOR, $path));
$balance = 0;
$parts = array();
// going backwards through the path, keep track of the dotdots and "work
// them off" by skipping a part. Only take over the respective part if the
// balance is at zero.
foreach ($source as $part) {
if ($part === '..') {
$balance++;
} else if ($balance > 0) {
$balance--;
} else {
array_push($parts, $part);
}
}
// special case: path begins with too many dotdots, references "outside
// knowledge".
if ($balance > 0) {
for ($i = 0; $i < $balance; $i++) {
array_push($parts, '..');
}
}
$parts = array_reverse($parts);
return implode(DIRECTORY_SEPARATOR, $parts);
}
I just encountered this same problem and I solved it in a mysterious way. After inserting a a filepath I copied from Windows File explorer. file_exists() keeps returning false continuously, but if I copy same path from VSCode editor it works perfectly.
After dumping variables with var_dump($path); I noticed something mysterious.
For path I copied from file explorer it shows length 94.
For path I copied from VSCode Editor it shows length 88.
Both path look same length on my code Editor.
My suggestion: if string contain hidden characters, it may fail and not work.

Categories