Overdevs.com

Uploading Content to DigitalOcean Spaces Using PHP

Uploading content to Digitalocean Spaces using PHP is simillar to AWS. Here is how to do that.

DigitalOcean Spaces is a cloud storage service that allows users to store and serve large amounts of unstructured data, such as images, videos, and backups. When I tried to use it for the first time I was advised that the uploading process should be simillar to Amazon's AWS. However I ran into some issues when using the AWS code directly. So here is the full code that I use to upload to Spaces.

Uploading a file to DigitalOcean Spaces

Here is the snippet if you just want to copy and paste. Even though I have commented the code, I will advice to read the article to the end to understand what the code does.

  <?php
// Digital Ocean Spaces API keys
$dosp_access_key_id = YOUR_ACCESS_KEY_ID;
$dosp_secret_access_key = YOUR_SECRET_ACCESS_KEY;

// Bucket
$bucket_name = YOUR_BUCKET_NAME;

// When setting up a space you get to select a reigon, use that here)
$dosp_region = 'nyc3';
$host_name = $bucket_name . '.nyc3.digitaloceanspaces.com';

// Server path where content is present. This is just an example
$sp_data = file_get_contents('yourfilepath');
$content = $sp_data;

// file permissions
$content_acl = 'public-read';

// MIME type of file. Very important to set if you later plan to load the file from a digialocean space url in the browser (images, for example will be image/png, image/jpeg etc)
$content_type =  $sp_type;
// Name of content on S3
$content_title =  $sp_name;

// Service name for S3
$dosp_service_name = 's3';

// UTC timestamp and date
$timestamp = gmdate('Ymd\THis\Z');
$date = gmdate('Ymd');

// HTTP request headers as key & value
$request_headers = array();
$request_headers['Content-Type'] = $content_type;
$request_headers['x-amz-date'] = $timestamp;
$request_headers['Host'] = $host_name;
$request_headers['x-amz-acl'] = $content_acl;
$request_headers['Cache-Control'] = 'max-age=2592000, public';
$request_headers['x-amz-content-sha256'] = hash('sha256', $content);
// Sort it in ascending order
ksort($request_headers);

// Canonical headers
$canonical_headers = [];
foreach($request_headers as $key => $value) {
	$canonical_headers[] = strtolower($key) . ":" . $value;
}
$canonical_headers = implode("\n", $canonical_headers);

// Signed headers
$signed_headers = [];
foreach($request_headers as $key => $value) {
	$signed_headers[] = strtolower($key);
}
$signed_headers = implode(";", $signed_headers);

// Cannonical request 
$canonical_request = [];
$canonical_request[] = "PUT";
$canonical_request[] = "/" . $content_title;
$canonical_request[] = "";
$canonical_request[] = $canonical_headers;
$canonical_request[] = "";
$canonical_request[] = $signed_headers;
$canonical_request[] = hash('sha256', $content);
$canonical_request = implode("\n", $canonical_request);
$hashed_canonical_request = hash('sha256', $canonical_request);

// Scope
$scope = [];
$scope[] = $date;
$scope[] = $dosp_region;
$scope[] = $dosp_service_name;
$scope[] = "aws4_request";

// String to sign
$string_to_sign = [];
$string_to_sign[] = "AWS4-HMAC-SHA256"; 
$string_to_sign[] = $timestamp; 
$string_to_sign[] = implode('/', $scope);
$string_to_sign[] = $hashed_canonical_request;
$string_to_sign = implode("\n", $string_to_sign);

// Signing key
$kSecret = 'AWS4' . $dosp_secret_access_key;
$kDate = hash_hmac('sha256', $date, $kSecret, true);
$kRegion = hash_hmac('sha256', $dosp_region, $kDate, true);
$kService = hash_hmac('sha256', $dosp_service_name, $kRegion, true);
$kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);

// Signature
$signature = hash_hmac('sha256', $string_to_sign, $kSigning);

// Authorization
$authorization = [
	'Credential=' . $dosp_access_key_id . '/' . implode('/', $scope),
	'SignedHeaders=' . $signed_headers,
	'Signature=' . $signature
];
$authorization = 'AWS4-HMAC-SHA256' . ' ' . implode( ',', $authorization);

// Curl headers
$curl_headers = [ 'Authorization: ' . $authorization ];
foreach($request_headers as $key => $value) {
	$curl_headers[] = $key . ": " . $value;
}

$url = 'https://' . $host_name . '/' . $content_title;

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_POSTFIELDS, $content);
curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($http_code != 200)  {
	exit('Error : '.$http_code.' Failed to upload');
}
else {
exit('URL : '.$url.' Upload success!');
}
 
?>
  

Now, let's break down the code and explain how it works:

  1. Digital Ocean Spaces API Keys: Replace 'YOUR_ACCESS_KEY_ID' and 'YOUR_SECRET_ACCESS_KEY' with your DigitalOcean Spaces API access key and secret access key.

  2. Bucket Name: Set the $bucket_name variable to the name of your DigitalOcean Spaces bucket.

  3. DigitalOcean Region: Update $dosp_region with the appropriate region for your DigitalOcean Spaces instance.

  4. Content to Upload: Replace 'yourfilepath' with the actual path to the file you want to upload.

  5. Content Permissions: You can set the $content_acl variable to control the permissions on the uploaded content. 'public-read' allows public access.

  6. MIME Type: Define the MIME type of your content using the $content_type variable.

  7. Content Title: Specify the name of the content as it will appear on DigitalOcean Spaces using the $content_title variable.

  8. HTTP Request Headers: Various headers are set for the HTTP request, including the Content-Type, x-amz-date, and others.

  9. Canonical Request: The code constructs a canonical request that includes the HTTP method, URI, headers, and payload.

  10. String to Sign: A string is generated for signing the request.

  11. Signing Key: The code generates a signing key for authorization.

  12. Signature: Compute the signature using the signing key and string to sign.

  13. Authorization Header: Create the Authorization header using the computed signature.

The rest of the code just makes a CURL request to upload your files. I hope this works for you. If not check for errors in the returned response.

Deleting a file from DigitalOcean Spaces

Well you uploaded your Cat's secret pictures to your DigitalOcean Space and now she is angry. So here is how to take that picture off from Space purr-fectly.

<?php
$dosp_access_key_id = YOUR_ACCESS_KEY_ID;
$dosp_secret_access_key = YOUR_SECRET_ACCESS_KEY;
$bucket_name = YOUR_BUCKET_NAME;

$dosp_region = 'nyc3';
$host_name = $bucket_name . '.nyc3.digitaloceanspaces.com';


$content_acl = 'public-read';


$content_title =  YOUR_FILE_URL;

$dosp_service_name = 's3';

$timestamp = gmdate('Ymd\THis\Z');
$date = gmdate('Ymd');

$request_headers = array();
$request_headers['x-amz-date'] = $timestamp;
$request_headers['Host'] = $host_name;
$request_headers['x-amz-content-sha256'] = hash('sha256', $content);
ksort($request_headers);

$canonical_headers = [];
foreach($request_headers as $key => $value) {
	$canonical_headers[] = strtolower($key) . ":" . $value;
}
$canonical_headers = implode("\n", $canonical_headers);

$signed_headers = [];
foreach($request_headers as $key => $value) {
	$signed_headers[] = strtolower($key);
}
$signed_headers = implode(";", $signed_headers);

$canonical_request = [];
$canonical_request[] = "DELETE";
$canonical_request[] = "/" . $content_title;
$canonical_request[] = "";
$canonical_request[] = $canonical_headers;
$canonical_request[] = "";
$canonical_request[] = $signed_headers;
$canonical_request[] = hash('sha256', $content);
$canonical_request = implode("\n", $canonical_request);
$hashed_canonical_request = hash('sha256', $canonical_request);

$scope = [];
$scope[] = $date;
$scope[] = $dosp_region;
$scope[] = $dosp_service_name;
$scope[] = "aws4_request";

$string_to_sign = [];
$string_to_sign[] = "AWS4-HMAC-SHA256"; 
$string_to_sign[] = $timestamp; 
$string_to_sign[] = implode('/', $scope);
$string_to_sign[] = $hashed_canonical_request;
$string_to_sign = implode("\n", $string_to_sign);

$kSecret = 'AWS4' . $dosp_secret_access_key;
$kDate = hash_hmac('sha256', $date, $kSecret, true);
$kRegion = hash_hmac('sha256', $dosp_region, $kDate, true);
$kService = hash_hmac('sha256', $dosp_service_name, $kRegion, true);
$kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);

$signature = hash_hmac('sha256', $string_to_sign, $kSigning);

$authorization = [
	'Credential=' . $dosp_access_key_id . '/' . implode('/', $scope),
	'SignedHeaders=' . $signed_headers,
	'Signature=' . $signature
];
$authorization = 'AWS4-HMAC-SHA256' . ' ' . implode( ',', $authorization);

$curl_headers = [ 'Authorization: ' . $authorization ];
foreach($request_headers as $key => $value) {
	$curl_headers[] = $key . ": " . $value;
}

$url = 'https://' . $host_name . '/' . $content_title;

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
echo curl_exec($ch);
print curl_error($ch);

$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($http_code != 200) 
	exit('Error : '.$http_code.' Failed to upload');
}
?>

I hope this helps you set up Digialocean Spaces in your PHP project.

Written by Avadhesh