1 <?php
  2 
  3 /**
  4  * Stream wrapper to include variables like files (php code will be evaluated).
  5  *
  6  * Example:
  7  * <code>
  8  * <?php
  9  *   include rex_stream::factory('myContent', '<?php echo "Hello World"; ?>');
 10  * ?>
 11  * </code>
 12  *
 13  * @author gharlan
 14  *
 15  * @package redaxo\core
 16  *
 17  * @see http://www.php.net/manual/en/class.streamwrapper.php
 18  */
 19 class rex_stream
 20 {
 21     private static $useRealFiles;
 22 
 23     private static $registered = false;
 24     private static $nextContent = [];
 25 
 26     private $position;
 27     private $content;
 28 
 29     /**
 30      * Prepares a new stream.
 31      *
 32      * @param string $path    Virtual path which should describe the content (e.g. "template/1"), only relevant for error messages
 33      * @param string $content Content which will be included
 34      *
 35      * @throws InvalidArgumentException
 36      *
 37      * @return string Full path with protocol (e.g. "rex:///template/1")
 38      */
 39     public static function factory($path, $content)
 40     {
 41         if (!is_string($path) || empty($path)) {
 42             throw new InvalidArgumentException('Expecting $path to be a string and not empty!');
 43         }
 44         if (!is_string($content)) {
 45             throw new InvalidArgumentException('Expecting $content to be a string!');
 46         }
 47 
 48         if (null === self::$useRealFiles) {
 49             self::$useRealFiles = extension_loaded('suhosin')
 50                 && !preg_match('/(?:^|,)rex(?::|,|$)/', ini_get('suhosin.executor.include.whitelist'));
 51         }
 52 
 53         if (self::$useRealFiles) {
 54             $hash = substr(sha1($content), 0, 7);
 55             $path = rex_path::coreCache('stream/'.$path.'/'.$hash);
 56 
 57             if (!file_exists($path)) {
 58                 rex_file::put($path, $content);
 59             }
 60 
 61             return $path;
 62         }
 63 
 64         if (!self::$registered) {
 65             stream_wrapper_register('rex', self::class);
 66             self::$registered = true;
 67         }
 68 
 69         // 3 slashes needed to sidestep some server url include protections
 70         // example: https://www.strato.de/faq/article/622/Warum-erhalte-ich-über-PHP-die-Fehlermeldung-%22Warning:-main()-…:-include(….).html
 71         $path = 'rex:///' . $path;
 72         self::$nextContent[$path] = $content;
 73 
 74         return $path;
 75     }
 76 
 77     /**
 78      * @see http://www.php.net/manual/en/streamwrapper.stream-open.php
 79      */
 80     public function stream_open($path, $mode, $options, &$opened_path)
 81     {
 82         if (!isset(self::$nextContent[$path]) || !is_string(self::$nextContent[$path])) {
 83             return false;
 84         }
 85 
 86         $this->position = 0;
 87         $this->content = self::$nextContent[$path];
 88         //unset(self::$nextContent[$path]);
 89 
 90         return true;
 91     }
 92 
 93     /**
 94      * @see http://www.php.net/manual/en/streamwrapper.stream-read.php
 95      */
 96     public function stream_read($count)
 97     {
 98         $ret = substr($this->content, $this->position, $count);
 99         $this->position += strlen($ret);
100         return $ret;
101     }
102 
103     /**
104      * @see http://www.php.net/manual/en/streamwrapper.stream-eof.php
105      */
106     public function stream_eof()
107     {
108         return $this->position >= strlen($this->content);
109     }
110 
111     /**
112      * @see http://www.php.net/manual/en/streamwrapper.stream-seek.php
113      */
114     public function stream_seek($offset, $whence = SEEK_SET)
115     {
116         switch ($whence) {
117             case SEEK_SET:
118                 $this->position = $offset;
119                 return true;
120             case SEEK_CUR:
121                 $this->position += $offset;
122                 return true;
123             case SEEK_END:
124                 $this->position = strlen($this->content) - 1 + $offset;
125                 return true;
126             default:
127                 return false;
128         }
129     }
130 
131     /**
132      * @see http://www.php.net/manual/en/streamwrapper.stream-tell.php
133      */
134     public function stream_tell()
135     {
136         return $this->position;
137     }
138 
139     /**
140      * @see http://www.php.net/manual/en/streamwrapper.stream-flush.php
141      */
142     public function stream_flush()
143     {
144         return true;
145     }
146 
147     /**
148      * @see http://www.php.net/manual/en/streamwrapper.stream-stat.php
149      */
150     public function stream_stat()
151     {
152         return null;
153     }
154 
155     /**
156      * @see http://www.php.net/manual/en/streamwrapper.url-stat.php
157      */
158     public function url_stat()
159     {
160         return null;
161     }
162 }
163