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