SCS.php 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307
  1. <?php
  2. /**
  3. * $Id$
  4. *
  5. * Copyright (c) 2014, Sina Cloud Storage. All rights reserved.
  6. * @author Bruce Chen <662005@qq.com>
  7. * @link http://weibo.com/smcz
  8. */
  9. /**
  10. * Sina Cloud Storage PHP class
  11. *
  12. * @link http://weibo.com/smcz
  13. * @version 0.1.0-dev
  14. */
  15. class SCS
  16. {
  17. // ACL flags
  18. const ACL_PRIVATE = 'private';
  19. const ACL_PUBLIC_READ = 'public-read';
  20. const ACL_PUBLIC_READ_WRITE = 'public-read-write';
  21. const ACL_AUTHENTICATED_READ = 'authenticated-read';
  22. const STORAGE_CLASS_STANDARD = 'STANDARD';
  23. const STORAGE_CLASS_RRS = 'REDUCED_REDUNDANCY';
  24. const SSE_NONE = '';
  25. const SSE_AES256 = 'AES256';
  26. /**
  27. * The SCS Access key
  28. *
  29. * @var string
  30. * @access private
  31. * @static
  32. */
  33. private static $__accessKey = null;
  34. /**
  35. * SCS Secret Key
  36. *
  37. * @var string
  38. * @access private
  39. * @static
  40. */
  41. private static $__secretKey = null;
  42. /**
  43. * SSL Client key
  44. *
  45. * @var string
  46. * @access private
  47. * @static
  48. */
  49. private static $__sslKey = null;
  50. /**
  51. * SCS URI
  52. *
  53. * @var string
  54. * @acess public
  55. * @static
  56. */
  57. public static $endpoint = 'sinacloud.net';
  58. /**
  59. * Proxy information
  60. *
  61. * @var null|array
  62. * @access public
  63. * @static
  64. */
  65. public static $proxy = null;
  66. /**
  67. * Connect using SSL?
  68. *
  69. * @var bool
  70. * @access public
  71. * @static
  72. */
  73. public static $useSSL = false;
  74. /**
  75. * Use SSL validation?
  76. *
  77. * @var bool
  78. * @access public
  79. * @static
  80. */
  81. public static $useSSLValidation = true;
  82. /**
  83. * Use PHP exceptions?
  84. *
  85. * @var bool
  86. * @access public
  87. * @static
  88. */
  89. public static $useExceptions = false;
  90. /**
  91. * Time offset applied to time()
  92. * @access private
  93. * @static
  94. */
  95. private static $__timeOffset = 5;
  96. /**
  97. * SSL client key
  98. *
  99. * @var bool
  100. * @access public
  101. * @static
  102. */
  103. public static $sslKey = null;
  104. /**
  105. * SSL client certfificate
  106. *
  107. * @var string
  108. * @acess public
  109. * @static
  110. */
  111. public static $sslCert = null;
  112. /**
  113. * SSL CA cert (only required if you are having problems with your system CA cert)
  114. *
  115. * @var string
  116. * @access public
  117. * @static
  118. */
  119. public static $sslCACert = null;
  120. /**
  121. * SCS Key Pair ID
  122. *
  123. * @var string
  124. * @access private
  125. * @static
  126. */
  127. private static $__signingKeyPairId = null;
  128. /**
  129. * Key resource, freeSigningKey() must be called to clear it from memory
  130. *
  131. * @var bool
  132. * @access private
  133. * @static
  134. */
  135. private static $__signingKeyResource = false;
  136. /**
  137. * Constructor - if you're not using the class statically
  138. *
  139. * @param string $accessKey Access key
  140. * @param string $secretKey Secret key
  141. * @param boolean $useSSL Enable SSL
  142. * @param string $endpoint Amazon URI
  143. * @return void
  144. */
  145. public function __construct($accessKey = null, $secretKey = null, $useSSL = false, $endpoint = 'sinacloud.net')
  146. {
  147. if ($accessKey !== null && $secretKey !== null)
  148. self::setAuth($accessKey, $secretKey);
  149. self::$useSSL = $useSSL;
  150. self::$endpoint = $endpoint;
  151. }
  152. /**
  153. * Set the service endpoint
  154. *
  155. * @param string $host Hostname
  156. * @return void
  157. */
  158. public function setEndpoint($host)
  159. {
  160. self::$endpoint = $host;
  161. }
  162. /**
  163. * Set SCS access key and secret key
  164. *
  165. * @param string $accessKey Access key
  166. * @param string $secretKey Secret key
  167. * @return void
  168. */
  169. public static function setAuth($accessKey, $secretKey)
  170. {
  171. self::$__accessKey = $accessKey;
  172. self::$__secretKey = $secretKey;
  173. }
  174. /**
  175. * Check if SCS keys have been set
  176. *
  177. * @return boolean
  178. */
  179. public static function hasAuth() {
  180. return (self::$__accessKey !== null && self::$__secretKey !== null);
  181. }
  182. /**
  183. * Set SSL on or off
  184. *
  185. * @param boolean $enabled SSL enabled
  186. * @param boolean $validate SSL certificate validation
  187. * @return void
  188. */
  189. public static function setSSL($enabled, $validate = true)
  190. {
  191. self::$useSSL = $enabled;
  192. self::$useSSLValidation = $validate;
  193. }
  194. /**
  195. * Set SSL client certificates (experimental)
  196. *
  197. * @param string $sslCert SSL client certificate
  198. * @param string $sslKey SSL client key
  199. * @param string $sslCACert SSL CA cert (only required if you are having problems with your system CA cert)
  200. * @return void
  201. */
  202. public static function setSSLAuth($sslCert = null, $sslKey = null, $sslCACert = null)
  203. {
  204. self::$sslCert = $sslCert;
  205. self::$sslKey = $sslKey;
  206. self::$sslCACert = $sslCACert;
  207. }
  208. /**
  209. * Set proxy information
  210. *
  211. * @param string $host Proxy hostname and port (localhost:1234)
  212. * @param string $user Proxy username
  213. * @param string $pass Proxy password
  214. * @param constant $type CURL proxy type
  215. * @return void
  216. */
  217. public static function setProxy($host, $user = null, $pass = null, $type = CURLPROXY_SOCKS5)
  218. {
  219. self::$proxy = array('host' => $host, 'type' => $type, 'user' => $user, 'pass' => $pass);
  220. }
  221. /**
  222. * Set the error mode to exceptions
  223. *
  224. * @param boolean $enabled Enable exceptions
  225. * @return void
  226. */
  227. public static function setExceptions($enabled = true)
  228. {
  229. self::$useExceptions = $enabled;
  230. }
  231. /**
  232. * Set SCS time correction offset (use carefully)
  233. *
  234. * This can be used when an inaccurate system time is generating
  235. * invalid request signatures. It should only be used as a last
  236. * resort when the system time cannot be changed.
  237. *
  238. * @param string $offset Time offset (set to zero to use SCS server time)
  239. * @return void
  240. */
  241. public static function setTimeCorrectionOffset($offset = 0)
  242. {
  243. if ($offset == 0)
  244. {
  245. $rest = new SCSRequest('HEAD');
  246. $rest = $rest->getResponse();
  247. $awstime = $rest->headers['date'];
  248. $systime = time();
  249. $offset = $systime > $awstime ? -($systime - $awstime) : ($awstime - $systime);
  250. }
  251. self::$__timeOffset = $offset;
  252. }
  253. /**
  254. * Set signing key
  255. *
  256. * @param string $keyPairId SCS Key Pair ID
  257. * @param string $signingKey Private Key
  258. * @param boolean $isFile Load private key from file, set to false to load string
  259. * @return boolean
  260. */
  261. public static function setSigningKey($keyPairId, $signingKey, $isFile = true)
  262. {
  263. self::$__signingKeyPairId = $keyPairId;
  264. if ((self::$__signingKeyResource = openssl_pkey_get_private($isFile ?
  265. file_get_contents($signingKey) : $signingKey)) !== false) return true;
  266. self::__triggerError('SCS::setSigningKey(): Unable to open load private key: '.$signingKey, __FILE__, __LINE__);
  267. return false;
  268. }
  269. /**
  270. * Free signing key from memory, MUST be called if you are using setSigningKey()
  271. *
  272. * @return void
  273. */
  274. public static function freeSigningKey()
  275. {
  276. if (self::$__signingKeyResource !== false)
  277. openssl_free_key(self::$__signingKeyResource);
  278. }
  279. /**
  280. * Internal error handler
  281. *
  282. * @internal Internal error handler
  283. * @param string $message Error message
  284. * @param string $file Filename
  285. * @param integer $line Line number
  286. * @param integer $code Error code
  287. * @return void
  288. */
  289. private static function __triggerError($message, $file, $line, $code = 0)
  290. {
  291. if (self::$useExceptions)
  292. throw new SCSException($message, $file, $line, $code);
  293. else
  294. trigger_error($message, E_USER_WARNING);
  295. }
  296. /**
  297. * Get a list of buckets
  298. *
  299. * @param boolean $detailed Returns detailed bucket list when true
  300. * @return array | false
  301. */
  302. public static function listBuckets($detailed = false)
  303. {
  304. $rest = new SCSRequest('GET', '', '', self::$endpoint);
  305. $rest = $rest->getResponse();
  306. if ($rest->error === false && $rest->code !== 200)
  307. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  308. if ($rest->error !== false)
  309. {
  310. self::__triggerError(sprintf("SCS::listBuckets(): [%s] %s", $rest->error['code'],
  311. $rest->error['message']), __FILE__, __LINE__);
  312. return false;
  313. }
  314. $results = array();
  315. if (!isset($rest->body->Buckets)) return $results;
  316. if ($detailed)
  317. {
  318. if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
  319. $results['owner'] = array(
  320. 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
  321. );
  322. $results['buckets'] = array();
  323. //foreach ($rest->body->Buckets->Bucket as $b)
  324. foreach ($rest->body->Buckets as $b)
  325. $results['buckets'][] = array(
  326. 'name' => (string)$b->Name, 'time' => strtotime((string)$b->CreationDate), 'consumed_bytes' => (string)$b->ConsumedBytes
  327. );
  328. } else
  329. foreach ($rest->body->Buckets as $b) $results[] = (string)$b->Name;
  330. //foreach ($rest->body->Buckets->Bucket as $b) $results[] = (string)$b->Name;
  331. return $results;
  332. }
  333. /**
  334. * Get contents for a bucket
  335. *
  336. * If maxKeys is null this method will loop through truncated result sets
  337. *
  338. * @param string $bucket Bucket name
  339. * @param string $prefix Prefix
  340. * @param string $marker Marker (last file listed)
  341. * @param string $maxKeys Max keys (maximum number of keys to return)
  342. * @param string $delimiter Delimiter
  343. * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes
  344. * @param boolean &$isTruncated
  345. * @return array | false
  346. */
  347. public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false, &$nextMarker = null, &$isTruncated = false)
  348. {
  349. $rest = new SCSRequest('GET', $bucket, '', self::$endpoint);
  350. if ($maxKeys == 0) $maxKeys = null;
  351. if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
  352. if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
  353. if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys);
  354. if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
  355. $response = $rest->getResponse();
  356. if ($response->error === false && $response->code !== 200)
  357. $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
  358. if ($response->error !== false)
  359. {
  360. self::__triggerError(sprintf("SCS::getBucket(): [%s] %s",
  361. $response->error['code'], $response->error['message']), __FILE__, __LINE__);
  362. return false;
  363. }
  364. $results = array();
  365. if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
  366. {
  367. foreach ($response->body->CommonPrefixes as $c)
  368. $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
  369. }
  370. $nextMarker = null;
  371. if (isset($response->body, $response->body->Contents))
  372. {
  373. foreach ($response->body->Contents as $c)
  374. {
  375. $a = get_object_vars($c);
  376. $results[(string)$c->Name] = array(
  377. 'name' => (string)$c->Name,
  378. 'time' => strtotime((string)$a['Last-Modified']),
  379. 'type' => (string)$a['Content-Type'],
  380. 'owner' => (string)$a['Owner'],
  381. 'size' => (int)$c->Size,
  382. //'hash' => substr((string)$c->ETag, 1, -1),
  383. 'md5' => (string)$c->MD5,
  384. 'sha1' => (string)$c->SHA1,
  385. );
  386. $nextMarker = (string)$c->Name;
  387. }
  388. }
  389. if (isset($response->body, $response->body->IsTruncated))
  390. $isTruncated = (bool)$response->body->IsTruncated;
  391. if (isset($response->body, $response->body->IsTruncated) && (bool)$response->body->IsTruncated == false)
  392. return $results;
  393. if (isset($response->body, $response->body->NextMarker))
  394. $nextMarker = (string)$response->body->NextMarker;
  395. // Loop through truncated results if maxKeys isn't specified
  396. if ($maxKeys == null && $nextMarker !== null && (bool)$response->body->IsTruncated == true)
  397. {
  398. do
  399. {
  400. $rest = new SCSRequest('GET', $bucket, '', self::$endpoint);
  401. if ($prefix !== null && $prefix !== '')
  402. $rest->setParameter('prefix', $prefix);
  403. $rest->setParameter('marker', $nextMarker);
  404. if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
  405. if (($response = $rest->getResponse()) == false || $response->code !== 200) break;
  406. if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
  407. {
  408. foreach ($response->body->CommonPrefixes as $c)
  409. $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
  410. }
  411. if (isset($response->body, $response->body->Contents))
  412. {
  413. foreach ($response->body->Contents as $c)
  414. {
  415. $a = get_object_vars($c);
  416. $results[(string)$c->Name] = array(
  417. 'name' => (string)$c->Name,
  418. 'time' => strtotime((string)$a['Last-Modified']),
  419. 'type' => (string)$a['Content-Type'],
  420. 'owner' => (string)$a['Owner'],
  421. 'size' => (int)$c->Size,
  422. //'hash' => substr((string)$c->ETag, 1, -1),
  423. 'md5' => (string)$c->MD5,
  424. 'sha1' => (string)$c->SHA1,
  425. );
  426. $nextMarker = (string)$c->Name;
  427. }
  428. }
  429. if (isset($response->body, $response->body->NextMarker))
  430. $nextMarker = (string)$response->body->NextMarker;
  431. } while ($response !== false && (bool)$response->body->IsTruncated == true);
  432. }
  433. return $results;
  434. }
  435. /**
  436. * Put a bucket
  437. *
  438. * @param string $bucket Bucket name
  439. * @param constant $acl ACL flag
  440. * @param string $location Set as "EU" to create buckets hosted in Europe
  441. * @return boolean
  442. */
  443. public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false)
  444. {
  445. $rest = new SCSRequest('PUT', $bucket, '', self::$endpoint);
  446. $rest->setAmzHeader('x-amz-acl', $acl);
  447. if ($location !== false)
  448. {
  449. $dom = new DOMDocument;
  450. $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration');
  451. $locationConstraint = $dom->createElement('LocationConstraint', $location);
  452. $createBucketConfiguration->appendChild($locationConstraint);
  453. $dom->appendChild($createBucketConfiguration);
  454. $rest->data = $dom->saveXML();
  455. $rest->size = strlen($rest->data);
  456. $rest->setHeader('Content-Type', 'application/xml');
  457. }
  458. $rest = $rest->getResponse();
  459. if ($rest->error === false && $rest->code !== 200)
  460. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  461. if ($rest->error !== false)
  462. {
  463. self::__triggerError(sprintf("SCS::putBucket({$bucket}, {$acl}, {$location}): [%s] %s",
  464. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  465. return false;
  466. }
  467. return true;
  468. }
  469. /**
  470. * Delete an empty bucket
  471. *
  472. * @param string $bucket Bucket name
  473. * @return boolean
  474. */
  475. public static function deleteBucket($bucket)
  476. {
  477. $rest = new SCSRequest('DELETE', $bucket, '', self::$endpoint);
  478. $rest = $rest->getResponse();
  479. if ($rest->error === false && $rest->code !== 204)
  480. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  481. if ($rest->error !== false)
  482. {
  483. self::__triggerError(sprintf("SCS::deleteBucket({$bucket}): [%s] %s",
  484. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  485. return false;
  486. }
  487. return true;
  488. }
  489. /**
  490. * realFileSize()
  491. *
  492. * @param string $file Input file path
  493. * @return long file bytes
  494. */
  495. public static function realFileSize($file)
  496. {
  497. $fp = fopen($file, 'rb');
  498. $pos = 0;
  499. $size = 1073741824;
  500. fseek($fp, 0, SEEK_SET);
  501. while ($size > 1)
  502. {
  503. fseek($fp, $size, SEEK_CUR);
  504. if (fgetc($fp) === false)
  505. {
  506. fseek($fp, -$size, SEEK_CUR);
  507. $size = (int)($size / 2);
  508. }
  509. else
  510. {
  511. fseek($fp, -1, SEEK_CUR);
  512. $pos += $size;
  513. }
  514. }
  515. while (fgetc($fp) !== false) $pos++;
  516. fclose($fp);
  517. return $pos;
  518. }
  519. /**
  520. * Create input info array for putObject()
  521. *
  522. * @param string $file Input file
  523. * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own)
  524. * @return array | false
  525. */
  526. public static function inputFile($file, $md5sum = true)
  527. {
  528. if (!file_exists($file) || !is_file($file) || !is_readable($file))
  529. {
  530. self::__triggerError('SCS::inputFile(): Unable to open input file: '.$file, __FILE__, __LINE__);
  531. return false;
  532. }
  533. return array('file' => $file, 'size' => self::realFileSize($file), 'md5sum' => $md5sum !== false ?
  534. (is_string($md5sum) ? $md5sum : base64_encode(md5_file($file, true))) : '');
  535. }
  536. /**
  537. * Create input array info for putObject() with a resource
  538. *
  539. * @param string $resource Input resource to read from
  540. * @param integer $bufferSize Input byte size
  541. * @param string $md5sum MD5 hash to send (optional)
  542. * @return array | false
  543. */
  544. public static function inputResource(&$resource, $bufferSize = false, $md5sum = '')
  545. {
  546. if (!is_resource($resource) || (int)$bufferSize < 0)
  547. {
  548. self::__triggerError('SCS::inputResource(): Invalid resource or buffer size', __FILE__, __LINE__);
  549. return false;
  550. }
  551. // Try to figure out the bytesize
  552. if ($bufferSize === false)
  553. {
  554. if (fseek($resource, 0, SEEK_END) < 0 || ($bufferSize = ftell($resource)) === false)
  555. {
  556. self::__triggerError('SCS::inputResource(): Unable to obtain resource size', __FILE__, __LINE__);
  557. return false;
  558. }
  559. fseek($resource, 0);
  560. }
  561. $input = array('size' => $bufferSize, 'md5sum' => $md5sum);
  562. $input['fp'] =& $resource;
  563. return $input;
  564. }
  565. /**
  566. * Create input array info for putObject() with a resource multipart
  567. *
  568. * @param string $resource Input resource to read from
  569. * @param integer $partSize
  570. * @param string $uploadId
  571. * @param integer $partNumber
  572. * @return array | false
  573. */
  574. public static function inputResourceMultipart(&$resource, $partSize, $uploadId, $partNumber)
  575. {
  576. if (!is_resource($resource) || (int)$partSize <= 0)
  577. {
  578. self::__triggerError('SCS::inputResourceMultipart(): Invalid resource or part size', __FILE__, __LINE__);
  579. return false;
  580. }
  581. $data = fread($resource, $partSize);
  582. $input = array(
  583. 'data' => $data,
  584. 'md5sum' => base64_encode(md5($data, true))
  585. );
  586. $input['uploadId'] = $uploadId;
  587. $input['partNumber'] = $partNumber;
  588. return $input;
  589. }
  590. /**
  591. * Put an object
  592. *
  593. * @param mixed $input Input data
  594. * @param string $bucket Bucket name
  595. * @param string $uri Object URI
  596. * @param constant $acl ACL constant
  597. * @param array $metaHeaders Array of x-amz-meta-* headers
  598. * @param array $requestHeaders Array of request headers or content type as a string
  599. * @param constant $storageClass Storage class constant
  600. * @param constant $serverSideEncryption Server-side encryption
  601. * @return boolean | mixed
  602. */
  603. public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE)
  604. {
  605. if ($input === false) return false;
  606. $rest = new SCSRequest('PUT', $bucket, $uri, self::$endpoint);
  607. if (!is_array($input)) $input = array(
  608. 'data' => $input, 'size' => strlen($input),
  609. 'md5sum' => base64_encode(md5($input, true))
  610. );
  611. // Data
  612. if (isset($input['fp']))
  613. $rest->fp =& $input['fp'];
  614. elseif (isset($input['file']))
  615. $rest->fp = @fopen($input['file'], 'rb');
  616. elseif (isset($input['data']))
  617. $rest->data = $input['data'];
  618. // Content-Length (required)
  619. if (isset($input['size']) && $input['size'] >= 0)
  620. $rest->size = $input['size'];
  621. else {
  622. if (isset($input['file']))
  623. $rest->size = self::realFileSize($input['file']);
  624. elseif (isset($input['data']))
  625. $rest->size = strlen($input['data']);
  626. }
  627. if (isset($input['uploadId'], $input['partNumber'])) {
  628. $rest->setParameter('uploadId', $input['uploadId']);
  629. $rest->setParameter('partNumber', $input['partNumber']);
  630. }
  631. // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
  632. if (is_array($requestHeaders))
  633. foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
  634. elseif (is_string($requestHeaders)) // Support for legacy contentType parameter
  635. $input['type'] = $requestHeaders;
  636. // Content-Type
  637. if (!isset($input['type']))
  638. {
  639. if (isset($requestHeaders['Content-Type']))
  640. $input['type'] =& $requestHeaders['Content-Type'];
  641. elseif (isset($input['file']))
  642. $input['type'] = self::__getMIMEType($input['file']);
  643. else
  644. $input['type'] = 'application/octet-stream';
  645. }
  646. if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
  647. $rest->setAmzHeader('x-amz-storage-class', $storageClass);
  648. if ($serverSideEncryption !== self::SSE_NONE) // Server-side encryption
  649. $rest->setAmzHeader('x-amz-server-side-encryption', $serverSideEncryption);
  650. // We need to post with Content-Length and Content-Type, MD5 is optional
  651. if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false))
  652. {
  653. $rest->setHeader('Content-Type', $input['type']);
  654. if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']);
  655. $rest->setAmzHeader('x-amz-acl', $acl);
  656. foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
  657. $rest->getResponse();
  658. } else
  659. $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters');
  660. if ($rest->response->error === false && $rest->response->code !== 200)
  661. $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
  662. if ($rest->response->error !== false)
  663. {
  664. self::__triggerError(sprintf("SCS::putObject(): [%s] %s",
  665. $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
  666. return false;
  667. }
  668. if (isset($input['uploadId'], $input['partNumber'])) {
  669. return $rest->response->headers;
  670. }
  671. return true;
  672. }
  673. /**
  674. * Put an object from a file (legacy function)
  675. *
  676. * @param string $file Input file path
  677. * @param string $bucket Bucket name
  678. * @param string $uri Object URI
  679. * @param constant $acl ACL constant
  680. * @param array $metaHeaders Array of x-amz-meta-* headers
  681. * @param string $contentType Content type
  682. * @return boolean
  683. */
  684. public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null)
  685. {
  686. return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType);
  687. }
  688. /**
  689. * Put an object from a string (legacy function)
  690. *
  691. * @param string $string Input data
  692. * @param string $bucket Bucket name
  693. * @param string $uri Object URI
  694. * @param constant $acl ACL constant
  695. * @param array $metaHeaders Array of x-amz-meta-* headers
  696. * @param string $contentType Content type
  697. * @return boolean
  698. */
  699. public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain')
  700. {
  701. return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType);
  702. }
  703. /**
  704. * Get an object
  705. *
  706. * @param string $bucket Bucket name
  707. * @param string $uri Object URI
  708. * @param mixed $saveTo Filename or resource to write to
  709. * @return mixed
  710. */
  711. public static function getObject($bucket, $uri, $saveTo = false)
  712. {
  713. $rest = new SCSRequest('GET', $bucket, $uri, self::$endpoint);
  714. if ($saveTo !== false)
  715. {
  716. if (is_resource($saveTo))
  717. $rest->fp =& $saveTo;
  718. else
  719. if (($rest->fp = @fopen($saveTo, 'wb')) !== false)
  720. $rest->file = realpath($saveTo);
  721. else
  722. $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo);
  723. }
  724. if ($rest->response->error === false) $rest->getResponse();
  725. if ($rest->response->error === false && $rest->response->code !== 200)
  726. $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
  727. if ($rest->response->error !== false)
  728. {
  729. self::__triggerError(sprintf("SCS::getObject({$bucket}, {$uri}): [%s] %s",
  730. $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
  731. return false;
  732. }
  733. return $rest->response;
  734. }
  735. /**
  736. * Get object information
  737. *
  738. * @param string $bucket Bucket name
  739. * @param string $uri Object URI
  740. * @param boolean $returnInfo Return response information
  741. * @return mixed | false
  742. */
  743. public static function getObjectInfo($bucket, $uri, $returnInfo = true)
  744. {
  745. $rest = new SCSRequest('HEAD', $bucket, $uri, self::$endpoint);
  746. $rest = $rest->getResponse();
  747. if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
  748. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  749. if ($rest->error !== false)
  750. {
  751. self::__triggerError(sprintf("SCS::getObjectInfo({$bucket}, {$uri}): [%s] %s",
  752. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  753. return false;
  754. }
  755. return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false;
  756. }
  757. /**
  758. * Get Meta
  759. *
  760. * @param string $bucket Bucket name
  761. * @param string $uri Object URI
  762. * @return mixed | false
  763. */
  764. public static function getMeta($bucket, $uri = '')
  765. {
  766. $rest = new SCSRequest('GET', $bucket, $uri, self::$endpoint);
  767. $rest->setParameter('meta', null);
  768. $rest = $rest->getResponse();
  769. if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
  770. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  771. if ($rest->error !== false)
  772. {
  773. self::__triggerError(sprintf("SCS::getMeta({$bucket}, {$uri}): [%s] %s",
  774. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  775. return false;
  776. }
  777. return $rest->code == 200 ? $rest->body : false;
  778. }
  779. /**
  780. * Copy an object
  781. *
  782. * @param string $srcBucket Source bucket name
  783. * @param string $srcUri Source object URI
  784. * @param string $bucket Destination bucket name
  785. * @param string $uri Destination object URI
  786. * @param constant $acl ACL constant
  787. * @param array $metaHeaders Optional array of x-amz-meta-* headers
  788. * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.)
  789. * @param constant $storageClass Storage class constant
  790. * @return mixed | false
  791. */
  792. public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
  793. {
  794. $rest = new SCSRequest('PUT', $bucket, $uri, self::$endpoint);
  795. $rest->setHeader('Content-Length', 0);
  796. foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
  797. foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
  798. if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
  799. $rest->setAmzHeader('x-amz-storage-class', $storageClass);
  800. $rest->setAmzHeader('x-amz-acl', $acl);
  801. $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, rawurlencode($srcUri)));
  802. //$rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, $srcUri));
  803. if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0)
  804. $rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE');
  805. $rest = $rest->getResponse();
  806. if ($rest->error === false && $rest->code !== 200)
  807. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  808. if ($rest->error !== false)
  809. {
  810. self::__triggerError(sprintf("SCS::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s",
  811. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  812. return false;
  813. }
  814. return true;
  815. }
  816. /**
  817. * Relax an object 秒传
  818. *
  819. * @param string $bucket Destination bucket name
  820. * @param string $uri Destination object URI
  821. * @param string $sha1 Destination object sha1
  822. * @param int64 $size Destination object size
  823. * @param constant $acl ACL constant
  824. * @param array $metaHeaders Optional array of x-amz-meta-* headers
  825. * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.)
  826. * @param constant $storageClass Storage class constant
  827. * @return mixed | false
  828. */
  829. public static function putObjectRelax($bucket, $uri, $sha1, $size, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
  830. {
  831. $rest = new SCSRequest('PUT', $bucket, $uri, self::$endpoint);
  832. $rest->setParameter('relax', null);
  833. $rest->setHeader('Content-Length', 0);
  834. $rest->setHeader('s-sina-sha1', $sha1);
  835. $rest->setHeader('s-sina-length', $size);
  836. foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
  837. foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
  838. if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
  839. $rest->setAmzHeader('x-amz-storage-class', $storageClass);
  840. $rest->setAmzHeader('x-amz-acl', $acl);
  841. $rest = $rest->getResponse();
  842. if ($rest->error === false && $rest->code !== 200)
  843. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  844. if ($rest->error !== false)
  845. {
  846. self::__triggerError(sprintf("SCS::putObjectRelax({$bucket}, {$uri}): [%s] %s",
  847. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  848. return false;
  849. }
  850. return $rest->headers;
  851. }
  852. /**
  853. * Set up a bucket redirection
  854. *
  855. * @param string $bucket Bucket name
  856. * @param string $location Target host name
  857. * @return boolean
  858. */
  859. public static function setBucketRedirect($bucket = NULL, $location = NULL)
  860. {
  861. $rest = new SCSRequest('PUT', $bucket, '', self::$endpoint);
  862. if( empty($bucket) || empty($location) ) {
  863. self::__triggerError("SCS::setBucketRedirect({$bucket}, {$location}): Empty parameter.", __FILE__, __LINE__);
  864. return false;
  865. }
  866. $dom = new DOMDocument;
  867. $websiteConfiguration = $dom->createElement('WebsiteConfiguration');
  868. $redirectAllRequestsTo = $dom->createElement('RedirectAllRequestsTo');
  869. $hostName = $dom->createElement('HostName', $location);
  870. $redirectAllRequestsTo->appendChild($hostName);
  871. $websiteConfiguration->appendChild($redirectAllRequestsTo);
  872. $dom->appendChild($websiteConfiguration);
  873. $rest->setParameter('website', null);
  874. $rest->data = $dom->saveXML();
  875. $rest->size = strlen($rest->data);
  876. $rest->setHeader('Content-Type', 'application/xml');
  877. $rest = $rest->getResponse();
  878. if ($rest->error === false && $rest->code !== 200)
  879. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  880. if ($rest->error !== false)
  881. {
  882. self::__triggerError(sprintf("SCS::setBucketRedirect({$bucket}, {$location}): [%s] %s",
  883. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  884. return false;
  885. }
  886. return true;
  887. }
  888. /**
  889. * Set logging for a bucket
  890. *
  891. * @param string $bucket Bucket name
  892. * @param string $targetBucket Target bucket (where logs are stored)
  893. * @param string $targetPrefix Log prefix (e,g; domain.com-)
  894. * @return boolean
  895. */
  896. public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null)
  897. {
  898. // The SCS log delivery group has to be added to the target bucket's ACP
  899. if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false)
  900. {
  901. // Only add permissions to the target bucket when they do not exist
  902. $aclWriteSet = false;
  903. $aclReadSet = false;
  904. foreach ($acp['acl'] as $acl)
  905. if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery')
  906. {
  907. if ($acl['permission'] == 'WRITE') $aclWriteSet = true;
  908. elseif ($acl['permission'] == 'READ_ACP') $aclReadSet = true;
  909. }
  910. if (!$aclWriteSet) $acp['acl'][] = array(
  911. 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'WRITE'
  912. );
  913. if (!$aclReadSet) $acp['acl'][] = array(
  914. 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'READ_ACP'
  915. );
  916. if (!$aclReadSet || !$aclWriteSet) self::setAccessControlPolicy($targetBucket, '', $acp);
  917. }
  918. $dom = new DOMDocument;
  919. $bucketLoggingStatus = $dom->createElement('BucketLoggingStatus');
  920. $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/');
  921. if ($targetBucket !== null)
  922. {
  923. if ($targetPrefix == null) $targetPrefix = $bucket . '-';
  924. $loggingEnabled = $dom->createElement('LoggingEnabled');
  925. $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket));
  926. $loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix));
  927. // TODO: Add TargetGrants?
  928. $bucketLoggingStatus->appendChild($loggingEnabled);
  929. }
  930. $dom->appendChild($bucketLoggingStatus);
  931. $rest = new SCSRequest('PUT', $bucket, '', self::$endpoint);
  932. $rest->setParameter('logging', null);
  933. $rest->data = $dom->saveXML();
  934. $rest->size = strlen($rest->data);
  935. $rest->setHeader('Content-Type', 'application/xml');
  936. $rest = $rest->getResponse();
  937. if ($rest->error === false && $rest->code !== 200)
  938. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  939. if ($rest->error !== false)
  940. {
  941. self::__triggerError(sprintf("SCS::setBucketLogging({$bucket}, {$targetBucket}): [%s] %s",
  942. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  943. return false;
  944. }
  945. return true;
  946. }
  947. /**
  948. * Get logging status for a bucket
  949. *
  950. * This will return false if logging is not enabled.
  951. * Note: To enable logging, you also need to grant write access to the log group
  952. *
  953. * @param string $bucket Bucket name
  954. * @return array | false
  955. */
  956. public static function getBucketLogging($bucket)
  957. {
  958. $rest = new SCSRequest('GET', $bucket, '', self::$endpoint);
  959. $rest->setParameter('logging', null);
  960. $rest = $rest->getResponse();
  961. if ($rest->error === false && $rest->code !== 200)
  962. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  963. if ($rest->error !== false)
  964. {
  965. self::__triggerError(sprintf("SCS::getBucketLogging({$bucket}): [%s] %s",
  966. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  967. return false;
  968. }
  969. if (!isset($rest->body->LoggingEnabled)) return false; // No logging
  970. return array(
  971. 'targetBucket' => (string)$rest->body->LoggingEnabled->TargetBucket,
  972. 'targetPrefix' => (string)$rest->body->LoggingEnabled->TargetPrefix,
  973. );
  974. }
  975. /**
  976. * Disable bucket logging
  977. *
  978. * @param string $bucket Bucket name
  979. * @return boolean
  980. */
  981. public static function disableBucketLogging($bucket)
  982. {
  983. return self::setBucketLogging($bucket, null);
  984. }
  985. /**
  986. * Get a bucket's location
  987. *
  988. * @param string $bucket Bucket name
  989. * @return string | false
  990. */
  991. public static function getBucketLocation($bucket)
  992. {
  993. $rest = new SCSRequest('GET', $bucket, '', self::$endpoint);
  994. $rest->setParameter('location', null);
  995. $rest = $rest->getResponse();
  996. if ($rest->error === false && $rest->code !== 200)
  997. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  998. if ($rest->error !== false)
  999. {
  1000. self::__triggerError(sprintf("SCS::getBucketLocation({$bucket}): [%s] %s",
  1001. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1002. return false;
  1003. }
  1004. return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US';
  1005. }
  1006. /**
  1007. * Set object or bucket Access Control Policy
  1008. *
  1009. * @param string $bucket Bucket name
  1010. * @param string $uri Object URI
  1011. * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy)
  1012. * @return boolean
  1013. */
  1014. public static function setAccessControlPolicy($bucket, $uri = '', $acp = array())
  1015. {
  1016. /*
  1017. $dom = new DOMDocument;
  1018. $dom->formatOutput = true;
  1019. $accessControlPolicy = $dom->createElement('AccessControlPolicy');
  1020. $accessControlList = $dom->createElement('AccessControlList');
  1021. // It seems the owner has to be passed along too
  1022. $owner = $dom->createElement('Owner');
  1023. $owner->appendChild($dom->createElement('ID', $acp['owner']['id']));
  1024. $owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name']));
  1025. $accessControlPolicy->appendChild($owner);
  1026. foreach ($acp['acl'] as $g)
  1027. {
  1028. $grant = $dom->createElement('Grant');
  1029. $grantee = $dom->createElement('Grantee');
  1030. $grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
  1031. if (isset($g['id']))
  1032. { // CanonicalUser (DisplayName is omitted)
  1033. $grantee->setAttribute('xsi:type', 'CanonicalUser');
  1034. $grantee->appendChild($dom->createElement('ID', $g['id']));
  1035. }
  1036. elseif (isset($g['email']))
  1037. { // AmazonCustomerByEmail
  1038. $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail');
  1039. $grantee->appendChild($dom->createElement('EmailAddress', $g['email']));
  1040. }
  1041. elseif ($g['type'] == 'Group')
  1042. { // Group
  1043. $grantee->setAttribute('xsi:type', 'Group');
  1044. $grantee->appendChild($dom->createElement('URI', $g['uri']));
  1045. }
  1046. $grant->appendChild($grantee);
  1047. $grant->appendChild($dom->createElement('Permission', $g['permission']));
  1048. $accessControlList->appendChild($grant);
  1049. }
  1050. $accessControlPolicy->appendChild($accessControlList);
  1051. $dom->appendChild($accessControlPolicy);
  1052. */
  1053. $rest = new SCSRequest('PUT', $bucket, $uri, self::$endpoint);
  1054. $rest->setParameter('acl', null);
  1055. //$rest->data = $dom->saveXML();
  1056. $rest->data = json_encode($acp);
  1057. $rest->size = strlen($rest->data);
  1058. //$rest->setHeader('Content-Type', 'application/xml');
  1059. $rest->setHeader('Content-Type', 'application/json');
  1060. $rest = $rest->getResponse();
  1061. if ($rest->error === false && $rest->code !== 200)
  1062. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1063. if ($rest->error !== false)
  1064. {
  1065. self::__triggerError(sprintf("SCS::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
  1066. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1067. return false;
  1068. }
  1069. return true;
  1070. }
  1071. /**
  1072. * Get object or bucket Access Control Policy
  1073. *
  1074. * @param string $bucket Bucket name
  1075. * @param string $uri Object URI
  1076. * @return mixed | false
  1077. */
  1078. public static function getAccessControlPolicy($bucket, $uri = '')
  1079. {
  1080. $rest = new SCSRequest('GET', $bucket, $uri, self::$endpoint);
  1081. $rest->setParameter('acl', null);
  1082. $rest = $rest->getResponse();
  1083. if ($rest->error === false && $rest->code !== 200)
  1084. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1085. if ($rest->error !== false)
  1086. {
  1087. self::__triggerError(sprintf("SCS::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
  1088. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1089. return false;
  1090. }
  1091. $acp = array();
  1092. /*
  1093. if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
  1094. $acp['owner'] = array(
  1095. 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
  1096. );
  1097. if (isset($rest->body->AccessControlList))
  1098. {
  1099. $acp['acl'] = array();
  1100. foreach ($rest->body->AccessControlList->Grant as $grant)
  1101. {
  1102. foreach ($grant->Grantee as $grantee)
  1103. {
  1104. if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser
  1105. $acp['acl'][] = array(
  1106. 'type' => 'CanonicalUser',
  1107. 'id' => (string)$grantee->ID,
  1108. 'name' => (string)$grantee->DisplayName,
  1109. 'permission' => (string)$grant->Permission
  1110. );
  1111. elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail
  1112. $acp['acl'][] = array(
  1113. 'type' => 'AmazonCustomerByEmail',
  1114. 'email' => (string)$grantee->EmailAddress,
  1115. 'permission' => (string)$grant->Permission
  1116. );
  1117. elseif (isset($grantee->URI)) // Group
  1118. $acp['acl'][] = array(
  1119. 'type' => 'Group',
  1120. 'uri' => (string)$grantee->URI,
  1121. 'permission' => (string)$grant->Permission
  1122. );
  1123. else continue;
  1124. }
  1125. }
  1126. }
  1127. */
  1128. if (isset($rest->body->Owner))
  1129. {
  1130. $acp['owner'] = $rest->body->Owner;
  1131. }
  1132. if (isset($rest->body->ACL)) {
  1133. $acp['acl'] = get_object_vars($rest->body->ACL);
  1134. }
  1135. return $acp;
  1136. }
  1137. /**
  1138. * Delete an object
  1139. *
  1140. * @param string $bucket Bucket name
  1141. * @param string $uri Object URI
  1142. * @return boolean
  1143. */
  1144. public static function deleteObject($bucket, $uri)
  1145. {
  1146. $rest = new SCSRequest('DELETE', $bucket, $uri, self::$endpoint);
  1147. $rest = $rest->getResponse();
  1148. if ($rest->error === false && $rest->code !== 204)
  1149. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1150. if ($rest->error !== false)
  1151. {
  1152. self::__triggerError(sprintf("SCS::deleteObject(): [%s] %s",
  1153. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1154. return false;
  1155. }
  1156. return true;
  1157. }
  1158. /**
  1159. * Get a query string authenticated URL
  1160. *
  1161. * @param string $bucket Bucket name
  1162. * @param string $uri Object URI
  1163. * @param integer $lifetime Lifetime in seconds
  1164. * @param boolean $hostBucket Use the bucket name as the hostname
  1165. * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification)
  1166. * @param string $ip
  1167. * @param string $verb
  1168. * @param string $content_type For PUT
  1169. * @return string
  1170. */
  1171. /*
  1172. public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false)
  1173. {
  1174. $expires = self::__getTime() + $lifetime;
  1175. $uri = str_replace(array('%2F', '%2B'), array('/', '+'), rawurlencode($uri));
  1176. return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s',
  1177. // $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires,
  1178. $hostBucket ? $bucket : self::$endpoint.'/'.$bucket, $uri, self::$__accessKey, $expires,
  1179. urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
  1180. }
  1181. */
  1182. public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false, $ip=null, $verb=null, $content_type=null)
  1183. {
  1184. $expires = self::__getTime() + $lifetime;
  1185. $uri = str_replace(array('%2F'), array('/'), rawurlencode($uri));
  1186. //$uri = str_replace(array('%2F', '%2B'), array('/', '+'), rawurlencode($uri));
  1187. if (!$verb || !in_array($verb, array('GET', 'POST', 'PUT', 'HEAD', 'DELETE', 'OPTIONS')))
  1188. {
  1189. $verb = 'GET';
  1190. }
  1191. if ($verb != 'POST' && $verb != 'PUT')
  1192. {
  1193. $content_type = null;
  1194. }
  1195. if (!$content_type)
  1196. {
  1197. $content_type = '';
  1198. }
  1199. if ( !$bucket )
  1200. {
  1201. return sprintf(($https ? 'https' : 'http') . '://%s/?' . ($ip ? 'ip=' . $ip . '&' : '') . 'KID=%s&Expires=%u&ssig=%s',
  1202. self::$endpoint,
  1203. 'sina,' . self::$__accessKey,
  1204. $expires,
  1205. urlencode(self::__getHash("{$verb}\n\n{$content_type}\n{$expires}\n/" . ($ip ? '?ip=' . $ip : ''))));
  1206. }
  1207. return sprintf(($https ? 'https' : 'http').'://%s/%s?' . ($ip ? 'ip=' . $ip . '&' : '') . 'KID=%s&Expires=%u&ssig=%s',
  1208. $hostBucket ? $bucket : self::$endpoint.'/'.$bucket, $uri, 'sina,' . self::$__accessKey, $expires,
  1209. urlencode(self::__getHash("{$verb}\n\n{$content_type}\n{$expires}\n/{$bucket}/{$uri}" . ($ip ? '?ip=' . $ip : ''))));
  1210. }
  1211. /**
  1212. * Get upload POST parameters for form uploads
  1213. *
  1214. * @param string $bucket Bucket name
  1215. * @param string $uriPrefix Object URI prefix
  1216. * @param constant $acl ACL constant
  1217. * @param integer $lifetime Lifetime in seconds
  1218. * @param integer $maxFileSize Maximum filesize in bytes (default 5MB)
  1219. * @param string $successRedirect Redirect URL or 200 / 201 status code
  1220. * @param array $amzHeaders Array of x-amz-meta-* headers
  1221. * @param array $headers Array of request headers or content type as a string
  1222. * @param boolean $flashVars Includes additional "Filename" variable posted by Flash
  1223. * @return object
  1224. */
  1225. public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600,
  1226. $maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false)
  1227. {
  1228. // Create policy object
  1229. $policy = new stdClass;
  1230. $policy->expiration = gmdate('Y-m-d\TH:i:s.000\Z', (self::__getTime() + $lifetime));
  1231. $policy->conditions = array();
  1232. $obj = new stdClass; $obj->bucket = $bucket; array_push($policy->conditions, $obj);
  1233. $obj = new stdClass; $obj->acl = $acl; array_push($policy->conditions, $obj);
  1234. $obj = new stdClass; // 200 for non-redirect uploads
  1235. if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
  1236. $obj->success_action_status = (string)$successRedirect;
  1237. else // URL
  1238. $obj->success_action_redirect = $successRedirect;
  1239. //array_push($policy->conditions, $obj);
  1240. if ($acl !== self::ACL_PUBLIC_READ)
  1241. array_push($policy->conditions, array('eq', '$acl', $acl));
  1242. array_push($policy->conditions, array('starts-with', '$key', $uriPrefix));
  1243. if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', ''));
  1244. foreach (array_keys($headers) as $headerKey)
  1245. array_push($policy->conditions, array('starts-with', '$'.$headerKey, ''));
  1246. foreach ($amzHeaders as $headerKey => $headerVal)
  1247. {
  1248. $obj = new stdClass;
  1249. $obj->{$headerKey} = (string)$headerVal;
  1250. array_push($policy->conditions, $obj);
  1251. }
  1252. array_push($policy->conditions, array('content-length-range', 0, $maxFileSize));
  1253. $policy = base64_encode(str_replace('\/', '/', json_encode($policy)));
  1254. // Create parameters
  1255. $params = new stdClass;
  1256. //$params->AWSAccessKeyId = 'SINA000000' . strtoupper(self::$__accessKey);
  1257. $params->AWSAccessKeyId = self::$__accessKey;
  1258. $params->key = $uriPrefix.'${filename}';
  1259. $params->acl = $acl;
  1260. $params->policy = $policy; unset($policy);
  1261. $params->signature = self::__getHashRaw($params->policy);
  1262. if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
  1263. $params->success_action_status = (string)$successRedirect;
  1264. else
  1265. $params->success_action_redirect = $successRedirect;
  1266. foreach ($headers as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
  1267. foreach ($amzHeaders as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
  1268. $params->Policy = $params->policy;
  1269. unset($params->policy);
  1270. $params->Signature = $params->signature;
  1271. unset($params->signature);
  1272. return $params;
  1273. }
  1274. /**
  1275. * Initiate Multipart Upload
  1276. *
  1277. * @param string $bucket Destination bucket name
  1278. * @param string $uri Destination object URI
  1279. * @param constant $acl ACL constant
  1280. * @param array $metaHeaders Optional array of x-amz-meta-* headers
  1281. * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.)
  1282. * @param constant $storageClass Storage class constant
  1283. * @return mixed | false
  1284. */
  1285. public static function initiateMultipartUpload($bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
  1286. {
  1287. $rest = new SCSRequest('POST', $bucket, $uri, self::$endpoint);
  1288. $rest->setParameter('multipart', null);
  1289. $rest->setHeader('Content-Length', 0);
  1290. // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
  1291. if (is_array($requestHeaders))
  1292. foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
  1293. elseif (is_string($requestHeaders)) // Support for legacy contentType parameter
  1294. $rest->setHeader('Content-Type', $requestHeaders);
  1295. foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
  1296. if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
  1297. $rest->setAmzHeader('x-amz-storage-class', $storageClass);
  1298. $rest->setAmzHeader('x-amz-acl', $acl);
  1299. // Content-Type
  1300. if (!isset($requestHeaders['Content-Type']))
  1301. {
  1302. $rest->setHeader('Content-Type', self::__getMIMEType($uri));
  1303. }
  1304. $rest = $rest->getResponse();
  1305. //print_r($rest);
  1306. if ($rest->error === false && $rest->code !== 200)
  1307. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1308. if ($rest->error !== false)
  1309. {
  1310. self::__triggerError(sprintf("SCS::initiateMultipartUpload({$bucket}, {$uri}): [%s] %s",
  1311. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1312. return false;
  1313. }
  1314. //print_r($rest->body);
  1315. return isset($rest->body->UploadId) ? array(
  1316. 'bucket' => $rest->body->Bucket,
  1317. 'key' => $rest->body->Key,
  1318. 'upload_id' => $rest->body->UploadId,
  1319. ) : false;
  1320. }
  1321. /**
  1322. * List Parts
  1323. *
  1324. * @param string $bucket Bucket name
  1325. * @param string $uri Object URI
  1326. * @param string $uploadId
  1327. * @return mixed | false
  1328. */
  1329. public static function listParts($bucket, $uri, $uploadId)
  1330. {
  1331. $rest = new SCSRequest('GET', $bucket, $uri, self::$endpoint);
  1332. $rest->setParameter('uploadId', $uploadId);
  1333. $response = $rest->getResponse();
  1334. if ($response->error === false && $response->code !== 200)
  1335. $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
  1336. if ($response->error !== false)
  1337. {
  1338. self::__triggerError(sprintf("SCS::listParts({$bucket}, {$uri}, {$uploadId}): [%s] %s",
  1339. $response->error['code'], $response->error['message']), __FILE__, __LINE__);
  1340. return false;
  1341. }
  1342. $results = array();
  1343. if (isset($response->body, $response->body->Parts))
  1344. {
  1345. foreach ($response->body->Parts as $c)
  1346. {
  1347. $a = get_object_vars($c);
  1348. $results[intval($c->PartNumber)] = array(
  1349. 'part_number' => intval($c->PartNumber),
  1350. 'time' => strtotime((string)$a['Last-Modified']),
  1351. 'size' => (int)$c->Size,
  1352. 'etag' => (string)$c->ETag
  1353. );
  1354. }
  1355. }
  1356. return $results;
  1357. }
  1358. /**
  1359. * Complete Multipart Upload
  1360. *
  1361. * @param string $bucket Bucket name
  1362. * @param string $uri Object URI
  1363. * @param string $uploadId
  1364. * @return boolean
  1365. */
  1366. public static function completeMultipartUpload($bucket, $uri, $uploadId, $parts)
  1367. {
  1368. $rest = new SCSRequest('POST', $bucket, $uri, self::$endpoint);
  1369. $rest->setParameter('uploadId', $uploadId);
  1370. if (true) //使用json格式
  1371. {
  1372. $rest->setHeader('Content-Type', 'application/json');
  1373. $rest->data = json_encode($parts);
  1374. }
  1375. else
  1376. {
  1377. $dom = new DOMDocument;
  1378. $dom->formatOutput = true;
  1379. $createCompleteMultipartUpload = $dom->createElement('CompleteMultipartUpload');
  1380. $dom->appendChild($createCompleteMultipartUpload);
  1381. foreach ($parts as $part)
  1382. {
  1383. $createPart = $dom->createElement('Part');
  1384. $createCompleteMultipartUpload->appendChild($createPart);
  1385. $createPart->appendChild($dom->createElement('PartNumber', $part['PartNumber']));
  1386. $createPart->appendChild($dom->createElement('ETag', $part['ETag']));
  1387. }
  1388. $rest->removeParameter('formatter');
  1389. $rest->setHeader('Content-Type', 'application/xml');
  1390. $rest->data = $dom->saveXML();
  1391. }
  1392. $rest->size = strlen($rest->data);
  1393. $rest = $rest->getResponse();
  1394. if ($rest->error === false && $rest->code !== 200)
  1395. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1396. if ($rest->error !== false)
  1397. {
  1398. self::__triggerError(sprintf("SCS::completeMultipartUpload({$bucket}, {$uri}, {$uploadId}, {$parts}): [%s] %s",
  1399. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1400. return false;
  1401. }
  1402. return true;
  1403. }
  1404. /**
  1405. * Get MIME type for file
  1406. *
  1407. * To override the putObject() Content-Type, add it to $requestHeaders
  1408. *
  1409. * To use fileinfo, ensure the MAGIC environment variable is set
  1410. *
  1411. * @internal Used to get mime types
  1412. * @param string &$file File path
  1413. * @return string
  1414. */
  1415. private static function __getMIMEType(&$file)
  1416. {
  1417. static $exts = array(
  1418. 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'gif' => 'image/gif',
  1419. 'png' => 'image/png', 'ico' => 'image/x-icon', 'pdf' => 'application/pdf',
  1420. 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'svg' => 'image/svg+xml',
  1421. 'svgz' => 'image/svg+xml', 'swf' => 'application/x-shockwave-flash',
  1422. 'zip' => 'application/zip', 'gz' => 'application/x-gzip',
  1423. 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
  1424. 'bz2' => 'application/x-bzip2', 'rar' => 'application/x-rar-compressed',
  1425. 'exe' => 'application/x-msdownload', 'msi' => 'application/x-msdownload',
  1426. 'cab' => 'application/vnd.ms-cab-compressed', 'txt' => 'text/plain',
  1427. 'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
  1428. 'css' => 'text/css', 'js' => 'text/javascript',
  1429. 'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
  1430. 'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
  1431. 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
  1432. 'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php'
  1433. );
  1434. $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
  1435. if (isset($exts[$ext])) return $exts[$ext];
  1436. // Use fileinfo if available
  1437. if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
  1438. ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false)
  1439. {
  1440. if (($type = finfo_file($finfo, $file)) !== false)
  1441. {
  1442. // Remove the charset and grab the last content-type
  1443. $type = explode(' ', str_replace('; charset=', ';charset=', $type));
  1444. $type = array_pop($type);
  1445. $type = explode(';', $type);
  1446. $type = trim(array_shift($type));
  1447. }
  1448. finfo_close($finfo);
  1449. if ($type !== false && strlen($type) > 0) return $type;
  1450. }
  1451. return 'application/octet-stream';
  1452. }
  1453. /**
  1454. * Get the current time
  1455. *
  1456. * @internal Used to apply offsets to sytem time
  1457. * @return integer
  1458. */
  1459. public static function __getTime()
  1460. {
  1461. return time() + self::$__timeOffset;
  1462. }
  1463. /**
  1464. * Generate the auth string: "SINA AccessKey:Signature"
  1465. *
  1466. * @internal Used by SCSRequest::getResponse()
  1467. * @param string $string String to sign
  1468. * @return string
  1469. */
  1470. public static function __getSignature($string)
  1471. {
  1472. return 'SINA '.self::$__accessKey.':'.self::__getHash($string);
  1473. }
  1474. /**
  1475. * Creates a HMAC-SHA1 hash
  1476. *
  1477. * This uses the hash extension if loaded
  1478. *
  1479. * @internal Used by __getSignature()
  1480. * @param string $string String to sign
  1481. * @return string
  1482. */
  1483. private static function __getHash($string)
  1484. {
  1485. return substr( self::__getHashRaw($string), 5, 10 ) ;
  1486. /*
  1487. return base64_encode(extension_loaded('hash') ?
  1488. hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1(
  1489. (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
  1490. pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^
  1491. (str_repeat(chr(0x36), 64))) . $string)))));
  1492. */
  1493. }
  1494. /**
  1495. * Creates a HMAC-SHA1 hash
  1496. *
  1497. * This uses the hash extension if loaded
  1498. *
  1499. * @internal Used by __getHash()
  1500. * @param string $string String to sign
  1501. * @return string
  1502. */
  1503. private static function __getHashRaw($string)
  1504. {
  1505. return base64_encode( hash_hmac( "sha1", $string, self::$__secretKey, true ) );
  1506. }
  1507. }
  1508. /**
  1509. * SCS Request class
  1510. *
  1511. * @link http://weibo.com/smcz
  1512. * @version 0.1.0-dev
  1513. */
  1514. final class SCSRequest
  1515. {
  1516. /**
  1517. * SCS URI
  1518. *
  1519. * @var string
  1520. * @access pricate
  1521. */
  1522. private $endpoint;
  1523. /**
  1524. * Verb
  1525. *
  1526. * @var string
  1527. * @access private
  1528. */
  1529. private $verb;
  1530. /**
  1531. * SCS bucket name
  1532. *
  1533. * @var string
  1534. * @access private
  1535. */
  1536. private $bucket;
  1537. /**
  1538. * Object URI
  1539. *
  1540. * @var string
  1541. * @access private
  1542. */
  1543. private $uri;
  1544. /**
  1545. * Final object URI
  1546. *
  1547. * @var string
  1548. * @access private
  1549. */
  1550. private $resource = '';
  1551. /**
  1552. * Additional request parameters
  1553. *
  1554. * @var array
  1555. * @access private
  1556. */
  1557. private $parameters = array();
  1558. /**
  1559. * Amazon specific request headers
  1560. *
  1561. * @var array
  1562. * @access private
  1563. */
  1564. private $amzHeaders = array();
  1565. /**
  1566. * HTTP request headers
  1567. *
  1568. * @var array
  1569. * @access private
  1570. */
  1571. private $headers = array(
  1572. 'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => ''
  1573. );
  1574. /**
  1575. * Use HTTP PUT?
  1576. *
  1577. * @var bool
  1578. * @access public
  1579. */
  1580. public $fp = false;
  1581. /**
  1582. * PUT file size
  1583. *
  1584. * @var int
  1585. * @access public
  1586. */
  1587. public $size = 0;
  1588. /**
  1589. * PUT post fields
  1590. *
  1591. * @var array
  1592. * @access public
  1593. */
  1594. public $data = false;
  1595. /**
  1596. * SCS request respone
  1597. *
  1598. * @var object
  1599. * @access public
  1600. */
  1601. public $response;
  1602. /**
  1603. * Constructor
  1604. *
  1605. * @param string $verb Verb
  1606. * @param string $bucket Bucket name
  1607. * @param string $uri Object URI
  1608. * @param string $endpoint SCS endpoint URI
  1609. * @return mixed
  1610. */
  1611. function __construct($verb, $bucket = '', $uri = '', $endpoint = 'sinacloud.net')
  1612. {
  1613. $this->endpoint = $endpoint;
  1614. $this->verb = $verb;
  1615. $this->bucket = $bucket;
  1616. $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/';
  1617. //if ($this->bucket !== '')
  1618. // $this->resource = '/'.$this->bucket.$this->uri;
  1619. //else
  1620. // $this->resource = $this->uri;
  1621. if ($this->bucket !== '')
  1622. {
  1623. if ($this->__dnsBucketName($this->bucket))
  1624. {
  1625. $this->headers['Host'] = $this->bucket.'.'.$this->endpoint;
  1626. $this->resource = '/'.$this->bucket.$this->uri;
  1627. }
  1628. else
  1629. {
  1630. $this->headers['Host'] = $this->endpoint;
  1631. $this->uri = $this->uri;
  1632. if ($this->bucket !== '') $this->uri = '/'.$this->bucket.$this->uri;
  1633. $this->bucket = '';
  1634. $this->resource = $this->uri;
  1635. }
  1636. }
  1637. else
  1638. {
  1639. $this->headers['Host'] = $this->endpoint;
  1640. $this->resource = $this->uri;
  1641. }
  1642. //$this->headers['Date'] = gmdate('D, d M Y H:i:s T');
  1643. $this->headers['Date'] = gmdate('D, d M Y H:i:s T', SCS::__getTime());
  1644. $this->response = new STDClass;
  1645. $this->response->error = false;
  1646. $this->response->body = null;
  1647. $this->response->headers = array();
  1648. $this->setParameter('formatter', 'json');
  1649. }
  1650. /**
  1651. * Set request parameter
  1652. *
  1653. * @param string $key Key
  1654. * @param string $value Value
  1655. * @return void
  1656. */
  1657. public function setParameter($key, $value)
  1658. {
  1659. $this->parameters[$key] = $value;
  1660. }
  1661. /**
  1662. * Remove request parameter
  1663. *
  1664. * @param string $key Key
  1665. * @return void
  1666. */
  1667. public function removeParameter($key)
  1668. {
  1669. unset($this->parameters[$key]);
  1670. }
  1671. /**
  1672. * Set request header
  1673. *
  1674. * @param string $key Key
  1675. * @param string $value Value
  1676. * @return void
  1677. */
  1678. public function setHeader($key, $value)
  1679. {
  1680. $this->headers[$key] = $value;
  1681. }
  1682. /**
  1683. * Set x-amz-meta-* header
  1684. *
  1685. * @param string $key Key
  1686. * @param string $value Value
  1687. * @return void
  1688. */
  1689. public function setAmzHeader($key, $value)
  1690. {
  1691. $this->amzHeaders[$key] = $value;
  1692. }
  1693. /**
  1694. * Get the SCS response
  1695. *
  1696. * @return object | false
  1697. */
  1698. public function getResponse()
  1699. {
  1700. $query = '';
  1701. if (sizeof($this->parameters) > 0)
  1702. {
  1703. $query = substr($this->uri, -1) !== '?' ? '?' : '&';
  1704. foreach ($this->parameters as $var => $value)
  1705. {
  1706. if ($value == null || $value == '')
  1707. $query .= $var.'&';
  1708. else
  1709. $query .= $var.'='.rawurlencode($value).'&';
  1710. }
  1711. $query = substr($query, 0, -1);
  1712. $this->uri .= $query;
  1713. /*
  1714. if (array_key_exists('acl', $this->parameters) ||
  1715. array_key_exists('location', $this->parameters) ||
  1716. array_key_exists('torrent', $this->parameters) ||
  1717. array_key_exists('website', $this->parameters) ||
  1718. array_key_exists('logging', $this->parameters))
  1719. {
  1720. $this->resource .= $query;
  1721. }
  1722. */
  1723. $single_filter_list = array('acl', 'location', 'torrent', 'website', 'logging', 'relax', 'meta', 'uploads', 'part', 'copy', 'multipart');
  1724. $double_filter_list = array('uploadId', 'ip', 'partNumber');
  1725. foreach ($this->parameters as $var => $value)
  1726. {
  1727. if (in_array($var, $single_filter_list)) {
  1728. $this->resource .= '?' . $var;
  1729. break;
  1730. }
  1731. }
  1732. $query_for_sign_list = array();
  1733. foreach ($this->parameters as $var => $value)
  1734. {
  1735. if (in_array($var, $double_filter_list)) {
  1736. $query_for_sign_list[$var] = $value;
  1737. }
  1738. }
  1739. if (count($query_for_sign_list) > 0)
  1740. {
  1741. ksort($query_for_sign_list);
  1742. $query_for_sign = '';
  1743. foreach ($query_for_sign_list as $key => $value)
  1744. {
  1745. $query_for_sign .= $key.'='.rawurlencode($value).'&';
  1746. }
  1747. $query_for_sign = substr($query_for_sign, 0, -1);
  1748. if ($query_for_sign)
  1749. {
  1750. $this->resource .= (strpos($this->resource, '?') === false ? '?' : '&') . $query_for_sign;
  1751. }
  1752. }
  1753. }
  1754. $url = (SCS::$useSSL ? 'https://' : 'http://') . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint) . $this->uri;
  1755. /* @TODO delete */
  1756. //$url = (SCS::$useSSL ? 'https://' : 'http://') . '58.63.236.206' . $this->uri;
  1757. //var_dump('bucket: ' . $this->bucket, 'uri: ' . $this->uri, 'resource: ' . $this->resource, 'url: ' . $url);
  1758. // Basic setup
  1759. $curl = curl_init();
  1760. curl_setopt($curl, CURLOPT_USERAGENT, 'SCS/console');
  1761. if (SCS::$useSSL)
  1762. {
  1763. // SSL Validation can now be optional for those with broken OpenSSL installations
  1764. curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, SCS::$useSSLValidation ? 2 : 0);
  1765. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, SCS::$useSSLValidation ? 1 : 0);
  1766. if (SCS::$sslKey !== null) curl_setopt($curl, CURLOPT_SSLKEY, SCS::$sslKey);
  1767. if (SCS::$sslCert !== null) curl_setopt($curl, CURLOPT_SSLCERT, SCS::$sslCert);
  1768. if (SCS::$sslCACert !== null) curl_setopt($curl, CURLOPT_CAINFO, SCS::$sslCACert);
  1769. }
  1770. curl_setopt($curl, CURLOPT_URL, $url);
  1771. if (SCS::$proxy != null && isset(SCS::$proxy['host']))
  1772. {
  1773. curl_setopt($curl, CURLOPT_PROXY, SCS::$proxy['host']);
  1774. curl_setopt($curl, CURLOPT_PROXYTYPE, SCS::$proxy['type']);
  1775. if (isset(SCS::$proxy['user'], SCS::$proxy['pass']) && SCS::$proxy['user'] != null && SCS::$proxy['pass'] != null)
  1776. curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', SCS::$proxy['user'], SCS::$proxy['pass']));
  1777. }
  1778. // Headers
  1779. $headers = array(); $amz = array();
  1780. foreach ($this->amzHeaders as $header => $value)
  1781. if (strlen($value) > 0) $headers[] = $header.': '.$value;
  1782. foreach ($this->headers as $header => $value)
  1783. if (strlen($value) > 0) $headers[] = $header.': '.$value;
  1784. // Collect AMZ headers for signature
  1785. foreach ($this->amzHeaders as $header => $value)
  1786. if (strlen($value) > 0) $amz[] = strtolower($header).':'.$value;
  1787. // AMZ headers must be sorted
  1788. if (sizeof($amz) > 0)
  1789. {
  1790. //sort($amz);
  1791. usort($amz, array(&$this, '__sortMetaHeadersCmp'));
  1792. $amz = "\n".implode("\n", $amz);
  1793. } else $amz = '';
  1794. if (SCS::hasAuth())
  1795. {
  1796. // Authorization string (CloudFront stringToSign should only contain a date)
  1797. if ($this->headers['Host'] == 'cloudfront.amazonaws.com')
  1798. $headers[] = 'Authorization: ' . SCS::__getSignature($this->headers['Date']);
  1799. else
  1800. {
  1801. if (isset($this->headers['s-sina-sha1'])) {
  1802. $this->headers['Content-MD5'] = $this->headers['s-sina-sha1'];
  1803. }
  1804. $headers[] = 'Authorization: ' . SCS::__getSignature(
  1805. $this->verb."\n".
  1806. $this->headers['Content-MD5']."\n".
  1807. $this->headers['Content-Type']."\n".
  1808. $this->headers['Date'].$amz."\n".
  1809. $this->resource
  1810. );
  1811. }
  1812. }
  1813. /* @todo delete */
  1814. //$headers[] = 'Host: ' . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint);
  1815. //print_r($headers);
  1816. curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  1817. curl_setopt($curl, CURLOPT_HEADER, false);
  1818. curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
  1819. curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
  1820. curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback'));
  1821. curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
  1822. /* 必要时设置超时时间
  1823. curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 60);
  1824. curl_setopt($curl, CURLOPT_TIMEOUT, 1200);
  1825. */
  1826. // Request types
  1827. switch ($this->verb)
  1828. {
  1829. case 'GET': break;
  1830. case 'PUT': case 'POST': // POST only used for CloudFront
  1831. if ($this->fp !== false)
  1832. {
  1833. curl_setopt($curl, CURLOPT_PUT, true);
  1834. curl_setopt($curl, CURLOPT_INFILE, $this->fp);
  1835. if ($this->size >= 0)
  1836. curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
  1837. }
  1838. elseif ($this->data !== false)
  1839. {
  1840. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
  1841. curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
  1842. }
  1843. else
  1844. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
  1845. break;
  1846. case 'HEAD':
  1847. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
  1848. curl_setopt($curl, CURLOPT_NOBODY, true);
  1849. break;
  1850. case 'DELETE':
  1851. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
  1852. break;
  1853. default: break;
  1854. }
  1855. // Execute, grab errors
  1856. if (curl_exec($curl))
  1857. $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
  1858. else
  1859. $this->response->error = array(
  1860. 'code' => curl_errno($curl),
  1861. 'message' => curl_error($curl),
  1862. 'resource' => $this->resource
  1863. );
  1864. //echo $this->response->body;
  1865. @curl_close($curl);
  1866. // Parse body into XML | JSON
  1867. if ($this->response->error === false && isset($this->response->headers['type']) &&
  1868. ($this->response->headers['type'] == 'application/xml' || $this->response->headers['type'] == 'application/json') && isset($this->response->body))
  1869. {
  1870. if ($this->response->headers['type'] == 'application/json')
  1871. {
  1872. $this->response->body = json_decode($this->response->body);
  1873. }
  1874. else
  1875. {
  1876. $this->response->body = simplexml_load_string($this->response->body);
  1877. }
  1878. // Grab SCS errors
  1879. if (!in_array($this->response->code, array(200, 204, 206)) &&
  1880. isset($this->response->body->Code, $this->response->body->Message))
  1881. {
  1882. $this->response->error = array(
  1883. 'code' => (string)$this->response->body->Code,
  1884. 'message' => (string)$this->response->body->Message
  1885. );
  1886. if (isset($this->response->body->Resource))
  1887. $this->response->error['resource'] = (string)$this->response->body->Resource;
  1888. unset($this->response->body);
  1889. }
  1890. }
  1891. // Clean up file resources
  1892. if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp);
  1893. return $this->response;
  1894. }
  1895. /**
  1896. * Sort compare for meta headers
  1897. *
  1898. * @internal Used to sort x-amz meta headers
  1899. * @param string $a String A
  1900. * @param string $b String B
  1901. * @return integer
  1902. */
  1903. private function __sortMetaHeadersCmp($a, $b)
  1904. {
  1905. $lenA = strpos($a, ':');
  1906. $lenB = strpos($b, ':');
  1907. $minLen = min($lenA, $lenB);
  1908. $ncmp = strncmp($a, $b, $minLen);
  1909. if ($lenA == $lenB) return $ncmp;
  1910. if (0 == $ncmp) return $lenA < $lenB ? -1 : 1;
  1911. return $ncmp;
  1912. }
  1913. /**
  1914. * CURL write callback
  1915. *
  1916. * @param resource &$curl CURL resource
  1917. * @param string &$data Data
  1918. * @return integer
  1919. */
  1920. private function __responseWriteCallback(&$curl, &$data)
  1921. {
  1922. if (in_array($this->response->code, array(200, 206)) && $this->fp !== false)
  1923. return fwrite($this->fp, $data);
  1924. else
  1925. $this->response->body .= $data;
  1926. return strlen($data);
  1927. }
  1928. /**
  1929. * Check DNS conformity
  1930. *
  1931. * @param string $bucket Bucket name
  1932. * @return boolean
  1933. */
  1934. private function __dnsBucketName($bucket)
  1935. {
  1936. if (strlen($bucket) > 63 || preg_match("/[^a-z0-9\.-]/", $bucket) > 0) return false;
  1937. if (strstr($bucket, '-.') !== false) return false;
  1938. if (strstr($bucket, '..') !== false) return false;
  1939. if (!preg_match("/^[0-9a-z]/", $bucket)) return false;
  1940. if (!preg_match("/[0-9a-z]$/", $bucket)) return false;
  1941. return true;
  1942. }
  1943. /**
  1944. * CURL header callback
  1945. *
  1946. * @param resource &$curl CURL resource
  1947. * @param string &$data Data
  1948. * @return integer
  1949. */
  1950. private function __responseHeaderCallback(&$curl, &$data)
  1951. {
  1952. if (($strlen = strlen($data)) <= 2) return $strlen;
  1953. if (substr($data, 0, 4) == 'HTTP')
  1954. $this->response->code = (int)substr($data, 9, 3);
  1955. else
  1956. {
  1957. $data = trim($data);
  1958. if (strpos($data, ': ') === false) return $strlen;
  1959. list($header, $value) = explode(': ', $data, 2);
  1960. if ($header == 'Last-Modified')
  1961. $this->response->headers['time'] = strtotime($value);
  1962. elseif ($header == 'Date')
  1963. $this->response->headers['date'] = strtotime($value);
  1964. elseif ($header == 'Content-Length')
  1965. $this->response->headers['size'] = (int)$value;
  1966. elseif ($header == 'Content-Type')
  1967. $this->response->headers['type'] = $value;
  1968. elseif ($header == 'ETag')
  1969. $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value;
  1970. elseif (preg_match('/^x-amz-meta-.*$/', $header))
  1971. $this->response->headers[$header] = $value;
  1972. }
  1973. return $strlen;
  1974. }
  1975. }
  1976. /**
  1977. * SCS exception class
  1978. *
  1979. * @link http://weibo.com/smcz
  1980. * @version 0.1.0-dev
  1981. */
  1982. class SCSException extends Exception {
  1983. /**
  1984. * Class constructor
  1985. *
  1986. * @param string $message Exception message
  1987. * @param string $file File in which exception was created
  1988. * @param string $line Line number on which exception was created
  1989. * @param int $code Exception code
  1990. */
  1991. function __construct($message, $file, $line, $code = 0)
  1992. {
  1993. parent::__construct($message, $code);
  1994. $this->file = $file;
  1995. $this->line = $line;
  1996. }
  1997. }