1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Twistor; |
4
|
|
|
|
5
|
|
|
use League\Flysystem\AdapterInterface; |
6
|
|
|
use League\Flysystem\FileNotFoundException; |
7
|
|
|
use League\Flysystem\FilesystemInterface; |
8
|
|
|
use League\Flysystem\Util; |
9
|
|
|
use Twistor\Flysystem\Exception\TriggerErrorException; |
10
|
|
|
use Twistor\Flysystem\Plugin\ForcedRename; |
11
|
|
|
use Twistor\Flysystem\Plugin\Mkdir; |
12
|
|
|
use Twistor\Flysystem\Plugin\Rmdir; |
13
|
|
|
use Twistor\Flysystem\Plugin\Stat; |
14
|
|
|
use Twistor\Flysystem\Plugin\Touch; |
15
|
|
|
use Twistor\StreamUtil; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* An adapter for Flysystem to a PHP stream wrapper. |
19
|
|
|
*/ |
20
|
|
|
class FlysystemStreamWrapper |
21
|
|
|
{ |
22
|
|
|
/** |
23
|
|
|
* A flag to tell FlysystemStreamWrapper::url_stat() to ignore the size. |
24
|
|
|
* |
25
|
|
|
* @var int |
26
|
|
|
*/ |
27
|
|
|
const STREAM_URL_IGNORE_SIZE = 8; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* The registered filesystems. |
31
|
|
|
* |
32
|
|
|
* @var \League\Flysystem\FilesystemInterface[] |
33
|
|
|
*/ |
34
|
|
|
protected static $filesystems = []; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Optional configuration. |
38
|
|
|
* |
39
|
|
|
* @var array |
40
|
|
|
*/ |
41
|
|
|
protected static $config = []; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* The default configuration. |
45
|
|
|
* |
46
|
|
|
* @var array |
47
|
|
|
*/ |
48
|
|
|
protected static $defaultConfiguration = [ |
49
|
|
|
'permissions' =>[ |
50
|
|
|
'dir' => [ |
51
|
|
|
'private' => 0700, |
52
|
|
|
'public' => 0755, |
53
|
|
|
], |
54
|
|
|
'file' => [ |
55
|
|
|
'private' => 0600, |
56
|
|
|
'public' => 0644, |
57
|
|
|
], |
58
|
|
|
], |
59
|
|
|
'metadata' => ['timestamp', 'size', 'visibility'], |
60
|
|
|
'public_mask' => 0044, |
61
|
|
|
]; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* The number of bytes that have been written since the last flush. |
65
|
|
|
* |
66
|
|
|
* @var int |
67
|
|
|
*/ |
68
|
|
|
protected $bytesWritten = 0; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* The filesystem of the current stream wrapper. |
72
|
|
|
* |
73
|
|
|
* @var \League\Flysystem\FilesystemInterface |
74
|
|
|
*/ |
75
|
|
|
protected $filesystem; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* A generic resource handle. |
79
|
|
|
* |
80
|
|
|
* @var resource|bool |
81
|
|
|
*/ |
82
|
|
|
protected $handle; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Whether the handle is in append mode. |
86
|
|
|
* |
87
|
|
|
* @var bool |
88
|
|
|
*/ |
89
|
|
|
protected $isAppendMode = false; |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* Whether the handle is read-only. |
93
|
|
|
* |
94
|
|
|
* The stream returned from Flysystem may not actually be read-only, This |
95
|
|
|
* ensures read-only behavior. |
96
|
|
|
* |
97
|
|
|
* @var bool |
98
|
|
|
*/ |
99
|
|
|
protected $isReadOnly = false; |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Whether the handle is write-only. |
103
|
|
|
* |
104
|
|
|
* @var bool |
105
|
|
|
*/ |
106
|
|
|
protected $isWriteOnly = false; |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* A directory listing. |
110
|
|
|
* |
111
|
|
|
* @var array |
112
|
|
|
*/ |
113
|
|
|
protected $listing; |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Whether this handle has been verified writable. |
117
|
|
|
* |
118
|
|
|
* @var bool |
119
|
|
|
*/ |
120
|
|
|
protected $needsCowCheck = false; |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Whether the handle should be flushed. |
124
|
|
|
* |
125
|
|
|
* @var bool |
126
|
|
|
*/ |
127
|
|
|
protected $needsFlush = false; |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* The handle used for calls to stream_lock. |
131
|
|
|
* |
132
|
|
|
* @var resource |
133
|
|
|
*/ |
134
|
|
|
protected $lockHandle; |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* If stream_set_write_buffer() is called, the arguments. |
138
|
|
|
* |
139
|
|
|
* @var int |
140
|
|
|
*/ |
141
|
|
|
protected $streamWriteBuffer; |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Instance URI (stream). |
145
|
|
|
* |
146
|
|
|
* A stream is referenced as "protocol://target". |
147
|
|
|
* |
148
|
|
|
* @var string |
149
|
|
|
*/ |
150
|
|
|
protected $uri; |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Registers the stream wrapper protocol if not already registered. |
154
|
|
|
* |
155
|
|
|
* @param string $protocol The protocol. |
156
|
|
|
* @param FilesystemInterface $filesystem The filesystem. |
157
|
|
|
* @param array|null $configuration Optional configuration. |
158
|
|
|
* |
159
|
|
|
* @return bool True if the protocal was registered, false if not. |
160
|
|
|
*/ |
161
|
198 |
|
public static function register($protocol, FilesystemInterface $filesystem, array $configuration = null) |
162
|
|
|
{ |
163
|
198 |
|
if (static::streamWrapperExists($protocol)) { |
164
|
3 |
|
return false; |
165
|
|
|
} |
166
|
|
|
|
167
|
198 |
|
static::$config[$protocol] = $configuration ?: static::$defaultConfiguration; |
168
|
198 |
|
static::registerPlugins($protocol, $filesystem); |
169
|
198 |
|
static::$filesystems[$protocol] = $filesystem; |
170
|
|
|
|
171
|
198 |
|
return stream_wrapper_register($protocol, __CLASS__); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Unegisters a stream wrapper. |
176
|
|
|
* |
177
|
|
|
* @param string $protocol The protocol. |
178
|
|
|
* |
179
|
|
|
* @return bool True if the protocal was unregistered, false if not. |
180
|
|
|
*/ |
181
|
195 |
|
public static function unregister($protocol) |
182
|
|
|
{ |
183
|
195 |
|
if (!static::streamWrapperExists($protocol)) { |
184
|
3 |
|
return false; |
185
|
|
|
} |
186
|
|
|
|
187
|
195 |
|
unset(static::$filesystems[$protocol]); |
188
|
|
|
|
189
|
195 |
|
return stream_wrapper_unregister($protocol); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Determines if a protocol is registered. |
194
|
|
|
* |
195
|
|
|
* @param string $protocol The protocol to check. |
196
|
|
|
* |
197
|
|
|
* @return bool True if it is registered, false if not. |
198
|
|
|
*/ |
199
|
198 |
|
protected static function streamWrapperExists($protocol) |
200
|
|
|
{ |
201
|
198 |
|
return in_array($protocol, stream_get_wrappers(), true); |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* Registers plugins on the filesystem. |
206
|
|
|
* |
207
|
|
|
* @param string $protocol |
208
|
|
|
* @param FilesystemInterface $filesystem |
209
|
|
|
*/ |
210
|
198 |
|
protected static function registerPlugins($protocol, FilesystemInterface $filesystem) |
211
|
|
|
{ |
212
|
198 |
|
$filesystem->addPlugin(new ForcedRename()); |
213
|
198 |
|
$filesystem->addPlugin(new Mkdir()); |
214
|
198 |
|
$filesystem->addPlugin(new Rmdir()); |
215
|
|
|
|
216
|
198 |
|
$stat = new Stat( |
217
|
198 |
|
static::$config[$protocol]['permissions'], |
218
|
198 |
|
static::$config[$protocol]['metadata'] |
219
|
198 |
|
); |
220
|
|
|
|
221
|
198 |
|
$filesystem->addPlugin($stat); |
222
|
198 |
|
$filesystem->addPlugin(new Touch()); |
223
|
198 |
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Closes the directory handle. |
227
|
|
|
* |
228
|
|
|
* @return bool True on success, false on failure. |
229
|
|
|
*/ |
230
|
12 |
|
public function dir_closedir() |
231
|
|
|
{ |
232
|
12 |
|
unset($this->listing); |
233
|
|
|
|
234
|
12 |
|
return true; |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* Opens a directory handle. |
239
|
|
|
* |
240
|
|
|
* @param string $uri The URL that was passed to opendir(). |
241
|
|
|
* @param int $options Whether or not to enforce safe_mode (0x04). |
242
|
|
|
* |
243
|
|
|
* @return bool True on success, false on failure. |
244
|
|
|
*/ |
245
|
18 |
|
public function dir_opendir($uri, $options) |
246
|
|
|
{ |
247
|
18 |
|
$this->uri = $uri; |
248
|
|
|
|
249
|
18 |
|
$path = Util::normalizePath($this->getTarget()); |
250
|
|
|
|
251
|
18 |
|
$this->listing = $this->invoke($this->getFilesystem(), 'listContents', [$path], 'opendir'); |
252
|
|
|
|
253
|
18 |
|
if ($this->listing === false) { |
254
|
6 |
|
return false; |
255
|
|
|
} |
256
|
|
|
|
257
|
12 |
|
if (!$dirlen = strlen($path)) { |
258
|
6 |
|
return true; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
// Remove the separator /. |
262
|
6 |
|
$dirlen++; |
263
|
|
|
|
264
|
|
|
// Remove directory prefix. |
265
|
6 |
|
foreach ($this->listing as $delta => $item) { |
266
|
6 |
|
$this->listing[$delta]['path'] = substr($item['path'], $dirlen); |
267
|
6 |
|
} |
268
|
|
|
|
269
|
6 |
|
reset($this->listing); |
270
|
|
|
|
271
|
6 |
|
return true; |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* Reads an entry from directory handle. |
276
|
|
|
* |
277
|
|
|
* @return string|bool The next filename, or false if there is no next file. |
278
|
|
|
*/ |
279
|
12 |
|
public function dir_readdir() |
280
|
|
|
{ |
281
|
12 |
|
$current = current($this->listing); |
282
|
12 |
|
next($this->listing); |
283
|
|
|
|
284
|
12 |
|
return $current ? $current['path'] : false; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
/** |
288
|
|
|
* Rewinds the directory handle. |
289
|
|
|
* |
290
|
|
|
* @return bool True on success, false on failure. |
291
|
|
|
*/ |
292
|
12 |
|
public function dir_rewinddir() |
293
|
|
|
{ |
294
|
12 |
|
reset($this->listing); |
295
|
|
|
|
296
|
12 |
|
return true; |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* Creates a directory. |
301
|
|
|
* |
302
|
|
|
* @param string $uri |
303
|
|
|
* @param int $mode |
304
|
|
|
* @param int $options |
305
|
|
|
* |
306
|
|
|
* @return bool True on success, false on failure. |
307
|
|
|
*/ |
308
|
60 |
|
public function mkdir($uri, $mode, $options) |
309
|
|
|
{ |
310
|
60 |
|
$this->uri = $uri; |
311
|
|
|
|
312
|
60 |
|
return $this->invoke($this->getFilesystem(), 'mkdir', [$this->getTarget(), $mode, $options]); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Renames a file or directory. |
317
|
|
|
* |
318
|
|
|
* @param string $uri_from |
319
|
|
|
* @param string $uri_to |
320
|
|
|
* |
321
|
|
|
* @return bool True on success, false on failure. |
322
|
|
|
*/ |
323
|
36 |
|
public function rename($uri_from, $uri_to) |
324
|
|
|
{ |
325
|
36 |
|
$this->uri = $uri_from; |
326
|
36 |
|
$args = [$this->getTarget($uri_from), $this->getTarget($uri_to)]; |
327
|
|
|
|
328
|
36 |
|
return $this->invoke($this->getFilesystem(), 'forcedRename', $args, 'rename'); |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
/** |
332
|
|
|
* Removes a directory. |
333
|
|
|
* |
334
|
|
|
* @param string $uri |
335
|
|
|
* @param int $options |
336
|
|
|
* |
337
|
|
|
* @return bool True on success, false on failure. |
338
|
|
|
*/ |
339
|
18 |
|
public function rmdir($uri, $options) |
340
|
|
|
{ |
341
|
18 |
|
$this->uri = $uri; |
342
|
|
|
|
343
|
18 |
|
return $this->invoke($this->getFilesystem(), 'rmdir', [$this->getTarget(), $options]); |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* Retrieves the underlaying resource. |
348
|
|
|
* |
349
|
|
|
* @param int $cast_as |
350
|
|
|
* |
351
|
|
|
* @return resource|bool The stream resource used by the wrapper, or false. |
352
|
|
|
*/ |
353
|
6 |
|
public function stream_cast($cast_as) |
354
|
|
|
{ |
355
|
6 |
|
return $this->handle; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* Closes the resource. |
360
|
|
|
*/ |
361
|
78 |
|
public function stream_close() |
362
|
|
|
{ |
363
|
|
|
// PHP 7 doesn't call flush automatically anymore for truncate() or when |
364
|
|
|
// writing an empty file. We need to ensure that the handle gets pushed |
365
|
|
|
// as needed in that case. This will be a no-op for php 5. |
366
|
78 |
|
$this->stream_flush(); |
367
|
|
|
|
368
|
78 |
|
fclose($this->handle); |
369
|
78 |
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* Tests for end-of-file on a file pointer. |
373
|
|
|
* |
374
|
|
|
* @return bool True if the file is at the end, false if not. |
375
|
|
|
*/ |
376
|
60 |
|
public function stream_eof() |
377
|
|
|
{ |
378
|
60 |
|
return feof($this->handle); |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
/** |
382
|
|
|
* Flushes the output. |
383
|
|
|
* |
384
|
|
|
* @return bool True on success, false on failure. |
385
|
|
|
*/ |
386
|
78 |
|
public function stream_flush() |
387
|
|
|
{ |
388
|
78 |
|
if (!$this->needsFlush) { |
389
|
78 |
|
return true; |
390
|
|
|
} |
391
|
|
|
|
392
|
78 |
|
$this->needsFlush = false; |
393
|
78 |
|
$this->bytesWritten = 0; |
394
|
|
|
|
395
|
|
|
// Calling putStream() will rewind our handle. flush() shouldn't change |
396
|
|
|
// the position of the file. |
397
|
78 |
|
$pos = ftell($this->handle); |
398
|
|
|
|
399
|
78 |
|
$args = [$this->getTarget(), $this->handle]; |
400
|
78 |
|
$success = $this->invoke($this->getFilesystem(), 'putStream', $args, 'fflush'); |
401
|
|
|
|
402
|
78 |
|
fseek($this->handle, $pos); |
403
|
|
|
|
404
|
78 |
|
return $success; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
/** |
408
|
|
|
* Advisory file locking. |
409
|
|
|
* |
410
|
|
|
* @param int $operation |
411
|
|
|
* |
412
|
|
|
* @return bool True on success, false on failure. |
413
|
|
|
*/ |
414
|
6 |
|
public function stream_lock($operation) |
415
|
|
|
{ |
416
|
6 |
|
$operation = (int) $operation; |
417
|
|
|
|
418
|
6 |
|
if (($operation & \LOCK_UN) === \LOCK_UN) { |
419
|
6 |
|
return $this->releaseLock($operation); |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
// If the caller calls flock() twice, there's no reason to re-create the |
423
|
|
|
// lock handle. |
424
|
6 |
|
if (is_resource($this->lockHandle)) { |
425
|
6 |
|
return flock($this->lockHandle, $operation); |
426
|
|
|
} |
427
|
|
|
|
428
|
6 |
|
$this->lockHandle = $this->openLockHandle(); |
|
|
|
|
429
|
|
|
|
430
|
6 |
|
return is_resource($this->lockHandle) && flock($this->lockHandle, $operation); |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
/** |
434
|
|
|
* Changes stream options. |
435
|
|
|
* |
436
|
|
|
* @param string $uri |
437
|
|
|
* @param int $option |
438
|
|
|
* @param mixed $value |
439
|
|
|
* |
440
|
|
|
* @return bool True on success, false on failure. |
441
|
|
|
*/ |
442
|
39 |
|
public function stream_metadata($uri, $option, $value) |
443
|
|
|
{ |
444
|
39 |
|
$this->uri = $uri; |
445
|
|
|
|
446
|
|
|
switch ($option) { |
447
|
39 |
|
case STREAM_META_ACCESS: |
448
|
15 |
|
$permissions = octdec(substr(decoct($value), -4)); |
449
|
15 |
|
$is_public = $permissions & $this->getConfiguration('public_mask'); |
450
|
15 |
|
$visibility = $is_public ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE; |
451
|
|
|
|
452
|
|
|
try { |
453
|
15 |
|
return $this->getFilesystem()->setVisibility($this->getTarget(), $visibility); |
454
|
|
|
|
455
|
9 |
|
} catch (\LogicException $e) { |
456
|
|
|
// The adapter doesn't support visibility. |
457
|
|
|
|
458
|
9 |
|
} catch (\Exception $e) { |
459
|
6 |
|
$this->triggerError('chmod', $e); |
460
|
6 |
|
return false; |
461
|
|
|
} |
462
|
|
|
|
463
|
3 |
|
return true; |
464
|
|
|
|
465
|
27 |
|
case STREAM_META_TOUCH: |
466
|
27 |
|
return $this->invoke($this->getFilesystem(), 'touch', [$this->getTarget()]); |
467
|
|
|
|
468
|
6 |
|
default: |
469
|
6 |
|
return false; |
470
|
6 |
|
} |
471
|
|
|
} |
472
|
|
|
|
473
|
|
|
/** |
474
|
|
|
* Opens file or URL. |
475
|
|
|
* |
476
|
|
|
* @param string $uri |
477
|
|
|
* @param string $mode |
478
|
|
|
* @param int $options |
479
|
|
|
* @param string &$opened_path |
480
|
|
|
* |
481
|
|
|
* @return bool True on success, false on failure. |
482
|
|
|
*/ |
483
|
96 |
|
public function stream_open($uri, $mode, $options, &$opened_path) |
484
|
|
|
{ |
485
|
96 |
|
$this->uri = $uri; |
486
|
96 |
|
$path = $this->getTarget(); |
487
|
|
|
|
488
|
96 |
|
$this->isReadOnly = StreamUtil::modeIsReadOnly($mode); |
489
|
96 |
|
$this->isWriteOnly = StreamUtil::modeIsWriteOnly($mode); |
490
|
96 |
|
$this->isAppendMode = StreamUtil::modeIsAppendable($mode); |
491
|
|
|
|
492
|
96 |
|
$this->handle = $this->invoke($this, 'getStream', [$path, $mode], 'fopen'); |
493
|
|
|
|
494
|
96 |
|
if ($this->handle && $options & STREAM_USE_PATH) { |
495
|
6 |
|
$opened_path = $path; |
496
|
6 |
|
} |
497
|
|
|
|
498
|
96 |
|
return is_resource($this->handle); |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
/** |
502
|
|
|
* Reads from stream. |
503
|
|
|
* |
504
|
|
|
* @param int $count |
505
|
|
|
* |
506
|
|
|
* @return string The bytes read. |
507
|
|
|
*/ |
508
|
60 |
|
public function stream_read($count) |
509
|
|
|
{ |
510
|
60 |
|
if ($this->isWriteOnly) { |
511
|
6 |
|
return ''; |
512
|
|
|
} |
513
|
|
|
|
514
|
60 |
|
return fread($this->handle, $count); |
515
|
|
|
} |
516
|
|
|
|
517
|
|
|
/** |
518
|
|
|
* Seeks to specific location in a stream. |
519
|
|
|
* |
520
|
|
|
* @param int $offset |
521
|
|
|
* @param int $whence |
522
|
|
|
* |
523
|
|
|
* @return bool True on success, false on failure. |
524
|
|
|
*/ |
525
|
24 |
|
public function stream_seek($offset, $whence = SEEK_SET) |
526
|
|
|
{ |
527
|
24 |
|
return fseek($this->handle, $offset, $whence) === 0; |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
/** |
531
|
|
|
* Changes stream options. |
532
|
|
|
* |
533
|
|
|
* @param int $option |
534
|
|
|
* @param int $arg1 |
535
|
|
|
* @param int $arg2 |
536
|
|
|
* |
537
|
|
|
* @return bool True on success, false on failure. |
538
|
|
|
*/ |
539
|
6 |
|
public function stream_set_option($option, $arg1, $arg2) |
540
|
|
|
{ |
541
|
|
|
switch ($option) { |
542
|
6 |
|
case STREAM_OPTION_BLOCKING: |
543
|
|
|
// This works for the local adapter. It doesn't do anything for |
544
|
|
|
// memory streams. |
545
|
6 |
|
return stream_set_blocking($this->handle, $arg1); |
546
|
|
|
|
547
|
6 |
|
case STREAM_OPTION_READ_TIMEOUT: |
548
|
6 |
|
return stream_set_timeout($this->handle, $arg1, $arg2); |
549
|
|
|
|
550
|
6 |
|
case STREAM_OPTION_READ_BUFFER: |
551
|
6 |
|
if ($arg1 === STREAM_BUFFER_NONE) { |
552
|
6 |
|
return stream_set_read_buffer($this->handle, 0) === 0; |
553
|
|
|
} |
554
|
|
|
|
555
|
6 |
|
return stream_set_read_buffer($this->handle, $arg2) === 0; |
556
|
|
|
|
557
|
6 |
|
case STREAM_OPTION_WRITE_BUFFER: |
558
|
6 |
|
$this->streamWriteBuffer = $arg1 === STREAM_BUFFER_NONE ? 0 : $arg2; |
559
|
|
|
|
560
|
6 |
|
return true; |
561
|
|
|
} |
562
|
|
|
|
563
|
6 |
|
return false; |
564
|
|
|
} |
565
|
|
|
|
566
|
|
|
/** |
567
|
|
|
* Retrieves information about a file resource. |
568
|
|
|
* |
569
|
|
|
* @return array A similar array to fstat(). |
570
|
|
|
* |
571
|
|
|
* @see fstat() |
572
|
|
|
*/ |
573
|
66 |
|
public function stream_stat() |
574
|
|
|
{ |
575
|
|
|
// Get metadata from original file. |
576
|
66 |
|
$stat = $this->url_stat($this->uri, static::STREAM_URL_IGNORE_SIZE | STREAM_URL_STAT_QUIET) ?: []; |
577
|
|
|
|
578
|
|
|
// Newly created file. |
579
|
66 |
|
if (empty($stat['mode'])) { |
580
|
6 |
|
$stat['mode'] = 0100000 + $this->getConfiguration('permissions')['file']['public']; |
581
|
6 |
|
$stat[2] = $stat['mode']; |
582
|
6 |
|
} |
583
|
|
|
|
584
|
|
|
// Use the size of our handle, since it could have been written to or |
585
|
|
|
// truncated. |
586
|
66 |
|
$stat['size'] = $stat[7] = StreamUtil::getSize($this->handle); |
587
|
|
|
|
588
|
66 |
|
return $stat; |
589
|
|
|
} |
590
|
|
|
|
591
|
|
|
/** |
592
|
|
|
* Retrieves the current position of a stream. |
593
|
|
|
* |
594
|
|
|
* @return int The current position of the stream. |
595
|
|
|
*/ |
596
|
24 |
|
public function stream_tell() |
597
|
|
|
{ |
598
|
24 |
|
if ($this->isAppendMode) { |
599
|
6 |
|
return 0; |
600
|
|
|
} |
601
|
18 |
|
return ftell($this->handle); |
602
|
|
|
} |
603
|
|
|
|
604
|
|
|
/** |
605
|
|
|
* Truncates the stream. |
606
|
|
|
* |
607
|
|
|
* @param int $new_size |
608
|
|
|
* |
609
|
|
|
* @return bool True on success, false on failure. |
610
|
|
|
*/ |
611
|
12 |
|
public function stream_truncate($new_size) |
612
|
|
|
{ |
613
|
12 |
|
if ($this->isReadOnly) { |
614
|
6 |
|
return false; |
615
|
|
|
} |
616
|
12 |
|
$this->needsFlush = true; |
617
|
12 |
|
$this->ensureWritableHandle(); |
618
|
|
|
|
619
|
12 |
|
return ftruncate($this->handle, $new_size); |
620
|
|
|
} |
621
|
|
|
|
622
|
|
|
/** |
623
|
|
|
* Writes to the stream. |
624
|
|
|
* |
625
|
|
|
* @param string $data |
626
|
|
|
* |
627
|
|
|
* @return int The number of bytes that were successfully stored. |
628
|
|
|
*/ |
629
|
60 |
|
public function stream_write($data) |
630
|
|
|
{ |
631
|
60 |
|
if ($this->isReadOnly) { |
632
|
6 |
|
return 0; |
633
|
|
|
} |
634
|
60 |
|
$this->needsFlush = true; |
635
|
60 |
|
$this->ensureWritableHandle(); |
636
|
|
|
|
637
|
|
|
// Enforce append semantics. |
638
|
60 |
|
if ($this->isAppendMode) { |
639
|
6 |
|
StreamUtil::trySeek($this->handle, 0, SEEK_END); |
640
|
6 |
|
} |
641
|
|
|
|
642
|
60 |
|
$written = fwrite($this->handle, $data); |
643
|
60 |
|
$this->bytesWritten += $written; |
644
|
|
|
|
645
|
60 |
|
if (isset($this->streamWriteBuffer) && $this->bytesWritten >= $this->streamWriteBuffer) { |
646
|
6 |
|
$this->stream_flush(); |
647
|
6 |
|
} |
648
|
|
|
|
649
|
60 |
|
return $written; |
650
|
|
|
} |
651
|
|
|
|
652
|
|
|
/** |
653
|
|
|
* Deletes a file. |
654
|
|
|
* |
655
|
|
|
* @param string $uri |
656
|
|
|
* |
657
|
|
|
* @return bool True on success, false on failure. |
658
|
|
|
*/ |
659
|
12 |
|
public function unlink($uri) |
660
|
|
|
{ |
661
|
12 |
|
$this->uri = $uri; |
662
|
|
|
|
663
|
12 |
|
return $this->invoke($this->getFilesystem(), 'delete', [$this->getTarget()], 'unlink'); |
664
|
|
|
} |
665
|
|
|
|
666
|
|
|
/** |
667
|
|
|
* Retrieves information about a file. |
668
|
|
|
* |
669
|
|
|
* @param string $uri |
670
|
|
|
* @param int $flags |
671
|
|
|
* |
672
|
|
|
* @return array Output similar to stat(). |
673
|
|
|
* |
674
|
|
|
* @see stat() |
675
|
|
|
*/ |
676
|
84 |
|
public function url_stat($uri, $flags) |
677
|
|
|
{ |
678
|
84 |
|
$this->uri = $uri; |
679
|
|
|
|
680
|
|
|
try { |
681
|
84 |
|
return $this->getFilesystem()->stat($this->getTarget(), $flags); |
682
|
|
|
|
683
|
36 |
|
} catch (FileNotFoundException $e) { |
684
|
|
|
// File doesn't exist. |
685
|
30 |
|
if (!($flags & STREAM_URL_STAT_QUIET)) { |
686
|
6 |
|
$this->triggerError('stat', $e); |
687
|
6 |
|
} |
688
|
|
|
|
689
|
36 |
|
} catch (\Exception $e) { |
690
|
6 |
|
$this->triggerError('stat', $e); |
691
|
|
|
} |
692
|
|
|
|
693
|
36 |
|
return false; |
694
|
|
|
} |
695
|
|
|
|
696
|
|
|
/** |
697
|
|
|
* Returns a stream for a given path and mode. |
698
|
|
|
* |
699
|
|
|
* @param string $path The path to open. |
700
|
|
|
* @param string $mode The mode to open the stream in. |
701
|
|
|
* |
702
|
|
|
* @return resource|bool The file handle, or false. |
703
|
|
|
*/ |
704
|
96 |
|
protected function getStream($path, $mode) |
705
|
|
|
{ |
706
|
96 |
|
switch ($mode[0]) { |
707
|
96 |
|
case 'r': |
708
|
66 |
|
$this->needsCowCheck = true; |
709
|
66 |
|
return $this->getFilesystem()->readStream($path); |
710
|
|
|
|
711
|
90 |
|
case 'w': |
712
|
72 |
|
$this->needsFlush = true; |
713
|
72 |
|
return fopen('php://temp', 'w+b'); |
714
|
|
|
|
715
|
24 |
|
case 'a': |
716
|
6 |
|
return $this->getAppendStream($path); |
717
|
|
|
|
718
|
24 |
|
case 'x': |
719
|
12 |
|
return $this->getXStream($path); |
720
|
|
|
|
721
|
12 |
|
case 'c': |
722
|
6 |
|
return $this->getWritableStream($path); |
723
|
6 |
|
} |
724
|
|
|
|
725
|
6 |
|
return false; |
726
|
|
|
} |
727
|
|
|
|
728
|
|
|
/** |
729
|
|
|
* Returns a writable stream for a given path and mode. |
730
|
|
|
* |
731
|
|
|
* @param string $path The path to open. |
732
|
|
|
* |
733
|
|
|
* @return resource|bool The file handle, or false. |
734
|
|
|
*/ |
735
|
6 |
|
protected function getWritableStream($path) |
736
|
|
|
{ |
737
|
|
|
try { |
738
|
6 |
|
$handle = $this->getFilesystem()->readStream($path); |
739
|
6 |
|
$this->needsCowCheck = true; |
740
|
|
|
|
741
|
6 |
|
} catch (FileNotFoundException $e) { |
742
|
6 |
|
$handle = fopen('php://temp', 'w+b'); |
743
|
6 |
|
$this->needsFlush = true; |
744
|
|
|
} |
745
|
|
|
|
746
|
6 |
|
return $handle; |
747
|
|
|
} |
748
|
|
|
|
749
|
|
|
/** |
750
|
|
|
* Returns an appendable stream for a given path and mode. |
751
|
|
|
* |
752
|
|
|
* @param string $path The path to open. |
753
|
|
|
* |
754
|
|
|
* @return resource|bool The file handle, or false. |
755
|
|
|
*/ |
756
|
6 |
|
protected function getAppendStream($path) |
757
|
|
|
{ |
758
|
6 |
|
if ($handle = $this->getWritableStream($path)) { |
759
|
6 |
|
StreamUtil::trySeek($handle, 0, SEEK_END); |
760
|
6 |
|
} |
761
|
|
|
|
762
|
6 |
|
return $handle; |
763
|
|
|
} |
764
|
|
|
|
765
|
|
|
/** |
766
|
|
|
* Returns a writable stream for a given path and mode. |
767
|
|
|
* |
768
|
|
|
* Triggers a warning if the file exists. |
769
|
|
|
* |
770
|
|
|
* @param string $path The path to open. |
771
|
|
|
* |
772
|
|
|
* @return resource|bool The file handle, or false. |
773
|
|
|
*/ |
774
|
12 |
|
protected function getXStream($path) |
775
|
|
|
{ |
776
|
12 |
|
if ($this->getFilesystem()->has($path)) { |
777
|
6 |
|
trigger_error('fopen(): failed to open stream: File exists', E_USER_WARNING); |
778
|
|
|
|
779
|
6 |
|
return false; |
780
|
|
|
} |
781
|
|
|
|
782
|
6 |
|
$this->needsFlush = true; |
783
|
|
|
|
784
|
6 |
|
return fopen('php://temp', 'w+b'); |
785
|
|
|
} |
786
|
|
|
|
787
|
|
|
/** |
788
|
|
|
* Guarantees that the handle is writable. |
789
|
|
|
*/ |
790
|
60 |
|
protected function ensureWritableHandle() |
791
|
|
|
{ |
792
|
60 |
|
if (!$this->needsCowCheck) { |
793
|
60 |
|
return; |
794
|
|
|
} |
795
|
|
|
|
796
|
18 |
|
$this->needsCowCheck = false; |
797
|
|
|
|
798
|
18 |
|
if (StreamUtil::isWritable($this->handle)) { |
799
|
9 |
|
return; |
800
|
|
|
} |
801
|
|
|
|
802
|
9 |
|
$this->handle = StreamUtil::copy($this->handle); |
803
|
9 |
|
} |
804
|
|
|
|
805
|
|
|
/** |
806
|
|
|
* Returns the protocol from the internal URI. |
807
|
|
|
* |
808
|
|
|
* @return string The protocol. |
809
|
|
|
*/ |
810
|
189 |
|
protected function getProtocol() |
811
|
|
|
{ |
812
|
189 |
|
return substr($this->uri, 0, strpos($this->uri, '://')); |
813
|
|
|
} |
814
|
|
|
|
815
|
|
|
/** |
816
|
|
|
* Returns the local writable target of the resource within the stream. |
817
|
|
|
* |
818
|
|
|
* @param string|null $uri The URI. |
819
|
|
|
* |
820
|
|
|
* @return string The path appropriate for use with Flysystem. |
821
|
|
|
*/ |
822
|
195 |
|
protected function getTarget($uri = null) |
823
|
|
|
{ |
824
|
195 |
|
if (!isset($uri)) { |
825
|
189 |
|
$uri = $this->uri; |
826
|
189 |
|
} |
827
|
|
|
|
828
|
195 |
|
$target = substr($uri, strpos($uri, '://') + 3); |
829
|
|
|
|
830
|
195 |
|
return $target === false ? '' : $target; |
831
|
|
|
} |
832
|
|
|
|
833
|
|
|
/** |
834
|
|
|
* Returns the configuration. |
835
|
|
|
* |
836
|
|
|
* @param string|null $key The optional configuration key. |
837
|
|
|
* |
838
|
|
|
* @return array The requested configuration. |
839
|
|
|
*/ |
840
|
21 |
|
protected function getConfiguration($key = null) |
841
|
|
|
{ |
842
|
21 |
|
return $key ? static::$config[$this->getProtocol()][$key] : static::$config[$this->getProtocol()]; |
843
|
|
|
} |
844
|
|
|
|
845
|
|
|
/** |
846
|
|
|
* Returns the filesystem. |
847
|
|
|
* |
848
|
|
|
* @return \League\Flysystem\FilesystemInterface The filesystem object. |
849
|
|
|
*/ |
850
|
189 |
|
protected function getFilesystem() |
851
|
|
|
{ |
852
|
189 |
|
if (isset($this->filesystem)) { |
853
|
66 |
|
return $this->filesystem; |
854
|
|
|
} |
855
|
|
|
|
856
|
189 |
|
$this->filesystem = static::$filesystems[$this->getProtocol()]; |
857
|
|
|
|
858
|
189 |
|
return $this->filesystem; |
859
|
|
|
} |
860
|
|
|
|
861
|
|
|
/** |
862
|
|
|
* Calls a method on an object, catching any exceptions. |
863
|
|
|
* |
864
|
|
|
* @param object $objet The object to call the method on. |
865
|
|
|
* @param string $method The method name. |
866
|
|
|
* @param array $args The arguments to the method. |
867
|
|
|
* @param string|null $errorname The name of the calling function. |
868
|
|
|
* |
869
|
|
|
* @return mixed|false The return value of the call, or false on failure. |
870
|
|
|
*/ |
871
|
177 |
|
protected function invoke($objet, $method, array $args, $errorname = null) |
872
|
|
|
{ |
873
|
|
|
try { |
874
|
177 |
|
return call_user_func_array([$objet, $method], $args); |
875
|
|
|
|
876
|
78 |
|
} catch (\Exception $e) { |
877
|
78 |
|
$errorname = $errorname ?: $method; |
878
|
78 |
|
$this->triggerError($errorname, $e); |
879
|
|
|
} |
880
|
|
|
|
881
|
78 |
|
return false; |
882
|
|
|
} |
883
|
|
|
|
884
|
|
|
/** |
885
|
|
|
* Calls trigger_error(), printing the appropriate message. |
886
|
|
|
* |
887
|
|
|
* @param string $function |
888
|
|
|
* @param \Exception $e |
889
|
|
|
*/ |
890
|
96 |
|
protected function triggerError($function, \Exception $e) |
891
|
|
|
{ |
892
|
96 |
|
if ($e instanceof TriggerErrorException) { |
893
|
30 |
|
trigger_error($e->formatMessage($function), E_USER_WARNING); |
894
|
30 |
|
return; |
895
|
|
|
} |
896
|
|
|
|
897
|
72 |
|
switch (get_class($e)) { |
898
|
72 |
|
case 'League\Flysystem\FileNotFoundException': |
899
|
42 |
|
trigger_error(sprintf('%s(): No such file or directory', $function), E_USER_WARNING); |
900
|
42 |
|
return; |
901
|
|
|
|
902
|
30 |
|
case 'League\Flysystem\RootViolationException': |
903
|
6 |
|
trigger_error(sprintf('%s(): Cannot remove the root directory', $function), E_USER_WARNING); |
904
|
6 |
|
return; |
905
|
24 |
|
} |
906
|
|
|
|
907
|
|
|
// Don't allow any exceptions to leak. |
908
|
24 |
|
trigger_error($e->getMessage(), E_USER_WARNING); |
909
|
24 |
|
} |
910
|
|
|
|
911
|
|
|
/** |
912
|
|
|
* Creates an advisory lock handle. |
913
|
|
|
* |
914
|
|
|
* @return resource|false |
915
|
|
|
*/ |
916
|
6 |
|
protected function openLockHandle() |
917
|
|
|
{ |
918
|
|
|
// We are using md5() to avoid the file name limits, and case |
919
|
|
|
// insensitivity on Windows. This is not security sensitive. |
920
|
|
|
|
921
|
|
|
// PHP allows periods, '.', to be scheme names. Normalize the scheme |
922
|
|
|
// name to something that won't cause problems. |
923
|
6 |
|
$sub_dir = md5($this->getProtocol()); |
924
|
|
|
|
925
|
|
|
// Since we're flattening out whole filesystems, at least create a |
926
|
|
|
// sub-directory for each scheme to attempt to limit the number of files |
927
|
|
|
// per directory. |
928
|
6 |
|
$temp_dir = sys_get_temp_dir() . '/flysystem-stream-wrapper/' . $sub_dir; |
929
|
|
|
|
930
|
|
|
// Race free directory creation. If @mkdir() fails, fopen() will fail |
931
|
|
|
// later, so there's no reason to test again. |
932
|
6 |
|
! is_dir($temp_dir) && @mkdir($temp_dir, 0777, true); |
933
|
|
|
|
934
|
|
|
// Normalize paths so that locks are consistent. |
935
|
6 |
|
$lock_key = md5(Util::normalizePath($this->getTarget())); |
936
|
|
|
|
937
|
|
|
// Relay the lock to a real filesystem lock. |
938
|
6 |
|
return fopen($temp_dir . '/' . $lock_key, 'c'); |
939
|
|
|
} |
940
|
|
|
|
941
|
|
|
/** |
942
|
|
|
* Releases the advisory lock. |
943
|
|
|
* |
944
|
|
|
* @param int $operation |
945
|
|
|
* |
946
|
|
|
* @return bool |
947
|
|
|
* |
948
|
|
|
* @see FlysystemStreamWrapper::stream_lock() |
949
|
|
|
*/ |
950
|
6 |
|
protected function releaseLock($operation) |
951
|
|
|
{ |
952
|
6 |
|
$exists = is_resource($this->lockHandle); |
953
|
|
|
|
954
|
6 |
|
$success = $exists && flock($this->lockHandle, $operation); |
955
|
|
|
|
956
|
6 |
|
$exists && fclose($this->lockHandle); |
957
|
6 |
|
$this->lockHandle = null; |
958
|
|
|
|
959
|
6 |
|
return $success; |
960
|
|
|
} |
961
|
|
|
} |
962
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.