Skip to content

Commit d8961ed

Browse files
committed
fix using FSEEK_END with SeekableHttpStream to get file size
Signed-off-by: Robin Appelman <robin@icewind.nl>
1 parent c41982e commit d8961ed

File tree

4 files changed

+93
-21
lines changed

4 files changed

+93
-21
lines changed

lib/private/Files/Stream/SeekableHttpStream.php

+60-21
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
class SeekableHttpStream implements File {
3333
private const PROTOCOL = 'httpseek';
3434

35-
private static $registered = false;
35+
private static bool $registered = false;
3636

3737
/**
3838
* Registers the stream wrapper using the `httpseek://` url scheme
@@ -73,24 +73,26 @@ public static function open(callable $callback) {
7373
/** @var callable */
7474
private $openCallback;
7575

76-
/** @var resource */
76+
/** @var ?resource|closed-resource */
7777
private $current;
78-
/** @var int */
79-
private $offset = 0;
80-
/** @var int */
81-
private $length = 0;
78+
private int $offset = 0;
79+
private int $length = 0;
80+
private bool $needReconnect = false;
8281

83-
private function reconnect(int $start) {
82+
private function reconnect(int $start): bool {
83+
$this->needReconnect = false;
8484
$range = $start . '-';
85-
if ($this->current != null) {
85+
if ($this->hasOpenStream()) {
8686
fclose($this->current);
8787
}
8888

89-
$this->current = ($this->openCallback)($range);
89+
$stream = ($this->openCallback)($range);
9090

91-
if ($this->current === false) {
91+
if ($stream === false) {
92+
$this->current = null;
9293
return false;
9394
}
95+
$this->current = $stream;
9496

9597
$responseHead = stream_get_meta_data($this->current)['wrapper_data'];
9698

@@ -109,6 +111,7 @@ private function reconnect(int $start) {
109111
return preg_match('#^content-range:#i', $v) === 1;
110112
}));
111113
if (!$rangeHeaders) {
114+
$this->current = null;
112115
return false;
113116
}
114117
$contentRange = $rangeHeaders[0];
@@ -119,6 +122,7 @@ private function reconnect(int $start) {
119122
$length = intval(explode('/', $range)[1]);
120123

121124
if ($begin !== $start) {
125+
$this->current = null;
122126
return false;
123127
}
124128

@@ -128,6 +132,28 @@ private function reconnect(int $start) {
128132
return true;
129133
}
130134

135+
/**
136+
* @return ?resource
137+
*/
138+
private function getCurrent() {
139+
if ($this->needReconnect) {
140+
$this->reconnect($this->offset);
141+
}
142+
if (is_resource($this->current)) {
143+
return $this->current;
144+
} else {
145+
return null;
146+
}
147+
}
148+
149+
/**
150+
* @return bool
151+
* @psalm-assert-if-true resource $this->current
152+
*/
153+
private function hasOpenStream(): bool {
154+
return is_resource($this->current);
155+
}
156+
131157
public function stream_open($path, $mode, $options, &$opened_path) {
132158
$options = stream_context_get_options($this->context)[self::PROTOCOL];
133159
$this->openCallback = $options['callback'];
@@ -136,10 +162,10 @@ public function stream_open($path, $mode, $options, &$opened_path) {
136162
}
137163

138164
public function stream_read($count) {
139-
if (!$this->current) {
165+
if (!$this->getCurrent()) {
140166
return false;
141167
}
142-
$ret = fread($this->current, $count);
168+
$ret = fread($this->getCurrent(), $count);
143169
$this->offset += strlen($ret);
144170
return $ret;
145171
}
@@ -149,48 +175,61 @@ public function stream_seek($offset, $whence = SEEK_SET) {
149175
case SEEK_SET:
150176
if ($offset === $this->offset) {
151177
return true;
178+
} else {
179+
$this->offset = $offset;
152180
}
153-
return $this->reconnect($offset);
181+
break;
154182
case SEEK_CUR:
155183
if ($offset === 0) {
156184
return true;
185+
} else {
186+
$this->offset += $offset;
157187
}
158-
return $this->reconnect($this->offset + $offset);
188+
break;
159189
case SEEK_END:
160190
if ($this->length === 0) {
161191
return false;
162192
} elseif ($this->length + $offset === $this->offset) {
163193
return true;
194+
} else {
195+
$this->offset = $this->length + $offset;
164196
}
165-
return $this->reconnect($this->length + $offset);
197+
break;
166198
}
167-
return false;
199+
200+
if ($this->hasOpenStream()) {
201+
fclose($this->current);
202+
}
203+
$this->current = null;
204+
$this->needReconnect = true;
205+
return true;
168206
}
169207

170208
public function stream_tell() {
171209
return $this->offset;
172210
}
173211

174212
public function stream_stat() {
175-
if (is_resource($this->current)) {
176-
return fstat($this->current);
213+
if ($this->getCurrent()) {
214+
return fstat($this->getCurrent());
177215
} else {
178216
return false;
179217
}
180218
}
181219

182220
public function stream_eof() {
183-
if (is_resource($this->current)) {
184-
return feof($this->current);
221+
if ($this->getCurrent()) {
222+
return feof($this->getCurrent());
185223
} else {
186224
return true;
187225
}
188226
}
189227

190228
public function stream_close() {
191-
if (is_resource($this->current)) {
229+
if ($this->hasOpenStream()) {
192230
fclose($this->current);
193231
}
232+
$this->current = null;
194233
}
195234

196235
public function stream_write($data) {

tests/lib/Files/ObjectStore/AzureTest.php

+4
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,8 @@ protected function getInstance() {
3535

3636
return new Azure($config['arguments']);
3737
}
38+
39+
public function testFseekSize() {
40+
$this->markTestSkipped('azure does not support seeking at the moment');
41+
}
3842
}

tests/lib/Files/ObjectStore/ObjectStoreTest.php

+15
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,19 @@ public function testCopy() {
143143

144144
$this->assertEquals('foobar', stream_get_contents($instance->readObject('target')));
145145
}
146+
147+
public function testFseekSize() {
148+
$instance = $this->getInstance();
149+
150+
$textFile = \OC::$SERVERROOT . '/tests/data/lorem.txt';
151+
$size = filesize($textFile);
152+
$instance->writeObject('source', fopen($textFile, 'r'));
153+
154+
$fh = $instance->readObject('source');
155+
156+
fseek($fh, 0, SEEK_END);
157+
$pos = ftell($fh);
158+
159+
$this->assertEquals($size, $pos);
160+
}
146161
}

tests/lib/Files/Storage/Storage.php

+14
Original file line numberDiff line numberDiff line change
@@ -664,4 +664,18 @@ public function testWriteStream() {
664664
$this->assertStringEqualsFile($textFile, $storage->file_get_contents('test.txt'));
665665
$this->assertEquals('resource (closed)', gettype($source));
666666
}
667+
668+
public function testFseekSize() {
669+
$textFile = \OC::$SERVERROOT . '/tests/data/lorem.txt';
670+
$this->instance->file_put_contents('bar.txt', file_get_contents($textFile));
671+
672+
$size = $this->instance->filesize('bar.txt');
673+
$this->assertEquals(filesize($textFile), $size);
674+
$fh = $this->instance->fopen('bar.txt', 'r');
675+
676+
fseek($fh, 0, SEEK_END);
677+
$pos = ftell($fh);
678+
679+
$this->assertEquals($size, $pos);
680+
}
667681
}

0 commit comments

Comments
 (0)