Mike Paradis
asked on
php function to remove a file
Why is this not working?
The command is something like this;
curl -s -u 123456:abcdef --connect-timeout 15 -X POST https://domain.com/app.php -F function=fwfinal -F filename="crazy.bin"
On the php server, the following code is supposed to remove this file when the funtion 'fwfinal' is used and the file name sent, in this case, 'crazy.bin'
The function is;
protected static function removeFiles($filename)
{
$fn = 'fwfiles/'.$filename;
if (file_exists($fn)) {
unlink($fn);
}
return new Response('Success!');
}
The file is never removed. This was working until php was upgraded over and over.
The command is something like this;
curl -s -u 123456:abcdef --connect-timeout 15 -X POST https://domain.com/app.php -F function=fwfinal -F filename="crazy.bin"
On the php server, the following code is supposed to remove this file when the funtion 'fwfinal' is used and the file name sent, in this case, 'crazy.bin'
The function is;
protected static function removeFiles($filename)
{
$fn = 'fwfiles/'.$filename;
if (file_exists($fn)) {
unlink($fn);
}
return new Response('Success!');
}
The file is never removed. This was working until php was upgraded over and over.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Note that 'unlink' doesn't work if the file is currently open.
To expand on Ray's version
protected static function removeFiles($filename)
{
$fn = $filename;
if (file_exists($fn)) {
if (!unlink($fn)) {
// Let's see the warning if suppressed
$result = error_get_last();
$msg = "FAILURE! unlink failed with : " . print_r($result, true);
return new Response($msg);
}
}
else
{
return new Response("FAILURE! $fn DOES NOT EXIST");
}
return new Response("Success! $fn HAS BEEN UNLINKED");
}
The upgrades meant going from php 5.4 to php 7.0 on Centos7.Might want to check the file paths and permissions. And it cannot hurt to raise the error_reporting() level when you're running in a dev environment!
Regarding this:
// Let's see the warning if suppressed
Strongly recommend that we don't suppress Warnings or any other messages from PHP (unless we expect a message and know why we would be willing to suppress the message). A "best practices" coding standard for development environments is to capture and report all messages (Error, Warning, Notice), with logging and concurrent display.
<?php
error_reporting(E_ALL);
ini_set('log_errors', TRUE);
ini_set('error_log', 'error_log');
ini_set('display_errors', TRUE);
In a deployed environment, you still want the reports, but you probably don't want concurrent display. To get the reports we want to leave the error_log in place.
<?php
error_reporting(E_ALL);
ini_set('log_errors', TRUE);
ini_set('error_log', 'error_log');
ini_set('display_errors', FALSE);
The "tail" command is useful with the error_log file.I probably should never have recommended adding conditional logic to the script. We may get better results if we just use the built-in reporting and logging (see examples here - the code is modified slightly because I don't have your class wrappers, but the logic is preserved).
When adding if{}else{} logic, it's easy to confuse the issues with too many conditional tests. Consider this script and imagine what it will do if it starts with the pre-existing condition of having both the file and the directory in existence. We wrote more code, and therefore introduced additional cyclomatic complexity with the attendant requirements for additional unit testing. But the PHP warning message that was issued contained 100% of the information that we got from error_get_last(). We don't have to test the logic of the PHP warning message. The warning is visible in the error_log file and in the browser output that would have been returned to the cURL call.
<?php // demo/temp_mark_lewis.php
/**
* https://www.experts-exchange.com/questions/28996808/php-function-to-remove-a-file.html#a41972756
*
* http://php.net/manual/en/function.clearstatcache.php !IMPORTANT
* http://php.net/manual/en/function.file-exists.php
* http://php.net/manual/en/function.unlink.php
*/
error_reporting(E_ALL);
echo '<pre>';
clearstatcache();
function removeFiles($filename)
{
// COPY THE FILE NAME FOR NO REASON
$fn = $filename;
// TEST TO SEE IF THE FILE EXISTS
if (file_exists($fn))
{
// IF THE ATTEMPT TO UNLINK THE FILE FAILS
if (!unlink($fn))
{
// Let's see the warning if suppressed
$result = error_get_last();
$msg = "FAILURE! unlink failed with : " . print_r($result, true);
return ($msg);
}
// THE ATTEMPT TO UNLINK WAS SUCCESSFUL
}
// THE FILE DOES NOT EXIST
else
{
return ("FAILURE! $fn DOES NOT EXIST");
}
return ("Success! $fn HAS BEEN UNLINKED");
}
// A FILE
$x = removeFiles('foo.txt');
var_dump($x);
// A DIRECTORY (EXPECTED FAILURE)
$x = removeFiles('foo');
var_dump($x);
// A FILE AGAIN (EXPECTED FAILURE)
$x = removeFiles('foo.txt');
var_dump($x);
Outputs:
string(34) "Success! foo.txt HAS BEEN UNLINKED"
Warning: unlink(foo): Is a directory in /home/iconoun/public_html/demo/temp_mark_lewis.php on line 23
string(183) "FAILURE! unlink failed with : Array
(
[type] => 2
[message] => unlink(foo): Is a directory
[file] => /home/iconoun/public_html/demo/temp_mark_lewis.php
[line] => 23
)
"
string(31) "FAILURE! foo.txt DOES NOT EXIST"
Error Log:
[21-Jan-2017 05:00:02 America/Chicago] PHP Warning: unlink(foo): Is a directory in /home/iconoun/public_html/demo/temp_mark_lewis.php on line 23
Now let's run it again when the file and directory do not exist. We get messages that make sense.
string(31) "FAILURE! foo.txt DOES NOT EXIST"
string(27) "FAILURE! foo DOES NOT EXIST"
string(31) "FAILURE! foo.txt DOES NOT EXIST"
The Error Log is empty. Note that if you want to put your own information into the error_log, PHP has a function for that: error_log()With the right error reporting in place, we can actually simplify things a lot.
<?php // demo/temp_mark_lewis.php
/**
* https://www.experts-exchange.com/questions/28996808/php-function-to-remove-a-file.html#a41972756
*
* http://php.net/manual/en/function.clearstatcache.php !IMPORTANT
* http://php.net/manual/en/function.unlink.php
*/
error_reporting(E_ALL);
echo '<pre>';
clearstatcache();
function removeFiles($filename)
{
return unlink($filename);
}
// A FILE
$x = removeFiles('foo.txt');
var_dump($x);
// A DIRECTORY (EXPECTED FAILURE)
$x = removeFiles('foo');
var_dump($x);
// A FILE AGAIN (EXPECTED FAILURE)
$x = removeFiles('foo.txt');
var_dump($x);
With pre-existing file and directory... Outputs:
bool(true)
Warning: unlink(foo): Is a directory in /home/iconoun/public_html/demo/temp_mark_lewis.php on line 16
bool(false)
Warning: unlink(foo.txt): No such file or directory in /home/iconoun/public_html/demo/temp_mark_lewis.php on line 16
bool(false)
And when they do not exist...
Warning: unlink(foo.txt): No such file or directory in /home/iconoun/public_html/demo/temp_mark_lewis.php on line 16
bool(false)
Warning: unlink(foo): No such file or directory in /home/iconoun/public_html/demo/temp_mark_lewis.php on line 16
bool(false)
Warning: unlink(foo.txt): No such file or directory in /home/iconoun/public_html/demo/temp_mark_lewis.php on line 16
bool(false)
Now the error log contains this, showing that each failing action got logged
[21-Jan-2017 05:30:01 America/Chicago] PHP Warning: unlink(foo): Is a directory in /home/iconoun/public_html/demo/temp_mark_lewis.php on line 16
[21-Jan-2017 05:30:01 America/Chicago] PHP Warning: unlink(foo.txt): No such file or directory in /home/iconoun/public_html/demo/temp_mark_lewis.php on line 16
[21-Jan-2017 05:33:04 America/Chicago] PHP Warning: unlink(foo.txt): No such file or directory in /home/iconoun/public_html/demo/temp_mark_lewis.php on line 16
[21-Jan-2017 05:33:04 America/Chicago] PHP Warning: unlink(foo): No such file or directory in /home/iconoun/public_html/demo/temp_mark_lewis.php on line 16
[21-Jan-2017 05:33:04 America/Chicago] PHP Warning: unlink(foo.txt): No such file or directory in /home/iconoun/public_html/demo/temp_mark_lewis.php on line 16
This article has a script you can run to find and display all of the error logs.https://www.experts-exchange.com/articles/29115/PHP-Error-Handling-Never-Say-die-Again.html
ASKER
Just getting back to this now and I see a lot of activity. I'll add some logging then test.
The point of the function is that curl looks for the file.
If the file exists, it downloads it. This works.
Since we don't want to download the same file again, the code running curl checks to make sure it's what we want.
If it is, then curl runs the 'fwfinal' function which is supposed to remove the file from the server.
The removal of the file is what is not working.
With the error logging, I am now seeing FAILURE when using curl and the 'fwfinal' function.
I don't see any other errors anywhere which is why it is not clear what is not working. There is nothing else showing up from the curl command nor on the server.
Downloading the file works but trying to remove it doesn't.
I tested adding the full path in the code.
from
$fn = 'fwfiles/'.$filename; (there is no path information anywhere else in the code)
to perhaps something like
$fn = '/var/www/html/files/updat es/fwfiles /'.$filena me;
Testing;
Success! /var/www/html/files/update s/fwfiles/ crazy.bin HAS BEEN UNLINKED
This worked but I'm not sure it's how it should be done.
The point of the function is that curl looks for the file.
If the file exists, it downloads it. This works.
Since we don't want to download the same file again, the code running curl checks to make sure it's what we want.
If it is, then curl runs the 'fwfinal' function which is supposed to remove the file from the server.
The removal of the file is what is not working.
With the error logging, I am now seeing FAILURE when using curl and the 'fwfinal' function.
I don't see any other errors anywhere which is why it is not clear what is not working. There is nothing else showing up from the curl command nor on the server.
Downloading the file works but trying to remove it doesn't.
I tested adding the full path in the code.
from
$fn = 'fwfiles/'.$filename; (there is no path information anywhere else in the code)
to perhaps something like
$fn = '/var/www/html/files/updat
Testing;
Success! /var/www/html/files/update
This worked but I'm not sure it's how it should be done.
You're doing it right. The URL path is not the same as the server path. When you use a browser, the URL path is the thing you need. When you want to manipulate files with PHP, you need the server path.
You can use the full path but looking at the path string above you have
/var/www/html/files/update s/fwfiles
Your script assumes that fwfiles is an immediate sub-folder of the folder in which the script is running - which based on your URL is running out of the webroot.
I see several possible web roots that are not immediate parents of fwfiles
/var/www
/var/www/html
/var/www/html/files
We can't tell where your web root is but I would check that your path is in fact correct relative to the root.
/var/www/html/files/update
Your script assumes that fwfiles is an immediate sub-folder of the folder in which the script is running - which based on your URL is running out of the webroot.
I see several possible web roots that are not immediate parents of fwfiles
/var/www
/var/www/html
/var/www/html/files
We can't tell where your web root is but I would check that your path is in fact correct relative to the root.
getcwd()
Lorem Ipsum Dolor Sit. Sorry about the "noise comment" but E-E does not allow us to post a link by itself, even when the link is posted by an experienced Expert and links to a canonical documentation source. Sheesh!
Lorem Ipsum Dolor Sit. Sorry about the "noise comment" but E-E does not allow us to post a link by itself, even when the link is posted by an experienced Expert and links to a canonical documentation source. Sheesh!
ASKER
I've tried using the web root path/s but nothing works.
The only time it works is if I use the full physical path on the server.
The only time it works is if I use the full physical path on the server.
In the path you gave us above where does app.php in the following reside?
curl -s -u 123456:abcdef --connect-timeout 15 -X POST https://domain.com/app.php -F function=fwfinal -F filename="crazy.bin"
ASKER
I was using an example above to keep things simple. I think that complicated things.
It is an mvc php setup.
There is a parameters.yml file which contains the following;
downloads_root: /var/www/html/downloads
the actual file location is;
/var/www/html/downloads/up dates/fwfi les
This works;
$fn = '/var/www/html/downloads/u pdates/fwf iles/'.$fi lename;
I tried using the following since the 'downloads' path is already in the mvc configuration file.
$fn = '/updates/fwfiles/'.$filen ame;
and
$fn = 'updates/fwfiles/'.$filena me;
Neither worked.
It is an mvc php setup.
There is a parameters.yml file which contains the following;
downloads_root: /var/www/html/downloads
the actual file location is;
/var/www/html/downloads/up
This works;
$fn = '/var/www/html/downloads/u
I tried using the following since the 'downloads' path is already in the mvc configuration file.
$fn = '/updates/fwfiles/'.$filen
and
$fn = 'updates/fwfiles/'.$filena
Neither worked.
The key point is - what folder does the script that has then unlink() code reside in.
ASKER
It isn't in the path, it is outside of the web path for security. It's an mvc setup.
So, the example would be;
/var/www/html/mvc/src/Cont roller/Con troller.ph p
The simplest fix seems to be putting the full path in the function, assuming this doesn't open up some sort of security hole.
So, the example would be;
/var/www/html/mvc/src/Cont
The simplest fix seems to be putting the full path in the function, assuming this doesn't open up some sort of security hole.
Then how do you get to it with curl ?
assuming this doesn't open up some sort of security hole.A path is a path - the point is if you move the solution then it breaks again - there is a solution using relative paths - all we need to do is find where the script that runs of the cUrl is located and work out the path from there.
ASKER
The physical path is
/var/www/html/downloads/up dates/fwfi les
To get the file;
curl "https://domain.com/updates/fwfiles/crazy.bin" -u "123456:abcdef" -o /tmp/crazy.bin
/var/www/html/downloads/up
To get the file;
curl "https://domain.com/updates/fwfiles/crazy.bin" -u "123456:abcdef" -o /tmp/crazy.bin
Let's go back to this for a moment.
This is relative to the WWW root directory: $fn = '/updates/fwfiles/'.$filen ame;
This is relative to the current directory: $fn = 'updates/fwfiles/'.$filena me;
These paths may not exist when you're using cURL to start the script. That's where getcwd() comes in.
Try creating this directory structure and running the script below in each of the directories. Do it via a web browser and then do it via cURL.
This works;When you're running a script in the WWW root, you get some of the advantages of relative addressing. You can address file paths relative to the WWW root or relative to the current directory.
$fn = '/var/www/html/downloads/updates/fwf iles/'.$fi lename;
I tried using the following since the 'downloads' path is already in the mvc configuration file.
$fn = '/updates/fwfiles/'.$filename;
and
$fn = 'updates/fwfiles/'.$filename;
Neither worked.
This is relative to the WWW root directory: $fn = '/updates/fwfiles/'.$filen
This is relative to the current directory: $fn = 'updates/fwfiles/'.$filena
These paths may not exist when you're using cURL to start the script. That's where getcwd() comes in.
Try creating this directory structure and running the script below in each of the directories. Do it via a web browser and then do it via cURL.
/var/www/ <== here
|
test/ <== here
|
path/ <== and here
<?php var_dump(getcwd());
ASKER
Just to be clear... the curl command is being run on a remote server, to a remote server.
Yes, cURL makes HTTP requests, usually GET or POST, as if it were a client in the classic definition of client/server.
ASKER
I think I can see what is happening, just not sure how to fix it, let alone explain it.
First, there is the path in the config file which points to a directory structure which is above the root of that site.
For example,
/var/www/downloads - this is where the config file points to the files
/var/www/html - this is the path to the root of the web server
/var/www/html/updates - this is the default path for the curl commands because once curl tries to connect to that subdirectory, it must be an authenticated connection in order to reach it otherwise, is denied access.
The problem is that when referencing the path, one is an alias, I guess, based on the config file and the other is the relative path or web home. If I don't enter the full path, then the relative path gets inserted into the mix so it's looking for something like;
So, what is happening is that curl is including the updates part of the url in order to do this as an authenticated connection but the config file points to a directory structure which is above the root of the web server.
There is a conflict in this.
First, there is the path in the config file which points to a directory structure which is above the root of that site.
For example,
/var/www/downloads - this is where the config file points to the files
/var/www/html - this is the path to the root of the web server
/var/www/html/updates - this is the default path for the curl commands because once curl tries to connect to that subdirectory, it must be an authenticated connection in order to reach it otherwise, is denied access.
The problem is that when referencing the path, one is an alias, I guess, based on the config file and the other is the relative path or web home. If I don't enter the full path, then the relative path gets inserted into the mix so it's looking for something like;
So, what is happening is that curl is including the updates part of the url in order to do this as an authenticated connection but the config file points to a directory structure which is above the root of the web server.
There is a conflict in this.
Maybe the solution goes something like this:
$current_dir = getcwd();
chdir('path/to/desirable/dir');
/* Do stuff */
chdir($current_dir);
ASKER
Putting the full path in works fine. Is there any security concern about doing it that way?
No security issues whatsoever, so long as the path is part of your PHP code, or protected from prying eyes with the same diligence that you would use to protect a password.
ASKER
Perfect, than we're done.
Thank you very much.
Thank you very much.
/var/www/html - this is the path to the root of the web serverAnd
Success! /var/www/html/files/updates/fwfiles/ crazy.bin HAS BEEN UNLINKED
Then I am guessing this will work - it does not appear from your posts that you attempted this option.
$fn = "files/updates/fwfiles/{$filename}";
unlink($fn);
As I said in this post - a path is a path - so no issue from security reasons but the absolute solution just means that if you move your site (for what ever reason) the solution breaks.
ASKER
@Julian
Actually, I did try this and other variations. The reason it doesn't work is because the curl connection is an authenticated one which has to go through the directory mentioned above. From there, the code points to a directory outside of the web service, not an alias, but a path that only the server can get to, internally, by the code.
That's why using a web path cannot work but using the full physical path does.
Actually, I did try this and other variations. The reason it doesn't work is because the curl connection is an authenticated one which has to go through the directory mentioned above. From there, the code points to a directory outside of the web service, not an alias, but a path that only the server can get to, internally, by the code.
That's why using a web path cannot work but using the full physical path does.
That does not make sense though.
When you curl to a page you have to get to a script that is in the web root.
Anything that is loaded from there is in the context of that first access script and the folder it exists in.
i.e
http://www.yoursite.com/script.php
script.php
Not trying to be pedantic here - I am just intrigued as to why this is not working.
When you curl to a page you have to get to a script that is in the web root.
Anything that is loaded from there is in the context of that first access script and the folder it exists in.
i.e
http://www.yoursite.com/script.php
script.php
<?php
require("/some/long/path/someotherscript.php");
...
Any reference to a path in "someotherscript.php" is still going to be from the webroot.Not trying to be pedantic here - I am just intrigued as to why this is not working.
ASKER
The dev is no longer around so I'm deciphering this as best I can, not being a programmer.
The updates directory as I understand, should be reached by the code only and never directly by a client, browser/curl/etc.
So, the server side running code knows how to get to these sub-directories so long as the request is by an authenticated connection.
Meaning, the client should not be able to get to that structure directly since anything https must be an authenticated connection.
I'm not sure if this helps.
The updates directory as I understand, should be reached by the code only and never directly by a client, browser/curl/etc.
So, the server side running code knows how to get to these sub-directories so long as the request is by an authenticated connection.
Meaning, the client should not be able to get to that structure directly since anything https must be an authenticated connection.
I'm not sure if this helps.
The auth is a separate thing - This is what interests me
To get the file
curl "https://domain.com/updates/fwfiles/crazy.bin" -u "123456:abcdef" -o /tmp/crazy.bin
So unless there is some URL rewriting and /updates/fwfiles/crazy.bin is being passed in as a parameter and then manipulated to get the file from outside of the web root (which is possible) I don't see that the curl file will not be available. A glance at the script will tell us.
But lets leave it there - it is working just bear in mind if you have to move anything you have to update your full path.
To get the file
curl "https://domain.com/updates/fwfiles/crazy.bin" -u "123456:abcdef" -o /tmp/crazy.bin
So unless there is some URL rewriting and /updates/fwfiles/crazy.bin
But lets leave it there - it is working just bear in mind if you have to move anything you have to update your full path.
ASKER
There might be some rewriting going on as I do recall at one point we broke the whole thing by changing a path and could not figure out why it broke. However, we then looked around for related code but could not find anything.
As you say, I'd have to post all the code which I cannot do. I'm just happy that it works and doesn't sound like a security hole.
Thanks to everyone who helped.
As you say, I'd have to post all the code which I cannot do. I'm just happy that it works and doesn't sound like a security hole.
Thanks to everyone who helped.
ASKER
The upgrades meant going from php 5.4 to php 7.0 on Centos7.