diff --git a/src/Server.php b/src/Server.php index c2331b3..a4f5f1f 100644 --- a/src/Server.php +++ b/src/Server.php @@ -273,13 +273,11 @@ protected function handlePATCH(): ResponseInterface // Detect when the upload is complete $cache = $this->backend->containerFetch($this->file); if ($cache->length <= $localSize) { - // Remove the cache container, we don't need - // it anymore + // Remove the cache container, we don't need it anymore $this->backend->containerDelete($this->file); // Extension Thunder TUS CrossCheck: verify if the uploaded file is as expected or delete it if ($this->extCrossCheck) { - $localChecksum = $this->backend->getHash($this->file, $cache->checksum->algorithm); - if ($localChecksum !== $cache->checksum->value) { + if (!$this->backend->hashMatch($this->file, $cache->checksum->algorithm, $cache->checksum->value)) { $this->backend->delete($this->file); return $this->response->withStatus(410); } diff --git a/src/Store/FileSystem.php b/src/Store/FileSystem.php index d4e922e..417eb5f 100644 --- a/src/Store/FileSystem.php +++ b/src/Store/FileSystem.php @@ -51,9 +51,9 @@ public function getSize(string $name): int return filesize($this->uploadDir . $name); } - public function getHash(string $name, string $algo): string + public function hashMatch(string $name, string $algo, string $expectedHash): bool { - return base64_encode(hash_file($algo, $this->uploadDir . $name, true)); + return base64_encode(hash_file($algo, $this->uploadDir . $name, true)) === $expectedHash; } public function append(string $name, $data): bool diff --git a/src/Store/Redis.php b/src/Store/Redis.php new file mode 100644 index 0000000..a24aef8 --- /dev/null +++ b/src/Store/Redis.php @@ -0,0 +1,116 @@ +client = $client; + } + + public function setTUSKeysPrefix(string $prefix): void + { + static::$prefix = $prefix . ":"; + static::$containerPrefix = $prefix . "container:"; + } + + public function setUploadTTL(int $ttlSeconds): void + { + static::$tusExpire = $ttlSeconds; + } + + public function get(string $name): ?string + { + return $this->client->get(static::$prefix . $name); + } + + public static function downloadIntoLocalFolder(ClientInterface $client, string $destinationDirectory, string $name): bool + { + $storage = new static($client); + $data = $storage->get($name); + if ($data === null) { + return false; + } + + $destinationDirectory = rtrim($destinationDirectory, "/") . "/"; + $result = file_put_contents($destinationDirectory . $name, $data); + if ($result === false) { + return false; + } + + $storage->delete($name); + return true; + } + + /** Implement StoreInterface */ + public function exists(string $name): bool + { + return $this->client->exists(static::$prefix . $name) === 1; + } + + public function create(string $name): bool + { + return $this->client->setex(static::$prefix . $name, self::$tusExpire, "") == "OK"; + } + + public function getSize(string $name): int + { + //return ((int)$this->client->bitcount(static::$prefix . $name))/8; + return (int)$this->client->strlen(static::$prefix . $name); + } + + public function hashMatch(string $name, string $algo, string $expectedHash): bool + { + // Redis backend storage doesn't support efficient hashing. + // Hashing is only used in the custom CrossCheck extension implemented by + // thunder-tus-php. This isn't a part of the TUS protocol, so it can be skipped safely. + return true; + } + + public function append(string $name, $data): bool + { + $result = $this->client->append(static::$prefix . $name, stream_get_contents($data)); + return $result; + } + + public function delete(string $name): bool + { + return $this->client->del([self::$prefix . $name]); + } + + public function containerExists(string $name): bool + { + return $this->client->exists(static::$containerPrefix . $name) == 1; + } + + public function containerCreate(string $name, ?\stdClass $data = null): bool + { + return $this->client->setex(static::$containerPrefix . $name, self::$tusExpire, \json_encode($data)) == "OK"; + } + + public function containerUpdate(string $name, \stdClass $data): bool + { + return $this->containerCreate($name, $data); + } + + public function containerFetch(string $name): \stdClass + { + return \json_decode($this->client->get(static::$containerPrefix . $name)); + } + + public function containerDelete(string $name): bool + { + return $this->client->del([self::$containerPrefix . $name]); + } + +} diff --git a/src/Store/StoreInterface.php b/src/Store/StoreInterface.php index b632aec..d7179a9 100644 --- a/src/Store/StoreInterface.php +++ b/src/Store/StoreInterface.php @@ -7,7 +7,7 @@ interface StoreInterface public function exists(string $name): bool; public function create(string $name): bool; public function getSize(string $name): int; - public function getHash(string $name, string $algo): string; + public function hashMatch(string $name, string $algo, string $expectedHash): bool; public function append(string $name, $data): bool; public function delete(string $name): bool;