Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a fake / "virtual" file?

I am trying to create a "virtual" file without using either memory or a temporary file. The "virtual" file needs to pass a check made with file_exists() while not throwing any error or warning when used with require or include.

Allows you to implement your own protocol handlers and streams for use with all the other filesystem functions (such as fopen(), fread() etc.).

…where file_exists() is one of them. The docs page states:

As of PHP 5.0.0, this function can also be used with some URL wrappers. Refer to Supported Protocols and Wrappers to determine which wrappers support stat() family of functionality.

My attempt there was to build a custom, virtual file wrapper

class VirtualFileWrapper
{
    public $context;

    public function stream_open( $path, $mode, $options, &$opened_path )
    {
        return TRUE;
    }

    public function stream_stat()
    {
        var_dump( __METHOD__ );
        $data = [
            'dev'     => 0,
            'ino'     => getmyinode(),
            'mode'    => 'r',
            'nlink'   => 0,
            'uid'     => getmyuid(),
            'gid'     => getmygid(),
            'rdev'    => 0,
            'size'    => 0,
            'atime'   => time(),
            'mtime'   => getlastmod(),
            'ctime'   => FALSE,
            'blksize' => 0,
            'blocks'  => 0,
        ];
        return array_merge( array_values( $data ), $data );
    }
}

stream_wrapper_register( 'virtual', 'VirtualFileWrapper' );

$file = fopen( "virtual://foo", 'r+' );

// Executes VirtualFileWrapper::stream_stat()
fstat( $file );

// Executes no VirtualFileWrapper method
file_exists( $file );

While the fstat() function executes the method, file_exists() does not execute any stream class method.

How could I get a virtual stream wrapper to work (with file_exists())?


I am fully aware that tempnam( __DIR__, '' ) will pass both:

  • var_dump( tempnam( __DIR__, '' ) ); Returns true
  • require tempnam( __DIR__, '' ); No error

but I do not want to use a temporary file as there might be a better way (performance wise).

like image 606
kaiser Avatar asked May 31 '16 19:05

kaiser


1 Answers

It looks like you just need to implement a public url_stat() method on VirtualFileWrapper for it to pass the file_exists() check.

To silence warnings and errors from include and require, you must implement stream_read() and stream_eof() methods:

class VirtualFileWrapper
{
    public $context;

    public function stream_open( $path, $mode, $options, &$opened_path )
    {
        return TRUE;
    }

    public function stream_stat()
    {
        var_dump( __METHOD__ );
        return [];
    }

    public function url_stat()
    {
        return array (
          0 => 0,
          1 => 0,
          2 => 0,
          3 => 0,
          4 => 0,
          5 => 0,
          6 => 0,
          7 => 0,
          8 => 0,
          9 => 0,
          10 => 0,
          11 => 0,
          12 => 0,
          'dev' => 0,
          'ino' => 0,
          'mode' => 0,
          'nlink' => 0,
          'uid' => 0,
          'gid' => 0,
          'rdev' => 0,
          'size' => 0,
          'atime' => 0,
          'mtime' => 0,
          'ctime' => 0,
          'blksize' => 0,
          'blocks' => 0
        );
    }

    public function stream_read(){
        return '';
    }

    public function stream_eof(){
        return '';
    }

}

stream_wrapper_register( 'virtual', 'VirtualFileWrapper' );

$file = fopen( "virtual://foo", 'r+' );

// Executes VirtualFileWrapper::stream_stat()
fstat( $file );

// Executes no VirtualFileWrapper method
file_exists("virtual://foo");

//Still no errors :-)!
require "virtual://foo";
include "virtual://foo";

Take care to pass file_exists() a string, rather than the resource you created with fopen().

like image 121
HPierce Avatar answered Nov 09 '22 16:11

HPierce