-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathGoogleStorage.class.php
More file actions
348 lines (319 loc) · 14.3 KB
/
GoogleStorage.class.php
File metadata and controls
348 lines (319 loc) · 14.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
<?php
/**
* Implements the Google Storage REST API
* @author Andries Mooij
* @author Slawomir Jasinski <slav123@gmail.com>
*
*
*/
/**
* Provides access to Google Storage
* Relies on the unsurpassed CURL Extension and PHP >= 5.1.2 (because of hash_hmac)
* This class returns the HTTP Code directly on failure.
*
* On ACL Strings:
* - private<br>
* Gives the requester FULL_CONTROL permission for a bucket or object. This is the default ACL that's applied when you upload an object or create a bucket.
* - public-read<br>
* Gives the requester FULL_CONTROL permission and gives all anonymous users READ permission. When you apply this to an object, anyone on the Internet can read the object without authenticating.
* - public-read-write<br>
* Gives the requester FULL_CONTROL permission and gives all anonymous users READ and WRITE permission. This ACL applies only to buckets.
* - authenticated-read<br>
* Gives the requester FULL_CONTROL permission and gives all authenticated Google account holders READ permission.
* - bucket-owner-read<br>
* Gives the requester FULL_CONTROL permission and gives the bucket owner READ permission. This is used only with objects.
* - bucket-owner-full-control<br>
* Gives the requester FULL_CONTROL permission and gives the bucket owner FULL_CONTROL permission. This is used only with objects.
*
* @package Util
*/
class GoogleStorage {
/**
* Google Storage Access Key
* @access protected
* @var string
*/
protected $accessKey;
/**
* Google Storage Secret
* @access protected
* @var string
*/
protected $secret;
/**
* The Host for Google Storage. You can change this if you're using CNAME access.
* @access public
* @var string
*/
public static $host = "commondatastorage.googleapis.com";
/**
* Enables debugging mode. Breaks some stuff, but helps you figure out errors.
* @access protected
* @var bool
*/
protected $debug = false;
/**
* Creates a new Google Storage class
*
* @param string $accessKey
* @param string $secret
*/
public function __construct($accessKey, $secret) {
$this->accessKey = $accessKey;
$this->secret = $secret;
}
/**
* Lists your buckets.
*
* @return SimpleXMLElement|integer
*/
public function listBuckets() {
$ret = $this->curlExec(self::$host, "GET", "/");
return is_object($ret) ? $ret->buckets : $ret;
}
/**
* Creates a bucket.
* Returns the HTTP Result Code.
*
* @param string $name
* @param string|SimpleXMLElement $acl
* @return integer
*/
public function createBucket($name, $acl = null) {
$body = $acl instanceof SimpleXMLElement ? (string)$acl : "";
if (is_string($acl) && strlen($acl)) {
$headers[] = "x-goog-acl: " . $acl;
}
return $this->curlExec($name . "." . self::$host, "PUT", "/", "", array(), $body);
$headers = array('Content-Length: ' . mb_strlen($body), 'Date: ' . date("r"), 'Host: ' . self::$host);
$headers[] = $this->getAuthorizationHeader($bucket . "." . self::$host, 'PUT', '/', $headers);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
$ret = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
return $code;
}
/**
* Retrieves information about a bucket and it's contents.
*
* @param string $name
* @param string $prefix
* @param string $delimiter
* @param string $marker
* @param string $maxKeys
* @return SimpleXMLElement|integer
*/
public function getBucket($name, $prefix = "", $delimiter = "", $marker = "", $maxKeys = -1) {
$parameters = array();
if (strlen($prefix)) {
$parameters[] = "prefix=" . urlencode($prefix);
}
if (strlen($delimiter)) {
$parameters[] = "delimiter=" . urlencode($delimiter);
}
if (strlen($marker)) {
$parameters[] = "marker=" . urlencode($marker);
}
if ($maxKeys != -1) {
$parameters[] = "max-keys=" . $maxKeys;
}
$parameters = (count($parameters) ? "?" : "") . implode("&", $parameters);
$ret = $this->curlExec($name . "." . self::$host, "GET", "/", $parameters);
return is_object($ret) ? $ret->ListBucketResult : $ret;
}
/**
* Retrieves the ACL Settings for a bucket.
*
* @param string $name
* @return SimpleXMLElement|integer
*/
public function getBucketAcl($name) {
$ret = $this->curlExec($name . "." . self::$host, "GET", "/?acl");
return is_object($ret) ? $ret->AccessControlList : $ret;
}
/**
* Deletes an empty bucket. You can't delete non-empty buckets.
*
* @param string $name
* @return integer
*/
public function deleteBucket($name) {
return $this->curlExec($name . "." . self::$host, "DELETE", "/");
}
/**
* Stores a file in an object.
*
* @param string $bucket
* @param string $name
* @param string $file
* @param string $aclString
* @return integer
*/
public function putObjectFile($bucket, $name, $file, string $aclString = NULL) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file);
finfo_close($finfo);
return $this->putObjectData($bucket, $name, $mime, '@' . $file, $aclString);
}
/**
* Stores data in an object.
*
* @param string $bucket
* @param string $name
* @param string $mimeType
* @param string $content
* @param string $aclString
* @return integer
*/
public function putObjectData($bucket, $name, $mimeType, $content, string $aclString = NULL) {
$headers = array(
'Content-Type: ' . $mimeType,
);
if (strlen($aclString)) {
$headers[] = "x-goog-acl: " . $aclString;
}
$rfname = substr($content, 1);
$fh = fopen($rfname, 'r');
$content = fread($fh, filesize($rfname));
fclose($fh);
return $this->curlExec($bucket . "." . self::$host, "PUT", "/" . $name, "", $headers, $content);
}
/**
* Retrieves an object.
*
* @param string $bucket
* @param string $name
* @return string
*/
public function getObject($bucket, $name) {
return $this->curlExec($bucket . "." . self::$host, "GET", "/" . $name);
}
/**
* Deletes an object.
*
* @param string $bucket
* @param string $name
* @return integer
*/
public function deleteObject($bucket, $name) {
return $this->curlExec($bucket . "." . self::$host, "DELETE", "/" . $name);
}
/**
* CURL Helper function. Does all the work.
*
* @param string $host
* @param string $method
* @param string $scope
* @param string $parameters
* @param array $headers
* @param string $body
* @return SimpleXMLElement|integer|string
*/
protected function curlExec($host, $method, $scope, $parameters = "", $headers = array(), $body = "") {
$headers[] = 'Content-Length: ' . strlen($body);
$headers[] = 'Date: ' . date("r");
$headers[] = 'Host: ' . $host;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $host . "." . $scope . $parameters);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if ($method != "GET") {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
if (strlen($body)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
}
$subdomain = str_replace(array(self::$host, "."), array("", ""), $host);
$subdomain = strlen($subdomain) ? ("/" . $subdomain) : "";
$headers[] = $this->getAuthorizationHeader($method, $subdomain . $scope, $headers);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
if ($this->debug) {
curl_setopt($ch, CURLOPT_HEADER, true);
}
$ret = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($this->debug) {
echo "<pre>" . $code . "\n" . $ret . "</pre>";
}
if ($code == 200) {
$size = strlen($ret);
if ($size) {
$type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
if ($type == "text/xml") {
$ret = new SimpleXMLElement($ret);
}
return $ret;
}
else {
return $code;
}
}
else {
return $code;
}
}
/**
* Creates the Authorization-header in the GOOG1 format.
*
* @param string $method
* @param string $resource
* @param array $headers
* @return string
*/
protected function getAuthorizationHeader($method, $resource, $headers) {
/*
CanonicalHeaders = HTTP-Verb + "\n" +
Content-MD5 + "\n" +
Content-Type + "\n" +
Date + "\n"
*/
$canonicalHeaders = $method . "\n";
$md5 = preg_grep("/^Content-MD5\s*:/i", $headers);
if (count($md5)) {
list($name, $content) = explode(": ", current($md5));
$canonicalHeaders .= $content . "\n";
}
else {
$canonicalHeaders .= "\n";
}
$contentType = preg_grep("/^Content-Type\s*:/i", $headers);
if (count($contentType)) {
list($name, $content) = explode(": ", current($contentType));
$canonicalHeaders .= $content . "\n";
}
else {
$canonicalHeaders .= "\n";
}
$date = preg_grep("/^Date\s*:/i", $headers);
if (count($date)) {
list($name, $content) = explode(": ", current($date));
$canonicalHeaders .= $content . "\n";
}
else {
$canonicalHeaders .= "\n";
}
/*
You construct the CanonicalExtensionHeaders portion of the message by concatenating all extension (custom) headers that begin with x-goog-. However, you cannot perform a simple concatenation. You must concatenate the headers using the following process:
- Make all custom header names lowercase.
- Sort all custom headers lexicographically by header name.
- Eliminate duplicate header names by creating one header name with a comma-separated list of values. Be sure there is no whitespace between the values and be sure that the order of the comma-separated list matches the order that the headers appear in your request. For more information, see RFC 2616 section 4.2.
- Replace any folding whitespace or newlines (CRLF or LF) with a single space. For more information about folding whitespace, see RFC 2822 section 2.2.3.
- Remove any whitespace around the colon that appears after the header name.
- Append a newline (U+000A) to each custom header.
- Concatenate all custom headers.
- It's important to note that you use both the header name and the header value when you construct the CanonicalExtensionHeaders portion of the message. This is different than the CanonicalHeaders portion of the message, which used only header values.
*/
$customHeaders = preg_grep("/^x-goog-/i", $headers);
$customHeaders = preg_replace("/^(x-goog-[^:]+)\s*:\s*/ie", "strtolower('$1:')", $customHeaders);
sort($customHeaders, SORT_STRING);
$customHeaders = preg_replace("/\r\n(\s)/s", "$1", $customHeaders);
$customHeaders = implode(",", $customHeaders) . (count($customHeaders) ? "\n" : "");
/*
Signature = Base64-Encoding-Of(HMAC-SHA1(UTF-8-Encoding-Of(YourGoogleStorageSecretKey, MessageToBeSigned)))
To create the signature you use a cryptographic hash function known as HMAC-SHA1. HMAC-SHA1 is a hash-based message authentication code (MAC) and is described in RFC 2104. It requires two input parameters, both UTF-8 encoded: a key and a message. You use your Google Storage secret as the key. You must construct the message by concatenating specific HTTP headers in a specific order.
The message that you sign is a UTF-8 encoded byte string. The following pseudocode notation shows how to construct this byte string:
MessageToBeSigned = UTF-8-Encoding-Of(CanonicalHeaders + CanonicalExtensionHeaders + CanonicalResource)
*/
return "Authorization: GOOG1 " . $this->accessKey . ":" . base64_encode(hash_hmac("sha1", $canonicalHeaders . $customHeaders . $resource, $this->secret, true));
}
}