1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Utils;
9:
10:
11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22:
23: class SafeStream
24: {
25:
26: const PROTOCOL = 'nette.safe';
27:
28:
29: private $handle;
30:
31:
32: private $tempHandle;
33:
34:
35: private $file;
36:
37:
38: private $tempFile;
39:
40:
41: private $deleteFile;
42:
43:
44: private $writeError = false;
45:
46:
47: 48: 49: 50:
51: public static function register()
52: {
53: foreach (array_intersect(stream_get_wrappers(), array('safe', self::PROTOCOL)) as $name) {
54: stream_wrapper_unregister($name);
55: }
56: stream_wrapper_register('safe', __CLASS__);
57: return stream_wrapper_register(self::PROTOCOL, __CLASS__);
58: }
59:
60:
61: 62: 63: 64: 65: 66: 67:
68: public function stream_open($path, $mode, $options)
69: {
70: $path = substr($path, strpos($path, ':') + 3);
71:
72: $flag = trim($mode, 'crwax+');
73: $mode = trim($mode, 'tb');
74: $use_path = (bool) (STREAM_USE_PATH & $options);
75:
76:
77: if ($mode === 'r') {
78: return $this->checkAndLock($this->tempHandle = fopen($path, 'r' . $flag, $use_path), LOCK_SH);
79:
80: } elseif ($mode === 'r+') {
81: if (!$this->checkAndLock($this->handle = fopen($path, 'r' . $flag, $use_path), LOCK_EX)) {
82: return false;
83: }
84:
85: } elseif ($mode[0] === 'x') {
86: if (!$this->checkAndLock($this->handle = fopen($path, 'x' . $flag, $use_path), LOCK_EX)) {
87: return false;
88: }
89: $this->deleteFile = true;
90:
91: } elseif ($mode[0] === 'w' || $mode[0] === 'a' || $mode[0] === 'c') {
92: if ($this->checkAndLock($this->handle = @fopen($path, 'x' . $flag, $use_path), LOCK_EX)) {
93: $this->deleteFile = true;
94:
95: } elseif (!$this->checkAndLock($this->handle = fopen($path, 'a+' . $flag, $use_path), LOCK_EX)) {
96: return false;
97: }
98:
99: } else {
100: trigger_error("Unknown mode $mode", E_USER_WARNING);
101: return false;
102: }
103:
104:
105: $tmp = '~~' . lcg_value() . '.tmp';
106: if (!$this->tempHandle = fopen($path . $tmp, (strpos($mode, '+') ? 'x+' : 'x') . $flag, $use_path)) {
107: $this->clean();
108: return false;
109: }
110: $this->tempFile = realpath($path . $tmp);
111: $this->file = substr($this->tempFile, 0, -strlen($tmp));
112:
113:
114: if ($mode === 'r+' || $mode[0] === 'a' || $mode[0] === 'c') {
115: $stat = fstat($this->handle);
116: fseek($this->handle, 0);
117: if (stream_copy_to_stream($this->handle, $this->tempHandle) !== $stat['size']) {
118: $this->clean();
119: return false;
120: }
121:
122: if ($mode[0] === 'a') {
123: fseek($this->tempHandle, 0, SEEK_END);
124: }
125: }
126:
127: return true;
128: }
129:
130:
131: 132: 133: 134:
135: private function checkAndLock($handle, $lock)
136: {
137: if (!$handle) {
138: return false;
139:
140: } elseif (!flock($handle, $lock)) {
141: fclose($handle);
142: return false;
143: }
144:
145: return true;
146: }
147:
148:
149: 150: 151:
152: private function clean()
153: {
154: flock($this->handle, LOCK_UN);
155: fclose($this->handle);
156: if ($this->deleteFile) {
157: unlink($this->file);
158: }
159: if ($this->tempHandle) {
160: fclose($this->tempHandle);
161: unlink($this->tempFile);
162: }
163: }
164:
165:
166: 167: 168: 169:
170: public function stream_close()
171: {
172: if (!$this->tempFile) {
173: flock($this->tempHandle, LOCK_UN);
174: fclose($this->tempHandle);
175: return;
176: }
177:
178: flock($this->handle, LOCK_UN);
179: fclose($this->handle);
180: fclose($this->tempHandle);
181:
182: if ($this->writeError || !rename($this->tempFile, $this->file)) {
183: unlink($this->tempFile);
184: if ($this->deleteFile) {
185: unlink($this->file);
186: }
187: }
188: }
189:
190:
191: 192: 193: 194: 195:
196: public function stream_read($length)
197: {
198: return fread($this->tempHandle, $length);
199: }
200:
201:
202: 203: 204: 205: 206:
207: public function stream_write($data)
208: {
209: $len = strlen($data);
210: $res = fwrite($this->tempHandle, $data, $len);
211:
212: if ($res !== $len) {
213: $this->writeError = true;
214: }
215:
216: return $res;
217: }
218:
219:
220: 221: 222: 223: 224:
225: public function stream_truncate($size)
226: {
227: return ftruncate($this->tempHandle, $size);
228: }
229:
230:
231: 232: 233: 234:
235: public function stream_tell()
236: {
237: return ftell($this->tempHandle);
238: }
239:
240:
241: 242: 243: 244:
245: public function stream_eof()
246: {
247: return feof($this->tempHandle);
248: }
249:
250:
251: 252: 253: 254: 255: 256:
257: public function stream_seek($offset, $whence)
258: {
259: return fseek($this->tempHandle, $offset, $whence) === 0;
260: }
261:
262:
263: 264: 265: 266:
267: public function stream_stat()
268: {
269: return fstat($this->tempHandle);
270: }
271:
272:
273: 274: 275: 276: 277: 278:
279: public function url_stat($path, $flags)
280: {
281:
282: $path = substr($path, strpos($path, ':') + 3);
283: return ($flags & STREAM_URL_STAT_LINK) ? @lstat($path) : @stat($path);
284: }
285:
286:
287: 288: 289: 290: 291: 292:
293: public function unlink($path)
294: {
295: $path = substr($path, strpos($path, ':') + 3);
296: return unlink($path);
297: }
298: }
299: