/[drupal]/contributions/modules/fileapi/drivers/s3.driver
ViewVC logotype

Contents of /contributions/modules/fileapi/drivers/s3.driver

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.3.4.3 - (show annotations) (download)
Thu Jan 4 16:41:48 2007 UTC (2 years, 10 months ago) by dopry
Branch: DRUPAL-5
Changes since 1.3.4.2: +117 -16 lines
adding bucket selection/creation to s3 driver settings form.
1 <?php
2
3 /**
4 * @file
5 * Amazon S3 storage driver for fileapi.
6 *
7 * We are treating S3 like a normal hierarchal filesystem. The bucket
8 * is predetermined by the mountpoint settings. Except for list buckets
9 * and put bucket functions.
10 *
11 * We use the keys with a delimiter of '/' to create our heirarchy.
12 *
13 * @todo examine adding caching to save some requests to S3.
14 * @todo wrap amazon s3 functions into a single class with multiple instance and
15 * caching support.
16 *
17 */
18
19
20 /**
21 * driver settings form. called by fileapi.module when creating a respository using this driver.
22 * @param $settings
23 * settings array from database.
24 * @todo convert form to multistep to allow input of credentials, then bucket selection/creation.
25 * @todo add bucket access control policy settings
26 */
27
28 function fileapi_driver_s3_settings_form($settings = array()) {
29 //debug_msg($settings);
30 $form = array(
31 '#type' => 'fieldset',
32 '#title' => t('Amazon S3 storage driver settings'),
33 '#tree' => TRUE,
34 '#validate' => array('fileapi_driver_s3_settings_form_validate' => array()),
35 '#submit' => array('fileapi_driver_s3_settings_form_submit' => array()),
36 );
37
38 $form['driver'] = array(
39 '#type' => 'hidden',
40 '#value' => 's3',
41 );
42
43 $form['s3 url'] = array(
44 '#type' => 'textfield',
45 '#title' => t('Amazon S3 url'),
46 '#description' => t('The URL must not contain a trailing slash.'),
47 '#default_value' => strlen($settings['s3 url']) ? $settings['s3 url'] : 'http://s3.amazonaws.com',
48 );
49
50 $form['awsID'] = array(
51 '#type' => 'textfield',
52 '#title' => t('AWS Access Key ID'),
53 '#default_value' => $settings['awsID'],
54 );
55
56 $form['awsSecret'] = array(
57 '#type' => 'textfield',
58 '#title' => t('AWS Secret'),
59 '#default_value' => $settings['awsSecret'],
60 );
61
62
63 if (isset($settings['awsID']) && isset($settings['awsSecret'])) {
64
65 $buckets = s3_buckets($settings);
66 debug_msg($buckets, 'buckets');
67 // use !== FALSE to pass on empty array checks.
68 if ($buckets !== FALSE) {
69 $buckets['Create a New Bucket'] = t('Create a New Bucket');
70 $form['bucket'] = array(
71 '#type' => 'select',
72 '#title' => 'Use Existing S3 Bucket',
73 '#options' => $buckets,
74 '#default_value' => (isset($settings['bucket'])) ? $settings['bucket'] : 'Create a New Bucket',
75 );
76
77 $form['new_bucket'] = array(
78 '#type' => 'textfield',
79 '#title' => t('New S3 Bucket'),
80 '#default_value' => '',
81 );
82 }
83 }
84
85
86 return $form;
87 }
88
89 function fileapi_driver_s3_settings_form_validate($element) {
90 //@todo test for trailing slash in 's3 url';
91 if (preg_match('/\/$/', $element['s3 url']['#value'])) {
92 form_set_error('s3 url', t('The URL must not include a trailing slash.'));
93 }
94
95
96
97 //@todo validate AWS credentials.
98 $settings = array(
99 's3 url' => $element['s3 url']['#value'],
100 'awsID' => $element['awsID']['#value'],
101 'awsSecret' => $element['awsSecret']['#value'],
102 );
103
104 debug_msg($settings);
105 $result = s3_buckets($settings);
106 debug_msg(htmlspecialchars($result));
107 if ($result === FALSE) {
108 form_set_error('awsID', t('Your AWS credentials seem to be invalid.'));
109 form_set_error('awsSecret','');
110 }
111
112 }
113
114 //function fileapi_driver_s3_settings_form_submit($form_id, &$form_values) {
115 function fileapi_driver_s3_settings_form_submit($element) {
116 $settings = array(
117 's3 url' => $element['s3 url']['#value'],
118 'awsID' => $element['awsID']['#value'],
119 'awsSecret' => $element['awsSecret']['#value'],
120 );
121 // @todo create a new bucket if one doesn't exist...
122 if (isset($element['awsID']['#value']) && isset($element['aws_Secret']['#value'])) {
123 if ($element['bucket']['#value'] == 'Create A New Bucket') {
124 s3_bucket_put($settings, $element['new_bucket']['#value']);
125 $element['bucket']['#value'] = $element['new_bucket']['#value'];
126 unset($element['new_bucket']['#value']);
127 }
128 }
129 else {
130 return FALSE;
131 }
132 }
133
134
135 /**
136 * test if a path is a file.
137 *
138 * for S3 we are going to operate on an object if an object
139 * has the key we're requesting.
140 * see: S3 Developers Guide pg 67.
141 *
142 * @param $path
143 * path to be tested
144 *
145 */
146 function fileapi_driver_s3_is_file($settings, $path) {
147 // request meta data for an object with the key $path.
148 $result = s3_request('HEAD', $settings['bucket'] .'/'. $path, $settings);
149
150 //parse results for request...
151 }
152
153 /**
154 * test if a path is a directory.
155 * @param $path
156 * path to be tested
157 */
158 function fileapi_driver_s3_is_dir($settings, $path) {
159 }
160
161
162 /**
163 * create a directory using standard php functions
164 * @param $path
165 */
166 function fileapi_driver_s3_mkdir($settings, $path) {
167 // there are technically no directories, so we can safely do
168 // nothing here.
169 return TRUE;
170 }
171
172
173 /**
174 * remove a directory using standard php functions
175 * @param $path
176 */
177
178 function fileapi_driver_s3_rmdir($settings, $path) {
179 // there are technically no directories.
180 // so we can test is there is a file in the 'directory' and return false here.
181 }
182
183
184 /**
185 * remove a file using standard php functions
186 * @param $path
187 */
188 function fileapi_driver_s3_remove($settings, $path) {
189
190 }
191
192 /**
193 * copy a file using standard php functions
194 * @param $src
195 * @param $dst
196 */
197 function fileapi_driver_s3_copy($settings, $src, $dst) {
198
199 }
200
201 /**
202 * move a file using standard php functions
203 * @param $src
204 * @param $dst
205 */
206 function fileapi_driver_s3_move($settings, $src, $dst) {
207
208 }
209
210 /**
211 * check if a file exists using standard php functions
212 * @param $path
213 */
214 function fileapi_driver_s3_exists($settings, $path) {
215 }
216
217 /**
218 * touch a path and create a file there.
219 * @param $path
220 * path to be touched.
221 */
222 function fileapi_driver_s3_touch($settings, $path) {
223 $tmp = file_directory_tmp() .'/'. basename($path);
224 touch($tmp);
225 $headers = array();
226 $headers['Content-Type'] = 'text/plain';
227 $body = file_get_contents($tmp);
228 $result = s3_request('PUT', $path, $settings, array(), $body);
229 unlink($tmp);
230 if ($result) {
231 return $TRUE;
232 }
233 else {
234 return FALSE;
235 }
236 }
237
238
239 /**
240 * read the contents of a directory.
241 * @param $path
242 * directory to be read.
243 * @return array
244 * action results and array of directory contents.
245 */
246 function fileapi_driver_s3_readdir($settings, $path) {
247 // $result = s3_request('GET', $settings['bucket'], $settings, array(), array(), NULL, array('prefix='. $path, 'delimiter=/');
248 }
249
250 /**
251 * read the contents of a file.
252 * @param $path
253 * path of file to be read.
254 *
255 * NOTE: we are not including offset and maxlen since it is only supported in php > 5.1.0
256 * and our support target is 4.3.0.
257 */
258 function fileapi_driver_s3_fileread($settings, $path) {
259 // $result = s3_request('GET', $path, $settings);
260 return $result;
261 }
262
263 function fileapi_driver_s3_filewrite($settings, $path, $data) {
264 $headers = array('Content-Type' => 'text/plain');
265 // $result = s3_request('PUT', $settings['bucket'] .'/'. $path, $settings, array(), $data);
266 return $result;
267 }
268
269
270
271 function s3_buckets($settings) {
272 $result = s3_request('GET','', $settings);
273 if ($result) {
274 $buckets = array();
275 $xml = simplexml_load_string($result);
276 foreach($xml->Buckets->Bucket->Name as $Name) {
277 $name = (string)$Name;
278 $buckets[$name] = $name;
279 }
280 return $buckets;
281 }
282 return FALSE;
283 }
284
285 /** Operations on Buckets **/
286 function s3_bucket_put($settings, $bucket = '') {
287 $result = s3_request('PUT', $bucket, $settings);
288 return $result;
289 }
290
291
292
293
294 function s3_request($method, $resource, $settings, $headers=array(), $amzheaders=array(), $body=NULL, $query = array()) {
295 $url = $settings['s3 url'];
296
297 // Set date to proper format for S3.
298 $headers['Date'] = gmdate(DATE_RFC822);
299 if (!is_null($body)) {
300 $headers['Content-MD5'] = hex2b64(md5($body));
301 }
302
303 // Build S3 authorization hash.
304 $auth_string = $method ."\n";
305 // Apparently we need a trailing \n whether we have a body or not...
306 if (isset($headers['Content-MD5'])) {
307 $auth_string .= $headers['Content-MD5'];
308 }
309 $auth_string .= "\n" . $headers['Content-Type'] ."\n" . $headers['Date'] ."\n";
310 // Apparently we DONT need a trailing \n here if its not present.
311 if (count($amzheaders)) {
312 $auth_string .= implode("\n",$amzheaders) ."\n";
313 }
314
315 $auth_string .= '/'. $resource;
316 $signature = hex2b64(s3_hmac_sha1($settings['awsSecret'], $auth_string));
317 //print "auth_string:\n$auth_string\n";
318 //print "signature: $signature\n";
319
320 // Set date to proper format for S3.
321 $headers['Date'] = gmdate(DATE_RFC822);
322 $headers['Authorization'] = 'AWS '. $settings['awsID'] .':'. $signature;
323
324 if (count($query)) {
325 $qs = '?'. implode('&', $query);
326 }
327
328 $result = drupal_http_request($url .'/'. $resource . $qs, array_merge($headers, $amzheaders), $method, $body);
329 if ($result->code == '403') {
330 //watchdog('fileapi', t('Amazon S3 Authentication Failed'));
331 // should we log our string to sign, amazon's string to sign, and our signature here?
332 return FALSE;
333 }
334 // clean up the start and finish lines from the XML data that S3 adds for some reason.
335 $result = split("\n", trim($result->data));
336 unset($result[0]);
337 unset($result[count($result)]);
338 $result = implode("\n", $result);
339 return $result;
340 }
341
342
343 /**
344 * Generate an sha1 signed hash of a string.
345 * @param K
346 * key to be used for the signature.
347 * @param string
348 * string to be signed.
349 */
350 function s3_hmac_sha1($K, $string) {
351 // hash function
352 $h = 'sha1';
353 // byte length of blocks operated on by hash function.
354 $B = '64';
355 // byte length of hash function output.
356 // seems unused in the HMAC algorithm
357 $L = '20';
358 return s3_hmac($h, $B, $K, $string);
359 }
360
361 /**
362 * HMAC implementation for drupal as per
363 * http://www.ietf.org/rfc/rfc2104.txt
364 *
365 * @param $h
366 * hashing function can be any
367 * @param $B
368 * block length iterated over by hashing algorithm
369 * @param $K
370 * key to be used for signing.
371 * @param $string
372 * string to be signed.
373 */
374 function s3_hmac($h, $B, $K, $string) {
375 // Hash the key if it exceeds the blocklength.
376 if (strlen($K) > $B) {
377 // Pack that dirty string.
378 $K = pack('H*', $h($K));
379 }
380 // pad it with 0 bytes to block length
381 $K = str_pad($K, $B, chr(0x00));
382
383 // Setup inner and outer pads as per the RFC.
384 $ipad = str_repeat(chr(0x36), $B)^$K;
385 $opad = str_repeat(chr(0x5C), $B)^$K;
386
387 // Do inner concat XOR hashing. Pack that dirty string.
388 $string = pack('H*', $h($ipad.$string));
389
390 // return outer concat XOR hash
391 return $h($opad.$string);
392 }
393
394 // conversion.
395 function hex2b64($str)
396 {
397 $raw = '';
398 for ($i=0; $i < strlen($str); $i+=2)
399 {
400 $raw .= chr(hexdec(substr($str, $i, 2)));
401 }
402 return base64_encode($raw);
403 }
404

  ViewVC Help
Powered by ViewVC 1.1.2