How to add required ContentMD5 to Bucket Policy PUT request for lifecycle, cors or acl?
I am going to answer my own question here. It took me hours to figure this out as there is no info on this anywhere so I thought I should post this somewhere I would look first.
I was using the AWS PHP SDK to send a PUT request to add a lifecycle policy to my Digital Ocean space and it would not take since it requires a ContentMD5 header. There are two problems here, the first problem is the SDK URL encodes the path/key, which is a problem with /?lifecycle, /?location, and /?acl since they become "/%3Flifecycle" -- skip this paragraph if this isn't part of your request path. To temporarily stop this to add or update a bucket policy you have to find the file RestSerializer.php in the SDK files, if you added the API with composer it will be in a path like /vendor/aws/aws-sdk-php/src/Api/Serializer/RestSerializer.php in your composer/websites root, which will likely be in /var/www. In RestSerializer.php find the two rawurlencode function calls and remove them but leave the value/argument "rawurlencode($varspecs[$k])" becomes "$varspecs[$k]".
Now the request is going to the correct URL, to generate the ContentMD5 you need to construct a little PHP code depending on what you're doing. If you have put the XML text for your policy in a file use md5file(PATHTOFILEHERE, true) if you are using a string use md5(STRINGHERE, true). Then wrap that in base64encode() so it looks something like base64encode(md5file('/path/file.xml', true)). Finally, add that to your putObject array with 'ContentMD5' => base64encode(md5file('/path/file.xml', true)) .
PHP Example with File:
// $spaceS3Client is a new S3Client object.
// since its a file, I need to get the file first
$xmlfile = fopen('/spaces.xml', 'r+');
$request = $spaceS3Client->putObject([
'Bucket' => 'myspacename',
'Key' => '?lifecycle',
'Body' => $xmlfile,
'ContentType' => 'application/xml',
'ContentMD5' => base64encode(md5file('/spaces.xml'', true))
// close file
// if you are having trouble connecting to your space in the first place with an S3Client object, since its set up for AWS and not DO you need to add an 'endpoint' to the array in new S3Client like 'endpoint' => 'https://'.$myspace.'.'.$myspaceregion.'.digitaloceanspaces.com'. You also need to add 'bucket_endpoint' => true.