├── README.markdown ├── s3up └── s3up.old /README.markdown: -------------------------------------------------------------------------------- 1 | s3up 2 | ========= 3 | 4 | s3up is a command line tool to store static content in [Amazon S3](http://aws.amazon.com/s3/) following the best practices of [YSlow](http://developer.yahoo.com/yslow/). Specifically, it's for users who want to use S3 as a content delivery network for their images, style sheets, JavaScript, etc. 5 | 6 | For each file it uploads, s3up sets a far future expiration date, gzips the content, and versions the file by combining the filename with a timestamp. Each of these actions are optional and can be controlled via the command line. s3up can also automatically compress your images using Yahoo!'s [Smush.it](http://www.smushit.com/ysmush.it/) web service. 7 | 8 | A longer explanation and example usage can be found [here](http://clickontyler.com/blog/2009/02/serving-static-content-on-amazon-s3-with-s3up/). 9 | 10 | FEATURES 11 | -------- 12 | 13 | * Upload multiple files at once - including directories 14 | * Automatically append timestamps onto files for versioning 15 | * Upload a gzipped version of each file 16 | * Add far future expiration header to each file 17 | * Losslessly compress your images using Smush.it 18 | 19 | INSTALL 20 | ------- 21 | 22 | Requires PHP5 and php_curl extension. 23 | 24 | * Place s3up in a convenient location and make executable. 25 | 26 | You're done! 27 | 28 | UPDATES 29 | ------- 30 | 31 | Code is hosted at GitHub: [http://github.com/tylerhall/s3up](http://github.com/tylerhall/s3up) 32 | 33 | LICENSE 34 | ------- 35 | 36 | The MIT License 37 | 38 | Copyright (c) 2009 Tyler Hall 39 | 40 | Permission is hereby granted, free of charge, to any person obtaining a copy 41 | of this software and associated documentation files (the "Software"), to deal 42 | in the Software without restriction, including without limitation the rights 43 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 44 | copies of the Software, and to permit persons to whom the Software is 45 | furnished to do so, subject to the following conditions: 46 | 47 | The above copyright notice and this permission notice shall be included in 48 | all copies or substantial portions of the Software. 49 | 50 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 51 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 52 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 53 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 54 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 55 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 56 | THE SOFTWARE. 57 | -------------------------------------------------------------------------------- /s3up: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | compressedUrl) && strlen($img->compressedUrl) > 0) 135 | { 136 | $the_real_file = tempnam('/tmp', 's3up'); 137 | $fh = fopen($the_real_file, 'w'); 138 | $ch = curl_init(); 139 | curl_setopt($ch, CURLOPT_URL,$img->compressedUrl); 140 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 141 | curl_setopt($ch, CURLOPT_FILE, $fh); 142 | curl_exec($ch); 143 | logMessage("This file is a image and was smushed."); 144 | upload_static_file($filename, $the_real_file); 145 | return; 146 | } 147 | } 148 | } 149 | 150 | upload_static_file($filename); 151 | } 152 | 153 | function upload_static_file($filename, $the_real_file = null) 154 | { 155 | global $AMZ_KEY, $AMZ_SECRET, $options, $base_dir; 156 | 157 | $bucket_and_path = rtrim($GLOBALS['argv'][$GLOBALS['argc'] - 2], '/') . '/'; 158 | $bucket_list = explode('/', $bucket_and_path); 159 | $bucket = array_shift($bucket_list); 160 | if(strpos($bucket_and_path, '/') !== false) 161 | $path = substr($bucket_and_path, strpos($bucket_and_path, '/')); 162 | else 163 | $path = ''; 164 | 165 | // Prepare any additional headers... 166 | $headers = array(); 167 | $headers['Expires'] = date('D, j M Y H:i:s', time() + (86400 * 365 * 10)) . ' GMT'; // 10 years 168 | 169 | $info = pathinfo($filename); 170 | $dir = $info['dirname'] . '/'; 171 | if (substr($dir, 0, 2) === './') $dir = substr($dir, 2); 172 | 173 | if (!empty($options['f'])) { 174 | $dir = $options['f'] . '/' . $dir; 175 | } 176 | 177 | // Version the file? 178 | if(array_key_exists('v', $options)) 179 | $remote_name = $path . $options['v'] . '/' . $dir . $info['filename']; 180 | elseif(array_key_exists('t', $options)) 181 | $remote_name = $path . $dir . $info['filename'] . '.' . date('YmdHis'); 182 | else 183 | $remote_name = $path . $dir . $info['filename']; 184 | 185 | if(!empty($info['extension'])) 186 | $remote_name .= '.' . $info['extension']; 187 | 188 | // $remote_name = preg_replace("!^$base_dir!", '', $remote_name); 189 | $remote_name = ltrim($remote_name, '/'); 190 | 191 | // Begin the upload proccess 192 | $s3 = new S3($AMZ_KEY, $AMZ_SECRET); 193 | 194 | // Should we check to see if the file has already been uploaded? 195 | $ok_to_upload = true; 196 | if(array_key_exists('c', $options)) 197 | { 198 | $info = $s3->getObjectInfo($bucket, $remote_name); 199 | if(substr($info['ETag'], 1, -2) == md5_file($filename)) 200 | { 201 | $ok_to_upload = false; 202 | echo "Skipping (MD5 match): http://$bucket.s3.amazonaws.com/$remote_name\n"; 203 | logMessage("Skipping (MD5 match): http://$bucket.s3.amazonaws.com/$remote_name"); 204 | } 205 | } 206 | 207 | // Actually upload the file... 208 | $isJsOrCss = !empty($info['extension']) && in_array(strtolower($info['extension']), array('css', 'js')); 209 | if($ok_to_upload && (!$isJsOrCss || !array_key_exists('o', $options))) 210 | { 211 | if(!is_null($the_real_file)) $filename = $the_real_file; 212 | logMessage("Uploading..."); 213 | if($s3->uploadFile($bucket, $remote_name, $filename, true, $headers)) { 214 | echo "http://$bucket.s3.amazonaws.com/$remote_name\n"; 215 | logMessage("File '$filename' uploaded to 'http://$bucket.s3.amazonaws.com/$remote_name'"); 216 | } else { 217 | echo "Error: Unable to upload '$filename'\n"; 218 | logMessage("Upload of '$filename' failed."); 219 | } 220 | } 221 | 222 | // Upload the gzipped version... 223 | if(array_key_exists('z', $options)) 224 | { 225 | $info = pathinfo($remote_name); 226 | if($isJsOrCss) 227 | { 228 | if (!array_key_exists('o', $options)) { 229 | $remote_name = $info['dirname'] . '/' . $info['filename'] . '.gz'; 230 | if(strlen($info['extension']) > 0) 231 | $remote_name .= '.' . $info['extension']; 232 | } 233 | 234 | $gzname = tempnam('/tmp', 's3up'); 235 | if(!is_null($the_real_file)) $filename = $the_real_file; 236 | shell_exec(sprintf('gzip -c %s > %s', escapeshellarg($filename), escapeshellarg($gzname))); 237 | logMessage("The '$filename' has gziped to '$gzname'"); 238 | 239 | $headers['Content-Encoding'] = 'gzip'; 240 | 241 | // Should we check to see if the file has already been uploaded? 242 | $ok_to_upload = true; 243 | if(array_key_exists('c', $options)) 244 | { 245 | $info = $s3->getObjectInfo($bucket, $remote_name); 246 | if(substr($info['ETag'], 1, -2) == md5_file($local_name)) 247 | { 248 | $ok_to_upload = false; 249 | echo "Skipping (MD5 match): http://$bucket.s3.amazonaws.com/$remote_name\n"; 250 | logMessage("Skipping (MD5 match): http://$bucket.s3.amazonaws.com/$remote_name"); 251 | } 252 | } 253 | 254 | // Actually upload the file... 255 | if($ok_to_upload) 256 | { 257 | logMessage("Uploading..."); 258 | if($s3->uploadFile($bucket, $remote_name, $gzname, true, $headers)) { 259 | echo "http://$bucket.s3.amazonaws.com/$remote_name\n"; 260 | logMessage("File '$filename' ($gzname) uploaded to 'http://$bucket.s3.amazonaws.com/$remote_name'"); 261 | } else { 262 | echo "Error: Unable to upload '$filename' (gzip version)\n"; 263 | logMessage("Error: Unable to upload '$filename' (gzip version)"); 264 | } 265 | } 266 | } 267 | } 268 | } 269 | 270 | // ################################################################ 271 | // ################################################################ 272 | // ################################################################ 273 | 274 | class S3 275 | { 276 | private $key; 277 | private $privateKey; 278 | private $host; 279 | private $date; 280 | private $curlInfo; 281 | 282 | public function __construct($key, $private_key, $host = 's3.amazonaws.com') 283 | { 284 | $this->key = $key; 285 | $this->privateKey = $private_key; 286 | $this->host = $host; 287 | $this->date = gmdate('D, d M Y H:i:s T'); 288 | return true; 289 | } 290 | 291 | public function uploadFile($bucket_name, $s3_path, $fs_path, $web_accessible = false, $headers = null) 292 | { 293 | $s3_path = str_replace(' ', '%20', $s3_path); 294 | 295 | $request = array('verb' => 'PUT', 296 | 'resource' => "/$bucket_name/$s3_path", 297 | 'content-md5' => $this->base64(md5_file($fs_path))); 298 | 299 | $fh = fopen($fs_path, 'r'); 300 | $curl_opts = array('CURLOPT_PUT' => true, 301 | 'CURLOPT_INFILE' => $fh, 302 | 'CURLOPT_INFILESIZE' => filesize($fs_path), 303 | 'CURLOPT_CUSTOMREQUEST' => 'PUT'); 304 | 305 | if(is_null($headers)) 306 | $headers = array(); 307 | 308 | $headers['Content-MD5'] = $request['content-md5']; 309 | 310 | if($web_accessible === true && !isset($headers['x-amz-acl'])) 311 | $headers['x-amz-acl'] = 'public-read'; 312 | 313 | if(!isset($headers['Content-Type'])) 314 | { 315 | $ext = pathinfo($s3_path, PATHINFO_EXTENSION); 316 | $headers['Content-Type'] = isset($this->mimeTypes[strtolower($ext)]) ? $this->mimeTypes[strtolower($ext)] : 'application/octet-stream'; 317 | // print_r($headers); 318 | } 319 | $request['content-type'] = $headers['Content-Type']; 320 | 321 | $result = $this->sendRequest($request, $headers, $curl_opts); 322 | fclose($fh); 323 | return $this->curlInfo['http_code'] == '200'; 324 | } 325 | 326 | public function getObjectInfo($bucket_name, $s3_path) 327 | { 328 | $request = array('verb' => 'HEAD', 'resource' => "/$bucket_name/$s3_path"); 329 | $curl_opts = array('CURLOPT_HEADER' => true, 'CURLOPT_NOBODY' => true); 330 | $result = $this->sendRequest($request, null, $curl_opts); 331 | $xml = @simplexml_load_string($result); 332 | 333 | if($xml !== false) 334 | return false; 335 | 336 | preg_match_all('/^(\S*?): (.*?)$/ms', $result, $matches); 337 | $info = array(); 338 | for($i = 0; $i < count($matches[1]); $i++) 339 | $info[$matches[1][$i]] = $matches[2][$i]; 340 | 341 | if(!isset($info['Last-Modified'])) 342 | return false; 343 | 344 | return $info; 345 | } 346 | 347 | private function sendRequest($request, $headers = null, $curl_opts = null) 348 | { 349 | if(is_null($headers)) 350 | $headers = array(); 351 | 352 | $headers['Date'] = $this->date; 353 | $headers['Authorization'] = 'AWS ' . $this->key . ':' . $this->signature($request, $headers); 354 | foreach($headers as $k => $v) 355 | $headers[$k] = "$k: $v"; 356 | 357 | $uri = 'http://' . $this->host . $request['resource']; 358 | $ch = curl_init(); 359 | curl_setopt($ch, CURLOPT_URL, $uri); 360 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $request['verb']); 361 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 362 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 363 | // curl_setopt($ch, CURLOPT_VERBOSE, true); 364 | 365 | if(is_array($curl_opts)) 366 | { 367 | foreach($curl_opts as $k => $v) 368 | curl_setopt($ch, constant($k), $v); 369 | } 370 | 371 | // Try request a few times because amazon is crazy sometimes 372 | $result = false; 373 | $retries = 0; 374 | while( ($result === false || $this->curlInfo['http_code'] != '200') && $retries < 3 ) 375 | { 376 | if($retries++ > 0) sleep($retries); 377 | $result = curl_exec($ch); 378 | $this->curlInfo = curl_getinfo($ch); 379 | } 380 | if($retries == 3) 381 | { 382 | fwrite(STDERR, 'Curl error: ' . curl_error($ch) . "\n"); 383 | // print_r($this->curlInfo); 384 | fwrite(STDERR,"AWS Response:\n$result"); 385 | } 386 | curl_close($ch); 387 | return $result; 388 | } 389 | 390 | private function signature($request, $headers = null) 391 | { 392 | if(is_null($headers)) 393 | $headers = array(); 394 | 395 | $CanonicalizedAmzHeadersArr = array(); 396 | $CanonicalizedAmzHeadersStr = ''; 397 | foreach($headers as $k => $v) 398 | { 399 | $k = strtolower($k); 400 | 401 | if(substr($k, 0, 5) != 'x-amz') continue; 402 | 403 | if(isset($CanonicalizedAmzHeadersArr[$k])) 404 | $CanonicalizedAmzHeadersArr[$k] .= ',' . trim($v); 405 | else 406 | $CanonicalizedAmzHeadersArr[$k] = trim($v); 407 | } 408 | ksort($CanonicalizedAmzHeadersArr); 409 | 410 | foreach($CanonicalizedAmzHeadersArr as $k => $v) 411 | $CanonicalizedAmzHeadersStr .= "$k:$v\n"; 412 | 413 | $str = $request['verb'] . "\n"; 414 | $str .= isset($request['content-md5']) ? $request['content-md5'] . "\n" : "\n"; 415 | $str .= isset($request['content-type']) ? $request['content-type'] . "\n" : "\n"; 416 | $str .= isset($request['date']) ? $request['date'] . "\n" : $this->date . "\n"; 417 | $str .= $CanonicalizedAmzHeadersStr . preg_replace('/\?.*/', '', $request['resource']); 418 | 419 | $sha1 = $this->hasher($str); 420 | return $this->base64($sha1); 421 | } 422 | 423 | // Algorithm adapted (stolen) from http://pear.php.net/package/Crypt_HMAC/) 424 | private function hasher($data) 425 | { 426 | $key = $this->privateKey; 427 | if(strlen($key) > 64) 428 | $key = pack('H40', sha1($key)); 429 | if(strlen($key) < 64) 430 | $key = str_pad($key, 64, chr(0)); 431 | $ipad = (substr($key, 0, 64) ^ str_repeat(chr(0x36), 64)); 432 | $opad = (substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64)); 433 | return sha1($opad . pack('H40', sha1($ipad . $data))); 434 | } 435 | 436 | private function base64($str) 437 | { 438 | $ret = ''; 439 | for($i = 0; $i < strlen($str); $i += 2) 440 | $ret .= chr(hexdec(substr($str, $i, 2))); 441 | return base64_encode($ret); 442 | } 443 | 444 | private function match($regex, $str, $i = 0) 445 | { 446 | if(preg_match($regex, $str, $match) == 1) 447 | return $match[$i]; 448 | else 449 | return false; 450 | } 451 | 452 | private $mimeTypes = array("323" => "text/h323", "acx" => "application/internet-property-stream", "ai" => "application/postscript", "aif" => "audio/x-aiff", "aifc" => "audio/x-aiff", "aiff" => "audio/x-aiff", 453 | "asf" => "video/x-ms-asf", "asr" => "video/x-ms-asf", "asx" => "video/x-ms-asf", "au" => "audio/basic", "avi" => "video/quicktime", "axs" => "application/olescript", "bas" => "text/plain", "bcpio" => "application/x-bcpio", "bin" => "application/octet-stream", "bmp" => "image/bmp", 454 | "c" => "text/plain", "cat" => "application/vnd.ms-pkiseccat", "cdf" => "application/x-cdf", "cer" => "application/x-x509-ca-cert", "class" => "application/octet-stream", "clp" => "application/x-msclip", "cmx" => "image/x-cmx", "cod" => "image/cis-cod", "cpio" => "application/x-cpio", "crd" => "application/x-mscardfile", 455 | "crl" => "application/pkix-crl", "crt" => "application/x-x509-ca-cert", "csh" => "application/x-csh", "css" => "text/css", "dcr" => "application/x-director", "der" => "application/x-x509-ca-cert", "dir" => "application/x-director", "dll" => "application/x-msdownload", "dms" => "application/octet-stream", "doc" => "application/msword", 456 | "dot" => "application/msword", "dvi" => "application/x-dvi", "dxr" => "application/x-director", "eps" => "application/postscript", "etx" => "text/x-setext", "evy" => "application/envoy", "exe" => "application/octet-stream", "fif" => "application/fractals", "flr" => "x-world/x-vrml", "gif" => "image/gif", 457 | "gtar" => "application/x-gtar", "gz" => "application/x-gzip", "h" => "text/plain", "hdf" => "application/x-hdf", "hlp" => "application/winhlp", "hqx" => "application/mac-binhex40", "hta" => "application/hta", "htc" => "text/x-component", "htm" => "text/html", "html" => "text/html", 458 | "htt" => "text/webviewhtml", "ico" => "image/x-icon", "ief" => "image/ief", "iii" => "application/x-iphone", "ins" => "application/x-internet-signup", "isp" => "application/x-internet-signup", "jfif" => "image/pipeg", "jpe" => "image/jpeg", "jpeg" => "image/jpeg", "jpg" => "image/jpeg", 459 | "js" => "application/x-javascript", "latex" => "application/x-latex", "lha" => "application/octet-stream", "lsf" => "video/x-la-asf", "lsx" => "video/x-la-asf", "lzh" => "application/octet-stream", "m13" => "application/x-msmediaview", "m14" => "application/x-msmediaview", "m3u" => "audio/x-mpegurl", "man" => "application/x-troff-man", 460 | "mdb" => "application/x-msaccess", "me" => "application/x-troff-me", "mht" => "message/rfc822", "mhtml" => "message/rfc822", "mid" => "audio/mid", "mny" => "application/x-msmoney", "mov" => "video/quicktime", "movie" => "video/x-sgi-movie", "mp2" => "video/mpeg", "mp3" => "audio/mpeg", 461 | "mpa" => "video/mpeg", "mpe" => "video/mpeg", "mpeg" => "video/mpeg", "mpg" => "video/mpeg", "mpp" => "application/vnd.ms-project", "mpv2" => "video/mpeg", "ms" => "application/x-troff-ms", "mvb" => "application/x-msmediaview", "nws" => "message/rfc822", "oda" => "application/oda", 462 | "p10" => "application/pkcs10", "p12" => "application/x-pkcs12", "p7b" => "application/x-pkcs7-certificates", "p7c" => "application/x-pkcs7-mime", "p7m" => "application/x-pkcs7-mime", "p7r" => "application/x-pkcs7-certreqresp", "p7s" => "application/x-pkcs7-signature", "pbm" => "image/x-portable-bitmap", "pdf" => "application/pdf", "pfx" => "application/x-pkcs12", 463 | "pgm" => "image/x-portable-graymap", "pko" => "application/ynd.ms-pkipko", "pma" => "application/x-perfmon", "pmc" => "application/x-perfmon", "pml" => "application/x-perfmon", "pmr" => "application/x-perfmon", "pmw" => "application/x-perfmon", "png" => "image/png", "pnm" => "image/x-portable-anymap", "pot" => "application/vnd.ms-powerpoint", "ppm" => "image/x-portable-pixmap", 464 | "pps" => "application/vnd.ms-powerpoint", "ppt" => "application/vnd.ms-powerpoint", "prf" => "application/pics-rules", "ps" => "application/postscript", "pub" => "application/x-mspublisher", "qt" => "video/quicktime", "ra" => "audio/x-pn-realaudio", "ram" => "audio/x-pn-realaudio", "ras" => "image/x-cmu-raster", "rgb" => "image/x-rgb", 465 | "rmi" => "audio/mid", "roff" => "application/x-troff", "rtf" => "application/rtf", "rtx" => "text/richtext", "scd" => "application/x-msschedule", "sct" => "text/scriptlet", "setpay" => "application/set-payment-initiation", "setreg" => "application/set-registration-initiation", "sh" => "application/x-sh", "shar" => "application/x-shar", 466 | "sit" => "application/x-stuffit", "snd" => "audio/basic", "spc" => "application/x-pkcs7-certificates", "spl" => "application/futuresplash", "src" => "application/x-wais-source", "sst" => "application/vnd.ms-pkicertstore", "stl" => "application/vnd.ms-pkistl", "stm" => "text/html", "svg" => "image/svg+xml", "sv4cpio" => "application/x-sv4cpio", 467 | "sv4crc" => "application/x-sv4crc", "t" => "application/x-troff", "tar" => "application/x-tar", "tcl" => "application/x-tcl", "tex" => "application/x-tex", "texi" => "application/x-texinfo", "texinfo" => "application/x-texinfo", "tgz" => "application/x-compressed", "tif" => "image/tiff", "tiff" => "image/tiff", 468 | "tr" => "application/x-troff", "trm" => "application/x-msterminal", "tsv" => "text/tab-separated-values", "txt" => "text/plain", "uls" => "text/iuls", "ustar" => "application/x-ustar", "vcf" => "text/x-vcard", "vrml" => "x-world/x-vrml", "wav" => "audio/x-wav", "wcm" => "application/vnd.ms-works", 469 | "wdb" => "application/vnd.ms-works", "wks" => "application/vnd.ms-works", "wmf" => "application/x-msmetafile", "wps" => "application/vnd.ms-works", "wri" => "application/x-mswrite", "wrl" => "x-world/x-vrml", "wrz" => "x-world/x-vrml", "xaf" => "x-world/x-vrml", "xbm" => "image/x-xbitmap", "xla" => "application/vnd.ms-excel", 470 | "xlc" => "application/vnd.ms-excel", "xlm" => "application/vnd.ms-excel", "xls" => "application/vnd.ms-excel", "xlt" => "application/vnd.ms-excel", "xlw" => "application/vnd.ms-excel", "xof" => "x-world/x-vrml", "xpm" => "image/x-xpixmap", "xwd" => "image/x-xwindowdump", "z" => "application/x-compress", "zip" => "application/zip"); 471 | } 472 | 473 | class SmushIt 474 | { 475 | const SMUSH_URL = 'http://www.smushit.com/ysmush.it/ws.php?'; 476 | 477 | public $filename; 478 | public $url; 479 | public $compressedUrl; 480 | public $size; 481 | public $compressedSize; 482 | public $savings; 483 | public $error; 484 | 485 | public function __construct($data = null) 486 | { 487 | if(!is_null($data)) 488 | { 489 | if(preg_match('/https?:\/\//', $data) == 1) 490 | $this->smushURL($data); 491 | else 492 | $this->smushFile($data); 493 | } 494 | } 495 | 496 | public function smushURL($url) 497 | { 498 | $this->url = $url; 499 | 500 | $ch = curl_init(); 501 | curl_setopt($ch, CURLOPT_URL, self::SMUSH_URL . 'img=' . $url); 502 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 503 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); 504 | $json_str = curl_exec($ch); 505 | curl_close($ch); 506 | 507 | return $this->parseResponse($json_str); 508 | } 509 | 510 | public function smushFile($filename) 511 | { 512 | $this->filename = $filename; 513 | 514 | if(!is_readable($filename)) 515 | { 516 | $this->error = 'Could not read file'; 517 | return false; 518 | } 519 | 520 | $ch = curl_init(); 521 | curl_setopt($ch, CURLOPT_URL, self::SMUSH_URL); 522 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 523 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); 524 | curl_setopt($ch, CURLOPT_POST, true); 525 | curl_setopt($ch, CURLOPT_POSTFIELDS, array('files' => '@' . $filename)); 526 | $json_str = curl_exec($ch); 527 | curl_close($ch); 528 | 529 | return $this->parseResponse($json_str); 530 | } 531 | 532 | private function parseResponse($json_str) 533 | { 534 | $this->error = null; 535 | $json = json_decode($json_str); 536 | 537 | if(is_null($json)) 538 | { 539 | $this->error = 'Bad response from Smush.it web service'; 540 | return false; 541 | } 542 | 543 | if(isset($json->error)) 544 | { 545 | $this->error = $json->error; 546 | return false; 547 | } 548 | 549 | $this->size = $json->src_size; 550 | $this->compressedUrl = $json->dest; 551 | $this->compressedSize = $json->dest_size; 552 | $this->savings = $json->percent; 553 | return true; 554 | } 555 | } -------------------------------------------------------------------------------- /s3up.old: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | args) < 2) 7 | { 8 | echo "Usage: {$GLOBALS['argv'][0]} [-ctxz] [options] s3-bucket s3-filename filename\n"; 9 | echo " -c Use MD5 checksum to avoid duplicate PUTs, useful for resuming uploads.\n"; 10 | echo " -t Append timestamp to uploaded filename for versioning.\n"; 11 | echo " -x Set far future expiry headers on uploaded file.\n"; 12 | echo " -z Upload a gzipped version in addition to the main file.\n"; 13 | echo "\n"; 14 | echo " --version Appends a timestamp (string) of your choosing to each file.\n"; 15 | echo "\n"; 16 | echo " --key Amazon S3 key\n"; 17 | echo " --secret Amazon S3 private key\n"; 18 | echo "\n"; 19 | echo " If --key and --secret are not specified,\n"; 20 | echo " s3up will read values from the AMZ_KEY\n"; 21 | echo " and AMZ_SECRET environment variables.\n\n"; 22 | exit; 23 | } 24 | 25 | // Get our Amazon credentials... 26 | $AMZ_KEY = $args->flag('key') ? $args->flag('key') : @$_ENV['AMZ_KEY']; 27 | $AMZ_SECRET = $args->flag('secret') ? $args->flag('secret') : @$_ENV['AMZ_SECRET']; 28 | if(strlen($AMZ_KEY) == 0 || strlen($AMZ_SECRET) == 0) 29 | die("Amazon key and private key must both be defined.\n"); 30 | 31 | // Gather our arguments... 32 | $s3_bucket = $args->args[0]; 33 | $s3_filename = $args->args[1]; 34 | 35 | // Accept file to upload via stdin... 36 | if(count($args->args) == 2) 37 | { 38 | $filename = tempnam('/tmp', 's3up'); 39 | $src = fopen('php://stdin', 'r'); 40 | $dest = fopen($filename, 'w'); 41 | stream_copy_to_stream($src, $dest); 42 | upload_static_file($s3_bucket, $s3_filename, $filename, $args); 43 | } 44 | elseif(substr($s3_filename, -1, 1) == '/') // Assume $s3_filename is actually a directory, so upload files "into" it instead 45 | { 46 | $s3_dir = $s3_filename; 47 | if($s3_dir == '/') $s3_dir = ''; 48 | $s3_dir = ltrim($s3_dir, '/'); 49 | 50 | for($i = 2; $i < count($args->args); $i++) 51 | { 52 | $info = pathinfo($args->args[$i]); 53 | if(is_dir($args->args[$i])) 54 | { 55 | upload_directory($s3_bucket, $s3_dir, $info['dirname'], $info['basename'], $args); 56 | } 57 | else 58 | { 59 | if(!file_exists($args->args[$i]) || !is_readable($args->args[$i])) 60 | die("Cannot read {$args->args[$i]}\n"); // TODO: Send to stderr instead 61 | else 62 | upload_static_file($s3_bucket, $s3_dir . $info['basename'], $args->args[$i], $args); 63 | } 64 | } 65 | } 66 | else // Upload a single file... 67 | { 68 | $filename = $args->args[2]; 69 | if(!file_exists($filename) || !is_readable($filename)) 70 | die("Cannot read $filename\n"); 71 | upload_static_file($s3_bucket, $s3_filename, $filename, $args); 72 | } 73 | 74 | // TODO: Can probably refactor this as it contains duplicate code 75 | function upload_directory($bucket, $s3_dir, $local_path, $local_dir, $args) 76 | { 77 | echo "Processing dir: $local_path/$local_dir --> $s3_dir$local_dir\n"; 78 | $dhandle = opendir("$local_path/$local_dir"); 79 | if(!$dhandle) 80 | die("Cannot open directory $local_dir\n"); 81 | 82 | while(false !== ($file = readdir($dhandle))) 83 | { 84 | if($file == '.' || $file == '..') continue; 85 | 86 | $file = "$local_path/$local_dir/$file"; 87 | if(is_dir($file)) 88 | { 89 | upload_directory($bucket, $s3_dir, $local_path, $local_dir . '/' . basename($file), $args); 90 | } 91 | else 92 | { 93 | if(!file_exists($file) || !is_readable($file)) 94 | die("Cannot read $file\n"); 95 | else 96 | upload_static_file($bucket, $s3_dir.$local_dir."/".basename($file), $file, $args); 97 | } 98 | } 99 | closedir($dhandle); 100 | } 101 | 102 | function upload_static_file($bucket, $remote_name, $local_name, $args) 103 | { 104 | global $AMZ_KEY, $AMZ_SECRET; 105 | 106 | // Prepare any additional headers... 107 | $headers = array(); 108 | if($args->flag('x')) 109 | $headers['Expires'] = date('D, j M Y H:i:s', time() + (86400 * 365 * 10)) . ' GMT'; // 10 years 110 | 111 | // Version the file? 112 | if($args->flag('version')) 113 | { 114 | $info = pathinfo($remote_name); 115 | $dir = $info['dirname'] . '/'; 116 | if($dir == './') $dir = ''; 117 | $remote_name = $dir . $info['filename'] . $args->flag('version') . '.' . $info['extension']; 118 | } 119 | elseif($args->flag('t')) 120 | { 121 | $info = pathinfo($remote_name); 122 | $dir = $info['dirname'] . '/'; 123 | if($dir == './') $dir = ''; 124 | $remote_name = $dir . $info['filename'] . '.' . date('YmdHis') . '.' . $info['extension']; 125 | } 126 | 127 | 128 | // Begin the upload proccess 129 | $s3 = new S3($AMZ_KEY, $AMZ_SECRET); 130 | 131 | // Should we check to see if the file has already been uploaded? 132 | $ok_to_upload = true; 133 | if($args->flag('c')) 134 | { 135 | $info = $s3->getObjectInfo($bucket, $remote_name); 136 | if(substr($info['ETag'], 1, -2) == md5_file($local_name)) 137 | { 138 | $ok_to_upload = false; 139 | echo "Skipping (MD5 match): http://$bucket.s3.amazonaws.com/$remote_name\n"; 140 | } 141 | } 142 | 143 | // Actually upload the file... 144 | if($ok_to_upload) 145 | { 146 | if($s3->uploadFile($bucket, $remote_name, $local_name, true, $headers)) 147 | echo "http://$bucket.s3.amazonaws.com/$remote_name\n"; 148 | else 149 | die("Unable to upload '$local_name' to '$remote_name'\n"); 150 | } 151 | 152 | // Upload the gzipped version... 153 | if($args->flag('z')) 154 | { 155 | $info = pathinfo($remote_name); 156 | $dir = $info['dirname'] . '/'; 157 | if($dir == './') $dir = ''; 158 | $remote_name = $dir . $info['filename'] . '.gz.' . $info['extension']; 159 | 160 | $gzname = tempnam('/tmp', 's3up') . '.' . $info['extension']; 161 | shell_exec(sprintf('gzip -c %s > %s', escapeshellarg($local_name), $gzname)); 162 | 163 | $headers['Content-Encoding'] = 'gzip'; 164 | 165 | // Should we check to see if the file has already been uploaded? 166 | $ok_to_upload = true; 167 | if($args->flag('c')) 168 | { 169 | $info = $s3->getObjectInfo($bucket, $remote_name); 170 | if(substr($info['ETag'], 1, -2) == md5_file($local_name)) 171 | { 172 | $ok_to_upload = false; 173 | echo "Skipping (MD5 match): http://$bucket.s3.amazonaws.com/$remote_name\n"; 174 | } 175 | } 176 | 177 | // Actually upload the file... 178 | if($ok_to_upload) 179 | { 180 | if($s3->uploadFile($bucket, $remote_name, $gzname, true, $headers)) 181 | echo "http://$bucket.s3.amazonaws.com/$remote_name\n"; 182 | else 183 | die("Unable to upload '$local_name' to '$remote_name' (gzip version)\n"); 184 | } 185 | } 186 | } 187 | 188 | // ################################################################ 189 | // ################################################################ 190 | // ################################################################ 191 | 192 | class S3 193 | { 194 | private $key; 195 | private $privateKey; 196 | private $host; 197 | private $date; 198 | private $curlInfo; 199 | 200 | public function __construct($key, $private_key, $host = 's3.amazonaws.com') 201 | { 202 | $this->key = $key; 203 | $this->privateKey = $private_key; 204 | $this->host = $host; 205 | $this->date = gmdate('D, d M Y H:i:s T'); 206 | return true; 207 | } 208 | 209 | public function listBuckets() 210 | { 211 | $request = array('verb' => 'GET', 'resource' => '/'); 212 | $result = $this->sendRequest($request); 213 | $xml = simplexml_load_string($result); 214 | 215 | if($xml === false || !isset($xml->Buckets->Bucket)) 216 | return false; 217 | 218 | $buckets = array(); 219 | foreach($xml->Buckets->Bucket as $bucket) 220 | $buckets[] = (string) $bucket->Name; 221 | return $buckets; 222 | } 223 | 224 | public function createBucket($name) 225 | { 226 | $request = array('verb' => 'PUT', 'resource' => "/$name/"); 227 | $result = $this->sendRequest($request); 228 | return $this->curlInfo['http_code'] == '200'; 229 | } 230 | 231 | public function deleteBucket($name) 232 | { 233 | $request = array('verb' => 'DELETE', 'resource' => "/$name/"); 234 | $result = $this->sendRequest($request); 235 | return $this->curlInfo['http_code'] == '204'; 236 | } 237 | 238 | public function getBucketLocation($name) 239 | { 240 | $request = array('verb' => 'GET', 'resource' => "/$name/?location"); 241 | $result = $this->sendRequest($request); 242 | $xml = simplexml_load_string($result); 243 | 244 | if($xml === false) 245 | return false; 246 | 247 | return (string) $xml->LocationConstraint; 248 | } 249 | 250 | public function getBucketContents($name, $prefix = null, $marker = null, $delimeter = null, $max_keys = null) 251 | { 252 | $contents = array(); 253 | 254 | do 255 | { 256 | $q = array(); 257 | if(!is_null($prefix)) $q[] = 'prefix=' . $prefix; 258 | if(!is_null($marker)) $q[] = 'marker=' . $marker; 259 | if(!is_null($delimeter)) $q[] = 'delimeter=' . $delimeter; 260 | if(!is_null($max_keys)) $q[] = 'max-keys=' . $max_keys; 261 | $q = implode('&', $q); 262 | if(strlen($q) > 0) 263 | $q = '?' . $q; 264 | 265 | $request = array('verb' => 'GET', 'resource' => "/$name/$q"); 266 | $result = $this->sendRequest($request); 267 | $xml = simplexml_load_string($result); 268 | 269 | if($xml === false) 270 | return false; 271 | 272 | foreach($xml->Contents as $item) 273 | $contents[(string) $item->Key] = array('LastModified' => (string) $item->LastModified, 'ETag' => (string) $item->ETag, 'Size' => (string) $item->Size); 274 | 275 | $marker = (string) $xml->Marker; 276 | } 277 | while((string) $xml->IsTruncated == 'true' && is_null($max_keys)); 278 | 279 | return $contents; 280 | } 281 | 282 | public function uploadFile($bucket_name, $s3_path, $fs_path, $web_accessible = false, $headers = null) 283 | { 284 | // Some useful headers you can set manually by passing in an associative array... 285 | // Cache-Control 286 | // Content-Type 287 | // Content-Disposition (alternate filename to present during web download) 288 | // Content-Encoding 289 | // x-amz-meta-* 290 | // x-amz-acl (private, public-read, public-read-write, authenticated-read) 291 | 292 | $s3_path = str_replace(' ', '%20', $s3_path); 293 | 294 | $request = array('verb' => 'PUT', 295 | 'resource' => "/$bucket_name/$s3_path", 296 | 'content-md5' => $this->base64(md5_file($fs_path))); 297 | 298 | $fh = fopen($fs_path, 'r'); 299 | $curl_opts = array('CURLOPT_PUT' => true, 300 | 'CURLOPT_INFILE' => $fh, 301 | 'CURLOPT_INFILESIZE' => filesize($fs_path), 302 | 'CURLOPT_CUSTOMREQUEST' => 'PUT'); 303 | 304 | if(is_null($headers)) 305 | $headers = array(); 306 | 307 | $headers['Content-MD5'] = $request['content-md5']; 308 | 309 | if($web_accessible === true && !isset($headers['x-amz-acl'])) 310 | $headers['x-amz-acl'] = 'public-read'; 311 | 312 | if(!isset($headers['Content-Type'])) 313 | { 314 | $ext = pathinfo($fs_path, PATHINFO_EXTENSION); 315 | $headers['Content-Type'] = isset($this->mimeTypes[$ext]) ? $this->mimeTypes[$ext] : 'application/octet-stream'; 316 | } 317 | $request['content-type'] = $headers['Content-Type']; 318 | 319 | $result = $this->sendRequest($request, $headers, $curl_opts); 320 | fclose($fh); 321 | return $this->curlInfo['http_code'] == '200'; 322 | } 323 | 324 | public function deleteObject($bucket_name, $s3_path) 325 | { 326 | $request = array('verb' => 'DELETE', 'resource' => "/$bucket_name/$s3_path"); 327 | $result = $this->sendRequest($request); 328 | return $this->curlInfo['http_code'] == '204'; 329 | } 330 | 331 | public function copyObject($bucket_name, $s3_path, $dest_bucket_name, $dest_s3_path) 332 | { 333 | $request = array('verb' => 'PUT', 'resource' => "/$dest_bucket_name/$dest_s3_path"); 334 | $headers = array('x-amz-copy-source' => "/$bucket_name/$s3_path"); 335 | $result = $this->sendRequest($request, $headers); 336 | 337 | if($this->curlInfo['http_code'] != '200') 338 | return false; 339 | 340 | $xml = simplexml_load_string($result); 341 | if($xml === false) 342 | return false; 343 | 344 | return isset($xml->LastModified); 345 | } 346 | 347 | public function getObjectInfo($bucket_name, $s3_path) 348 | { 349 | $request = array('verb' => 'HEAD', 'resource' => "/$bucket_name/$s3_path"); 350 | $curl_opts = array('CURLOPT_HEADER' => true, 'CURLOPT_NOBODY' => true); 351 | $result = $this->sendRequest($request, null, $curl_opts); 352 | $xml = @simplexml_load_string($result); 353 | 354 | if($xml !== false) 355 | return false; 356 | 357 | preg_match_all('/^(\S*?): (.*?)$/ms', $result, $matches); 358 | $info = array(); 359 | for($i = 0; $i < count($matches[1]); $i++) 360 | $info[$matches[1][$i]] = $matches[2][$i]; 361 | 362 | if(!isset($info['Last-Modified'])) 363 | return false; 364 | 365 | return $info; 366 | } 367 | 368 | public function downloadFile($bucket_name, $s3_path, $fs_path) 369 | { 370 | $request = array('verb' => 'GET', 'resource' => "/$bucket_name/$s3_path"); 371 | 372 | $fh = fopen($fs_path, 'w'); 373 | $curl_opts = array('CURLOPT_FILE' => $fh); 374 | 375 | if(is_null($headers)) 376 | $headers = array(); 377 | 378 | $result = $this->sendRequest($request, $headers, $curl_opts); 379 | fclose($fh); 380 | return $this->curlInfo['http_code'] == '200'; 381 | 382 | } 383 | 384 | public function getAuthenticatedURLRelative($bucket_name, $s3_path, $seconds_till_expires = 3600) 385 | { 386 | return $this->getAuthenticatedURL($bucket_name, $s3_path, gmmktime() + $seconds_till_expires); 387 | } 388 | 389 | public function getAuthenticatedURL($bucket_name, $s3_path, $expires_on) 390 | { 391 | // $expires_on must be a GMT Unix timestamp 392 | 393 | $request = array('verb' => 'GET', 'resource' => "/$bucket_name/$s3_path", 'date' => $expires_on); 394 | $signature = urlencode($this->signature($request)); 395 | 396 | $url = sprintf("http://%s.s3.amazonaws.com/%s?AWSAccessKeyId=%s&Expires=%s&Signature=%s", 397 | $bucket_name, 398 | $s3_path, 399 | $this->key, 400 | $expires_on, 401 | $signature); 402 | return $url; 403 | } 404 | 405 | private function sendRequest($request, $headers = null, $curl_opts = null) 406 | { 407 | if(is_null($headers)) 408 | $headers = array(); 409 | 410 | $headers['Date'] = $this->date; 411 | $headers['Authorization'] = 'AWS ' . $this->key . ':' . $this->signature($request, $headers); 412 | foreach($headers as $k => $v) 413 | $headers[$k] = "$k: $v"; 414 | 415 | $uri = 'http://' . $this->host . $request['resource']; 416 | $ch = curl_init(); 417 | curl_setopt($ch, CURLOPT_URL, $uri); 418 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $request['verb']); 419 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 420 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 421 | // curl_setopt($ch, CURLOPT_VERBOSE, true); 422 | 423 | if(is_array($curl_opts)) 424 | { 425 | foreach($curl_opts as $k => $v) 426 | curl_setopt($ch, constant($k), $v); 427 | } 428 | 429 | // Try request a few times because amazon is crazy sometimes 430 | $result = false; 431 | $retries = 0; 432 | while( ($result === false || $this->curlInfo['http_code'] != '200') && $retries < 3 ) 433 | { 434 | if($retries++ > 0) sleep($retries); 435 | $result = curl_exec($ch); 436 | $this->curlInfo = curl_getinfo($ch); 437 | } 438 | if($retries == 3) 439 | { 440 | fwrite(STDERR, 'Curl error: ' . curl_error($ch) . "\n"); 441 | print_r($this->curlInfo); 442 | fwrite(STDERR,"AWS Response:\n$result"); 443 | } 444 | curl_close($ch); 445 | return $result; 446 | } 447 | 448 | private function signature($request, $headers = null) 449 | { 450 | if(is_null($headers)) 451 | $headers = array(); 452 | 453 | $CanonicalizedAmzHeadersArr = array(); 454 | $CanonicalizedAmzHeadersStr = ''; 455 | foreach($headers as $k => $v) 456 | { 457 | $k = strtolower($k); 458 | 459 | if(substr($k, 0, 5) != 'x-amz') continue; 460 | 461 | if(isset($CanonicalizedAmzHeadersArr[$k])) 462 | $CanonicalizedAmzHeadersArr[$k] .= ',' . trim($v); 463 | else 464 | $CanonicalizedAmzHeadersArr[$k] = trim($v); 465 | } 466 | ksort($CanonicalizedAmzHeadersArr); 467 | 468 | foreach($CanonicalizedAmzHeadersArr as $k => $v) 469 | $CanonicalizedAmzHeadersStr .= "$k:$v\n"; 470 | 471 | $str = $request['verb'] . "\n"; 472 | $str .= isset($request['content-md5']) ? $request['content-md5'] . "\n" : "\n"; 473 | $str .= isset($request['content-type']) ? $request['content-type'] . "\n" : "\n"; 474 | $str .= isset($request['date']) ? $request['date'] . "\n" : $this->date . "\n"; 475 | $str .= $CanonicalizedAmzHeadersStr . preg_replace('/\?.*/', '', $request['resource']); 476 | 477 | $sha1 = $this->hasher($str); 478 | return $this->base64($sha1); 479 | } 480 | 481 | // Algorithm adapted (stolen) from http://pear.php.net/package/Crypt_HMAC/) 482 | private function hasher($data) 483 | { 484 | $key = $this->privateKey; 485 | if(strlen($key) > 64) 486 | $key = pack('H40', sha1($key)); 487 | if(strlen($key) < 64) 488 | $key = str_pad($key, 64, chr(0)); 489 | $ipad = (substr($key, 0, 64) ^ str_repeat(chr(0x36), 64)); 490 | $opad = (substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64)); 491 | return sha1($opad . pack('H40', sha1($ipad . $data))); 492 | } 493 | 494 | private function base64($str) 495 | { 496 | $ret = ''; 497 | for($i = 0; $i < strlen($str); $i += 2) 498 | $ret .= chr(hexdec(substr($str, $i, 2))); 499 | return base64_encode($ret); 500 | } 501 | 502 | private function match($regex, $str, $i = 0) 503 | { 504 | if(preg_match($regex, $str, $match) == 1) 505 | return $match[$i]; 506 | else 507 | return false; 508 | } 509 | 510 | private $mimeTypes = array("323" => "text/h323", "acx" => "application/internet-property-stream", "ai" => "application/postscript", "aif" => "audio/x-aiff", "aifc" => "audio/x-aiff", "aiff" => "audio/x-aiff", 511 | "asf" => "video/x-ms-asf", "asr" => "video/x-ms-asf", "asx" => "video/x-ms-asf", "au" => "audio/basic", "avi" => "video/quicktime", "axs" => "application/olescript", "bas" => "text/plain", "bcpio" => "application/x-bcpio", "bin" => "application/octet-stream", "bmp" => "image/bmp", 512 | "c" => "text/plain", "cat" => "application/vnd.ms-pkiseccat", "cdf" => "application/x-cdf", "cer" => "application/x-x509-ca-cert", "class" => "application/octet-stream", "clp" => "application/x-msclip", "cmx" => "image/x-cmx", "cod" => "image/cis-cod", "cpio" => "application/x-cpio", "crd" => "application/x-mscardfile", 513 | "crl" => "application/pkix-crl", "crt" => "application/x-x509-ca-cert", "csh" => "application/x-csh", "css" => "text/css", "dcr" => "application/x-director", "der" => "application/x-x509-ca-cert", "dir" => "application/x-director", "dll" => "application/x-msdownload", "dms" => "application/octet-stream", "doc" => "application/msword", 514 | "dot" => "application/msword", "dvi" => "application/x-dvi", "dxr" => "application/x-director", "eps" => "application/postscript", "etx" => "text/x-setext", "evy" => "application/envoy", "exe" => "application/octet-stream", "fif" => "application/fractals", "flr" => "x-world/x-vrml", "gif" => "image/gif", 515 | "gtar" => "application/x-gtar", "gz" => "application/x-gzip", "h" => "text/plain", "hdf" => "application/x-hdf", "hlp" => "application/winhlp", "hqx" => "application/mac-binhex40", "hta" => "application/hta", "htc" => "text/x-component", "htm" => "text/html", "html" => "text/html", 516 | "htt" => "text/webviewhtml", "ico" => "image/x-icon", "ief" => "image/ief", "iii" => "application/x-iphone", "ins" => "application/x-internet-signup", "isp" => "application/x-internet-signup", "jfif" => "image/pipeg", "jpe" => "image/jpeg", "jpeg" => "image/jpeg", "jpg" => "image/jpeg", 517 | "js" => "application/x-javascript", "latex" => "application/x-latex", "lha" => "application/octet-stream", "lsf" => "video/x-la-asf", "lsx" => "video/x-la-asf", "lzh" => "application/octet-stream", "m13" => "application/x-msmediaview", "m14" => "application/x-msmediaview", "m3u" => "audio/x-mpegurl", "man" => "application/x-troff-man", 518 | "mdb" => "application/x-msaccess", "me" => "application/x-troff-me", "mht" => "message/rfc822", "mhtml" => "message/rfc822", "mid" => "audio/mid", "mny" => "application/x-msmoney", "mov" => "video/quicktime", "movie" => "video/x-sgi-movie", "mp2" => "video/mpeg", "mp3" => "audio/mpeg", 519 | "mpa" => "video/mpeg", "mpe" => "video/mpeg", "mpeg" => "video/mpeg", "mpg" => "video/mpeg", "mpp" => "application/vnd.ms-project", "mpv2" => "video/mpeg", "ms" => "application/x-troff-ms", "mvb" => "application/x-msmediaview", "nws" => "message/rfc822", "oda" => "application/oda", 520 | "p10" => "application/pkcs10", "p12" => "application/x-pkcs12", "p7b" => "application/x-pkcs7-certificates", "p7c" => "application/x-pkcs7-mime", "p7m" => "application/x-pkcs7-mime", "p7r" => "application/x-pkcs7-certreqresp", "p7s" => "application/x-pkcs7-signature", "pbm" => "image/x-portable-bitmap", "pdf" => "application/pdf", "pfx" => "application/x-pkcs12", 521 | "pgm" => "image/x-portable-graymap", "pko" => "application/ynd.ms-pkipko", "pma" => "application/x-perfmon", "pmc" => "application/x-perfmon", "pml" => "application/x-perfmon", "pmr" => "application/x-perfmon", "pmw" => "application/x-perfmon", "png" => "image/png", "pnm" => "image/x-portable-anymap", "pot" => "application/vnd.ms-powerpoint", "ppm" => "image/x-portable-pixmap", 522 | "pps" => "application/vnd.ms-powerpoint", "ppt" => "application/vnd.ms-powerpoint", "prf" => "application/pics-rules", "ps" => "application/postscript", "pub" => "application/x-mspublisher", "qt" => "video/quicktime", "ra" => "audio/x-pn-realaudio", "ram" => "audio/x-pn-realaudio", "ras" => "image/x-cmu-raster", "rgb" => "image/x-rgb", 523 | "rmi" => "audio/mid", "roff" => "application/x-troff", "rtf" => "application/rtf", "rtx" => "text/richtext", "scd" => "application/x-msschedule", "sct" => "text/scriptlet", "setpay" => "application/set-payment-initiation", "setreg" => "application/set-registration-initiation", "sh" => "application/x-sh", "shar" => "application/x-shar", 524 | "sit" => "application/x-stuffit", "snd" => "audio/basic", "spc" => "application/x-pkcs7-certificates", "spl" => "application/futuresplash", "src" => "application/x-wais-source", "sst" => "application/vnd.ms-pkicertstore", "stl" => "application/vnd.ms-pkistl", "stm" => "text/html", "svg" => "image/svg+xml", "sv4cpio" => "application/x-sv4cpio", 525 | "sv4crc" => "application/x-sv4crc", "t" => "application/x-troff", "tar" => "application/x-tar", "tcl" => "application/x-tcl", "tex" => "application/x-tex", "texi" => "application/x-texinfo", "texinfo" => "application/x-texinfo", "tgz" => "application/x-compressed", "tif" => "image/tiff", "tiff" => "image/tiff", 526 | "tr" => "application/x-troff", "trm" => "application/x-msterminal", "tsv" => "text/tab-separated-values", "txt" => "text/plain", "uls" => "text/iuls", "ustar" => "application/x-ustar", "vcf" => "text/x-vcard", "vrml" => "x-world/x-vrml", "wav" => "audio/x-wav", "wcm" => "application/vnd.ms-works", 527 | "wdb" => "application/vnd.ms-works", "wks" => "application/vnd.ms-works", "wmf" => "application/x-msmetafile", "wps" => "application/vnd.ms-works", "wri" => "application/x-mswrite", "wrl" => "x-world/x-vrml", "wrz" => "x-world/x-vrml", "xaf" => "x-world/x-vrml", "xbm" => "image/x-xbitmap", "xla" => "application/vnd.ms-excel", 528 | "xlc" => "application/vnd.ms-excel", "xlm" => "application/vnd.ms-excel", "xls" => "application/vnd.ms-excel", "xlt" => "application/vnd.ms-excel", "xlw" => "application/vnd.ms-excel", "xof" => "x-world/x-vrml", "xpm" => "image/x-xpixmap", "xwd" => "image/x-xwindowdump", "z" => "application/x-compress", "zip" => "application/zip"); 529 | } 530 | 531 | class Args 532 | { 533 | public $flags; 534 | public $args; 535 | 536 | public function __construct() 537 | { 538 | $this->flags = array(); 539 | $this->args = array(); 540 | 541 | $argv = $GLOBALS['argv']; 542 | array_shift($argv); 543 | 544 | for($i = 0; $i < count($argv); $i++) 545 | { 546 | $str = $argv[$i]; 547 | 548 | // --foo 549 | if(strlen($str) > 2 && substr($str, 0, 2) == '--') 550 | { 551 | $str = substr($str, 2); 552 | $parts = explode('=', $str); 553 | $this->flags[$parts[0]] = true; 554 | 555 | // Does not have an =, so choose the next arg as its value 556 | if(count($parts) == 1 && isset($argv[$i + 1]) && preg_match('/^--?.+/', $argv[$i + 1]) == 0) 557 | { 558 | $this->flags[$parts[0]] = $argv[$i + 1]; 559 | } 560 | elseif(count($parts) == 2) // Has a =, so pick the second piece 561 | { 562 | $this->flags[$parts[0]] = $parts[1]; 563 | } 564 | } 565 | elseif(strlen($str) == 2 && $str[0] == '-') // -a 566 | { 567 | $this->flags[$str[1]] = true; 568 | if(isset($argv[$i + 1]) && preg_match('/^--?.+/', $argv[$i + 1]) == 0) 569 | $this->flags[$str[1]] = $argv[$i + 1]; 570 | } 571 | elseif(strlen($str) > 1 && $str[0] == '-') // -abcdef 572 | { 573 | for($j = 1; $j < strlen($str); $j++) 574 | $this->flags[$str[$j]] = true; 575 | } 576 | } 577 | 578 | for($i = count($argv) - 1; $i >= 0; $i--) 579 | { 580 | if(preg_match('/^--?.+/', $argv[$i]) == 0) 581 | $this->args[] = $argv[$i]; 582 | else 583 | break; 584 | } 585 | 586 | $this->args = array_reverse($this->args); 587 | } 588 | 589 | public function flag($name) 590 | { 591 | return isset($this->flags[$name]) ? $this->flags[$name] : false; 592 | } 593 | } 594 | --------------------------------------------------------------------------------