Skip to content

Instantly share code, notes, and snippets.

@humbletiger
Last active November 10, 2022 21:44
Show Gist options
  • Save humbletiger/a5f6e94b62d9a5efded5eb218be661a6 to your computer and use it in GitHub Desktop.
Save humbletiger/a5f6e94b62d9a5efded5eb218be661a6 to your computer and use it in GitHub Desktop.
s3SimpleUpload.php is a standalone (no AWS SDK), intentionally verbose, upload only S3 class utilizing the AWS API, Signature Version 4.
<?php
/*
ABSTRACT
s3SimpleUpload.php is a standalone (no AWS SDK), intentionally verbose, upload only S3 class utilizing the AWS API, Signature Version 4.
Options include defining the s3_prefix path, regardless of the origination path, or using the source file's origination prefix path (default).
USAGE
Works with remote files, e.g.:
$file_path = 'https://www.google.com/robots.txt'
Example generating remote testing images on the fly with dummyimage.com:
$file_path = '/301.png' ; // set size of resulting image
$file_path = '/305/999/blah.png' ; // set size/color/name of resulting image
$file_path = 'https://dummyimage.com'.$file_path ;
Works with local files, e.g.:
$file_path = /tmp/myimage.jpg
$file_path = /tmp/some/path/myimage.jpg
S3 prefix path configuration options:
$s3_prefix = ''; // null|empty|false: The resulting bucket s3_prefix will match its origination
$s3_prefix - '/' // Uploaded to defined path (root in this case), regardless of origination
$s3_prefix = 'this/is/a/path/'; // Uploaded to defined path, regardless of origination
S3 configuration example:
s3SimpleUpload::$key='KAIATMRL6QMWQKZFXZ7U';
s3SimpleUpload::$secret='x5QmHhEv9qTBe76sM0Uwul8joS47uFqcooxLTBgq';
s3SimpleUpload::$bucket='myspecialbucket';
s3SimpleUpload::$acl="public-read"; // public-read is default (see class config note)
s3SimpleUpload::$print_log=true;
$result = s3SimpleUpload::uploadFile($file_path,$s3_prefix);
echo '<pre>'; echo($res); echo '</pre>';
Author: Eric Pecoraro
License: Unlicense (https://unlicense.org/)
Comments/questions (via Gist): https://gist.github.com/EricP/a5f6e94b62d9a5efded5eb218be661a6
Test cases:
$file_path = '/250.png' ;
$file_path = '/250/999/file.png' ;
$file_path = 'https://dummyimage.com'.$file_path ; // Dynamically create remote file
if ( !strstr($file_path,'//') ) {
$file_path = sys_get_temp_dir().$file_path; // Uncomment dummyimage.com & use locally created file
}
$s3_prefix = '/';
$s3_prefix = false;
$s3_prefix = '/a/path/to/the/';
$res = s3SimpleUpload::uploadFile($file_path,$s3_prefix);
echo '<pre>'; echo($res); echo '</pre>';
*/
class s3SimpleUpload {
// Available configurations/defaults.
public static $key = '';
public static $secret = '';
public static $region = 'us-east-1';
public static $bucket = '';
public static $host = 's3.amazonaws.com';
// ACL: public-read|private|bucket-owner-full-control (more:https://docs.aws.amazon.com/cli/latest/reference/s3api/put-object-acl.html)
public static $acl = 'public-read';
public static $print_log = false;
public static function uploadFile($file_path,$s3_prefix=null) {
$log="\n";
$default_timezone = date_default_timezone_get();
date_default_timezone_set('UTC');
$file_name = basename($file_path);
$file_contents = file_get_contents($file_path); // Payload
$remote=false;
if ( strstr($file_path,'//') ) {
$remote = true;
}
$prefix=false;
if ( strlen($s3_prefix)>0 ) {
$prefix = true;
}
if ( $s3_prefix === true ) {
$s3_prefix = null;
}
// Upload remote file, with existing remote path.
if ( $remote && !$prefix ) {
$local_path = sys_get_temp_dir().parse_url($file_path,PHP_URL_PATH);
$local_dir = dirname($local_path);
if ( !is_dir($local_dir) ) {
mkdir( $local_dir, 0777, true );
}
file_put_contents($local_path,$file_contents);
$s3_prefix = dirname( parse_url($file_path,PHP_URL_PATH) );
$s3_prefix = trim($s3_prefix,'/').'/'; // trailing slash
$file_path = str_replace('//','/',$local_path); // define local
}
// Upload remote file with defined s3_prefix.
if ( $remote && $prefix ) {
$local_dir = sys_get_temp_dir().'/'.trim($s3_prefix,'/');
if ( !is_dir($local_dir) ) {
mkdir( $local_dir, 0777, true );
}
$s3_prefix = trim($s3_prefix,'/').'/';
$file_path = str_replace('//','/',$local_dir.'/'.$file_name); // define local
file_put_contents($file_path,$file_contents);
}
// Upload local file, with existing path.
if ( !$remote && !$prefix ) {
$local_dir = dirname($file_path);
$s3_prefix = ltrim($local_dir,sys_get_temp_dir()); // Remove system tmp dir
$s3_prefix = trim($s3_prefix,'/').'/'; // + trailing slash
}
// Upload local file, with defined s3_prefix.
if ( !$remote && $prefix ) {
$s3_prefix = trim($s3_prefix,'/').'/';
}
if ( $s3_prefix == '/' ) {
$s3_prefix = null; // Bucket root
}
if ( !realpath($file_path) ) {
echo "Local file does not exist ".print_r($file_path,true)."\n";
return;
}
$log.= "<hr>file_path: ".print_r($file_path,true)."\n";
$log.= "<hr>s3_prefix: ".print_r($s3_prefix,true)."\n";
$hashed_payload = hash('sha256', $file_contents);
$file_type = mime_content_type($file_path);
if ( empty($file_type) ) {
// fallback if mime_content_type() unavailable
$file_type = exec("file -bi '".$file_path."'");
}
$log.= "<hr>file_type: ".print_r($file_type,true)."\n";
$date_full = date('D, d M Y H:i:s \G\M\T'); // @todo don't force GMT!
$date_time = date('Ymd\THisZ'); // @todo don't force Z!
$date = date('Ymd');
// Set headers.
$headers = array(
'Host' => self::$bucket . '.' . self::$host,
'Content-Type' => $file_type,
'x-amz-content-sha256' => $hashed_payload,
'x-amz-date' => $date_full,
'x-amz-acl' => self::$acl
);
ksort($headers);
$signed_headers_string = strtolower(implode(';', array_keys($headers)));
// Build canonical request.
$canonical_request = "PUT\n"; // HTTP Method
$canonical_request .= '/' . $s3_prefix.$file_name . "\n"; // Canonical uri
$canonical_request .= "\n"; // CanonicalQueryString
foreach ($headers as $header => $value) {
$canonical_request .= strtolower($header) . ':' . trim($value) . "\n";
}
$canonical_request .= "\n";
$canonical_request .= $signed_headers_string . "\n"; // SignedHeaders
$canonical_request .= $hashed_payload; // HashedPayload
$log.= "<hr>canonical_request: ".print_r($canonical_request,true)."\n";
// Build string to sign.
$string_to_sign = "AWS4-HMAC-SHA256\n";
$string_to_sign .= $date_full . "\n";
$string_to_sign .= $date . '/' . self::$region . "/s3/aws4_request\n";
$string_to_sign .= hash('sha256', $canonical_request);
$log.= "<hr>string_to_sign: ".print_r($string_to_sign,true)."\n";
// Calculate signature.
$signature_date = hash_hmac('sha256', $date, 'AWS4' . self::$secret, true);
$signature_region = hash_hmac('sha256', self::$region, $signature_date, true);
$signature_service = hash_hmac('sha256', 's3', $signature_region, true);
$signature_request = hash_hmac('sha256', 'aws4_request', $signature_service, true);
$signature = hash_hmac('sha256', $string_to_sign, $signature_request);
// Calculate final `Authorization` header.
$headers['Authorization'] = 'AWS4-HMAC-SHA256 Credential=' . self::$key . '/' . $date . '/' . self::$region . '/s3/aws4_request,';
$headers['Authorization'] .= 'SignedHeaders=' . $signed_headers_string . ',';
$headers['Authorization'] .= 'Signature=' . $signature;
$log.= "<hr>Authorization: ".print_r($headers,true)."\n";
// Convert headers to key:value strings.
$curl_headers = array();
foreach ($headers as $header => $value) {
$curl_headers[] = "{$header}:{$value}";
}
// Init curl.
$curl = curl_init();
$url = 'https://' . self::$bucket . '.' . self::$host . '/' . $s3_prefix.$file_name ;
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_VERBOSE, 1);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($curl, CURLINFO_HEADER_OUT, true);
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $curl_headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $file_contents);
// Run request.
if ($result = curl_exec($curl)) {
$res = "<hr>Curl Success:"."\n";
$curl_info = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$headers_sent = curl_getinfo($curl, CURLINFO_HEADER_OUT);
$res.= "<hr>curl_info: ".print_r($curl_info,true)."\n";
$res.= "<hr>result: ".print_r($result,true)."\n";
$res.= "<hr>headers_sent: ".print_r($headers_sent,true)."\n";
//$site = 'https://' . self::$bucket . '/' . $s3_prefix.$file_name; // if hosting bucket as s3 website
$site = 'https://' . self::$bucket . '.' . self::$host . '/' . $s3_prefix.$file_name ; // direct uri link
$site = '<br><br>Site &rarr; <a href="'.$site.'" target="_blank">'.$site.'</a><br><br>';
}
else {
$res = "<hr>Curl Error:"."\n";
$res.= "<hr>curl_errno: ".print_r(curl_errno($curl),true)."\n";
$res.= "<hr>curl_errno: ".print_r(curl_error($curl),true)."\n";
}
if ( self::$print_log ) {
echo $site;
echo '<pre>'; echo($log); echo '</pre>';
}
return $res;
@curl_close($curl);
date_default_timezone_set($default_timezone);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment