1<?php
2/**
3 * Copyright 2007-2017 Horde LLC (http://www.horde.org/)
4 *
5 * See the enclosed file LICENSE for license information (BSD). If you
6 * did not receive this file, see http://www.horde.org/licenses/bsd.
7 *
8 * @category  Horde
9 * @copyright 2007-2017 Horde LLC
10 * @license   http://www.horde.org/licenses/bsd BSD
11 * @package   Stream_Wrapper
12 */
13
14/**
15 * A stream wrapper that will treat a native PHP string as a stream.
16 *
17 * @author    Chuck Hagenbuch <chuck@horde.org>
18 * @author    Michael Slusarz <slusarz@horde.org>
19 * @category  Horde
20 * @copyright 2007-2017 Horde LLC
21 * @license   http://www.horde.org/licenses/bsd BSD
22 * @package   Stream_Wrapper
23 */
24class Horde_Stream_Wrapper_String
25{
26    /**/
27    const WRAPPER_NAME = 'horde-stream-wrapper-string';
28
29    /**
30     * The current context.
31     *
32     * @var resource
33     */
34    public $context;
35
36    /**
37     * String position.
38     *
39     * @var integer
40     */
41    protected $_pos;
42
43    /**
44     * The string.
45     *
46     * @var string
47     */
48    protected $_string;
49
50    /**
51     * Unique ID tracker for the streams.
52     *
53     * @var integer
54     */
55    private static $_id = 0;
56
57    /**
58     * Create a stream from a PHP string.
59     *
60     * @since 2.1.0
61     *
62     * @param string &$string  A PHP string variable.
63     *
64     * @return resource  A PHP stream pointing to the variable.
65     */
66    public static function getStream(&$string)
67    {
68        if (!self::$_id) {
69            stream_wrapper_register(self::WRAPPER_NAME, __CLASS__);
70        }
71
72        /* Needed to keep reference. */
73        $ob = new stdClass;
74        $ob->string = &$string;
75
76        return fopen(
77            self::WRAPPER_NAME . '://' . ++self::$_id,
78            'wb',
79            false,
80            stream_context_create(array(
81                self::WRAPPER_NAME => array(
82                    'string' => $ob
83                )
84            ))
85        );
86    }
87
88    /**
89     * @see streamWrapper::stream_open()
90     */
91    public function stream_open($path, $mode, $options, &$opened_path)
92    {
93        $opts = stream_context_get_options($this->context);
94
95        if (isset($opts[self::WRAPPER_NAME]['string'])) {
96            $this->_string =& $opts[self::WRAPPER_NAME]['string']->string;
97        } elseif (isset($opts['horde-string']['string'])) {
98            // @deprecated
99            $this->_string =& $opts['horde-string']['string']->getString();
100        } else {
101            throw new Exception('Use ' . __CLASS__ . '::getStream() to initialize the stream.');
102        }
103
104        if (is_null($this->_string)) {
105            return false;
106        }
107
108        $this->_pos = 0;
109
110        return true;
111    }
112
113    /**
114     * @see streamWrapper::stream_close()
115     */
116    public function stream_close()
117    {
118        $this->_string = '';
119        $this->_pos = 0;
120    }
121
122    /**
123     * @see streamWrapper::stream_read()
124     */
125    public function stream_read($count)
126    {
127        $curr = $this->_pos;
128        $this->_pos += $count;
129        return substr($this->_string, $curr, $count);
130    }
131
132    /**
133     * @see streamWrapper::stream_write()
134     */
135    public function stream_write($data)
136    {
137        $len = strlen($data);
138
139        $this->_string = substr_replace($this->_string, $data, $this->_pos, $len);
140        $this->_pos += $len;
141
142        return $len;
143    }
144
145    /**
146     * @see streamWrapper::stream_tell()
147     */
148    public function stream_tell()
149    {
150        return $this->_pos;
151    }
152
153    /**
154     * @see streamWrapper::stream_eof()
155     */
156    public function stream_eof()
157    {
158        return ($this->_pos > strlen($this->_string));
159    }
160
161    /**
162     * @see streamWrapper::stream_stat()
163     */
164    public function stream_stat()
165    {
166        return array(
167            'dev' => 0,
168            'ino' => 0,
169            'mode' => 0,
170            'nlink' => 0,
171            'uid' => 0,
172            'gid' => 0,
173            'rdev' => 0,
174            'size' => strlen($this->_string),
175            'atime' => 0,
176            'mtime' => 0,
177            'ctime' => 0,
178            'blksize' => 0,
179            'blocks' => 0
180        );
181    }
182
183    /**
184     * @see streamWrapper::stream_seek()
185     */
186    public function stream_seek($offset, $whence)
187    {
188        switch ($whence) {
189        case SEEK_SET:
190            $pos = $offset;
191            break;
192
193        case SEEK_CUR:
194            $pos = $this->_pos + $offset;
195            break;
196
197        case SEEK_END:
198            $pos = strlen($this->_string) + $offset;
199            break;
200        }
201
202        if (($pos < 0) || ($pos > strlen($this->_string))) {
203            return false;
204        }
205
206        $this->_pos = $pos;
207
208        return true;
209    }
210
211}
212