3fbb3b48c0579a770afe893b84dc65e713f94ec3
[debian/amanda] / device-src / s3.c
1 /*
2  * Copyright (c) 2005-2008 Zmanda Inc.  All Rights Reserved.
3  * 
4  * This library is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License version 2.1 as 
6  * published by the Free Software Foundation.
7  * 
8  * This library is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
11  * License for more details.
12  * 
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this library; if not, write to the Free Software Foundation,
15  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA.
16  * 
17  * Contact information: Zmanda Inc., 465 S Mathlida Ave, Suite 300
18  * Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
19  */
20
21 /* TODO
22  * - collect speed statistics
23  * - debugging mode
24  */
25
26 #ifdef HAVE_CONFIG_H
27 /* use a relative path here to avoid conflicting with Perl's config.h. */
28 #include "../config/config.h"
29 #endif
30 #include <string.h>
31 #include "s3.h"
32 #include "s3-util.h"
33 #ifdef HAVE_REGEX_H
34 #include <regex.h>
35 #endif
36 #ifdef HAVE_SYS_TYPES_H
37 #include <sys/types.h>
38 #endif
39 #ifdef HAVE_SYS_STAT_H
40 #include <sys/stat.h>
41 #endif
42 #ifdef HAVE_UNISTD_H
43 #include <unistd.h>
44 #endif
45 #ifdef HAVE_DIRENT_H
46 #include <dirent.h>
47 #endif
48 #ifdef HAVE_TIME_H
49 #include <time.h>
50 #endif
51 #ifdef HAVE_UTIL_H
52 #include "util.h"
53 #endif
54 #ifdef HAVE_AMANDA_H
55 #include "amanda.h"
56 #endif
57
58 #include <curl/curl.h>
59
60 /* Constant renamed after version 7.10.7 */
61 #ifndef CURLINFO_RESPONSE_CODE
62 #define CURLINFO_RESPONSE_CODE CURLINFO_HTTP_CODE
63 #endif
64
65 /* We don't need OpenSSL's kerberos support, and it's broken in
66  * RHEL 3 anyway. */
67 #define OPENSSL_NO_KRB5
68
69 #ifdef HAVE_OPENSSL_HMAC_H
70 # include <openssl/hmac.h>
71 #else
72 # ifdef HAVE_CRYPTO_HMAC_H
73 #  include <crypto/hmac.h>
74 # else
75 #  ifdef HAVE_HMAC_H
76 #   include <hmac.h>
77 #  endif
78 # endif
79 #endif
80
81 #include <openssl/err.h>
82 #include <openssl/ssl.h>
83 #include <openssl/md5.h>
84
85 /* Maximum key length as specified in the S3 documentation
86  * (*excluding* null terminator) */
87 #define S3_MAX_KEY_LENGTH 1024
88
89 #define AMAZON_SECURITY_HEADER "x-amz-security-token"
90 #define AMAZON_BUCKET_CONF_TEMPLATE "\
91   <CreateBucketConfiguration>\n\
92     <LocationConstraint>%s</LocationConstraint>\n\
93   </CreateBucketConfiguration>"
94
95 /* parameters for exponential backoff in the face of retriable errors */
96
97 /* start at 0.01s */
98 #define EXPONENTIAL_BACKOFF_START_USEC G_USEC_PER_SEC/100
99 /* double at each retry */
100 #define EXPONENTIAL_BACKOFF_BASE 2
101 /* retry 14 times (for a total of about 3 minutes spent waiting) */
102 #define EXPONENTIAL_BACKOFF_MAX_RETRIES 14
103
104 /* general "reasonable size" parameters */
105 #define MAX_ERROR_RESPONSE_LEN (100*1024)
106
107 /* Results which should always be retried */
108 #define RESULT_HANDLING_ALWAYS_RETRY \
109         { 400,  S3_ERROR_RequestTimeout,     0,                         S3_RESULT_RETRY }, \
110         { 404,  S3_ERROR_NoSuchBucket,       0,                         S3_RESULT_RETRY }, \
111         { 409,  S3_ERROR_OperationAborted,   0,                         S3_RESULT_RETRY }, \
112         { 412,  S3_ERROR_PreconditionFailed, 0,                         S3_RESULT_RETRY }, \
113         { 500,  S3_ERROR_InternalError,      0,                         S3_RESULT_RETRY }, \
114         { 501,  S3_ERROR_NotImplemented,     0,                         S3_RESULT_RETRY }, \
115         { 0,    0,                           CURLE_COULDNT_CONNECT,     S3_RESULT_RETRY }, \
116         { 0,    0,                           CURLE_PARTIAL_FILE,        S3_RESULT_RETRY }, \
117         { 0,    0,                           CURLE_OPERATION_TIMEOUTED, S3_RESULT_RETRY }, \
118         { 0,    0,                           CURLE_SEND_ERROR,          S3_RESULT_RETRY }, \
119         { 0,    0,                           CURLE_RECV_ERROR,          S3_RESULT_RETRY }, \
120         { 0,    0,                           CURLE_GOT_NOTHING,         S3_RESULT_RETRY }
121
122 /*
123  * Data structures and associated functions
124  */
125
126 struct S3Handle {
127     /* (all strings in this struct are freed by s3_free()) */
128
129     char *access_key;
130     char *secret_key;
131     char *user_token;
132
133     char *bucket_location;
134
135     CURL *curl;
136
137     gboolean verbose;
138     gboolean use_ssl;
139
140     /* information from the last request */
141     char *last_message;
142     guint last_response_code;
143     s3_error_code_t last_s3_error_code;
144     CURLcode last_curl_code;
145     guint last_num_retries;
146     void *last_response_body;
147     guint last_response_body_size;
148 };
149
150 typedef struct {
151     CurlBuffer resp_buf;
152     s3_write_func write_func;
153     s3_reset_func reset_func;
154     gpointer write_data;
155
156     gboolean headers_done;
157     gboolean int_write_done;
158     char *etag;
159 } S3InternalData;
160
161 /* Callback function to examine headers one-at-a-time
162  * 
163  * @note this is the same as CURLOPT_HEADERFUNCTION
164  *
165  * @param data: The pointer to read data from
166  * @param size: The size of each "element" of the data buffer in bytes
167  * @param nmemb: The number of elements in the data buffer.
168  * So, the buffer's size is size*nmemb bytes.
169  * @param stream: the header_data (an opaque pointer)
170  *
171  * @return The number of bytes written to the buffer or
172  * CURL_WRITEFUNC_PAUSE to pause.
173  * If it's the number of bytes written, it should match the buffer size
174  */
175 typedef size_t (*s3_header_func)(void *data, size_t size, size_t nmemb, void *stream);
176
177
178 /*
179  * S3 errors */
180
181 /* (see preprocessor magic in s3.h) */
182
183 static char * s3_error_code_names[] = {
184 #define S3_ERROR(NAME) #NAME
185     S3_ERROR_LIST
186 #undef S3_ERROR
187 };
188
189 /* Convert an s3 error name to an error code.  This function
190  * matches strings case-insensitively, and is appropriate for use
191  * on data from the network.
192  *
193  * @param s3_error_code: the error name
194  * @returns: the error code (see constants in s3.h)
195  */
196 static s3_error_code_t
197 s3_error_code_from_name(char *s3_error_name);
198
199 /* Convert an s3 error code to a string
200  *
201  * @param s3_error_code: the error code to convert
202  * @returns: statically allocated string
203  */
204 static const char *
205 s3_error_name_from_code(s3_error_code_t s3_error_code);
206
207
208 /*
209  * result handling */
210
211 /* result handling is specified by a static array of result_handling structs,
212  * which match based on response_code (from HTTP) and S3 error code.  The result
213  * given for the first match is used.  0 acts as a wildcard for both response_code
214  * and s3_error_code.  The list is terminated with a struct containing 0 for both
215  * response_code and s3_error_code; the result for that struct is the default
216  * result.
217  *
218  * See RESULT_HANDLING_ALWAYS_RETRY for an example.
219  */
220 typedef enum {
221     S3_RESULT_RETRY = -1,
222     S3_RESULT_FAIL = 0,
223     S3_RESULT_OK = 1
224 } s3_result_t;
225
226 typedef struct result_handling {
227     guint response_code;
228     s3_error_code_t s3_error_code;
229     CURLcode curl_code;
230     s3_result_t result;
231 } result_handling_t;
232
233 /* Lookup a result in C{result_handling}.
234  *
235  * @param result_handling: array of handling specifications
236  * @param response_code: response code from operation
237  * @param s3_error_code: s3 error code from operation, if any
238  * @param curl_code: the CURL error, if any
239  * @returns: the matching result
240  */
241 static s3_result_t
242 lookup_result(const result_handling_t *result_handling,
243               guint response_code,
244               s3_error_code_t s3_error_code,
245               CURLcode curl_code);
246
247 /*
248  * Precompiled regular expressions */
249 static regex_t etag_regex, error_name_regex, message_regex, subdomain_regex,
250     location_con_regex;
251
252 /*
253  * Utility functions
254  */
255
256 /* Construct the URL for an Amazon S3 REST request.
257  *
258  * A new string is allocated and returned; it is the responsiblity of the caller.
259  *
260  * @param hdl: the S3Handle object
261  * @param verb: capitalized verb for this request ('PUT', 'GET', etc.)
262  * @param bucket: the bucket being accessed, or NULL for none
263  * @param key: the key being accessed, or NULL for none
264  * @param subresource: the sub-resource being accessed (e.g. "acl"), or NULL for none
265  * @param use_subdomain: if TRUE, a subdomain of s3.amazonaws.com will be used
266  */
267 static char *
268 build_url(const char *bucket,
269       const char *key,
270       const char *subresource,
271       const char *query,
272       gboolean use_subdomain,
273       gboolean use_ssl);
274
275 /* Create proper authorization headers for an Amazon S3 REST
276  * request to C{headers}.
277  *
278  * @note: C{X-Amz} headers (in C{headers}) must
279  *  - be in lower-case
280  *  - be in alphabetical order
281  *  - have no spaces around the colon
282  * (don't yell at me -- see the Amazon Developer Guide)
283  *
284  * @param hdl: the S3Handle object
285  * @param verb: capitalized verb for this request ('PUT', 'GET', etc.)
286  * @param bucket: the bucket being accessed, or NULL for none
287  * @param key: the key being accessed, or NULL for none
288  * @param subresource: the sub-resource being accessed (e.g. "acl"), or NULL for none
289  * @param md5_hash: the MD5 hash of the request body, or NULL for none
290  * @param use_subdomain: if TRUE, a subdomain of s3.amazonaws.com will be used
291  */
292 static struct curl_slist *
293 authenticate_request(S3Handle *hdl,
294                      const char *verb,
295                      const char *bucket,
296                      const char *key,
297                      const char *subresource,
298                      const char *md5_hash,
299                      gboolean use_subdomain);
300
301
302
303 /* Interpret the response to an S3 operation, assuming CURL completed its request
304  * successfully.  This function fills in the relevant C{hdl->last*} members.
305  *
306  * @param hdl: The S3Handle object
307  * @param body: the response body
308  * @param body_len: the length of the response body
309  * @param etag: The response's ETag header
310  * @param content_md5: The hex-encoded MD5 hash of the request body, 
311  *     which will be checked against the response's ETag header.
312  *     If NULL, the header is not checked.
313  *     If non-NULL, then the body should have the response headers at its beginnning.
314  * @returns: TRUE if the response should be retried (e.g., network error)
315  */
316 static gboolean
317 interpret_response(S3Handle *hdl,
318                    CURLcode curl_code,
319                    char *curl_error_buffer,
320                    gchar *body,
321                    guint body_len,
322                    const char *etag,
323                    const char *content_md5);
324
325 /* Perform an S3 operation.  This function handles all of the details
326  * of retryig requests and so on.
327  * 
328  * The concepts of bucket and keys are defined by the Amazon S3 API.
329  * See: "Components of Amazon S3" - API Version 2006-03-01 pg. 8
330  *
331  * Individual sub-resources are defined in several places. In the REST API, 
332  * they they are represented by a "flag" in the "query string".
333  * See: "Constructing the CanonicalizedResource Element" - API Version 2006-03-01 pg. 60
334  *
335  * @param hdl: the S3Handle object
336  * @param verb: the HTTP request method
337  * @param bucket: the bucket to access, or NULL for none
338  * @param key: the key to access, or NULL for none
339  * @param subresource: the "sub-resource" to request (e.g. "acl") or NULL for none
340  * @param query: the query string to send (not including th initial '?'),
341  * or NULL for none
342  * @param read_func: the callback for reading data
343  *   Will use s3_empty_read_func if NULL is passed in.
344  * @param read_reset_func: the callback for to reset reading data
345  * @param size_func: the callback to get the number of bytes to upload
346  * @param md5_func: the callback to get the MD5 hash of the data to upload
347  * @param read_data: pointer to pass to the above functions
348  * @param write_func: the callback for writing data.
349  *   Will use s3_counter_write_func if NULL is passed in.
350  * @param write_reset_func: the callback for to reset writing data
351  * @param write_data: pointer to pass to C{write_func}
352  * @param progress_func: the callback for progress information
353  * @param progress_data: pointer to pass to C{progress_func}
354  * @param result_handling: instructions for handling the results; see above.
355  * @returns: the result specified by result_handling; details of the response
356  * are then available in C{hdl->last*}
357  */
358 static s3_result_t
359 perform_request(S3Handle *hdl,
360                 const char *verb,
361                 const char *bucket,
362                 const char *key,
363                 const char *subresource,
364                 const char *query,
365                 s3_read_func read_func,
366                 s3_reset_func read_reset_func,
367                 s3_size_func size_func,
368                 s3_md5_func md5_func,
369                 gpointer read_data,
370                 s3_write_func write_func,
371                 s3_reset_func write_reset_func,
372                 gpointer write_data,
373                 s3_progress_func progress_func,
374                 gpointer progress_data,
375                 const result_handling_t *result_handling);
376
377 /*
378  * a CURLOPT_WRITEFUNCTION to save part of the response in memory and 
379  * call an external function if one was provided.
380  */
381 static size_t
382 s3_internal_write_func(void *ptr, size_t size, size_t nmemb, void * stream);
383
384 /*
385  * a function to reset to our internal buffer
386  */
387 static void
388 s3_internal_reset_func(void * stream);
389
390 /*
391  * a CURLOPT_HEADERFUNCTION to save the ETag header only.
392  */
393 static size_t
394 s3_internal_header_func(void *ptr, size_t size, size_t nmemb, void * stream);
395
396 static gboolean
397 compile_regexes(void);
398
399 /*
400  * Static function implementations
401  */
402 static s3_error_code_t
403 s3_error_code_from_name(char *s3_error_name)
404 {
405     int i;
406
407     if (!s3_error_name) return S3_ERROR_Unknown;
408
409     /* do a brute-force search through the list, since it's not sorted */
410     for (i = 0; i < S3_ERROR_END; i++) {
411         if (g_strcasecmp(s3_error_name, s3_error_code_names[i]) == 0)
412             return i;
413     }
414
415     return S3_ERROR_Unknown;
416 }
417
418 static const char *
419 s3_error_name_from_code(s3_error_code_t s3_error_code)
420 {
421     if (s3_error_code >= S3_ERROR_END)
422         s3_error_code = S3_ERROR_Unknown;
423
424     return s3_error_code_names[s3_error_code];
425 }
426
427 gboolean
428 s3_curl_supports_ssl(void)
429 {
430     static int supported = -1;
431     if (supported == -1) {
432 #if defined(CURL_VERSION_SSL)
433     curl_version_info_data *info = curl_version_info(CURLVERSION_NOW);
434     if (info->features & CURL_VERSION_SSL)
435         supported = 1;
436     else
437         supported = 0;
438 #else
439     supported = 0;
440 #endif
441     }
442
443     return supported;
444 }
445
446 static s3_result_t
447 lookup_result(const result_handling_t *result_handling,
448               guint response_code,
449               s3_error_code_t s3_error_code,
450               CURLcode curl_code)
451 {
452     while (result_handling->response_code
453         || result_handling->s3_error_code 
454         || result_handling->curl_code) {
455         if ((result_handling->response_code && result_handling->response_code != response_code)
456          || (result_handling->s3_error_code && result_handling->s3_error_code != s3_error_code)
457          || (result_handling->curl_code && result_handling->curl_code != curl_code)) {
458             result_handling++;
459             continue;
460         }
461
462         return result_handling->result;
463     }
464
465     /* return the result for the terminator, as the default */
466     return result_handling->result;
467 }
468
469 static char *
470 build_url(const char *bucket,
471       const char *key,
472       const char *subresource,
473       const char *query,
474       gboolean use_subdomain,
475       gboolean use_ssl)
476 {
477     GString *url = NULL;
478     char *esc_bucket = NULL, *esc_key = NULL;
479
480     /* scheme */
481     url = g_string_new("http");
482     if (use_ssl)
483         g_string_append(url, "s");
484
485     g_string_append(url, "://");
486
487     /* domain */
488     if (use_subdomain && bucket)
489         g_string_append_printf(url, "%s.s3.amazonaws.com/", bucket);
490     else
491         g_string_append(url, "s3.amazonaws.com/");
492     
493     /* path */
494     if (!use_subdomain && bucket) {
495         esc_bucket = curl_escape(bucket, 0);
496     if (!esc_bucket) goto cleanup;
497         g_string_append_printf(url, "%s", esc_bucket);
498         if (key)
499             g_string_append(url, "/");
500     }
501
502     if (key) {
503         esc_key = curl_escape(key, 0);
504     if (!esc_key) goto cleanup;
505         g_string_append_printf(url, "%s", esc_key);
506     }
507
508     /* query string */
509     if (subresource || query)
510         g_string_append(url, "?");
511
512     if (subresource)
513         g_string_append(url, subresource);
514
515     if (subresource && query)
516         g_string_append(url, "&");
517
518     if (query)
519         g_string_append(url, query);
520
521 cleanup:
522     if (esc_bucket) curl_free(esc_bucket);
523     if (esc_key) curl_free(esc_key);
524
525     return g_string_free(url, FALSE);
526 }
527
528 static struct curl_slist *
529 authenticate_request(S3Handle *hdl,
530                      const char *verb,
531                      const char *bucket,
532                      const char *key,
533                      const char *subresource,
534                      const char *md5_hash,
535              gboolean use_subdomain) 
536 {
537     time_t t;
538     struct tm tmp;
539     char date[100];
540     char *buf = NULL;
541     HMAC_CTX ctx;
542     GByteArray *md = NULL;
543     char *auth_base64 = NULL;
544     struct curl_slist *headers = NULL;
545     char *esc_bucket = NULL, *esc_key = NULL;
546     GString *auth_string = NULL;
547
548     /* Build the string to sign, per the S3 spec.
549      * See: "Authenticating REST Requests" - API Version 2006-03-01 pg 58
550      */
551     
552     /* verb */
553     auth_string = g_string_new(verb);
554     g_string_append(auth_string, "\n");
555     
556     /* Content-MD5 header */
557     if (md5_hash)
558         g_string_append(auth_string, md5_hash);
559     g_string_append(auth_string, "\n");
560
561     /* Content-Type is empty*/
562     g_string_append(auth_string, "\n");
563
564
565     /* calculate the date */
566     t = time(NULL);
567 #ifdef _WIN32
568     if (!localtime_s(&tmp, &t)) g_debug("localtime error");
569 #else
570     if (!localtime_r(&t, &tmp)) perror("localtime");
571 #endif
572     if (!strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %Z", &tmp)) 
573         perror("strftime");
574
575     g_string_append(auth_string, date);
576     g_string_append(auth_string, "\n");
577
578     if (hdl->user_token) {
579         g_string_append(auth_string, AMAZON_SECURITY_HEADER);
580         g_string_append(auth_string, ":");
581         g_string_append(auth_string, hdl->user_token);
582         g_string_append(auth_string, ",");
583         g_string_append(auth_string, STS_PRODUCT_TOKEN);
584         g_string_append(auth_string, "\n");
585     }
586
587     /* CanonicalizedResource */
588     g_string_append(auth_string, "/");
589     if (bucket) {
590         if (use_subdomain)
591             g_string_append(auth_string, bucket);
592         else {
593             esc_bucket = curl_escape(bucket, 0);
594             if (!esc_bucket) goto cleanup;
595             g_string_append(auth_string, esc_bucket);
596         }
597     }
598
599     if (bucket && (use_subdomain || key))
600         g_string_append(auth_string, "/");
601
602     if (key) {
603             esc_key = curl_escape(key, 0);
604             if (!esc_key) goto cleanup;
605             g_string_append(auth_string, esc_key);
606     }
607
608     if (subresource) {
609         g_string_append(auth_string, "?");
610         g_string_append(auth_string, subresource);
611     }
612
613     /* run HMAC-SHA1 on the canonicalized string */
614     md = g_byte_array_sized_new(EVP_MAX_MD_SIZE+1);
615     HMAC_CTX_init(&ctx);
616     HMAC_Init_ex(&ctx, hdl->secret_key, (int) strlen(hdl->secret_key), EVP_sha1(), NULL);
617     HMAC_Update(&ctx, (unsigned char*) auth_string->str, auth_string->len);
618     HMAC_Final(&ctx, md->data, &md->len);
619     HMAC_CTX_cleanup(&ctx);
620     auth_base64 = s3_base64_encode(md);
621
622     /* append the new headers */
623     if (hdl->user_token) {
624         /* Devpay headers are included in hash. */
625         buf = g_strdup_printf(AMAZON_SECURITY_HEADER ": %s", hdl->user_token);
626         headers = curl_slist_append(headers, buf);
627         g_free(buf);
628
629         buf = g_strdup_printf(AMAZON_SECURITY_HEADER ": %s", STS_PRODUCT_TOKEN);
630         headers = curl_slist_append(headers, buf);
631         g_free(buf);
632     }
633
634     buf = g_strdup_printf("Authorization: AWS %s:%s",
635                           hdl->access_key, auth_base64);
636     headers = curl_slist_append(headers, buf);
637     g_free(buf);
638     
639     if (md5_hash && '\0' != md5_hash[0]) {
640         buf = g_strdup_printf("Content-MD5: %s", md5_hash);
641         headers = curl_slist_append(headers, buf);
642         g_free(buf);
643     }
644
645     buf = g_strdup_printf("Date: %s", date);
646     headers = curl_slist_append(headers, buf);
647     g_free(buf);
648 cleanup:
649     g_free(esc_bucket);
650     g_free(esc_key);
651     g_byte_array_free(md, TRUE);
652     g_free(auth_base64);
653     g_string_free(auth_string, TRUE);
654
655     return headers;
656 }
657
658 static gboolean
659 interpret_response(S3Handle *hdl,
660                    CURLcode curl_code,
661                    char *curl_error_buffer,
662                    gchar *body,
663                    guint body_len,
664                    const char *etag,
665                    const char *content_md5)
666 {
667     long response_code = 0;
668     regmatch_t pmatch[2];
669     char *error_name = NULL, *message = NULL;
670     char *body_copy = NULL;
671     gboolean ret = TRUE;
672
673     if (!hdl) return FALSE;
674
675     if (hdl->last_message) g_free(hdl->last_message);
676     hdl->last_message = NULL;
677
678     /* bail out from a CURL error */
679     if (curl_code != CURLE_OK) {
680         hdl->last_curl_code = curl_code;
681         hdl->last_message = g_strdup_printf("CURL error: %s", curl_error_buffer);
682         return FALSE;
683     }
684
685     /* CURL seems to think things were OK, so get its response code */
686     curl_easy_getinfo(hdl->curl, CURLINFO_RESPONSE_CODE, &response_code);
687     hdl->last_response_code = response_code;
688
689     /* check ETag, if present */
690     if (etag && content_md5 && 200 == response_code) {
691         if (etag && g_strcasecmp(etag, content_md5))
692             hdl->last_message = g_strdup("S3 Error: Possible data corruption (ETag returned by Amazon did not match the MD5 hash of the data sent)");
693         else
694             ret = FALSE;
695         return ret;
696     }
697
698     if (200 <= response_code && response_code < 400) {
699         /* 2xx and 3xx codes won't have a response body we care about */
700         hdl->last_s3_error_code = S3_ERROR_None;
701         return FALSE;
702     }
703
704     /* Now look at the body to try to get the actual Amazon error message. Rather
705      * than parse out the XML, just use some regexes. */
706
707     /* impose a reasonable limit on body size */
708     if (body_len > MAX_ERROR_RESPONSE_LEN) {
709         hdl->last_message = g_strdup("S3 Error: Unknown (response body too large to parse)");
710         return FALSE;
711     } else if (!body || body_len == 0) {
712         hdl->last_message = g_strdup("S3 Error: Unknown (empty response body)");
713         return TRUE; /* perhaps a network error; retry the request */
714     }
715
716     /* use strndup to get a zero-terminated string */
717     body_copy = g_strndup(body, body_len);
718     if (!body_copy) goto cleanup;
719
720     if (!s3_regexec_wrap(&error_name_regex, body_copy, 2, pmatch, 0))
721         error_name = find_regex_substring(body_copy, pmatch[1]);
722
723     if (!s3_regexec_wrap(&message_regex, body_copy, 2, pmatch, 0))
724         message = find_regex_substring(body_copy, pmatch[1]);
725
726     if (error_name) {
727         hdl->last_s3_error_code = s3_error_code_from_name(error_name);
728     }
729
730     if (message) {
731         hdl->last_message = message;
732         message = NULL; /* steal the reference to the string */
733     }
734
735 cleanup:
736     g_free(body_copy);
737     g_free(message);
738     g_free(error_name);
739
740     return FALSE;
741 }
742
743 /* a CURLOPT_READFUNCTION to read data from a buffer. */
744 size_t
745 s3_buffer_read_func(void *ptr, size_t size, size_t nmemb, void * stream)
746 {
747     CurlBuffer *data = stream;
748     guint bytes_desired = (guint) size * nmemb;
749
750     /* check the number of bytes remaining, just to be safe */
751     if (bytes_desired > data->buffer_len - data->buffer_pos)
752         bytes_desired = data->buffer_len - data->buffer_pos;
753
754     memcpy((char *)ptr, data->buffer + data->buffer_pos, bytes_desired);
755     data->buffer_pos += bytes_desired;
756
757     return bytes_desired;
758 }
759
760 size_t
761 s3_buffer_size_func(void *stream)
762 {
763     CurlBuffer *data = stream;
764     return data->buffer_len;
765 }
766
767 GByteArray*
768 s3_buffer_md5_func(void *stream)
769 {
770     CurlBuffer *data = stream;
771     GByteArray req_body_gba = {(guint8 *)data->buffer, data->buffer_len};
772
773     return s3_compute_md5_hash(&req_body_gba);
774 }
775
776 void
777 s3_buffer_reset_func(void *stream)
778 {
779     CurlBuffer *data = stream;
780     data->buffer_pos = 0;
781 }
782
783 /* a CURLOPT_WRITEFUNCTION to write data to a buffer. */
784 size_t
785 s3_buffer_write_func(void *ptr, size_t size, size_t nmemb, void *stream)
786 {
787     CurlBuffer * data = stream;
788     guint new_bytes = (guint) size * nmemb;
789     guint bytes_needed = data->buffer_pos + new_bytes;
790
791     /* error out if the new size is greater than the maximum allowed */
792     if (data->max_buffer_size && bytes_needed > data->max_buffer_size)
793         return 0;
794
795     /* reallocate if necessary. We use exponential sizing to make this
796      * happen less often. */
797     if (bytes_needed > data->buffer_len) {
798         guint new_size = MAX(bytes_needed, data->buffer_len * 2);
799         if (data->max_buffer_size) {
800             new_size = MIN(new_size, data->max_buffer_size);
801         }
802         data->buffer = g_realloc(data->buffer, new_size);
803         data->buffer_len = new_size;
804     }
805     if (!data->buffer)
806         return 0; /* returning zero signals an error to libcurl */
807
808     /* actually copy the data to the buffer */
809     memcpy(data->buffer + data->buffer_pos, ptr, new_bytes);
810     data->buffer_pos += new_bytes;
811
812     /* signal success to curl */
813     return new_bytes;
814 }
815
816 /* a CURLOPT_READFUNCTION that writes nothing. */
817 size_t
818 s3_empty_read_func(G_GNUC_UNUSED void *ptr, G_GNUC_UNUSED size_t size, G_GNUC_UNUSED size_t nmemb, G_GNUC_UNUSED void * stream)
819 {
820     return 0;
821 }
822
823 size_t
824 s3_empty_size_func(G_GNUC_UNUSED void *stream)
825 {
826     return 0;
827 }
828
829 GByteArray*
830 s3_empty_md5_func(G_GNUC_UNUSED void *stream)
831 {
832     static const GByteArray empty = {(guint8 *) "", 0};
833
834     return s3_compute_md5_hash(&empty);
835 }
836
837 /* a CURLOPT_WRITEFUNCTION to write data that just counts data.
838  * s3_write_data should be NULL or a pointer to an gint64.
839  */
840 size_t
841 s3_counter_write_func(G_GNUC_UNUSED void *ptr, size_t size, size_t nmemb, void *stream)
842 {
843     gint64 *count = (gint64*) stream, inc = nmemb*size;
844     
845     if (count) *count += inc;
846     return inc;
847 }
848
849 void
850 s3_counter_reset_func(void *stream)
851 {
852     gint64 *count = (gint64*) stream;
853
854     if (count) *count = 0;
855 }
856
857 #ifdef _WIN32
858 /* a CURLOPT_READFUNCTION to read data from a file. */
859 size_t
860 s3_file_read_func(void *ptr, size_t size, size_t nmemb, void * stream)
861 {
862     HANDLE *hFile = (HANDLE *) stream;
863     DWORD bytes_read;
864
865     ReadFile(hFile, ptr, (DWORD) size*nmemb, &bytes_read, NULL);
866     return bytes_read;
867 }
868
869 size_t
870 s3_file_size_func(void *stream)
871 {
872     HANDLE *hFile = (HANDLE *) stream;
873     DWORD size = GetFileSize(hFile, NULL);
874
875     if (INVALID_FILE_SIZE == size) {
876         return -1;
877     } else {
878         return size;
879     }
880 }
881
882 GByteArray*
883 s3_file_md5_func(void *stream)
884 {
885 #define S3_MD5_BUF_SIZE (10*1024)
886     HANDLE *hFile = (HANDLE *) stream;
887     guint8 buf[S3_MD5_BUF_SIZE];
888     DWORD bytes_read;
889     MD5_CTX md5_ctx;
890     GByteArray *ret = NULL;
891
892     g_assert(INVALID_SET_FILE_POINTER != SetFilePointer(hFile, 0, NULL, FILE_BEGIN));
893
894     ret = g_byte_array_sized_new(S3_MD5_HASH_BYTE_LEN);
895     g_byte_array_set_size(ret, S3_MD5_HASH_BYTE_LEN);    
896     MD5_Init(&md5_ctx);
897
898     while (ReadFile(hFile, buf, S3_MD5_BUF_SIZE, &bytes_read, NULL)) {
899         MD5_Update(&md5_ctx, buf, bytes_read);
900     }
901     MD5_Final(ret->data, &md5_ctx);
902
903     g_assert(INVALID_SET_FILE_POINTER != SetFilePointer(hFile, 0, NULL, FILE_BEGIN));
904     return ret;
905 #undef S3_MD5_BUF_SIZE
906 }
907
908 GByteArray*
909 s3_file_reset_func(void *stream)
910 {
911     g_assert(INVALID_SET_FILE_POINTER != SetFilePointer(hFile, 0, NULL, FILE_BEGIN));
912 }
913
914 /* a CURLOPT_WRITEFUNCTION to write data to a file. */
915 size_t
916 s3_file_write_func(void *ptr, size_t size, size_t nmemb, void *stream)
917 {
918     HANDLE *hFile = (HANDLE *) stream;
919     DWORD bytes_written;
920
921     WriteFile(hFile, ptr, (DWORD) size*nmemb, &bytes_written, NULL);
922     return bytes_written;
923 }
924 #endif
925
926 static int 
927 curl_debug_message(CURL *curl G_GNUC_UNUSED, 
928            curl_infotype type, 
929            char *s, 
930            size_t len, 
931            void *unused G_GNUC_UNUSED)
932 {
933     char *lineprefix;
934     char *message;
935     char **lines, **line;
936
937     switch (type) {
938     case CURLINFO_TEXT:
939         lineprefix="";
940         break;
941
942     case CURLINFO_HEADER_IN:
943         lineprefix="Hdr In: ";
944         break;
945
946     case CURLINFO_HEADER_OUT:
947         lineprefix="Hdr Out: ";
948         break;
949
950     default:
951         /* ignore data in/out -- nobody wants to see that in the
952          * debug logs! */
953         return 0;
954     }
955
956     /* split the input into lines */
957     message = g_strndup(s, (gsize) len);
958     lines = g_strsplit(message, "\n", -1);
959     g_free(message);
960
961     for (line = lines; *line; line++) {
962     if (**line == '\0') continue; /* skip blank lines */
963     g_debug("%s%s", lineprefix, *line);
964     }
965     g_strfreev(lines);
966
967     return 0;
968 }
969
970 static s3_result_t
971 perform_request(S3Handle *hdl,
972                 const char *verb,
973                 const char *bucket,
974                 const char *key,
975                 const char *subresource,
976                 const char *query,
977                 s3_read_func read_func,
978                 s3_reset_func read_reset_func,
979                 s3_size_func size_func,
980                 s3_md5_func md5_func,
981                 gpointer read_data,
982                 s3_write_func write_func,
983                 s3_reset_func write_reset_func,
984                 gpointer write_data,
985                 s3_progress_func progress_func,
986                 gpointer progress_data,
987                 const result_handling_t *result_handling)
988 {
989     gboolean use_subdomain;
990     char *url = NULL;
991     s3_result_t result = S3_RESULT_FAIL; /* assume the worst.. */
992     CURLcode curl_code = CURLE_OK;
993     char curl_error_buffer[CURL_ERROR_SIZE] = "";
994     struct curl_slist *headers = NULL;
995     S3InternalData int_writedata = {{NULL, 0, 0, MAX_ERROR_RESPONSE_LEN}, NULL, NULL, NULL, FALSE, FALSE, NULL};
996     gboolean should_retry;
997     guint retries = 0;
998     gulong backoff = EXPONENTIAL_BACKOFF_START_USEC;
999     /* corresponds to PUT, HEAD, GET, and POST */
1000     int curlopt_upload = 0, curlopt_nobody = 0, curlopt_httpget = 0, curlopt_post = 0;
1001     /* do we want to examine the headers */
1002     const char *curlopt_customrequest = NULL;
1003     /* for MD5 calculation */
1004     GByteArray *md5_hash = NULL;
1005     gchar *md5_hash_hex = NULL, *md5_hash_b64 = NULL;
1006     size_t request_body_size = 0;
1007
1008     g_assert(hdl != NULL && hdl->curl != NULL);
1009
1010     s3_reset(hdl);
1011
1012     use_subdomain = hdl->bucket_location? TRUE : FALSE;
1013     url = build_url(bucket, key, subresource, query, use_subdomain, hdl->use_ssl);
1014     if (!url) goto cleanup;
1015
1016     /* libcurl may behave strangely if these are not set correctly */
1017     if (!strncmp(verb, "PUT", 4)) {
1018         curlopt_upload = 1;
1019     } else if (!strncmp(verb, "GET", 4)) {
1020         curlopt_httpget = 1;
1021     } else if (!strncmp(verb, "POST", 5)) {
1022         curlopt_post = 1;
1023     } else if (!strncmp(verb, "HEAD", 5)) {
1024         curlopt_nobody = 1;
1025     } else {
1026         curlopt_customrequest = verb;
1027     }
1028
1029     if (size_func) {
1030         request_body_size = size_func(read_data);
1031     }
1032     if (md5_func) {
1033
1034         md5_hash = md5_func(read_data);
1035         if (md5_hash) {
1036             md5_hash_b64 = s3_base64_encode(md5_hash);
1037             md5_hash_hex = s3_hex_encode(md5_hash);
1038             g_byte_array_free(md5_hash, TRUE);
1039         }
1040     }
1041     if (!read_func) {
1042         /* Curl will use fread() otherwise */
1043         read_func = s3_empty_read_func;
1044     }
1045
1046     if (write_func) {
1047         int_writedata.write_func = write_func;
1048         int_writedata.reset_func = write_reset_func;
1049         int_writedata.write_data = write_data;
1050     } else {
1051         /* Curl will use fwrite() otherwise */
1052         int_writedata.write_func = s3_counter_write_func;
1053         int_writedata.reset_func = s3_counter_reset_func;
1054         int_writedata.write_data = NULL;
1055     }
1056
1057     while (1) {
1058         /* reset things */
1059         if (headers) {
1060             curl_slist_free_all(headers);
1061         }
1062         curl_error_buffer[0] = '\0';
1063         if (read_reset_func) {
1064             read_reset_func(read_data);
1065         }
1066         /* calls write_reset_func */
1067         s3_internal_reset_func(&int_writedata);
1068
1069         /* set up the request */
1070         headers = authenticate_request(hdl, verb, bucket, key, subresource,
1071             md5_hash_b64, hdl->bucket_location? TRUE : FALSE);
1072
1073         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_VERBOSE, hdl->verbose)))
1074             goto curl_error;
1075         if (hdl->verbose) {
1076             if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_DEBUGFUNCTION, 
1077                               curl_debug_message)))
1078                 goto curl_error;
1079         }
1080         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_ERRORBUFFER,
1081                                           curl_error_buffer)))
1082             goto curl_error;
1083         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_NOPROGRESS, 1)))
1084             goto curl_error;
1085         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_FOLLOWLOCATION, 1)))
1086             goto curl_error;
1087         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_URL, url)))
1088             goto curl_error;
1089         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_HTTPHEADER,
1090                                           headers)))
1091             goto curl_error;
1092         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_WRITEFUNCTION, s3_internal_write_func))) 
1093             goto curl_error;
1094         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_WRITEDATA, &int_writedata))) 
1095             goto curl_error;
1096         /* Note: we always have to set this apparently, for consistent "end of header" detection */
1097         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_HEADERFUNCTION, s3_internal_header_func))) 
1098             goto curl_error;
1099         /* Note: if set, CURLOPT_HEADERDATA seems to also be used for CURLOPT_WRITEDATA ? */
1100         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_HEADERDATA, &int_writedata))) 
1101             goto curl_error;
1102         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_PROGRESSFUNCTION, progress_func))) 
1103             goto curl_error;
1104         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_PROGRESSDATA, progress_data))) 
1105             goto curl_error;
1106
1107 #ifdef CURLOPT_INFILESIZE_LARGE
1108         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)request_body_size)))
1109             goto curl_error;
1110 #else
1111         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_INFILESIZE, (long)request_body_size)))
1112             goto curl_error;
1113 #endif
1114
1115         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_HTTPGET, curlopt_httpget)))
1116             goto curl_error;
1117         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_UPLOAD, curlopt_upload)))
1118             goto curl_error;
1119         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_POST, curlopt_post)))
1120             goto curl_error;
1121         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_NOBODY, curlopt_nobody)))
1122             goto curl_error;
1123         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_CUSTOMREQUEST,
1124                                           curlopt_customrequest)))
1125             goto curl_error;
1126
1127
1128         if (curlopt_upload) {
1129             if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_READFUNCTION, read_func))) 
1130                 goto curl_error;
1131             if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_READDATA, read_data))) 
1132                 goto curl_error;
1133         } else {
1134             /* Clear request_body options. */
1135             if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_READFUNCTION,
1136                                               NULL)))
1137                 goto curl_error;
1138             if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_READDATA, 
1139                                               NULL)))
1140                 goto curl_error;
1141         }
1142
1143         /* Perform the request */
1144         curl_code = curl_easy_perform(hdl->curl);
1145
1146
1147         /* interpret the response into hdl->last* */
1148     curl_error: /* (label for short-circuiting the curl_easy_perform call) */
1149         should_retry = interpret_response(hdl, curl_code, curl_error_buffer, 
1150             int_writedata.resp_buf.buffer, int_writedata.resp_buf.buffer_pos, int_writedata.etag, md5_hash_hex);
1151         
1152         /* and, unless we know we need to retry, see what we're to do now */
1153         if (!should_retry) {
1154             result = lookup_result(result_handling, hdl->last_response_code, 
1155                                    hdl->last_s3_error_code, hdl->last_curl_code);
1156
1157             /* break out of the while(1) unless we're retrying */
1158             if (result != S3_RESULT_RETRY)
1159                 break;
1160         }
1161
1162         if (retries >= EXPONENTIAL_BACKOFF_MAX_RETRIES) {
1163             /* we're out of retries, so annotate hdl->last_message appropriately and bail
1164              * out. */
1165             char *m = g_strdup_printf("Too many retries; last message was '%s'", hdl->last_message);
1166             if (hdl->last_message) g_free(hdl->last_message);
1167             hdl->last_message = m;
1168             result = S3_RESULT_FAIL;
1169             break;
1170         }
1171
1172         g_usleep(backoff);
1173         retries++;
1174         backoff *= EXPONENTIAL_BACKOFF_BASE;
1175     }
1176
1177     if (result != S3_RESULT_OK) {
1178         g_debug(_("%s %s failed with %d/%s"), verb, url,
1179                 hdl->last_response_code,
1180                 s3_error_name_from_code(hdl->last_s3_error_code)); 
1181     }
1182
1183 cleanup:
1184     g_free(url);
1185     if (headers) curl_slist_free_all(headers);
1186     g_free(md5_hash_b64);
1187     g_free(md5_hash_hex);
1188     
1189     /* we don't deallocate the response body -- we keep it for later */
1190     hdl->last_response_body = int_writedata.resp_buf.buffer;
1191     hdl->last_response_body_size = int_writedata.resp_buf.buffer_pos;
1192     hdl->last_num_retries = retries;
1193
1194     return result;
1195 }
1196
1197
1198 static size_t
1199 s3_internal_write_func(void *ptr, size_t size, size_t nmemb, void * stream)
1200 {
1201     S3InternalData *data = (S3InternalData *) stream;
1202     size_t bytes_saved;
1203
1204     if (!data->headers_done)
1205         return size*nmemb;
1206
1207     /* call write on internal buffer (if not full) */
1208     if (data->int_write_done) {
1209         bytes_saved = 0;
1210     } else {
1211         bytes_saved = s3_buffer_write_func(ptr, size, nmemb, &data->resp_buf);
1212         if (!bytes_saved) {
1213             data->int_write_done = TRUE;
1214         }
1215     }
1216     /* call write on user buffer */
1217     if (data->write_func) {
1218         return data->write_func(ptr, size, nmemb, data->write_data);
1219     } else {
1220         return bytes_saved;
1221     }
1222 }
1223
1224 static void
1225 s3_internal_reset_func(void * stream)
1226 {
1227     S3InternalData *data = (S3InternalData *) stream;
1228
1229     s3_buffer_reset_func(&data->resp_buf);
1230     data->headers_done = FALSE;
1231     data->int_write_done = FALSE;
1232     data->etag = NULL;
1233     if (data->reset_func) {
1234         data->reset_func(data->write_data);
1235     }
1236 }
1237
1238 static size_t
1239 s3_internal_header_func(void *ptr, size_t size, size_t nmemb, void * stream)
1240 {
1241     static const char *final_header = "\r\n";
1242     char *header;
1243     regmatch_t pmatch[2];
1244     S3InternalData *data = (S3InternalData *) stream;
1245
1246     header = g_strndup((gchar *) ptr, (gsize) size*nmemb);
1247     if (!s3_regexec_wrap(&etag_regex, header, 2, pmatch, 0))
1248             data->etag = find_regex_substring(header, pmatch[1]);
1249     if (!strcmp(final_header, header))
1250         data->headers_done = TRUE;
1251
1252     return size*nmemb;
1253 }
1254
1255 static gboolean
1256 compile_regexes(void)
1257 {
1258 #ifdef HAVE_REGEX_H
1259
1260   /* using POSIX regular expressions */
1261   struct {const char * str; int flags; regex_t *regex;} regexes[] = {
1262         {"<Code>[[:space:]]*([^<]*)[[:space:]]*</Code>", REG_EXTENDED | REG_ICASE, &error_name_regex},
1263         {"^ETag:[[:space:]]*\"([^\"]+)\"[[:space:]]*$", REG_EXTENDED | REG_ICASE | REG_NEWLINE, &etag_regex},
1264         {"<Message>[[:space:]]*([^<]*)[[:space:]]*</Message>", REG_EXTENDED | REG_ICASE, &message_regex},
1265         {"^[a-z0-9]((-*[a-z0-9])|(\\.[a-z0-9])){2,62}$", REG_EXTENDED | REG_NOSUB, &subdomain_regex},
1266         {"(/>)|(>([^<]*)</LocationConstraint>)", REG_EXTENDED | REG_ICASE, &location_con_regex},
1267         {NULL, 0, NULL}
1268     };
1269     char regmessage[1024];
1270     int size, i;
1271     int reg_result;
1272
1273     for (i = 0; regexes[i].str; i++) {
1274         reg_result = regcomp(regexes[i].regex, regexes[i].str, regexes[i].flags);
1275         if (reg_result != 0) {
1276             size = regerror(reg_result, regexes[i].regex, regmessage, sizeof(regmessage));
1277             g_error(_("Regex error: %s"), regmessage);
1278             return FALSE;
1279         }
1280     }
1281 #else /* ! HAVE_REGEX_H */
1282   /* using PCRE via GLib */
1283   struct {const char * str; int flags; regex_t *regex;} regexes[] = {
1284         {"<Code>\\s*([^<]*)\\s*</Code>",
1285          G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
1286          &error_name_regex},
1287         {"^ETag:\\s*\"([^\"]+)\"\\s*$",
1288          G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
1289          &etag_regex},
1290         {"<Message>\\s*([^<]*)\\s*</Message>",
1291          G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
1292          &message_regex},
1293         {"^[a-z0-9]((-*[a-z0-9])|(\\.[a-z0-9])){2,62}$",
1294          G_REGEX_OPTIMIZE | G_REGEX_NO_AUTO_CAPTURE,
1295          &subdomain_regex},
1296         {"(/>)|(>([^<]*)</LocationConstraint>)",
1297          G_REGEX_CASELESS,
1298          &location_con_regex},
1299         {NULL, 0, NULL}
1300   };
1301   int i;
1302   GError *err = NULL;
1303
1304   for (i = 0; regexes[i].str; i++) {
1305       *(regexes[i].regex) = g_regex_new(regexes[i].str, regexes[i].flags, 0, &err);
1306       if (err) {
1307           g_error(_("Regex error: %s"), err->message);
1308           g_error_free(err);
1309           return FALSE;
1310       }
1311   }
1312 #endif
1313     return TRUE;
1314 }
1315
1316 /*
1317  * Public function implementations
1318  */
1319
1320 gboolean s3_init(void)
1321 {
1322     static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
1323     static gboolean init = FALSE, ret;
1324     
1325     /* n.b. curl_global_init is called in common-src/glib-util.c:glib_init() */
1326
1327     g_static_mutex_lock (&mutex);
1328     if (!init) {
1329         ret = compile_regexes();
1330         init = TRUE;
1331     }
1332     g_static_mutex_unlock(&mutex);
1333     return ret;
1334 }
1335
1336 gboolean
1337 s3_curl_location_compat(void)
1338 {
1339     curl_version_info_data *info;
1340
1341     info = curl_version_info(CURLVERSION_NOW);
1342     return info->version_num > 0x070a02;
1343 }
1344
1345 gboolean
1346 s3_bucket_location_compat(const char *bucket)
1347 {
1348     return !s3_regexec_wrap(&subdomain_regex, bucket, 0, NULL, 0);
1349 }
1350
1351 S3Handle *
1352 s3_open(const char *access_key,
1353         const char *secret_key,
1354         const char *user_token,
1355         const char *bucket_location
1356         ) {
1357     S3Handle *hdl;
1358
1359     hdl = g_new0(S3Handle, 1);
1360     if (!hdl) goto error;
1361
1362     hdl->verbose = FALSE;
1363     hdl->use_ssl = s3_curl_supports_ssl();
1364
1365     g_assert(access_key);
1366     hdl->access_key = g_strdup(access_key);
1367     g_assert(secret_key);
1368     hdl->secret_key = g_strdup(secret_key);
1369     /* NULL is okay */
1370     hdl->user_token = g_strdup(user_token);
1371
1372     /* NULL is okay */
1373     hdl->bucket_location = g_strdup(bucket_location);
1374
1375     hdl->curl = curl_easy_init();
1376     if (!hdl->curl) goto error;
1377
1378     return hdl;
1379
1380 error:
1381     s3_free(hdl);
1382     return NULL;
1383 }
1384
1385 void
1386 s3_free(S3Handle *hdl)
1387 {
1388     s3_reset(hdl);
1389
1390     if (hdl) {
1391         g_free(hdl->access_key);
1392         g_free(hdl->secret_key);
1393         if (hdl->user_token) g_free(hdl->user_token);
1394         if (hdl->bucket_location) g_free(hdl->bucket_location);
1395         if (hdl->curl) curl_easy_cleanup(hdl->curl);
1396
1397         g_free(hdl);
1398     }
1399 }
1400
1401 void
1402 s3_reset(S3Handle *hdl)
1403 {
1404     if (hdl) {
1405         /* We don't call curl_easy_reset here, because doing that in curl
1406          * < 7.16 blanks the default CA certificate path, and there's no way
1407          * to get it back. */
1408         if (hdl->last_message) {
1409             g_free(hdl->last_message);
1410             hdl->last_message = NULL;
1411         }
1412
1413         hdl->last_response_code = 0;
1414         hdl->last_curl_code = 0;
1415         hdl->last_s3_error_code = 0;
1416         hdl->last_num_retries = 0;
1417
1418         if (hdl->last_response_body) {
1419             g_free(hdl->last_response_body);
1420             hdl->last_response_body = NULL;
1421         }
1422
1423         hdl->last_response_body_size = 0;
1424     }
1425 }
1426
1427 void
1428 s3_error(S3Handle *hdl,
1429          const char **message,
1430          guint *response_code,
1431          s3_error_code_t *s3_error_code,
1432          const char **s3_error_name,
1433          CURLcode *curl_code,
1434          guint *num_retries)
1435 {
1436     if (hdl) {
1437         if (message) *message = hdl->last_message;
1438         if (response_code) *response_code = hdl->last_response_code;
1439         if (s3_error_code) *s3_error_code = hdl->last_s3_error_code;
1440         if (s3_error_name) *s3_error_name = s3_error_name_from_code(hdl->last_s3_error_code);
1441         if (curl_code) *curl_code = hdl->last_curl_code;
1442         if (num_retries) *num_retries = hdl->last_num_retries;
1443     } else {
1444         /* no hdl? return something coherent, anyway */
1445         if (message) *message = "NULL S3Handle";
1446         if (response_code) *response_code = 0;
1447         if (s3_error_code) *s3_error_code = 0;
1448         if (s3_error_name) *s3_error_name = NULL;
1449         if (curl_code) *curl_code = 0;
1450         if (num_retries) *num_retries = 0;
1451     }
1452 }
1453
1454 void
1455 s3_verbose(S3Handle *hdl, gboolean verbose)
1456 {
1457     hdl->verbose = verbose;
1458 }
1459
1460 gboolean
1461 s3_use_ssl(S3Handle *hdl, gboolean use_ssl)
1462 {
1463     gboolean ret = TRUE;
1464     if (use_ssl & !s3_curl_supports_ssl()) {
1465         ret = FALSE;
1466     } else {
1467         hdl->use_ssl = use_ssl;
1468     }
1469     return ret;
1470 }
1471
1472 char *
1473 s3_strerror(S3Handle *hdl)
1474 {
1475     const char *message;
1476     guint response_code;
1477     const char *s3_error_name;
1478     CURLcode curl_code;
1479     guint num_retries;
1480
1481     char s3_info[256] = "";
1482     char response_info[16] = "";
1483     char curl_info[32] = "";
1484     char retries_info[32] = "";
1485
1486     s3_error(hdl, &message, &response_code, NULL, &s3_error_name, &curl_code, &num_retries);
1487
1488     if (!message) 
1489         message = "Unknown S3 error";
1490     if (s3_error_name)
1491         g_snprintf(s3_info, sizeof(s3_info), " (%s)", s3_error_name);
1492     if (response_code)
1493         g_snprintf(response_info, sizeof(response_info), " (HTTP %d)", response_code);
1494     if (curl_code)
1495         g_snprintf(curl_info, sizeof(curl_info), " (CURLcode %d)", curl_code);
1496     if (num_retries) 
1497         g_snprintf(retries_info, sizeof(retries_info), " (after %d retries)", num_retries);
1498
1499     return g_strdup_printf("%s%s%s%s%s", message, s3_info, curl_info, response_info, retries_info);
1500 }
1501
1502 /* Perform an upload. When this function returns, KEY and
1503  * BUFFER remain the responsibility of the caller.
1504  *
1505  * @param self: the s3 device
1506  * @param bucket: the bucket to which the upload should be made
1507  * @param key: the key to which the upload should be made
1508  * @param buffer: the data to be uploaded
1509  * @param buffer_len: the length of the data to upload
1510  * @returns: false if an error ocurred
1511  */
1512 gboolean
1513 s3_upload(S3Handle *hdl,
1514           const char *bucket,
1515           const char *key, 
1516           s3_read_func read_func,
1517           s3_reset_func reset_func,
1518           s3_size_func size_func,
1519           s3_md5_func md5_func,
1520           gpointer read_data,
1521           s3_progress_func progress_func,
1522           gpointer progress_data)
1523 {
1524     s3_result_t result = S3_RESULT_FAIL;
1525     static result_handling_t result_handling[] = {
1526         { 200,  0,          0,                   S3_RESULT_OK },
1527         RESULT_HANDLING_ALWAYS_RETRY,
1528         { 0, 0,    0,                /* default: */ S3_RESULT_FAIL }
1529         };
1530
1531     g_assert(hdl != NULL);
1532
1533     result = perform_request(hdl, "PUT", bucket, key, NULL, NULL,
1534                  read_func, reset_func, size_func, md5_func, read_data,
1535                  NULL, NULL, NULL, progress_func, progress_data,
1536                  result_handling);
1537
1538     return result == S3_RESULT_OK;
1539 }
1540
1541
1542 /* Private structure for our "thunk", which tracks where the user is in the list
1543  * of keys. */
1544 struct list_keys_thunk {
1545     GSList *filename_list; /* all pending filenames */
1546
1547     gboolean in_contents; /* look for "key" entities in here */
1548     gboolean in_common_prefixes; /* look for "prefix" entities in here */
1549
1550     gboolean is_truncated;
1551     gchar *next_marker;
1552
1553     gboolean want_text;
1554     
1555     gchar *text;
1556     gsize text_len;
1557 };
1558
1559 /* Functions for a SAX parser to parse the XML from Amazon */
1560
1561 static void
1562 list_start_element(GMarkupParseContext *context G_GNUC_UNUSED, 
1563                    const gchar *element_name, 
1564                    const gchar **attribute_names G_GNUC_UNUSED, 
1565                    const gchar **attribute_values G_GNUC_UNUSED, 
1566                    gpointer user_data, 
1567                    GError **error G_GNUC_UNUSED)
1568 {
1569     struct list_keys_thunk *thunk = (struct list_keys_thunk *)user_data;
1570
1571     thunk->want_text = 0;
1572     if (g_strcasecmp(element_name, "contents") == 0) {
1573         thunk->in_contents = 1;
1574     } else if (g_strcasecmp(element_name, "commonprefixes") == 0) {
1575         thunk->in_common_prefixes = 1;
1576     } else if (g_strcasecmp(element_name, "prefix") == 0 && thunk->in_common_prefixes) {
1577         thunk->want_text = 1;
1578     } else if (g_strcasecmp(element_name, "key") == 0 && thunk->in_contents) {
1579         thunk->want_text = 1;
1580     } else if (g_strcasecmp(element_name, "istruncated")) {
1581         thunk->want_text = 1;
1582     } else if (g_strcasecmp(element_name, "nextmarker")) {
1583         thunk->want_text = 1;
1584     }
1585 }
1586
1587 static void
1588 list_end_element(GMarkupParseContext *context G_GNUC_UNUSED, 
1589                  const gchar *element_name,
1590                  gpointer user_data, 
1591                  GError **error G_GNUC_UNUSED)
1592 {
1593     struct list_keys_thunk *thunk = (struct list_keys_thunk *)user_data;
1594
1595     if (g_strcasecmp(element_name, "contents") == 0) {
1596         thunk->in_contents = 0;
1597     } else if (g_strcasecmp(element_name, "commonprefixes") == 0) {
1598         thunk->in_common_prefixes = 0;
1599     } else if (g_strcasecmp(element_name, "key") == 0 && thunk->in_contents) {
1600         thunk->filename_list = g_slist_prepend(thunk->filename_list, thunk->text);
1601         thunk->text = NULL;
1602     } else if (g_strcasecmp(element_name, "prefix") == 0 && thunk->in_common_prefixes) {
1603         thunk->filename_list = g_slist_prepend(thunk->filename_list, thunk->text);
1604         thunk->text = NULL;
1605     } else if (g_strcasecmp(element_name, "istruncated") == 0) {
1606         if (thunk->text && g_strncasecmp(thunk->text, "false", 5) != 0)
1607             thunk->is_truncated = TRUE;
1608     } else if (g_strcasecmp(element_name, "nextmarker") == 0) {
1609         if (thunk->next_marker) g_free(thunk->next_marker);
1610         thunk->next_marker = thunk->text;
1611         thunk->text = NULL;
1612     }
1613 }
1614
1615 static void
1616 list_text(GMarkupParseContext *context G_GNUC_UNUSED,
1617           const gchar *text, 
1618           gsize text_len, 
1619           gpointer user_data, 
1620           GError **error G_GNUC_UNUSED)
1621 {
1622     struct list_keys_thunk *thunk = (struct list_keys_thunk *)user_data;
1623
1624     if (thunk->want_text) {
1625         if (thunk->text) g_free(thunk->text);
1626         thunk->text = g_strndup(text, text_len);
1627     }
1628 }
1629
1630 /* Perform a fetch from S3; several fetches may be involved in a
1631  * single listing operation */
1632 static s3_result_t
1633 list_fetch(S3Handle *hdl,
1634            const char *bucket,
1635            const char *prefix, 
1636            const char *delimiter, 
1637            const char *marker,
1638            const char *max_keys,
1639            CurlBuffer *buf)
1640 {
1641     s3_result_t result = S3_RESULT_FAIL;    
1642     static result_handling_t result_handling[] = {
1643         { 200,  0,          0,                   S3_RESULT_OK },
1644         RESULT_HANDLING_ALWAYS_RETRY,
1645         { 0, 0,    0,                /* default: */ S3_RESULT_FAIL  }
1646         };
1647    const char* pos_parts[][2] = {
1648         {"prefix", prefix},
1649         {"delimiter", delimiter},
1650         {"marker", marker},
1651         {"max-keys", max_keys},
1652         {NULL, NULL}
1653         };
1654     char *esc_value;
1655     GString *query;
1656     guint i;
1657     gboolean have_prev_part = FALSE;
1658
1659     /* loop over possible parts to build query string */
1660     query = g_string_new("");
1661     for (i = 0; pos_parts[i][0]; i++) {
1662       if (pos_parts[i][1]) {
1663           if (have_prev_part)
1664               g_string_append(query, "&");
1665           else
1666               have_prev_part = TRUE;
1667           esc_value = curl_escape(pos_parts[i][1], 0);
1668           g_string_append_printf(query, "%s=%s", pos_parts[i][0], esc_value);
1669           curl_free(esc_value);
1670       }
1671     }
1672
1673     /* and perform the request on that URI */
1674     result = perform_request(hdl, "GET", bucket, NULL, NULL, query->str,
1675                              NULL, NULL, NULL, NULL, NULL,
1676                              S3_BUFFER_WRITE_FUNCS, buf, NULL, NULL,
1677                              result_handling);
1678
1679     if (query) g_string_free(query, TRUE);
1680
1681     return result;
1682 }
1683
1684 gboolean
1685 s3_list_keys(S3Handle *hdl,
1686               const char *bucket,
1687               const char *prefix,
1688               const char *delimiter,
1689               GSList **list)
1690 {
1691     /*
1692      * max len of XML variables:
1693      * bucket: 255 bytes (p12 API Version 2006-03-01)
1694      * key: 1024 bytes (p15 API Version 2006-03-01)
1695      * size per key: 5GB bytes (p6 API Version 2006-03-01)
1696      * size of size 10 bytes (i.e. 10 decimal digits)
1697      * etag: 44 (observed+assumed)
1698      * owner ID: 64 (observed+assumed)
1699      * owner DisplayName: 255 (assumed)
1700      * StorageClass: const (p18 API Version 2006-03-01)
1701      */
1702     static const guint MAX_RESPONSE_LEN = 1000*2000;
1703     static const char *MAX_KEYS = "1000";
1704     struct list_keys_thunk thunk;
1705     GMarkupParseContext *ctxt = NULL;
1706     static GMarkupParser parser = { list_start_element, list_end_element, list_text, NULL, NULL };
1707     GError *err = NULL;
1708     s3_result_t result = S3_RESULT_FAIL;
1709     CurlBuffer buf = {NULL, 0, 0, MAX_RESPONSE_LEN};
1710
1711     g_assert(list);
1712     *list = NULL;
1713     thunk.filename_list = NULL;
1714     thunk.text = NULL;
1715     thunk.next_marker = NULL;
1716
1717     /* Loop until S3 has given us the entire picture */
1718     do {
1719         s3_buffer_reset_func(&buf);
1720         /* get some data from S3 */
1721         result = list_fetch(hdl, bucket, prefix, delimiter, thunk.next_marker, MAX_KEYS, &buf);
1722         if (result != S3_RESULT_OK) goto cleanup;
1723
1724         /* run the parser over it */
1725         thunk.in_contents = FALSE;
1726         thunk.in_common_prefixes = FALSE;
1727         thunk.is_truncated = FALSE;
1728         thunk.want_text = FALSE;
1729
1730         ctxt = g_markup_parse_context_new(&parser, 0, (gpointer)&thunk, NULL);
1731
1732         if (!g_markup_parse_context_parse(ctxt, buf.buffer, buf.buffer_pos, &err)) {
1733             if (hdl->last_message) g_free(hdl->last_message);
1734             hdl->last_message = g_strdup(err->message);
1735             result = S3_RESULT_FAIL;
1736             goto cleanup;
1737         }
1738
1739         if (!g_markup_parse_context_end_parse(ctxt, &err)) {
1740             if (hdl->last_message) g_free(hdl->last_message);
1741             hdl->last_message = g_strdup(err->message);
1742             result = S3_RESULT_FAIL;
1743             goto cleanup;
1744         }
1745         
1746         g_markup_parse_context_free(ctxt);
1747         ctxt = NULL;
1748     } while (thunk.next_marker);
1749
1750 cleanup:
1751     if (err) g_error_free(err);
1752     if (thunk.text) g_free(thunk.text);
1753     if (thunk.next_marker) g_free(thunk.next_marker);
1754     if (ctxt) g_markup_parse_context_free(ctxt);
1755     if (buf.buffer) g_free(buf.buffer);
1756
1757     if (result != S3_RESULT_OK) {
1758         g_slist_free(thunk.filename_list);
1759         return FALSE;
1760     } else {
1761         *list = thunk.filename_list;
1762         return TRUE;
1763     }
1764 }
1765
1766 gboolean
1767 s3_read(S3Handle *hdl,
1768         const char *bucket,
1769         const char *key,
1770         s3_write_func write_func,
1771         s3_reset_func reset_func,
1772         gpointer write_data,
1773         s3_progress_func progress_func,
1774         gpointer progress_data)
1775 {
1776     s3_result_t result = S3_RESULT_FAIL;
1777     static result_handling_t result_handling[] = {
1778         { 200,  0,          0,                   S3_RESULT_OK },
1779         RESULT_HANDLING_ALWAYS_RETRY,
1780         { 0, 0,    0,                /* default: */ S3_RESULT_FAIL  }
1781         };
1782
1783     g_assert(hdl != NULL);
1784     g_assert(write_func != NULL);
1785
1786     result = perform_request(hdl, "GET", bucket, key, NULL, NULL,
1787         NULL, NULL, NULL, NULL, NULL, write_func, reset_func, write_data,
1788         progress_func, progress_data, result_handling);
1789
1790     return result == S3_RESULT_OK;
1791 }
1792
1793 gboolean
1794 s3_delete(S3Handle *hdl,
1795           const char *bucket,
1796           const char *key)
1797 {
1798     s3_result_t result = S3_RESULT_FAIL;
1799     static result_handling_t result_handling[] = {
1800         { 204,  0,          0,                   S3_RESULT_OK },
1801         RESULT_HANDLING_ALWAYS_RETRY,
1802         { 0, 0,    0,                /* default: */ S3_RESULT_FAIL  }
1803         };
1804
1805     g_assert(hdl != NULL);
1806
1807     result = perform_request(hdl, "DELETE", bucket, key, NULL, NULL,
1808                  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1809                  result_handling);
1810
1811     return result == S3_RESULT_OK;
1812 }
1813
1814 gboolean
1815 s3_make_bucket(S3Handle *hdl,
1816                const char *bucket)
1817 {
1818     char *body = NULL;
1819     s3_result_t result = S3_RESULT_FAIL;
1820     static result_handling_t result_handling[] = {
1821         { 200,  0,          0,                   S3_RESULT_OK },
1822         RESULT_HANDLING_ALWAYS_RETRY,
1823         { 0, 0,    0,                /* default: */ S3_RESULT_FAIL  }
1824         };
1825     regmatch_t pmatch[4];
1826     char *loc_end_open, *loc_content;
1827     CurlBuffer buf = {NULL, 0, 0, 0}, *ptr = NULL;
1828     s3_read_func read_func = NULL;
1829     s3_reset_func reset_func = NULL;
1830     s3_md5_func md5_func = NULL;
1831     s3_size_func size_func = NULL;
1832
1833     g_assert(hdl != NULL);
1834     
1835     if (hdl->bucket_location && hdl->bucket_location[0]) {
1836         if (s3_bucket_location_compat(bucket)) {
1837             ptr = &buf;
1838             buf.buffer = g_strdup_printf(AMAZON_BUCKET_CONF_TEMPLATE, hdl->bucket_location);
1839             buf.buffer_len = (guint) strlen(buf.buffer);
1840             buf.buffer_pos = 0;
1841             buf.max_buffer_size = buf.buffer_len;
1842             read_func = s3_buffer_read_func;
1843             reset_func = s3_buffer_reset_func;
1844             size_func = s3_buffer_size_func;
1845             md5_func = s3_buffer_md5_func;
1846         } else {
1847             hdl->last_message = g_strdup_printf(_(
1848                 "Location constraint given for Amazon S3 bucket, "
1849                 "but the bucket name (%s) is not usable as a subdomain."), bucket);
1850             return FALSE;
1851         }
1852     }
1853
1854     result = perform_request(hdl, "PUT", bucket, NULL, NULL, NULL,
1855                  read_func, reset_func, size_func, md5_func, ptr,
1856                  NULL, NULL, NULL, NULL, NULL, result_handling);
1857
1858    if (result == S3_RESULT_OK ||
1859         (hdl->bucket_location && result != S3_RESULT_OK 
1860          && hdl->last_s3_error_code == S3_ERROR_BucketAlreadyOwnedByYou)) {
1861         /* verify the that the location constraint on the existing bucket matches
1862          * the one that's configured.
1863          */
1864         result = perform_request(hdl, "GET", bucket, NULL, "location", NULL,
1865                                  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1866                                  NULL, NULL, result_handling);
1867
1868         /* note that we can check only one of the three AND conditions above 
1869          * and infer that the others are true
1870          */
1871         if (result == S3_RESULT_OK && hdl->bucket_location) {
1872             /* return to the default state of failure */
1873             result = S3_RESULT_FAIL;
1874
1875             if (body) g_free(body);
1876             /* use strndup to get a null-terminated string */
1877             body = g_strndup(hdl->last_response_body, hdl->last_response_body_size);
1878             if (!body) {
1879                 hdl->last_message = g_strdup(_("No body received for location request"));
1880                 goto cleanup;
1881             }
1882             
1883             if (!s3_regexec_wrap(&location_con_regex, body, 4, pmatch, 0)) {
1884                 loc_end_open = find_regex_substring(body, pmatch[1]);
1885                 loc_content = find_regex_substring(body, pmatch[3]);
1886
1887                 /* The case of an empty string is special because XML allows
1888                  * "self-closing" tags
1889                  */
1890                 if ('\0' == hdl->bucket_location[0] &&
1891                     '/' != loc_end_open[0] && '\0' != hdl->bucket_location[0])
1892                     hdl->last_message = g_strdup(_("An empty location constraint is "
1893                         "configured, but the bucket has a non-empty location constraint"));
1894                 else if (strncmp(loc_content, hdl->bucket_location, strlen(hdl->bucket_location)))
1895                     hdl->last_message = g_strdup(_("The location constraint configured "
1896                         "does not match the constraint currently on the bucket"));
1897                 else
1898                     result = S3_RESULT_OK;
1899             } else {
1900                 hdl->last_message = g_strdup(_("Unexpected location response from Amazon S3"));
1901             }
1902         }
1903    }
1904
1905 cleanup:
1906     if (body) g_free(body);
1907     
1908     return result == S3_RESULT_OK;
1909
1910 }