src/Server.php
protected function handlePATCH(): ResponseInterface
	{

		$offset = $this->request->getHeaderLine("Upload-Offset");
		if ($offset === "") // Note: Some PSR7 implementations discard headers set to 0.
			return $this->response->withStatus(400);

		// Check if the server supports the proposed checksum algorithm
		$checksum = self::parseChecksum($this->request->getHeaderLine("Upload-Checksum"));
		if ($checksum === false)
			return $this->response->withStatus(400);

		// Extension Thunder TUS Express: create the file on PATCH request
		if ($this->extExpress && $offset == 0) {
			$this->response = $this->handlePOST();
			$code = $this->response->getStatusCode();
			if ($code !== 201 && $code !== 409)
				return $this->response;
			if ($code === 409 && !file_exists($this->file . ".cachecontainer")) // Avoid overwriting completed uploads
				return $this->response;
		}
		else { // For Standard TUS
			if (!is_file($this->file))
				return $this->response->withStatus(404);
		}

		// Check if the current stored file offset is different from the proposed upload offset
		// This ensures we're getting the file block by block in the right order and nothing is missing
		if (filesize($this->file) != $offset) {
			return $this->response->withStatus(409)
			                      ->withHeader("Upload-Offset", filesize($this->file));
		}

		// Compare proposed checksum with the received data checksum
		$hashContext = hash_init($checksum->algorithm);
		hash_update_stream($hashContext, $this->stream);
		$localChecksum = base64_encode(hash_final($hashContext, true));
		if ($localChecksum !== $checksum->value)
			return $this->response->withStatus(460, "Checksum Mismatch")
			                      ->withHeader("Upload-Offset", filesize($this->file));

		rewind($this->stream);

		// Write the uploaded chunk to the file
		$file = fopen($this->file, "ab");
		stream_copy_to_stream($this->stream, $file);
		fclose($file);

		clearstatcache(true, $this->file);
		$localSize = filesize($this->file);

		// Detect when the upload is complete
		$cache = \json_decode(file_get_contents($this->file . ".cachecontainer"));
		if ($cache->length <= $localSize) {

			// Remove the cache container, we don't need it anymore
			unlink($this->file . ".cachecontainer");

			// Extension Thunder TUS CrossCheck: verify if the uploaded file is as expected or delete it
			if ($this->extCrossCheck) {
				$localChecksum = base64_encode(hash_file($cache->checksum->algorithm, $this->file, true));
				if ($localChecksum !== $cache->checksum->value) {
					unlink($this->file);

					return $this->response->withStatus(410);
				}
			}

		}

		if ($this->extCrossCheck)
			$this->response = $this->response->withHeader("Location", $this->location);

		// File uploaded successfully!
		return $this->response->withStatus(204)
		                      ->withHeader("Upload-Offset", $localSize);

	}
protected function handlePATCH(): ResponseInterface
	{

		$offset = $this->request->getHeaderLine("Upload-Offset");
		if ($offset === "") // Note: Some PSR7 implementations discard headers set to 0.
			return $this->response->withStatus(400);

		// Check if the server supports the proposed checksum algorithm
		$checksum = self::parseChecksum($this->request->getHeaderLine("Upload-Checksum"));
		if ($checksum === false)
			return $this->response->withStatus(400);

		// Extension Thunder TUS Express: create the file on PATCH request
		if ($this->extExpress && $offset == 0) {
			$this->response = $this->handlePOST();
			$code = $this->response->getStatusCode();
			if ($code !== 201 && $code !== 409)
				return $this->response;
			if ($code === 409 && !file_exists($this->file . ".cachecontainer")) // Avoid overwriting completed uploads
				return $this->response;
		}
		else { // For Standard TUS
			if (!is_file($this->file))
				return $this->response->withStatus(404);
		}

		// Check if the current stored file offset is different from the proposed upload offset
		// This ensures we're getting the file block by block in the right order and nothing is missing
		if (filesize($this->file) != $offset) {
			return $this->response->withStatus(409)
			                      ->withHeader("Upload-Offset", filesize($this->file));
		}

		// Compare proposed checksum with the received data checksum
		$hashContext = hash_init($checksum->algorithm);
		hash_update_stream($hashContext, $this->stream);
		$localChecksum = base64_encode(hash_final($hashContext, true));
		if ($localChecksum !== $checksum->value)
			return $this->response->withStatus(460, "Checksum Mismatch")
			                      ->withHeader("Upload-Offset", filesize($this->file));

		rewind($this->stream);

		// Write the uploaded chunk to the file
		$file = fopen($this->file, "ab");
		stream_copy_to_stream($this->stream, $file);
		fclose($file);

		clearstatcache(true, $this->file);
		$localSize = filesize($this->file);

		// Detect when the upload is complete
		$cache = \json_decode(file_get_contents($this->file . ".cachecontainer"));
		if ($cache->length <= $localSize) {

			// Remove the cache container, we don't need it anymore
			unlink($this->file . ".cachecontainer");

			// Extension Thunder TUS CrossCheck: verify if the uploaded file is as expected or delete it
			if ($this->extCrossCheck) {
				$localChecksum = base64_encode(hash_file($cache->checksum->algorithm, $this->file, true));
				if ($localChecksum !== $cache->checksum->value) {
					unlink($this->file);

					return $this->response->withStatus(410);
				}
			}

		}

		if ($this->extCrossCheck)
			$this->response = $this->response->withHeader("Location", $this->location);

		// File uploaded successfully!
		return $this->response->withStatus(204)
		                      ->withHeader("Upload-Offset", $localSize);

	}
protected function handlePATCH ( ) : ResponseInterface { $ offset = $ this -> request -> getHeaderLine ( "Upload-Offset" ) ; if ( $ offset === "" ) // Note: Some PSR7 implementations discard headers set to 0. return $ this -> response -> withStatus ( 400 ) ; // Check if the server supports the proposed checksum algorithm $ checksum = self :: parseChecksum ( $ this -> request -> getHeaderLine ( "Upload-Checksum" ) ) ; if ( $ checksum === false ) return $ this -> response -> withStatus ( 400 ) ; // Extension Thunder TUS Express: create the file on PATCH request if ( $ this -> extExpress && $ offset == 0 ) { $ this -> response = $ this -> handlePOST ( ) ; $ code = $ this -> response -> getStatusCode ( ) ; if ( $ code !== 201 && $ code !== 409 ) return $ this -> response ; if ( $ code === 409 && ! file_exists ( $ this -> file . ".cachecontainer" ) ) // Avoid overwriting completed uploads return $ this -> response ; } else { // For Standard TUS if ( ! is_file ( $ this -> file ) ) return $ this -> response -> withStatus ( 404 ) ; } // Check if the current stored file offset is different from the proposed upload offset // This ensures we're getting the file block by block in the right order and nothing is missing if ( filesize ( $ this -> file ) != $ offset ) { return $ this -> response -> withStatus ( 409 ) -> withHeader ( "Upload-Offset" , filesize ( $ this -> file ) ) ; } // Compare proposed checksum with the received data checksum $ hashContext = hash_init ( $ checksum -> algorithm ) ; hash_update_stream ( $ hashContext , $ this -> stream ) ; $ localChecksum = base64_encode ( hash_final ( $ hashContext , true ) ) ; if ( $ localChecksum !== $ checksum -> value ) return $ this -> response -> withStatus ( 460 , "Checksum Mismatch" ) -> withHeader ( "Upload-Offset" , filesize ( $ this -> file ) ) ; rewind ( $ this -> stream ) ; // Write the uploaded chunk to the file $ file = fopen ( $ this -> file , "ab" ) ; stream_copy_to_stream ( $ this -> stream , $ file ) ; fclose ( $ file ) ; clearstatcache ( true , $ this -> file ) ; $ localSize = filesize ( $ this -> file ) ; // Detect when the upload is complete $ cache = \ json_decode ( file_get_contents ( $ this -> file . ".cachecontainer" ) ) ; if ( $ cache -> length <= $ localSize ) { // Remove the cache container, we don't need it anymore unlink ( $ this -> file . ".cachecontainer" ) ; // Extension Thunder TUS CrossCheck: verify if the uploaded file is as expected or delete it if ( $ this -> extCrossCheck ) { $ localChecksum = base64_encode ( hash_file ( $ cache -> checksum -> algorithm , $ this -> file , true ) ) ; if ( $ localChecksum !== $ cache -> checksum -> value ) { unlink ( $ this -> file ) ; return $ this -> response -> withStatus ( 410 ) ; } } } if ( $ this -> extCrossCheck ) $ this -> response = $ this -> response -> withHeader ( "Location" , $ this -> location ) ; // File uploaded successfully! return $ this -> response -> withStatus ( 204 ) -> withHeader ( "Upload-Offset" , $ localSize ) ; }
Handle PATCH requests.

Receives a chuck of a file and stores it on the server. Implements the checksum extension to validate
the integrity of the chunk.

If the client uses the ThunderTUS Express extension this method will also create new files bypassing
the need to call POST/HEAD.

If the client uses the ThunderTUS CrossCheck extension this method will also verify the final file
checksum (after all chunks are received) against checksum previously stored in the cache.

@return \Psr\Http\Message\ResponseInterface
Handle PATCH requests .