Say I want to tell whether a string passed to fopen represents either a file path or a valid wrapper (e.g. "/home/user/example.txt"
vs "php://input"
). This is for the purpose of creating a tmpfile from what's in php://input
to work around fseek
ing limitations for PHP wrappers.
As shown below, file_exists
works for files, but not for wrapper URIs:
var_dump(file_exists("php://input"));
var_dump(file_exists("./exists.txt"));
var_dump(file_exists("./non_existent.txt"));
var_dump(file_exists("php://garbage"));
gives
bool(false)
bool(true)
bool(false)
bool(false)
The only one returning true is the file. I've found stream_get_wrappers() but I want to avoid complicating the check too much (such as using string comparison to try to detect a wrapper).
Using stream_get_meta_data does also seem to work, but it requires a call to fopen first, which would clog up error logs.
var_dump(stream_get_meta_data(fopen("php://input","r+")));
var_dump(stream_get_meta_data(fopen("./exists.txt","r+")));
var_dump(stream_get_meta_data(fopen("./non_existent.txt","r+")));
var_dump(stream_get_meta_data(fopen("php://garbage","r+")));
produces
array(9) {
["timed_out"]=>
bool(false)
["blocked"]=>
bool(true)
["eof"]=>
bool(false)
["wrapper_type"]=>
string(3) "PHP"
["stream_type"]=>
string(5) "Input"
["mode"]=>
string(2) "rb"
["unread_bytes"]=>
int(0)
["seekable"]=>
bool(true)
["uri"]=>
string(11) "php://input"
}
array(9) {
["timed_out"]=>
bool(false)
["blocked"]=>
bool(true)
["eof"]=>
bool(false)
["wrapper_type"]=>
string(9) "plainfile"
["stream_type"]=>
string(5) "STDIO"
["mode"]=>
string(2) "r+"
["unread_bytes"]=>
int(0)
["seekable"]=>
bool(true)
["uri"]=>
string(10) "./exists.txt"
}
NULL
NULL
I can use the wrapper_type
from the array returned by stream_get_meta_data
, but it still will spew garbage into logs if the file or wrapper URI doesn't exist, which I want to avoid.
What's the best way to detect whether my input string (to be passed to fopen) contains either a valid file path for an existing file or a valid PHP wrapper, or neither?
Update: I found a workaround that solves the problem, at the expense of an extra fopen
call. I've put this in an answer below.
Update
I was able to work around it like this:
class example {
var $file;
function open($path) {
$testHandle = fopen($path,"rb");
if(!$testHandle) {
error_log("Error parsing file: could not open $path");
return false;
}
$wrapperType = stream_get_meta_data($testHandle)["wrapper_type"];
if ($wrapperType != "plainfile") {
$this->file = tmpfile();
fwrite($this->file,file_get_contents($path));
fclose($testHandle);
} else {
$this->file = $testHandle;
}
}
}
If the passed $path
(e.g. php://input
) is not a directly-opened file, it will create a temporary file (with tmpfile()
) and write the contents of the stream into that temporary file, closing $testHandle
after. If, however, it is a file opened off the filesystem, (e.g. /path/to/file
) it will simply set $this->file to $testHandle.
This ensures that I am working with a file handle consistently; it should work out fine for me as none of the files I'm reading will be larger than a megabyte or so. However, I'd still like to be able to ditch the extra fopen call.