class CloudflareR2Deleter {
private $accountId;
private $accessKey;
private $secretKey;
private $bucketName;
private $region = 'auto';
private $service = 's3';
public function __construct($accountId, $accessKey, $secretKey, $bucketName) {
$this->accountId = $accountId;
$this->accessKey = $accessKey;
$this->secretKey = $secretKey;
$this->bucketName = $bucketName;
}
public function delete($objectKey) {
$encodedKey = $objectKey;
$url = "https://{$this->accountId}.r2.cloudflarestorage.com/{$this->bucketName}/{$encodedKey}";
$date = $this->getDateString();
$timeString = $this->getTimeString();
$payloadHash = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
$host = "{$this->accountId}.r2.cloudflarestorage.com";
$canonicalHeaders = [
'host' => $host,
'x-amz-content-sha256' => $payloadHash,
'x-amz-date' => $timeString,
'content-length' => '0'
];
ksort($canonicalHeaders);
$canonicalHeadersStr = '';
foreach ($canonicalHeaders as $key => $value) {
$canonicalHeadersStr .= $key . ':' . trim($value) . "\n";
}
$signedHeaders = implode(';', array_keys($canonicalHeaders));
$canonicalRequest = "DELETE\n/{$this->bucketName}/{$encodedKey}\n\n" .
rtrim($canonicalHeadersStr, "\n") . "\n\n" .
$signedHeaders . "\n" .
$payloadHash;
$canonicalRequestHash = hash('sha256', $canonicalRequest);
$stringToSign = "AWS4-HMAC-SHA256\n{$timeString}\n{$this->getCredentialScope()}\n{$canonicalRequestHash}";
$signature = $this->generateSignature($stringToSign);
$authorizationHeader = "AWS4-HMAC-SHA256 Credential={$this->accessKey}/{$this->getCredentialScope()}, SignedHeaders={$signedHeaders}, Signature={$signature}";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HEADER => true,
CURLOPT_HTTPHEADER => [
"Authorization: {$authorizationHeader}",
"x-amz-content-sha256: {$payloadHash}",
"x-amz-date: {$timeString}",
"Host: {$host}",
"Content-Length: 0"
],
CURLOPT_VERBOSE => false
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
throw new Exception("cURL Error: $error");
}
if ($httpCode !== 204) {
throw new Exception("Failed to delete file. HTTP Code: $httpCode");
}
return true;
}
private function generateSignature($stringToSign) {
$kDate = hash_hmac('sha256', $this->getDateString(), "AWS4{$this->secretKey}", true);
$kRegion = hash_hmac('sha256', $this->region, $kDate, true);
$kService = hash_hmac('sha256', $this->service, $kRegion, true);
$kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);
return hash_hmac('sha256', $stringToSign, $kSigning);
}
private function getDateString() {
return gmdate('Ymd');
}
private function getTimeString() {
return gmdate('Ymd\THis\Z');
}
private function getCredentialScope() {
return "{$this->getDateString()}/{$this->region}/{$this->service}/aws4_request";
}
}Usage Example:
function cloudflare_delete($key) {
$deleter = new CloudflareR2Deleter(
$accountId,
$accessKey,
$accessSecret,
$bucket
);
try {
return $deleter->delete($key);
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
}