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:
If the passed
$path
(e.g.php://input
) is not a directly-opened file, it will create a temporary file (withtmpfile()
) 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.