@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Update S3 external library

Summary:
This fixes at least two issues with the S3 library on newer versions of cURL/PHP:

- NOTICE: PHP message: [2013-07-02 22:15:54] ERROR 8: curl_setopt(): CURLOPT_SSL_VERIFYHOST with value 1 is deprecated and will be removed as of libcurl 7.28.1. It is recommended to use value 2 instead at [/core/lib/phabricator/externals/s3/S3.php:1744]
- `$this->request->body` was appended to without initializing it, which rasies an error on PHP 5.5.0.

I looked over the rest of the changes briefly and they all seem reasonable-ish.

Test Plan:
- Uploaded a file to S3.
- Downloaded a file from S3.
- Deleted a file from S3.
- Checked error logs for anything suspicious.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Differential Revision: https://secure.phabricator.com/D6349

+457 -63
+457 -63
externals/s3/S3.php
··· 2 2 /** 3 3 * $Id$ 4 4 * 5 - * Copyright (c) 2011, Donovan Schönknecht. All rights reserved. 5 + * Copyright (c) 2013, Donovan Schönknecht. All rights reserved. 6 6 * 7 7 * Redistribution and use in source and binary forms, with or without 8 8 * modification, are permitted provided that the following conditions are met: ··· 45 45 const STORAGE_CLASS_STANDARD = 'STANDARD'; 46 46 const STORAGE_CLASS_RRS = 'REDUCED_REDUNDANCY'; 47 47 48 - private static $__accessKey = null; // AWS Access key 49 - private static $__secretKey = null; // AWS Secret key 48 + const SSE_NONE = ''; 49 + const SSE_AES256 = 'AES256'; 50 + 51 + /** 52 + * The AWS Access key 53 + * 54 + * @var string 55 + * @access private 56 + * @static 57 + */ 58 + private static $__accessKey = null; 59 + 60 + /** 61 + * AWS Secret Key 62 + * 63 + * @var string 64 + * @access private 65 + * @static 66 + */ 67 + private static $__secretKey = null; 68 + 69 + /** 70 + * SSL Client key 71 + * 72 + * @var string 73 + * @access private 74 + * @static 75 + */ 50 76 private static $__sslKey = null; 51 - 77 + 78 + /** 79 + * AWS URI 80 + * 81 + * @var string 82 + * @acess public 83 + * @static 84 + */ 52 85 public static $endpoint = 's3.amazonaws.com'; 86 + 87 + /** 88 + * Proxy information 89 + * 90 + * @var null|array 91 + * @access public 92 + * @static 93 + */ 53 94 public static $proxy = null; 54 - 95 + 96 + /** 97 + * Connect using SSL? 98 + * 99 + * @var bool 100 + * @access public 101 + * @static 102 + */ 55 103 public static $useSSL = false; 104 + 105 + /** 106 + * Use SSL validation? 107 + * 108 + * @var bool 109 + * @access public 110 + * @static 111 + */ 56 112 public static $useSSLValidation = true; 113 + 114 + /** 115 + * Use PHP exceptions? 116 + * 117 + * @var bool 118 + * @access public 119 + * @static 120 + */ 57 121 public static $useExceptions = false; 58 122 59 123 // SSL CURL SSL options - only needed if you are experiencing problems with your OpenSSL configuration 124 + 125 + /** 126 + * SSL client key 127 + * 128 + * @var bool 129 + * @access public 130 + * @static 131 + */ 60 132 public static $sslKey = null; 133 + 134 + /** 135 + * SSL client certfificate 136 + * 137 + * @var string 138 + * @acess public 139 + * @static 140 + */ 61 141 public static $sslCert = null; 142 + 143 + /** 144 + * SSL CA cert (only required if you are having problems with your system CA cert) 145 + * 146 + * @var string 147 + * @access public 148 + * @static 149 + */ 62 150 public static $sslCACert = null; 63 - 64 - private static $__signingKeyPairId = null; // AWS Key Pair ID 65 - private static $__signingKeyResource = false; // Key resource, freeSigningKey() must be called to clear it from memory 151 + 152 + /** 153 + * AWS Key Pair ID 154 + * 155 + * @var string 156 + * @access private 157 + * @static 158 + */ 159 + private static $__signingKeyPairId = null; 160 + 161 + /** 162 + * Key resource, freeSigningKey() must be called to clear it from memory 163 + * 164 + * @var bool 165 + * @access private 166 + * @static 167 + */ 168 + private static $__signingKeyResource = false; 66 169 67 170 68 171 /** ··· 71 174 * @param string $accessKey Access key 72 175 * @param string $secretKey Secret key 73 176 * @param boolean $useSSL Enable SSL 177 + * @param string $endpoint Amazon URI 74 178 * @return void 75 179 */ 76 180 public function __construct($accessKey = null, $secretKey = null, $useSSL = false, $endpoint = 's3.amazonaws.com') ··· 83 187 84 188 85 189 /** 86 - * Set the sertvice endpoint 190 + * Set the service endpoint 87 191 * 88 192 * @param string $host Hostname 89 193 * @return void ··· 158 262 */ 159 263 public static function setProxy($host, $user = null, $pass = null, $type = CURLPROXY_SOCKS5) 160 264 { 161 - self::$proxy = array('host' => $host, 'type' => $type, 'user' => null, 'pass' => 'null'); 265 + self::$proxy = array('host' => $host, 'type' => $type, 'user' => $user, 'pass' => $pass); 162 266 } 163 267 164 268 ··· 262 366 } 263 367 264 368 265 - /* 369 + /** 266 370 * Get contents for a bucket 267 371 * 268 372 * If maxKeys is null this method will loop through truncated result sets ··· 327 431 $rest->setParameter('marker', $nextMarker); 328 432 if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter); 329 433 330 - if (($response = $rest->getResponse(true)) == false || $response->code !== 200) break; 434 + if (($response = $rest->getResponse()) == false || $response->code !== 200) break; 331 435 332 436 if (isset($response->body, $response->body->Contents)) 333 437 foreach ($response->body->Contents as $c) ··· 371 475 { 372 476 $dom = new DOMDocument; 373 477 $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration'); 374 - $locationConstraint = $dom->createElement('LocationConstraint', strtoupper($location)); 478 + $locationConstraint = $dom->createElement('LocationConstraint', $location); 375 479 $createBucketConfiguration->appendChild($locationConstraint); 376 480 $dom->appendChild($createBucketConfiguration); 377 481 $rest->data = $dom->saveXML(); ··· 441 545 * @param string $md5sum MD5 hash to send (optional) 442 546 * @return array | false 443 547 */ 444 - public static function inputResource(&$resource, $bufferSize, $md5sum = '') 548 + public static function inputResource(&$resource, $bufferSize = false, $md5sum = '') 445 549 { 446 - if (!is_resource($resource) || $bufferSize < 0) 550 + if (!is_resource($resource) || (int)$bufferSize < 0) 447 551 { 448 552 self::__triggerError('S3::inputResource(): Invalid resource or buffer size', __FILE__, __LINE__); 449 553 return false; 450 554 } 555 + 556 + // Try to figure out the bytesize 557 + if ($bufferSize === false) 558 + { 559 + if (fseek($resource, 0, SEEK_END) < 0 || ($bufferSize = ftell($resource)) === false) 560 + { 561 + self::__triggerError('S3::inputResource(): Unable to obtain resource size', __FILE__, __LINE__); 562 + return false; 563 + } 564 + fseek($resource, 0); 565 + } 566 + 451 567 $input = array('size' => $bufferSize, 'md5sum' => $md5sum); 452 568 $input['fp'] =& $resource; 453 569 return $input; ··· 464 580 * @param array $metaHeaders Array of x-amz-meta-* headers 465 581 * @param array $requestHeaders Array of request headers or content type as a string 466 582 * @param constant $storageClass Storage class constant 583 + * @param constant $serverSideEncryption Server-side encryption 467 584 * @return boolean 468 585 */ 469 - public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD) 586 + public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE) 470 587 { 471 588 if ($input === false) return false; 472 589 $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint); 473 590 474 - if (is_string($input)) $input = array( 591 + if (!is_array($input)) $input = array( 475 592 'data' => $input, 'size' => strlen($input), 476 593 'md5sum' => base64_encode(md5($input, true)) 477 594 ); ··· 514 631 if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class 515 632 $rest->setAmzHeader('x-amz-storage-class', $storageClass); 516 633 634 + if ($serverSideEncryption !== self::SSE_NONE) // Server-side encryption 635 + $rest->setAmzHeader('x-amz-server-side-encryption', $serverSideEncryption); 636 + 517 637 // We need to post with Content-Length and Content-Type, MD5 is optional 518 638 if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false)) 519 639 { ··· 528 648 529 649 if ($rest->response->error === false && $rest->response->code !== 200) 530 650 $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status'); 531 - 532 651 if ($rest->response->error !== false) 533 652 { 534 653 self::__triggerError(sprintf("S3::putObject(): [%s] %s", ··· 635 754 /** 636 755 * Copy an object 637 756 * 638 - * @param string $bucket Source bucket name 639 - * @param string $uri Source object URI 757 + * @param string $srcBucket Source bucket name 758 + * @param string $srcUri Source object URI 640 759 * @param string $bucket Destination bucket name 641 760 * @param string $uri Destination object URI 642 761 * @param constant $acl ACL constant ··· 653 772 foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v); 654 773 if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class 655 774 $rest->setAmzHeader('x-amz-storage-class', $storageClass); 656 - $rest->setAmzHeader('x-amz-acl', $acl); // Added rawurlencode() for $srcUri (thanks a.yamanoi) 775 + $rest->setAmzHeader('x-amz-acl', $acl); 657 776 $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, rawurlencode($srcUri))); 658 777 if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0) 659 778 $rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE'); ··· 675 794 676 795 677 796 /** 797 + * Set up a bucket redirection 798 + * 799 + * @param string $bucket Bucket name 800 + * @param string $location Target host name 801 + * @return boolean 802 + */ 803 + public static function setBucketRedirect($bucket = NULL, $location = NULL) 804 + { 805 + $rest = new S3Request('PUT', $bucket, '', self::$endpoint); 806 + 807 + if( empty($bucket) || empty($location) ) { 808 + self::__triggerError("S3::setBucketRedirect({$bucket}, {$location}): Empty parameter.", __FILE__, __LINE__); 809 + return false; 810 + } 811 + 812 + $dom = new DOMDocument; 813 + $websiteConfiguration = $dom->createElement('WebsiteConfiguration'); 814 + $redirectAllRequestsTo = $dom->createElement('RedirectAllRequestsTo'); 815 + $hostName = $dom->createElement('HostName', $location); 816 + $redirectAllRequestsTo->appendChild($hostName); 817 + $websiteConfiguration->appendChild($redirectAllRequestsTo); 818 + $dom->appendChild($websiteConfiguration); 819 + $rest->setParameter('website', null); 820 + $rest->data = $dom->saveXML(); 821 + $rest->size = strlen($rest->data); 822 + $rest->setHeader('Content-Type', 'application/xml'); 823 + $rest = $rest->getResponse(); 824 + 825 + if ($rest->error === false && $rest->code !== 200) 826 + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 827 + if ($rest->error !== false) 828 + { 829 + self::__triggerError(sprintf("S3::setBucketRedirect({$bucket}, {$location}): [%s] %s", 830 + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); 831 + return false; 832 + } 833 + return true; 834 + } 835 + 836 + 837 + /** 678 838 * Set logging for a bucket 679 839 * 680 840 * @param string $bucket Bucket name ··· 729 889 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 730 890 if ($rest->error !== false) 731 891 { 732 - self::__triggerError(sprintf("S3::setBucketLogging({$bucket}, {$uri}): [%s] %s", 892 + self::__triggerError(sprintf("S3::setBucketLogging({$bucket}, {$targetBucket}): [%s] %s", 733 893 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); 734 894 return false; 735 895 } ··· 966 1126 public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false) 967 1127 { 968 1128 $expires = time() + $lifetime; 969 - $uri = str_replace('%2F', '/', rawurlencode($uri)); // URI should be encoded (thanks Sean O'Dea) 1129 + $uri = str_replace(array('%2F', '%2B'), array('/', '+'), rawurlencode($uri)); 970 1130 return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s', 971 - $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires, 1131 + // $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires, 1132 + $hostBucket ? $bucket : self::$endpoint.'/'.$bucket, $uri, self::$__accessKey, $expires, 972 1133 urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}"))); 973 1134 } 974 1135 ··· 989 1150 $signature = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($signature)); 990 1151 991 1152 $url = $policy['Statement'][0]['Resource'] . '?'; 992 - 993 1153 foreach (array('Policy' => $encoded, 'Signature' => $signature, 'Key-Pair-Id' => self::$__signingKeyPairId) as $k => $v) 994 1154 $url .= $k.'='.str_replace('%2F', '/', rawurlencode($v)).'&'; 995 1155 return substr($url, 0, -1); ··· 999 1159 /** 1000 1160 * Get a CloudFront canned policy URL 1001 1161 * 1002 - * @param string $string URL to sign 1162 + * @param string $url URL to sign 1003 1163 * @param integer $lifetime URL lifetime 1004 1164 * @return string 1005 1165 */ ··· 1390 1550 * 1391 1551 * @internal Used to create XML in invalidateDistribution() 1392 1552 * @param array $paths Paths to objects to invalidateDistribution 1553 + * @param int $callerReference 1393 1554 * @return string 1394 1555 */ 1395 - private static function __getCloudFrontInvalidationBatchXML($paths, $callerReference = '0') { 1556 + private static function __getCloudFrontInvalidationBatchXML($paths, $callerReference = '0') 1557 + { 1396 1558 $dom = new DOMDocument('1.0', 'UTF-8'); 1397 1559 $dom->formatOutput = true; 1398 1560 $invalidationBatch = $dom->createElement('InvalidationBatch'); ··· 1406 1568 1407 1569 1408 1570 /** 1571 + * List your invalidation batches for invalidateDistribution() in a CloudFront distribution 1572 + * 1573 + * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/ListInvalidation.html 1574 + * returned array looks like this: 1575 + * Array 1576 + * ( 1577 + * [I31TWB0CN9V6XD] => InProgress 1578 + * [IT3TFE31M0IHZ] => Completed 1579 + * [I12HK7MPO1UQDA] => Completed 1580 + * [I1IA7R6JKTC3L2] => Completed 1581 + * ) 1582 + * 1583 + * @param string $distributionId Distribution ID from listDistributions() 1584 + * @return array 1585 + */ 1586 + public static function getDistributionInvalidationList($distributionId) 1587 + { 1588 + if (!extension_loaded('openssl')) 1589 + { 1590 + self::__triggerError(sprintf("S3::getDistributionInvalidationList(): [%s] %s", 1591 + "CloudFront functionality requires SSL"), __FILE__, __LINE__); 1592 + return false; 1593 + } 1594 + 1595 + $useSSL = self::$useSSL; 1596 + self::$useSSL = true; // CloudFront requires SSL 1597 + $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com'); 1598 + $rest = self::__getCloudFrontResponse($rest); 1599 + self::$useSSL = $useSSL; 1600 + 1601 + if ($rest->error === false && $rest->code !== 200) 1602 + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 1603 + if ($rest->error !== false) 1604 + { 1605 + trigger_error(sprintf("S3::getDistributionInvalidationList('{$distributionId}'): [%s]", 1606 + $rest->error['code'], $rest->error['message']), E_USER_WARNING); 1607 + return false; 1608 + } 1609 + elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->InvalidationSummary)) 1610 + { 1611 + $list = array(); 1612 + foreach ($rest->body->InvalidationSummary as $summary) 1613 + $list[(string)$summary->Id] = (string)$summary->Status; 1614 + 1615 + return $list; 1616 + } 1617 + return array(); 1618 + } 1619 + 1620 + 1621 + /** 1409 1622 * Get a DistributionConfig DOMDocument 1410 1623 * 1411 1624 * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?PutConfig.html ··· 1546 1759 /** 1547 1760 * Get MIME type for file 1548 1761 * 1762 + * To override the putObject() Content-Type, add it to $requestHeaders 1763 + * 1764 + * To use fileinfo, ensure the MAGIC environment variable is set 1765 + * 1549 1766 * @internal Used to get mime types 1550 1767 * @param string &$file File path 1551 1768 * @return string 1552 1769 */ 1553 - public static function __getMimeType(&$file) 1770 + private static function __getMimeType(&$file) 1554 1771 { 1555 - $type = false; 1556 - // Fileinfo documentation says fileinfo_open() will use the 1557 - // MAGIC env var for the magic file 1772 + // Use fileinfo if available 1558 1773 if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) && 1559 1774 ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false) 1560 1775 { ··· 1567 1782 $type = trim(array_shift($type)); 1568 1783 } 1569 1784 finfo_close($finfo); 1785 + if ($type !== false && strlen($type) > 0) return $type; 1786 + } 1570 1787 1571 - // If anyone is still using mime_content_type() 1572 - } elseif (function_exists('mime_content_type')) 1573 - $type = trim(mime_content_type($file)); 1574 - 1575 - if ($type !== false && strlen($type) > 0) return $type; 1576 - 1577 - // Otherwise do it the old fashioned way 1578 1788 static $exts = array( 1579 - 'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png', 1580 - 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon', 1581 - 'swf' => 'application/x-shockwave-flash', 'pdf' => 'application/pdf', 1789 + 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'gif' => 'image/gif', 1790 + 'png' => 'image/png', 'ico' => 'image/x-icon', 'pdf' => 'application/pdf', 1791 + 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'svg' => 'image/svg+xml', 1792 + 'svgz' => 'image/svg+xml', 'swf' => 'application/x-shockwave-flash', 1582 1793 'zip' => 'application/zip', 'gz' => 'application/x-gzip', 1583 1794 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip', 1584 - 'bz2' => 'application/x-bzip2', 'txt' => 'text/plain', 1795 + 'bz2' => 'application/x-bzip2', 'rar' => 'application/x-rar-compressed', 1796 + 'exe' => 'application/x-msdownload', 'msi' => 'application/x-msdownload', 1797 + 'cab' => 'application/vnd.ms-cab-compressed', 'txt' => 'text/plain', 1585 1798 'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html', 1586 1799 'css' => 'text/css', 'js' => 'text/javascript', 1587 1800 'xml' => 'text/xml', 'xsl' => 'application/xsl+xml', ··· 1589 1802 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg', 1590 1803 'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php' 1591 1804 ); 1592 - $ext = strtolower(pathInfo($file, PATHINFO_EXTENSION)); 1593 - return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream'; 1805 + $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); 1806 + // mime_content_type() is deprecated, fileinfo should be configured 1807 + $type = isset($exts[$ext]) ? $exts[$ext] : trim(mime_content_type($file)); 1808 + 1809 + return ($type !== false && strlen($type) > 0) ? $type : 'application/octet-stream'; 1594 1810 } 1595 1811 1596 1812 ··· 1627 1843 1628 1844 } 1629 1845 1846 + /** 1847 + * S3 Request class 1848 + * 1849 + * @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class 1850 + * @version 0.5.0-dev 1851 + */ 1630 1852 final class S3Request 1631 1853 { 1632 - private $endpoint, $verb, $bucket, $uri, $resource = '', $parameters = array(), 1633 - $amzHeaders = array(), $headers = array( 1854 + /** 1855 + * AWS URI 1856 + * 1857 + * @var string 1858 + * @access pricate 1859 + */ 1860 + private $endpoint; 1861 + 1862 + /** 1863 + * Verb 1864 + * 1865 + * @var string 1866 + * @access private 1867 + */ 1868 + private $verb; 1869 + 1870 + /** 1871 + * S3 bucket name 1872 + * 1873 + * @var string 1874 + * @access private 1875 + */ 1876 + private $bucket; 1877 + 1878 + /** 1879 + * Object URI 1880 + * 1881 + * @var string 1882 + * @access private 1883 + */ 1884 + private $uri; 1885 + 1886 + /** 1887 + * Final object URI 1888 + * 1889 + * @var string 1890 + * @access private 1891 + */ 1892 + private $resource = ''; 1893 + 1894 + /** 1895 + * Additional request parameters 1896 + * 1897 + * @var array 1898 + * @access private 1899 + */ 1900 + private $parameters = array(); 1901 + 1902 + /** 1903 + * Amazon specific request headers 1904 + * 1905 + * @var array 1906 + * @access private 1907 + */ 1908 + private $amzHeaders = array(); 1909 + 1910 + /** 1911 + * HTTP request headers 1912 + * 1913 + * @var array 1914 + * @access private 1915 + */ 1916 + private $headers = array( 1634 1917 'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => '' 1635 1918 ); 1636 - public $fp = false, $size = 0, $data = false, $response; 1919 + 1920 + /** 1921 + * Use HTTP PUT? 1922 + * 1923 + * @var bool 1924 + * @access public 1925 + */ 1926 + public $fp = false; 1927 + 1928 + /** 1929 + * PUT file size 1930 + * 1931 + * @var int 1932 + * @access public 1933 + */ 1934 + public $size = 0; 1935 + 1936 + /** 1937 + * PUT post fields 1938 + * 1939 + * @var array 1940 + * @access public 1941 + */ 1942 + public $data = false; 1943 + 1944 + /** 1945 + * S3 request respone 1946 + * 1947 + * @var object 1948 + * @access public 1949 + */ 1950 + public $response; 1637 1951 1638 1952 1639 1953 /** ··· 1642 1956 * @param string $verb Verb 1643 1957 * @param string $bucket Bucket name 1644 1958 * @param string $uri Object URI 1959 + * @param string $endpoint AWS endpoint URI 1645 1960 * @return mixed 1646 1961 */ 1647 1962 function __construct($verb, $bucket = '', $uri = '', $endpoint = 's3.amazonaws.com') 1648 1963 { 1964 + 1649 1965 $this->endpoint = $endpoint; 1650 1966 $this->verb = $verb; 1651 1967 $this->bucket = $bucket; 1652 1968 $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/'; 1969 + 1970 + //if ($this->bucket !== '') 1971 + // $this->resource = '/'.$this->bucket.$this->uri; 1972 + //else 1973 + // $this->resource = $this->uri; 1653 1974 1654 1975 if ($this->bucket !== '') 1655 1976 { 1656 - $this->headers['Host'] = $this->bucket.'.'.$this->endpoint; 1657 - $this->resource = '/'.$this->bucket.$this->uri; 1977 + if ($this->__dnsBucketName($this->bucket)) 1978 + { 1979 + $this->headers['Host'] = $this->bucket.'.'.$this->endpoint; 1980 + $this->resource = '/'.$this->bucket.$this->uri; 1981 + } 1982 + else 1983 + { 1984 + $this->headers['Host'] = $this->endpoint; 1985 + $this->uri = $this->uri; 1986 + if ($this->bucket !== '') $this->uri = '/'.$this->bucket.$this->uri; 1987 + $this->bucket = ''; 1988 + $this->resource = $this->uri; 1989 + } 1658 1990 } 1659 1991 else 1660 1992 { 1661 1993 $this->headers['Host'] = $this->endpoint; 1662 1994 $this->resource = $this->uri; 1663 1995 } 1664 - $this->headers['Date'] = gmdate('D, d M Y H:i:s T'); 1996 + 1665 1997 1998 + $this->headers['Date'] = gmdate('D, d M Y H:i:s T'); 1666 1999 $this->response = new STDClass; 1667 2000 $this->response->error = false; 2001 + $this->response->body = null; 2002 + $this->response->headers = array(); 1668 2003 } 1669 2004 1670 2005 ··· 1720 2055 $query = substr($this->uri, -1) !== '?' ? '?' : '&'; 1721 2056 foreach ($this->parameters as $var => $value) 1722 2057 if ($value == null || $value == '') $query .= $var.'&'; 1723 - // Parameters should be encoded (thanks Sean O'Dea) 1724 2058 else $query .= $var.'='.rawurlencode($value).'&'; 1725 2059 $query = substr($query, 0, -1); 1726 2060 $this->uri .= $query; ··· 1728 2062 if (array_key_exists('acl', $this->parameters) || 1729 2063 array_key_exists('location', $this->parameters) || 1730 2064 array_key_exists('torrent', $this->parameters) || 2065 + array_key_exists('website', $this->parameters) || 1731 2066 array_key_exists('logging', $this->parameters)) 1732 2067 $this->resource .= $query; 1733 2068 } 1734 - $url = (S3::$useSSL ? 'https://' : 'http://') . $this->headers['Host'].$this->uri; 1735 - //var_dump($this->bucket, $this->uri, $this->resource, $url); 2069 + $url = (S3::$useSSL ? 'https://' : 'http://') . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint) . $this->uri; 2070 + 2071 + //var_dump('bucket: ' . $this->bucket, 'uri: ' . $this->uri, 'resource: ' . $this->resource, 'url: ' . $url); 1736 2072 1737 2073 // Basic setup 1738 2074 $curl = curl_init(); ··· 1741 2077 if (S3::$useSSL) 1742 2078 { 1743 2079 // SSL Validation can now be optional for those with broken OpenSSL installations 1744 - curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, S3::$useSSLValidation ? 1 : 0); 2080 + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, S3::$useSSLValidation ? 2 : 0); 1745 2081 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, S3::$useSSLValidation ? 1 : 0); 1746 2082 1747 2083 if (S3::$sslKey !== null) curl_setopt($curl, CURLOPT_SSLKEY, S3::$sslKey); ··· 1755 2091 { 1756 2092 curl_setopt($curl, CURLOPT_PROXY, S3::$proxy['host']); 1757 2093 curl_setopt($curl, CURLOPT_PROXYTYPE, S3::$proxy['type']); 1758 - if (isset(S3::$proxy['user'], S3::$proxy['pass']) && $proxy['user'] != null && $proxy['pass'] != null) 2094 + if (isset(S3::$proxy['user'], S3::$proxy['pass']) && S3::$proxy['user'] != null && S3::$proxy['pass'] != null) 1759 2095 curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', S3::$proxy['user'], S3::$proxy['pass'])); 1760 2096 } 1761 2097 ··· 1773 2109 // AMZ headers must be sorted 1774 2110 if (sizeof($amz) > 0) 1775 2111 { 1776 - sort($amz); 2112 + //sort($amz); 2113 + usort($amz, array(&$this, '__sortMetaHeadersCmp')); 1777 2114 $amz = "\n".implode("\n", $amz); 1778 2115 } else $amz = ''; 1779 2116 1780 2117 if (S3::hasAuth()) 1781 2118 { 1782 2119 // Authorization string (CloudFront stringToSign should only contain a date) 1783 - $headers[] = 'Authorization: ' . S3::__getSignature( 1784 - $this->headers['Host'] == 'cloudfront.amazonaws.com' ? $this->headers['Date'] : 1785 - $this->verb."\n".$this->headers['Content-MD5']."\n". 1786 - $this->headers['Content-Type']."\n".$this->headers['Date'].$amz."\n".$this->resource 1787 - ); 1788 - } 2120 + if ($this->headers['Host'] == 'cloudfront.amazonaws.com') 2121 + $headers[] = 'Authorization: ' . S3::__getSignature($this->headers['Date']); 2122 + else 2123 + { 2124 + $headers[] = 'Authorization: ' . S3::__getSignature( 2125 + $this->verb."\n". 2126 + $this->headers['Content-MD5']."\n". 2127 + $this->headers['Content-Type']."\n". 2128 + $this->headers['Date'].$amz."\n". 2129 + $this->resource 2130 + ); 2131 + } 2132 + } 1789 2133 1790 2134 curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); 1791 2135 curl_setopt($curl, CURLOPT_HEADER, false); ··· 1862 2206 return $this->response; 1863 2207 } 1864 2208 2209 + /** 2210 + * Sort compare for meta headers 2211 + * 2212 + * @internal Used to sort x-amz meta headers 2213 + * @param string $a String A 2214 + * @param string $b String B 2215 + * @return integer 2216 + */ 2217 + private function __sortMetaHeadersCmp($a, $b) 2218 + { 2219 + $lenA = strpos($a, ':'); 2220 + $lenB = strpos($b, ':'); 2221 + $minLen = min($lenA, $lenB); 2222 + $ncmp = strncmp($a, $b, $minLen); 2223 + if ($lenA == $lenB) return $ncmp; 2224 + if (0 == $ncmp) return $lenA < $lenB ? -1 : 1; 2225 + return $ncmp; 2226 + } 1865 2227 1866 2228 /** 1867 2229 * CURL write callback ··· 1881 2243 1882 2244 1883 2245 /** 2246 + * Check DNS conformity 2247 + * 2248 + * @param string $bucket Bucket name 2249 + * @return boolean 2250 + */ 2251 + private function __dnsBucketName($bucket) 2252 + { 2253 + if (strlen($bucket) > 63 || preg_match("/[^a-z0-9\.-]/", $bucket) > 0) return false; 2254 + if (strstr($bucket, '-.') !== false) return false; 2255 + if (strstr($bucket, '..') !== false) return false; 2256 + if (!preg_match("/^[0-9a-z]/", $bucket)) return false; 2257 + if (!preg_match("/[0-9a-z]$/", $bucket)) return false; 2258 + return true; 2259 + } 2260 + 2261 + 2262 + /** 1884 2263 * CURL header callback 1885 2264 * 1886 2265 * @param resource &$curl CURL resource ··· 1906 2285 elseif ($header == 'ETag') 1907 2286 $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value; 1908 2287 elseif (preg_match('/^x-amz-meta-.*$/', $header)) 1909 - $this->response->headers[$header] = is_numeric($value) ? (int)$value : $value; 2288 + $this->response->headers[$header] = $value; 1910 2289 } 1911 2290 return $strlen; 1912 2291 } 1913 2292 1914 2293 } 2294 + 2295 + /** 2296 + * S3 exception class 2297 + * 2298 + * @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class 2299 + * @version 0.5.0-dev 2300 + */ 1915 2301 1916 2302 class S3Exception extends Exception { 2303 + /** 2304 + * Class constructor 2305 + * 2306 + * @param string $message Exception message 2307 + * @param string $file File in which exception was created 2308 + * @param string $line Line number on which exception was created 2309 + * @param int $code Exception code 2310 + */ 1917 2311 function __construct($message, $file, $line, $code = 0) 1918 2312 { 1919 2313 parent::__construct($message, $code);