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