Using file_exists with safe_mode enabled - php

I am trying to check if a file exists on the server. The difficulty that I am having is that file_exists returns false for a file that I know exists on the server.
My server is configured withsafe_mode = on in php.ini. Because of this I've added the directory that contains the file I want to check on using
include_path=".:/path/to/dir"
I've also added the path to
safe_mode_include_dir = /path/to/dir
file_exists("/path/to/dir/file") still returns FALSE.
Thanks in advance

If you have the ability to do so, disable safe_mode. It has been deprecated by the PHP development team, and is gone as of PHP 5.4.0, so you will need to stop depending on it sooner or later.
If you must use safe_mode, add the directory to open_basedir, not include_path.

Sorry, but it's just not possible to circumvent safe mode restrictions - that's sort of the point of safe mode. If you want to include the file, you will still be able to do so if you have set safe_mode_include_dir to a path that will allow it to be accessed, but there is no way to get stat() related functions to work with it.
EDIT
A horrible and incredibly dangerous and unreliable work around might be this:
function file_exists_safemode ($file) {
$oldErrorLevel = error_reporting(E_ALL);
$oldDisplayErrors = ini_get('display_errors');
ini_set('display_errors', 1);
ob_start();
include $file;
$result = ob_get_clean();
ini_set('display_errors', $oldDisplayErrors);
error_reporting($oldErrorLevel);
return strpos($result, 'failed to open stream') === FALSE;
}
...but it is so nasty in so many ways, and I definitely do not recommend this as an approach.

Related

Does PHP's require() function cache its results in PHP 5.6?

In a PHP script intended to work on generic shared LAMP hosting, I am using require() as a function to read data from a file. The data file looks like this:
<?php
# storage.php
return [
// Rotating secrets
'last_rand' => '532e89355b78aafdb85f5f01f0eed20440d6bd9e0a2d6ae1bd17be4e1d7d21c7bb7a822a2077e3f4',
];
And I read it like this:
$data = require($root . '/storage.php');
$lastRand = $data['last_rand'];
In my script, I will read information from a configuration file using this approach. In some cases, the operation will re-write a new config file (using file_put_contents()) and then re-read it, again using require().
This has worked solidly so far, on a variety of LAMP hosts, but today I found a web host where the require() does not seem to notice changes in the file. This host is using PHP 5.6, whereas all the others I have tested are using 7+.
What's extremely odd is that require() gets the old contents of the file even though file_get_contents() can see the new version, as if require() is doing its own internal caching.
Failed fix attempts
I have even tried waiting for require() to catch up, in case some underlying cache needed time to expire:
$ok = true;
$t = microtime(true);
while ($oldRandom != $this->getFileService()->requirePhp($this->getStoragePath())['last_rand'])
{
$elapsedTime = microtime(true) - $t;
if ($elapsedTime > 4) // Wait 4 seconds
{
$ok = false;
break;
}
usleep(1000);
}
That did not work either (the timeout just expires with no change in the results brought back from require()).
However, upon the next run of the PHP script, require() suddenly sees the new file contents.
I've also tried to delete the storage.php file before re-reading it, and that has no effect either! I was really expecting that to help.
Future actions
So, I don't understand this behaviour at all. To work around it I could:
rather than modifying a new config, saving it to file and then reloading it from file, I could refactor my code so that I save the new config to memory, thus avoiding having to reload it
refactor so that I use file_get_contents() rather than require() (for rather dull reasons it's not as convenient, but I'll prefer whatever works reliably).
However, these both feel like I am ignoring a bug, and that I should investigate the cause. I happen to know also that this host (a free one) is nearly always under heavy CPU load. It's a 64 bit server running Linux on the rather old 2.6 kernel, and phpinfo() indicates the CGI/FastCGI server API is in force.
Can anything be done to mitigate this behaviour?
More attempts
Some helpful commenters suggest this problem could be related to opcaching, which seems to fit the general purpose of require(). I've added this code after the file_put_contents() that re-writes the config:
$reset = opcache_reset(); // Returns true
$invalid = opcache_invalidate($this->getStoragePath()); // Returns false
However, it makes no difference - require() stubbornly reads the same value. I have confirmed this by doing a require() before and after the file write, and also a file_get_contents() to get the true contents.
When I originally encountered this error, I was hesitant to work around it, since it felt like a critical bug that ought not be ignored. However, now that the culprit is likely to be opcaching (and thus related to require() specifically), I think working around it by keeping values in memory is not a bad solution. I shall have to remember that require() is only good for one call per HTTP request, even if that does not hold true for all PHP installations.
I am conscious also that if I did try to work around this, I would have to contend with a number of opcache invalidation mechanisms, which is more complexity than I am comfortable with.
I found the same problem with a config file, the only valid solution for now is to change the name of the config file with each modification:
1 - load config file
// get the config file name
$config_file=glob('config/config*.php'))[0] ?? null;
// load the settings
if($config_file) $settings=require_once($config_file);
2 - save the config file when needed
...
$data=['var'=>'value','other-var'=>'other value'];
$vars=var_export($data, TRUE);
// new file name
$file_name='config/config-'.time().'.php';
file_put_contents($file_name,"<?php\n return $vars;");
// delete old config file
if($config_file) #unlink($config_file);

PHP require_once not working on Linux

I've transferred a PHP web-system from a Windows hosting provider to a Linux based hosting service.
In the system's scripts, when it comes to require_once, the script simply stops and leaves the user at a blank white page.
I've tried both of the below:
Try 1
require_once($_SERVER['DOCUMENT_ROOT'] . '\library\data\Dbec.php') or die("could not load file");
Try 2
require_once(dirname(__FILE__) . '/library/data/Dbec.php') or die("could not load file");
In both cases, the text in the die parenthesis is not showing and the page remains blank.
The script that is requiring the above files is in '/library/membership/theScript.php'
Based on the reading I've done on line up to now, maybe it has to do with changing the include_path in php.ini file or writing the paths in a different way.
If its any of the above, or something different, I'd appreciate some hints.
Check your error log, to see if anything is visibly wrong. Also try setting error_reporting = E_ALL, and make sure display_errors = On and log_errors = On in your php.ini.
Your file your trying to include is in '/library/membership/theScript.php', try doing:
require_once '../data/Dbec.php';
This isn't going to do what you want. Everything after require_once is being interpreted as a conditional. It's running ($_SERVER['DOCUMENT_ROOT'] . '\library\data\Dbec.php') or die("could not load file") and returning 1, then running require_once 1.
To make it work as you expect, you would need an extra set of parentheses:
(require_once($_SERVER['DOCUMENT_ROOT'] . '\library\data\Dbec.php')) or die("could not load file");
Although, I'm not certain the die() will ever get called. It's up to you to figure out.
See this related bug report which was ruled "not a bug."
Also problem can be in file permissions and filename. Windows filesystem is caseinsesetive but linux is not. So you have to check the filename and the fact that user who executes this script has read persmission on the file you try to include with require_once

Is there a way in PHP to output the error log defined in the apache configuration?

I created a PHP script to read the error log file in a customized visual format, but right now, I have the path to the error log file hard-coded in, which works fine for me, but I would like to find out if there's a way to pull the path to the error_log automatically so it can work on any server without further configuration.
You can use ini_get to obtain the error_log path in PHP.
$error_log = ini_get('error_log');
Otherwise, you'd be relegated to using something like:
<?php
ob_start();
phpinfo(INFO_CONFIGURATION);
$phpinfo = ob_get_contents();
ob_end_clean();
preg_match('#error_log</td><td\b[^>]*>(.*?)</td>#', $phpinfo, $matches);
$error_log = $matches[1];
Note that if there is no error_log set, $error_log will return:
<i>no value</i>
you can pull it from the ErrorLog Directive
http://httpd.apache.org/docs/current/mod/core.html#errorlog
The only way to get it in PHP is by installing something like ApacheAccessor. Wouldn't call it portable though, as I've seldomly seen it installed, but wildly guessing default paths is default distro's is about your only other option.

can't include("absolute_path")

I can't figure out why this won't work.
$docRoot = getenv("DOCUMENT_ROOT");
include_once($docRoot."/conn/connection.php");
include_once($docRoot."/auth/user.php");
It works locally through wamp, but it won't work on my live server. I tried this:
if(!include_once($docRoot."/auth/user.php")){
session_start();
$debug = array();
$debug["docRoot"] = $docRoot;
$debug["inc_path"] = $docRoot."/auth/user.php";
$debug["file_exists"] = file_exists($debug["inc_path"]);
$_SESSION['DEBUG'] = $debug;
// exit
header("Location:debug.php");
exit();
}
The debug page just echoes that array and it shows the correct absolute paths and indicates that the file does in fact exist. So why didn't the include_once() work? The server (a DV account on a MediaTemple server) has not been configured at all, so I wonder if there is an apache or php setting that is messing with me.
Ultimately, what I want here is a way to refer to a file in such a way that if I move the file, or include it in another file, nothing will break. Any ideas?
In your debugging you might try is_readable($docRoot."/conn/connection.php"). The file_exists function will return true even if the file does not have readable permissions.
If you get an error code we might be able to provide more info as to what is going wrong.
Turn on error reporting dummy. It turns out one of the includes on another file was breaking this page and I wasn't able to trace that out until I turned on error reporting.
Incidentally, the problematic include was missing a leading slash in the filepath ( include("dir/file.ext"); ) which works on my local wamp setup and breaks on the linux server.

How can I change php.ini by PHP?

I want to do the equivalent to the following line in file php.ini, but from PHP.
short_open_tag = On
Is it possible?
I tried this:
<?php
if (!ini_get('short_open_tag')) {
ini_set('short_open_tag', 'On');
}
$a = 1;
?>
<?=$a;?>
which outputs <?=$a;?>, so it's not working.
Yes, ini_set() is what you want.
An example:
if (!ini_get('short_open_tag')) {
ini_set('short_open_tag', 'on');
}
If you are using PHP 5.3, short_open_tag is no longer an option.
Description of core php.ini directives
Short tags have been deprecated as of PHP 5.3 and may be removed in PHP 6.0.
If you want to change it during a session and forget about it later, use ini_get() and ini_set(). If you want to actually modify php.ini programmatically, you can parse the ini file using parse_ini_file(), change your options and rewrite back to disk. See here for more.
Or you can write your own string replacement routine using the normal opening of a file, preg_replace(), etc.
Although you can use ini_set, be careful (quoted from the PHP documentation):
Not all the available options can be changed using ini_set(). There is a list of all available options in the appendix.
If you are changing options, like magic_quotes and short_open_tags, that's OK. But if you are going to change safe_mode, enable_dl, etc., the function will fail silently.
Many of the options specified above as examples are obsolete/removed security options in former versions of PHP. Consult the documentation if the behavior of ini_set is unexpected (e.g., does not work)
Please edit the php.ini file (just remove the ; and restart your Apache server):
Replace
;short_open_tag = On
with
short_open_tag = On
Now it will work.

Categories