Last active
November 10, 2022 21:44
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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 → <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