Imported Upstream version 3.3.2
[debian/amanda] / device-src / s3.c
1 /*
2  * Copyright (c) 2008-2012 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%s>\n\
92     <LocationConstraint>%s</LocationConstraint>\n\
93   </CreateBucketConfiguration>"
94
95 #define AMAZON_STORAGE_CLASS_HEADER "x-amz-storage-class"
96
97 #define AMAZON_SERVER_SIDE_ENCRYPTION_HEADER "x-amz-server-side-encryption"
98
99 #define AMAZON_WILDCARD_LOCATION "*"
100
101 /* parameters for exponential backoff in the face of retriable errors */
102
103 /* start at 0.01s */
104 #define EXPONENTIAL_BACKOFF_START_USEC G_USEC_PER_SEC/100
105 /* double at each retry */
106 #define EXPONENTIAL_BACKOFF_BASE 2
107 /* retry 14 times (for a total of about 3 minutes spent waiting) */
108 #define EXPONENTIAL_BACKOFF_MAX_RETRIES 14
109
110 /* general "reasonable size" parameters */
111 #define MAX_ERROR_RESPONSE_LEN (100*1024)
112
113 /* Results which should always be retried */
114 #define RESULT_HANDLING_ALWAYS_RETRY \
115         { 400,  S3_ERROR_RequestTimeout,     0,                          S3_RESULT_RETRY }, \
116         { 403,  S3_ERROR_RequestTimeTooSkewed,0,                          S3_RESULT_RETRY }, \
117         { 409,  S3_ERROR_OperationAborted,   0,                          S3_RESULT_RETRY }, \
118         { 412,  S3_ERROR_PreconditionFailed, 0,                          S3_RESULT_RETRY }, \
119         { 500,  S3_ERROR_InternalError,      0,                          S3_RESULT_RETRY }, \
120         { 501,  S3_ERROR_NotImplemented,     0,                          S3_RESULT_RETRY }, \
121         { 0,    0,                           CURLE_COULDNT_CONNECT,      S3_RESULT_RETRY }, \
122         { 0,    0,                           CURLE_COULDNT_RESOLVE_HOST, S3_RESULT_RETRY }, \
123         { 0,    0,                           CURLE_PARTIAL_FILE,         S3_RESULT_RETRY }, \
124         { 0,    0,                           CURLE_OPERATION_TIMEOUTED,  S3_RESULT_RETRY }, \
125         { 0,    0,                           CURLE_SSL_CONNECT_ERROR,    S3_RESULT_RETRY }, \
126         { 0,    0,                           CURLE_SEND_ERROR,           S3_RESULT_RETRY }, \
127         { 0,    0,                           CURLE_RECV_ERROR,           S3_RESULT_RETRY }, \
128         { 0,    0,                           CURLE_GOT_NOTHING,          S3_RESULT_RETRY }
129
130 /*
131  * Data structures and associated functions
132  */
133
134 struct S3Handle {
135     /* (all strings in this struct are freed by s3_free()) */
136
137     char *access_key;
138     char *secret_key;
139     char *user_token;
140     char *swift_account_id;
141     char *swift_access_key;
142     char *username;
143     char *password;
144     char *tenant_id;
145     char *tenant_name;
146     char *client_id;
147     char *client_secret;
148     char *refresh_token;
149     char *access_token;
150     time_t expires;
151     gboolean getting_oauth2_access_token;
152     gboolean getting_swift_2_token;
153
154     /* attributes for new objects */
155     char *bucket_location;
156     char *storage_class;
157     char *server_side_encryption;
158     char *proxy;
159     char *host;
160     char *service_path;
161     gboolean use_subdomain;
162     S3_api s3_api;
163     char *ca_info;
164     char *x_auth_token;
165     char *x_storage_url;
166
167     CURL *curl;
168
169     gboolean verbose;
170     gboolean use_ssl;
171
172     guint64 max_send_speed;
173     guint64 max_recv_speed;
174
175     /* information from the last request */
176     char *last_message;
177     guint last_response_code;
178     s3_error_code_t last_s3_error_code;
179     CURLcode last_curl_code;
180     guint last_num_retries;
181     void *last_response_body;
182     guint last_response_body_size;
183
184     /* offset with s3 */
185     time_t time_offset_with_s3;
186     char *content_type;
187
188     gboolean reuse_connection;
189 };
190
191 typedef struct {
192     CurlBuffer resp_buf;
193     s3_write_func write_func;
194     s3_reset_func reset_func;
195     gpointer write_data;
196
197     gboolean headers_done;
198     gboolean int_write_done;
199     char *etag;
200     /* Points to current handle: Added to get hold of s3 offset */
201     struct S3Handle *hdl;
202 } S3InternalData;
203
204 /* Callback function to examine headers one-at-a-time
205  *
206  * @note this is the same as CURLOPT_HEADERFUNCTION
207  *
208  * @param data: The pointer to read data from
209  * @param size: The size of each "element" of the data buffer in bytes
210  * @param nmemb: The number of elements in the data buffer.
211  * So, the buffer's size is size*nmemb bytes.
212  * @param stream: the header_data (an opaque pointer)
213  *
214  * @return The number of bytes written to the buffer or
215  * CURL_WRITEFUNC_PAUSE to pause.
216  * If it's the number of bytes written, it should match the buffer size
217  */
218 typedef size_t (*s3_header_func)(void *data, size_t size, size_t nmemb, void *stream);
219
220
221 /*
222  * S3 errors */
223
224 /* (see preprocessor magic in s3.h) */
225
226 static char * s3_error_code_names[] = {
227 #define S3_ERROR(NAME) #NAME
228     S3_ERROR_LIST
229 #undef S3_ERROR
230 };
231
232 /* Convert an s3 error name to an error code.  This function
233  * matches strings case-insensitively, and is appropriate for use
234  * on data from the network.
235  *
236  * @param s3_error_code: the error name
237  * @returns: the error code (see constants in s3.h)
238  */
239 static s3_error_code_t
240 s3_error_code_from_name(char *s3_error_name);
241
242 /* Convert an s3 error code to a string
243  *
244  * @param s3_error_code: the error code to convert
245  * @returns: statically allocated string
246  */
247 static const char *
248 s3_error_name_from_code(s3_error_code_t s3_error_code);
249
250
251 /*
252  * result handling */
253
254 /* result handling is specified by a static array of result_handling structs,
255  * which match based on response_code (from HTTP) and S3 error code.  The result
256  * given for the first match is used.  0 acts as a wildcard for both response_code
257  * and s3_error_code.  The list is terminated with a struct containing 0 for both
258  * response_code and s3_error_code; the result for that struct is the default
259  * result.
260  *
261  * See RESULT_HANDLING_ALWAYS_RETRY for an example.
262  */
263 typedef enum {
264     S3_RESULT_RETRY = -1,
265     S3_RESULT_FAIL = 0,
266     S3_RESULT_OK = 1,
267     S3_RESULT_NOTIMPL = 2
268 } s3_result_t;
269
270 typedef struct result_handling {
271     guint response_code;
272     s3_error_code_t s3_error_code;
273     CURLcode curl_code;
274     s3_result_t result;
275 } result_handling_t;
276
277 /*
278  * get the access token for OAUTH2
279  */
280 static gboolean oauth2_get_access_token(S3Handle *hdl);
281
282 /* Lookup a result in C{result_handling}.
283  *
284  * @param result_handling: array of handling specifications
285  * @param response_code: response code from operation
286  * @param s3_error_code: s3 error code from operation, if any
287  * @param curl_code: the CURL error, if any
288  * @returns: the matching result
289  */
290 static s3_result_t
291 lookup_result(const result_handling_t *result_handling,
292               guint response_code,
293               s3_error_code_t s3_error_code,
294               CURLcode curl_code);
295
296 /*
297  * Precompiled regular expressions */
298 static regex_t etag_regex, error_name_regex, message_regex, subdomain_regex,
299     location_con_regex, date_sync_regex, x_auth_token_regex,
300     x_storage_url_regex, access_token_regex, expires_in_regex,
301     content_type_regex, details_regex, code_regex;
302
303
304 /*
305  * Utility functions
306  */
307
308 /* Check if a string is non-empty
309  *
310  * @param str: string to check
311  * @returns: true iff str is non-NULL and not "\0"
312  */
313 static gboolean is_non_empty_string(const char *str);
314
315 /* Construct the URL for an Amazon S3 REST request.
316  *
317  * A new string is allocated and returned; it is the responsiblity of the caller.
318  *
319  * @param hdl: the S3Handle object
320  * @param service_path: A path to add in the URL, or NULL for none.
321  * @param bucket: the bucket being accessed, or NULL for none
322  * @param key: the key being accessed, or NULL for none
323  * @param subresource: the sub-resource being accessed (e.g. "acl"), or NULL for none
324  * @param query: the query being accessed (e.g. "acl"), or NULL for none
325  *
326  * !use_subdomain: http://host/service_path/bucket/key
327  * use_subdomain : http://bucket.host/service_path/key
328  *
329  */
330 static char *
331 build_url(
332       S3Handle *hdl,
333       const char *bucket,
334       const char *key,
335       const char *subresource,
336       const char *query);
337
338 /* Create proper authorization headers for an Amazon S3 REST
339  * request to C{headers}.
340  *
341  * @note: C{X-Amz} headers (in C{headers}) must
342  *  - be in lower-case
343  *  - be in alphabetical order
344  *  - have no spaces around the colon
345  * (don't yell at me -- see the Amazon Developer Guide)
346  *
347  * @param hdl: the S3Handle object
348  * @param verb: capitalized verb for this request ('PUT', 'GET', etc.)
349  * @param bucket: the bucket being accessed, or NULL for none
350  * @param key: the key being accessed, or NULL for none
351  * @param subresource: the sub-resource being accessed (e.g. "acl"), or NULL for none
352  * @param md5_hash: the MD5 hash of the request body, or NULL for none
353  */
354 static struct curl_slist *
355 authenticate_request(S3Handle *hdl,
356                      const char *verb,
357                      const char *bucket,
358                      const char *key,
359                      const char *subresource,
360                      const char *md5_hash,
361                      const char *content_type,
362                      const size_t content_length,
363                      const char *project_id);
364
365
366
367 /* Interpret the response to an S3 operation, assuming CURL completed its request
368  * successfully.  This function fills in the relevant C{hdl->last*} members.
369  *
370  * @param hdl: The S3Handle object
371  * @param body: the response body
372  * @param body_len: the length of the response body
373  * @param etag: The response's ETag header
374  * @param content_md5: The hex-encoded MD5 hash of the request body,
375  *     which will be checked against the response's ETag header.
376  *     If NULL, the header is not checked.
377  *     If non-NULL, then the body should have the response headers at its beginnning.
378  * @returns: TRUE if the response should be retried (e.g., network error)
379  */
380 static gboolean
381 interpret_response(S3Handle *hdl,
382                    CURLcode curl_code,
383                    char *curl_error_buffer,
384                    gchar *body,
385                    guint body_len,
386                    const char *etag,
387                    const char *content_md5);
388
389 /* Perform an S3 operation.  This function handles all of the details
390  * of retryig requests and so on.
391  *
392  * The concepts of bucket and keys are defined by the Amazon S3 API.
393  * See: "Components of Amazon S3" - API Version 2006-03-01 pg. 8
394  *
395  * Individual sub-resources are defined in several places. In the REST API,
396  * they they are represented by a "flag" in the "query string".
397  * See: "Constructing the CanonicalizedResource Element" - API Version 2006-03-01 pg. 60
398  *
399  * @param hdl: the S3Handle object
400  * @param verb: the HTTP request method
401  * @param bucket: the bucket to access, or NULL for none
402  * @param key: the key to access, or NULL for none
403  * @param subresource: the "sub-resource" to request (e.g. "acl") or NULL for none
404  * @param query: the query string to send (not including th initial '?'),
405  * or NULL for none
406  * @param read_func: the callback for reading data
407  *   Will use s3_empty_read_func if NULL is passed in.
408  * @param read_reset_func: the callback for to reset reading data
409  * @param size_func: the callback to get the number of bytes to upload
410  * @param md5_func: the callback to get the MD5 hash of the data to upload
411  * @param read_data: pointer to pass to the above functions
412  * @param write_func: the callback for writing data.
413  *   Will use s3_counter_write_func if NULL is passed in.
414  * @param write_reset_func: the callback for to reset writing data
415  * @param write_data: pointer to pass to C{write_func}
416  * @param progress_func: the callback for progress information
417  * @param progress_data: pointer to pass to C{progress_func}
418  * @param result_handling: instructions for handling the results; see above.
419  * @returns: the result specified by result_handling; details of the response
420  * are then available in C{hdl->last*}
421  */
422 static s3_result_t
423 perform_request(S3Handle *hdl,
424                 const char *verb,
425                 const char *bucket,
426                 const char *key,
427                 const char *subresource,
428                 const char *query,
429                 const char *content_type,
430                 const char *project_id,
431                 s3_read_func read_func,
432                 s3_reset_func read_reset_func,
433                 s3_size_func size_func,
434                 s3_md5_func md5_func,
435                 gpointer read_data,
436                 s3_write_func write_func,
437                 s3_reset_func write_reset_func,
438                 gpointer write_data,
439                 s3_progress_func progress_func,
440                 gpointer progress_data,
441                 const result_handling_t *result_handling);
442
443 /*
444  * a CURLOPT_WRITEFUNCTION to save part of the response in memory and
445  * call an external function if one was provided.
446  */
447 static size_t
448 s3_internal_write_func(void *ptr, size_t size, size_t nmemb, void * stream);
449
450 /*
451  * a function to reset to our internal buffer
452  */
453 static void
454 s3_internal_reset_func(void * stream);
455
456 /*
457  * a CURLOPT_HEADERFUNCTION to save the ETag header only.
458  */
459 static size_t
460 s3_internal_header_func(void *ptr, size_t size, size_t nmemb, void * stream);
461
462 static gboolean
463 compile_regexes(void);
464
465 static gboolean get_openstack_swift_api_v1_setting(S3Handle *hdl);
466 static gboolean get_openstack_swift_api_v2_setting(S3Handle *hdl);
467
468 /*
469  * Static function implementations
470  */
471 static s3_error_code_t
472 s3_error_code_from_name(char *s3_error_name)
473 {
474     int i;
475
476     if (!s3_error_name) return S3_ERROR_Unknown;
477
478     /* do a brute-force search through the list, since it's not sorted */
479     for (i = 0; i < S3_ERROR_END; i++) {
480         if (g_ascii_strcasecmp(s3_error_name, s3_error_code_names[i]) == 0)
481             return i;
482     }
483
484     return S3_ERROR_Unknown;
485 }
486
487 static const char *
488 s3_error_name_from_code(s3_error_code_t s3_error_code)
489 {
490     if (s3_error_code >= S3_ERROR_END)
491         s3_error_code = S3_ERROR_Unknown;
492
493     return s3_error_code_names[s3_error_code];
494 }
495
496 gboolean
497 s3_curl_supports_ssl(void)
498 {
499     static int supported = -1;
500     if (supported == -1) {
501 #if defined(CURL_VERSION_SSL)
502     curl_version_info_data *info = curl_version_info(CURLVERSION_NOW);
503     if (info->features & CURL_VERSION_SSL)
504         supported = 1;
505     else
506         supported = 0;
507 #else
508     supported = 0;
509 #endif
510     }
511
512     return supported;
513 }
514
515 static gboolean
516 s3_curl_throttling_compat(void)
517 {
518 /* CURLOPT_MAX_SEND_SPEED_LARGE added in 7.15.5 */
519 #if LIBCURL_VERSION_NUM >= 0x070f05
520     curl_version_info_data *info;
521
522     /* check the runtime version too */
523     info = curl_version_info(CURLVERSION_NOW);
524     return info->version_num >= 0x070f05;
525 #else
526     return FALSE;
527 #endif
528 }
529
530 static s3_result_t
531 lookup_result(const result_handling_t *result_handling,
532               guint response_code,
533               s3_error_code_t s3_error_code,
534               CURLcode curl_code)
535 {
536     while (result_handling->response_code
537         || result_handling->s3_error_code
538         || result_handling->curl_code) {
539         if ((result_handling->response_code && result_handling->response_code != response_code)
540          || (result_handling->s3_error_code && result_handling->s3_error_code != s3_error_code)
541          || (result_handling->curl_code && result_handling->curl_code != curl_code)) {
542             result_handling++;
543             continue;
544         }
545
546         return result_handling->result;
547     }
548
549     /* return the result for the terminator, as the default */
550     return result_handling->result;
551 }
552
553 static time_t
554 rfc3339_date(
555     const char *date)
556 {
557     gint year, month, day, hour, minute, seconds;
558     const char *atz;
559
560     if (strlen(date) < 19)
561         return 1073741824;
562
563     year = atoi(date);
564     month = atoi(date+5);
565     day = atoi(date+8);
566     hour = atoi(date+11);
567     minute = atoi(date+14);
568     seconds = atoi(date+17);
569     atz = date+19;
570     if (*atz == '.') {   /* skip decimal seconds */
571         atz++;
572         while (*atz >= '0' && *atz <= '9') {
573             atz++;
574         }
575     }
576
577 #if GLIB_CHECK_VERSION(2,26,0)
578     if (!glib_check_version(2,26,0)) {
579         GTimeZone *tz;
580         GDateTime *dt;
581         time_t a;
582
583         tz = g_time_zone_new(atz);
584         dt = g_date_time_new(tz, year, month, day, hour, minute, seconds);
585         a = g_date_time_to_unix(dt);
586         g_time_zone_unref(tz);
587         g_date_time_unref(dt);
588         return a;
589     } else
590 #endif
591     {
592         struct tm tm;
593         time_t t;
594
595         tm.tm_year = year - 1900;
596         tm.tm_mon = month - 1;
597         tm.tm_mday = day;
598         tm.tm_hour = hour;
599         tm.tm_min = minute;
600         tm.tm_sec = seconds;
601         tm.tm_wday = 0;
602         tm.tm_yday = 0;
603         tm.tm_isdst = -1;
604         t = time(NULL);
605
606         if (*atz == '-' || *atz == '+') {  /* numeric timezone */
607             time_t lt, gt;
608             time_t a;
609             struct tm ltt, gtt;
610             gint Hour = atoi(atz);
611             gint Min  = atoi(atz+4);
612
613             if (Hour < 0)
614                 Min = -Min;
615             tm.tm_hour -= Hour;
616             tm.tm_min -= Min;
617             tm.tm_isdst = 0;
618             localtime_r(&t, &ltt);
619             lt = mktime(&ltt);
620             gmtime_r(&t, &gtt);
621             gt = mktime(&gtt);
622             tm.tm_sec += lt - gt;
623             a = mktime(&tm);
624             return a;
625         } else if (*atz == 'Z' && *(atz+1) == '\0') { /* Z timezone */
626             time_t lt, gt;
627             time_t a;
628             struct tm ltt, gtt;
629
630             tm.tm_isdst = 0;
631             localtime_r(&t, &ltt);
632             lt = mktime(&ltt);
633             gmtime_r(&t, &gtt);
634             gt = mktime(&gtt);
635             tm.tm_sec += lt - gt;
636             a = mktime(&tm);
637             return a;
638         } else { /* named timezone */
639             int pid;
640             int fd[2];
641             char buf[101];
642             time_t a;
643             size_t size;
644
645             if (pipe(fd) == -1)
646                 return 1073741824;
647             pid = fork();
648             switch (pid) {
649                 case -1:
650                     close(fd[0]);
651                     close(fd[1]);
652                     return 1073741824;
653                     break;
654                 case 0:
655                     close(fd[0]);
656                     setenv("TZ", atz, 1);
657                     tzset();
658                     a = mktime(&tm);
659                     g_snprintf(buf, 100, "%d", (int)a);
660                     size = write(fd[1], buf, strlen(buf));
661                     close(fd[1]);
662                     exit(0);
663                 default:
664                     close(fd[1]);
665                     size = read(fd[0], buf, 100);
666                     close(fd[0]);
667                     buf[size] = '\0';
668                     waitpid(pid, NULL, 0);
669                     break;
670             }
671             return atoi(buf);
672         }
673     }
674 }
675
676
677 static gboolean
678 is_non_empty_string(const char *str)
679 {
680     return str && str[0] != '\0';
681 }
682
683 static char *
684 build_url(
685       S3Handle   *hdl,
686       const char *bucket,
687       const char *key,
688       const char *subresource,
689       const char *query)
690 {
691     GString *url = NULL;
692     char *esc_bucket = NULL, *esc_key = NULL;
693
694     if ((hdl->s3_api == S3_API_SWIFT_1 || hdl->s3_api == S3_API_SWIFT_2 ||
695          hdl->s3_api == S3_API_OAUTH2) &&
696          hdl->x_storage_url) {
697         url = g_string_new(hdl->x_storage_url);
698         g_string_append(url, "/");
699     } else {
700         /* scheme */
701         url = g_string_new("http");
702         if (hdl->use_ssl)
703             g_string_append(url, "s");
704
705         g_string_append(url, "://");
706
707         /* domain */
708         if (hdl->use_subdomain && bucket)
709             g_string_append_printf(url, "%s.%s", bucket, hdl->host);
710         else
711             g_string_append_printf(url, "%s", hdl->host);
712
713         if (hdl->service_path) {
714             g_string_append_printf(url, "%s/", hdl->service_path);
715         } else {
716             g_string_append(url, "/");
717         }
718     }
719
720     /* path */
721     if (!hdl->use_subdomain && bucket) {
722         /* curl_easy_escape addeded in 7.15.4 */
723         #if LIBCURL_VERSION_NUM >= 0x070f04
724             curl_version_info_data *info;
725             /* check the runtime version too */
726             info = curl_version_info(CURLVERSION_NOW);
727             if (info->version_num >= 0x070f04)
728                 esc_bucket = curl_easy_escape(hdl->curl, bucket, 0);
729             else
730                 esc_bucket = curl_escape(bucket, 0);
731         #else
732             esc_bucket = curl_escape(bucket, 0);
733         #endif
734         if (!esc_bucket) goto cleanup;
735         g_string_append_printf(url, "%s", esc_bucket);
736         if (key)
737             g_string_append(url, "/");
738         curl_free(esc_bucket);
739     }
740
741     if (key) {
742         /* curl_easy_escape addeded in 7.15.4 */
743         #if LIBCURL_VERSION_NUM >= 0x070f04
744             curl_version_info_data *info;
745             /* check the runtime version too */
746             info = curl_version_info(CURLVERSION_NOW);
747             if (info->version_num >= 0x070f04)
748                 esc_key = curl_easy_escape(hdl->curl, key, 0);
749             else
750                 esc_key = curl_escape(key, 0);
751         #else
752             esc_key = curl_escape(key, 0);
753         #endif
754         if (!esc_key) goto cleanup;
755         g_string_append_printf(url, "%s", esc_key);
756         curl_free(esc_key);
757     }
758
759     if (url->str[strlen(url->str)-1] == '/') {
760         g_string_truncate(url, strlen(url->str)-1);
761     }
762
763     /* query string */
764     if (subresource || query)
765         g_string_append(url, "?");
766
767     if (subresource)
768         g_string_append(url, subresource);
769
770     if (subresource && query)
771         g_string_append(url, "&");
772
773     if (query)
774         g_string_append(url, query);
775
776 cleanup:
777
778     return g_string_free(url, FALSE);
779 }
780
781 static struct curl_slist *
782 authenticate_request(S3Handle *hdl,
783                      const char *verb,
784                      const char *bucket,
785                      const char *key,
786                      const char *subresource,
787                      const char *md5_hash,
788                      const char *content_type,
789                      const size_t content_length,
790                      const char *project_id)
791 {
792     time_t t;
793     struct tm tmp;
794     char *date = NULL;
795     char *buf = NULL;
796     HMAC_CTX ctx;
797     GByteArray *md = NULL;
798     char *auth_base64 = NULL;
799     struct curl_slist *headers = NULL;
800     char *esc_bucket = NULL, *esc_key = NULL;
801     GString *auth_string = NULL;
802
803     /* From RFC 2616 */
804     static const char *wkday[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
805     static const char *month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
806         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
807
808     /* calculate the date */
809     t = time(NULL);
810
811     /* sync clock with amazon s3 */
812     t = t + hdl->time_offset_with_s3;
813
814 #ifdef _WIN32
815     if (!gmtime_s(&tmp, &t)) g_debug("localtime error");
816 #else
817     if (!gmtime_r(&t, &tmp)) perror("localtime");
818 #endif
819
820
821     date = g_strdup_printf("%s, %02d %s %04d %02d:%02d:%02d GMT",
822         wkday[tmp.tm_wday], tmp.tm_mday, month[tmp.tm_mon], 1900+tmp.tm_year,
823         tmp.tm_hour, tmp.tm_min, tmp.tm_sec);
824
825     if (hdl->s3_api == S3_API_SWIFT_1) {
826         if (!bucket) {
827             buf = g_strdup_printf("X-Auth-User: %s", hdl->swift_account_id);
828             headers = curl_slist_append(headers, buf);
829             g_free(buf);
830             buf = g_strdup_printf("X-Auth-Key: %s", hdl->swift_access_key);
831             headers = curl_slist_append(headers, buf);
832             g_free(buf);
833         } else {
834             buf = g_strdup_printf("X-Auth-Token: %s", hdl->x_auth_token);
835             headers = curl_slist_append(headers, buf);
836             g_free(buf);
837         }
838     } else if (hdl->s3_api == S3_API_SWIFT_2) {
839         if (bucket) {
840             buf = g_strdup_printf("X-Auth-Token: %s", hdl->x_auth_token);
841             headers = curl_slist_append(headers, buf);
842             g_free(buf);
843         }
844         buf = g_strdup_printf("Accept: %s", "application/xml");
845         headers = curl_slist_append(headers, buf);
846         g_free(buf);
847     } else if (hdl->s3_api == S3_API_OAUTH2) {
848         if (bucket) {
849             buf = g_strdup_printf("Authorization: Bearer %s", hdl->access_token);
850             headers = curl_slist_append(headers, buf);
851             g_free(buf);
852         }
853     } else {
854         /* Build the string to sign, per the S3 spec.
855          * See: "Authenticating REST Requests" - API Version 2006-03-01 pg 58
856          */
857
858         /* verb */
859         auth_string = g_string_new(verb);
860         g_string_append(auth_string, "\n");
861
862         /* Content-MD5 header */
863         if (md5_hash)
864             g_string_append(auth_string, md5_hash);
865         g_string_append(auth_string, "\n");
866
867         if (content_type) {
868             g_string_append(auth_string, content_type);
869         }
870         g_string_append(auth_string, "\n");
871
872         /* Date */
873         g_string_append(auth_string, date);
874         g_string_append(auth_string, "\n");
875
876         /* CanonicalizedAmzHeaders, sorted lexicographically */
877         if (is_non_empty_string(hdl->user_token)) {
878             g_string_append(auth_string, AMAZON_SECURITY_HEADER);
879             g_string_append(auth_string, ":");
880             g_string_append(auth_string, hdl->user_token);
881             g_string_append(auth_string, ",");
882             g_string_append(auth_string, STS_PRODUCT_TOKEN);
883             g_string_append(auth_string, "\n");
884         }
885
886         if (g_str_equal(verb,"PUT") &&
887             is_non_empty_string(hdl->server_side_encryption)) {
888             g_string_append(auth_string, AMAZON_SERVER_SIDE_ENCRYPTION_HEADER);
889             g_string_append(auth_string, ":");
890             g_string_append(auth_string, hdl->server_side_encryption);
891             g_string_append(auth_string, "\n");
892         }
893
894         if (is_non_empty_string(hdl->storage_class)) {
895             g_string_append(auth_string, AMAZON_STORAGE_CLASS_HEADER);
896             g_string_append(auth_string, ":");
897             g_string_append(auth_string, hdl->storage_class);
898             g_string_append(auth_string, "\n");
899         }
900
901         /* CanonicalizedResource */
902         if (hdl->service_path) {
903             g_string_append(auth_string, hdl->service_path);
904         }
905         g_string_append(auth_string, "/");
906         if (bucket) {
907             if (hdl->use_subdomain)
908                 g_string_append(auth_string, bucket);
909             else {
910                 esc_bucket = curl_escape(bucket, 0);
911                 if (!esc_bucket) goto cleanup;
912                 g_string_append(auth_string, esc_bucket);
913             }
914         }
915
916         if (bucket && (hdl->use_subdomain || key))
917             g_string_append(auth_string, "/");
918
919         if (key) {
920             esc_key = curl_escape(key, 0);
921             if (!esc_key) goto cleanup;
922             g_string_append(auth_string, esc_key);
923         }
924
925         if (subresource) {
926             g_string_append(auth_string, "?");
927             g_string_append(auth_string, subresource);
928         }
929
930         /* run HMAC-SHA1 on the canonicalized string */
931         md = g_byte_array_sized_new(EVP_MAX_MD_SIZE+1);
932         HMAC_CTX_init(&ctx);
933         HMAC_Init_ex(&ctx, hdl->secret_key, (int) strlen(hdl->secret_key),
934                      EVP_sha1(), NULL);
935         HMAC_Update(&ctx, (unsigned char*) auth_string->str, auth_string->len);
936         HMAC_Final(&ctx, md->data, &md->len);
937         HMAC_CTX_cleanup(&ctx);
938         auth_base64 = s3_base64_encode(md);
939         /* append the new headers */
940         if (is_non_empty_string(hdl->user_token)) {
941             /* Devpay headers are included in hash. */
942             buf = g_strdup_printf(AMAZON_SECURITY_HEADER ": %s",
943                                   hdl->user_token);
944             headers = curl_slist_append(headers, buf);
945             g_free(buf);
946
947             buf = g_strdup_printf(AMAZON_SECURITY_HEADER ": %s",
948                                   STS_PRODUCT_TOKEN);
949             headers = curl_slist_append(headers, buf);
950             g_free(buf);
951         }
952
953         if (g_str_equal(verb,"PUT") &&
954             is_non_empty_string(hdl->server_side_encryption)) {
955             buf = g_strdup_printf(AMAZON_SERVER_SIDE_ENCRYPTION_HEADER ": %s",
956                                   hdl->server_side_encryption);
957             headers = curl_slist_append(headers, buf);
958             g_free(buf);
959         }
960
961         if (is_non_empty_string(hdl->storage_class)) {
962             buf = g_strdup_printf(AMAZON_STORAGE_CLASS_HEADER ": %s",
963                                   hdl->storage_class);
964             headers = curl_slist_append(headers, buf);
965             g_free(buf);
966         }
967
968         buf = g_strdup_printf("Authorization: AWS %s:%s",
969                           hdl->access_key, auth_base64);
970         headers = curl_slist_append(headers, buf);
971         g_free(buf);
972     }
973
974     if (md5_hash && '\0' != md5_hash[0]) {
975         buf = g_strdup_printf("Content-MD5: %s", md5_hash);
976         headers = curl_slist_append(headers, buf);
977         g_free(buf);
978     }
979     if (content_length > 0) {
980         buf = g_strdup_printf("Content-Length: %zu", content_length);
981         headers = curl_slist_append(headers, buf);
982         g_free(buf);
983     }
984
985     if (content_type) {
986         buf = g_strdup_printf("Content-Type: %s", content_type);
987         headers = curl_slist_append(headers, buf);
988         g_free(buf);
989     }
990
991     if (hdl->s3_api == S3_API_OAUTH2) {
992         buf = g_strdup_printf("x-goog-api-version: 2");
993         headers = curl_slist_append(headers, buf);
994         g_free(buf);
995     }
996
997     if (project_id && hdl->s3_api == S3_API_OAUTH2) {
998         buf = g_strdup_printf("x-goog-project-id: %s", project_id);
999         headers = curl_slist_append(headers, buf);
1000         g_free(buf);
1001     }
1002
1003     buf = g_strdup_printf("Date: %s", date);
1004     headers = curl_slist_append(headers, buf);
1005     g_free(buf);
1006
1007 cleanup:
1008     g_free(date);
1009     g_free(esc_bucket);
1010     g_free(esc_key);
1011     if (md) g_byte_array_free(md, TRUE);
1012     g_free(auth_base64);
1013     if (auth_string) g_string_free(auth_string, TRUE);
1014
1015     return headers;
1016 }
1017
1018 /* Functions for a SAX parser to parse the XML failure from Amazon */
1019
1020 /* Private structure for our "thunk", which tracks where the user is in the list
1021  *  * of keys. */
1022 struct failure_thunk {
1023     gboolean want_text;
1024
1025     gboolean in_title;
1026     gboolean in_body;
1027     gboolean in_code;
1028     gboolean in_message;
1029     gboolean in_details;
1030     gboolean in_access;
1031     gboolean in_token;
1032     gboolean in_serviceCatalog;
1033     gboolean in_service;
1034     gboolean in_endpoint;
1035     gint     in_others;
1036
1037     gchar *text;
1038     gsize text_len;
1039
1040     gchar *message;
1041     gchar *details;
1042     gchar *error_name;
1043     gchar *token_id;
1044     gchar *service_type;
1045     gchar *service_public_url;
1046     gint64 expires;
1047 };
1048
1049 static void
1050 failure_start_element(GMarkupParseContext *context G_GNUC_UNUSED,
1051                    const gchar *element_name,
1052                    const gchar **attribute_names,
1053                    const gchar **attribute_values,
1054                    gpointer user_data,
1055                    GError **error G_GNUC_UNUSED)
1056 {
1057     struct failure_thunk *thunk = (struct failure_thunk *)user_data;
1058     const gchar **att_name, **att_value;
1059
1060     if (g_ascii_strcasecmp(element_name, "title") == 0) {
1061         thunk->in_title = 1;
1062         thunk->in_others = 0;
1063         thunk->want_text = 1;
1064     } else if (g_ascii_strcasecmp(element_name, "body") == 0) {
1065         thunk->in_body = 1;
1066         thunk->in_others = 0;
1067         thunk->want_text = 1;
1068     } else if (g_ascii_strcasecmp(element_name, "code") == 0) {
1069         thunk->in_code = 1;
1070         thunk->in_others = 0;
1071         thunk->want_text = 1;
1072     } else if (g_ascii_strcasecmp(element_name, "message") == 0) {
1073         thunk->in_message = 1;
1074         thunk->in_others = 0;
1075         thunk->want_text = 1;
1076     } else if (g_ascii_strcasecmp(element_name, "details") == 0) {
1077         thunk->in_details = 1;
1078         thunk->in_others = 0;
1079         thunk->want_text = 1;
1080     } else if (g_ascii_strcasecmp(element_name, "access") == 0) {
1081         thunk->in_access = 1;
1082         thunk->in_others = 0;
1083     } else if (g_ascii_strcasecmp(element_name, "token") == 0) {
1084         thunk->in_token = 1;
1085         thunk->in_others = 0;
1086         for (att_name=attribute_names, att_value=attribute_values;
1087              *att_name != NULL;
1088              att_name++, att_value++) {
1089             if (g_str_equal(*att_name, "id")) {
1090                 thunk->token_id = g_strdup(*att_value);
1091             }
1092             if (g_str_equal(*att_name, "expires") && strlen(*att_value) >= 19) {
1093                 thunk->expires = rfc3339_date(*att_value) - 600;
1094             }
1095         }
1096     } else if (g_ascii_strcasecmp(element_name, "serviceCatalog") == 0) {
1097         thunk->in_serviceCatalog = 1;
1098         thunk->in_others = 0;
1099     } else if (g_ascii_strcasecmp(element_name, "service") == 0) {
1100         thunk->in_service = 1;
1101         thunk->in_others = 0;
1102         for (att_name=attribute_names, att_value=attribute_values;
1103              *att_name != NULL;
1104              att_name++, att_value++) {
1105             if (g_str_equal(*att_name, "type")) {
1106                 thunk->service_type = g_strdup(*att_value);
1107             }
1108         }
1109     } else if (g_ascii_strcasecmp(element_name, "endpoint") == 0) {
1110         thunk->in_endpoint = 1;
1111         thunk->in_others = 0;
1112         if (thunk->service_type &&
1113             g_str_equal(thunk->service_type, "object-store")) {
1114             for (att_name=attribute_names, att_value=attribute_values;
1115                  *att_name != NULL;
1116                  att_name++, att_value++) {
1117                 if (g_str_equal(*att_name, "publicURL")) {
1118                     thunk->service_public_url = g_strdup(*att_value);
1119                 }
1120             }
1121         }
1122     } else if (g_ascii_strcasecmp(element_name, "error") == 0) {
1123         for (att_name=attribute_names, att_value=attribute_values;
1124              *att_name != NULL;
1125              att_name++, att_value++) {
1126             if (g_str_equal(*att_name, "message")) {
1127                 thunk->message = g_strdup(*att_value);
1128             }
1129         }
1130     } else {
1131         thunk->in_others++;
1132     }
1133 }
1134
1135 static void
1136 failure_end_element(GMarkupParseContext *context G_GNUC_UNUSED,
1137                  const gchar *element_name,
1138                  gpointer user_data,
1139                  GError **error G_GNUC_UNUSED)
1140 {
1141     struct failure_thunk *thunk = (struct failure_thunk *)user_data;
1142
1143     if (g_ascii_strcasecmp(element_name, "title") == 0) {
1144         char *p = strchr(thunk->text, ' ');
1145         if (p) {
1146             p++;
1147             if (*p) {
1148                 thunk->error_name = g_strdup(p);
1149             }
1150         }
1151         g_free(thunk->text);
1152         thunk->text = NULL;
1153         thunk->in_title = 0;
1154     } else if (g_ascii_strcasecmp(element_name, "body") == 0) {
1155         thunk->message = thunk->text;
1156         g_strstrip(thunk->message);
1157         thunk->text = NULL;
1158         thunk->in_body = 0;
1159     } else if (g_ascii_strcasecmp(element_name, "code") == 0) {
1160         thunk->error_name = thunk->text;
1161         thunk->text = NULL;
1162         thunk->in_code = 0;
1163     } else if (g_ascii_strcasecmp(element_name, "message") == 0) {
1164         thunk->message = thunk->text;
1165         thunk->text = NULL;
1166         thunk->in_message = 0;
1167     } else if (g_ascii_strcasecmp(element_name, "details") == 0) {
1168         thunk->details = thunk->text;
1169         thunk->text = NULL;
1170         thunk->in_details = 0;
1171     } else if (g_ascii_strcasecmp(element_name, "access") == 0) {
1172         thunk->message = thunk->text;
1173         thunk->text = NULL;
1174         thunk->in_access = 0;
1175     } else if (g_ascii_strcasecmp(element_name, "token") == 0) {
1176         thunk->message = thunk->text;
1177         thunk->text = NULL;
1178         thunk->in_token = 0;
1179     } else if (g_ascii_strcasecmp(element_name, "serviceCatalog") == 0) {
1180         thunk->message = thunk->text;
1181         thunk->text = NULL;
1182         thunk->in_serviceCatalog = 0;
1183     } else if (g_ascii_strcasecmp(element_name, "service") == 0) {
1184         thunk->message = thunk->text;
1185         thunk->text = NULL;
1186         g_free(thunk->service_type);
1187         thunk->service_type = NULL;
1188         thunk->in_service = 0;
1189     } else if (g_ascii_strcasecmp(element_name, "endpoint") == 0) {
1190         thunk->message = thunk->text;
1191         thunk->text = NULL;
1192         thunk->in_endpoint = 0;
1193     } else {
1194         thunk->in_others--;
1195     }
1196 }
1197
1198 static void
1199 failure_text(GMarkupParseContext *context G_GNUC_UNUSED,
1200           const gchar *text,
1201           gsize text_len,
1202           gpointer user_data,
1203           GError **error G_GNUC_UNUSED)
1204 {
1205     struct failure_thunk *thunk = (struct failure_thunk *)user_data;
1206
1207     if (thunk->want_text && thunk->in_others == 0) {
1208         char *new_text;
1209
1210         new_text = g_strndup(text, text_len);
1211         if (thunk->text) {
1212             strappend(thunk->text, new_text);
1213             g_free(new_text);
1214         } else {
1215             thunk->text = new_text;
1216         }
1217     }
1218 }
1219
1220 static gboolean
1221 interpret_response(S3Handle *hdl,
1222                    CURLcode curl_code,
1223                    char *curl_error_buffer,
1224                    gchar *body,
1225                    guint body_len,
1226                    const char *etag,
1227                    const char *content_md5)
1228 {
1229     long response_code = 0;
1230     gboolean ret = TRUE;
1231     struct failure_thunk thunk;
1232     GMarkupParseContext *ctxt = NULL;
1233     static GMarkupParser parser = { failure_start_element, failure_end_element, failure_text, NULL, NULL };
1234     GError *err = NULL;
1235
1236     if (!hdl) return FALSE;
1237
1238     if (hdl->last_message) g_free(hdl->last_message);
1239     hdl->last_message = NULL;
1240
1241     /* bail out from a CURL error */
1242     if (curl_code != CURLE_OK) {
1243         hdl->last_curl_code = curl_code;
1244         hdl->last_message = g_strdup_printf("CURL error: %s", curl_error_buffer);
1245         return FALSE;
1246     }
1247
1248     /* CURL seems to think things were OK, so get its response code */
1249     curl_easy_getinfo(hdl->curl, CURLINFO_RESPONSE_CODE, &response_code);
1250     hdl->last_response_code = response_code;
1251
1252     /* check ETag, if present */
1253     if (etag && content_md5 && 200 == response_code) {
1254         if (etag && g_ascii_strcasecmp(etag, content_md5))
1255             hdl->last_message = g_strdup("S3 Error: Possible data corruption (ETag returned by Amazon did not match the MD5 hash of the data sent)");
1256         else
1257             ret = FALSE;
1258         return ret;
1259     }
1260
1261     /* Now look at the body to try to get the actual Amazon error message. */
1262
1263     /* impose a reasonable limit on body size */
1264     if (body_len > MAX_ERROR_RESPONSE_LEN) {
1265         hdl->last_message = g_strdup("S3 Error: Unknown (response body too large to parse)");
1266         return FALSE;
1267     } else if (!body || body_len == 0) {
1268         if (response_code < 100 || response_code >= 400) {
1269             hdl->last_message =
1270                         g_strdup("S3 Error: Unknown (empty response body)");
1271             return TRUE; /* perhaps a network error; retry the request */
1272         } else {
1273             /* 2xx and 3xx codes without body are good result */
1274             hdl->last_s3_error_code = S3_ERROR_None;
1275             return FALSE;
1276         }
1277     }
1278
1279     thunk.in_title = FALSE;
1280     thunk.in_body = FALSE;
1281     thunk.in_code = FALSE;
1282     thunk.in_message = FALSE;
1283     thunk.in_details = FALSE;
1284     thunk.in_access = FALSE;
1285     thunk.in_token = FALSE;
1286     thunk.in_serviceCatalog = FALSE;
1287     thunk.in_service = FALSE;
1288     thunk.in_endpoint = FALSE;
1289     thunk.in_others = 0;
1290     thunk.text = NULL;
1291     thunk.want_text = FALSE;
1292     thunk.text_len = 0;
1293     thunk.message = NULL;
1294     thunk.details = NULL;
1295     thunk.error_name = NULL;
1296     thunk.token_id = NULL;
1297     thunk.service_type = NULL;
1298     thunk.service_public_url = NULL;
1299     thunk.expires = 0;
1300
1301     if ((hdl->s3_api == S3_API_SWIFT_1 ||
1302          hdl->s3_api == S3_API_SWIFT_2) &&
1303         hdl->content_type &&
1304         (g_str_equal(hdl->content_type, "text/html") ||
1305          g_str_equal(hdl->content_type, "text/plain"))) {
1306
1307         char *body_copy = g_strndup(body, body_len);
1308         char *b = body_copy;
1309         char *p = strchr(b, '\n');
1310         char *p1;
1311         if (p) { /* first line: error code */
1312             *p = '\0';
1313             p++;
1314             p1 = strchr(b, ' ');
1315             if (p1) {
1316                 p1++;
1317                 if (*p1) {
1318                     thunk.error_name = g_strdup(p1);
1319                 }
1320             }
1321             b = p;
1322         }
1323         p = strchr(b, '\n');
1324         if (p) { /* second line: error message */
1325             *p = '\0';
1326             p++;
1327             thunk.message = g_strdup(p);
1328             g_strstrip(thunk.message);
1329             b = p;
1330         }
1331         goto parsing_done;
1332     } else if ((hdl->s3_api == S3_API_SWIFT_1 ||
1333                 hdl->s3_api == S3_API_SWIFT_2) &&
1334                hdl->content_type &&
1335                g_str_equal(hdl->content_type, "application/json")) {
1336         char *body_copy = g_strndup(body, body_len);
1337         char *code = NULL;
1338         char *details = NULL;
1339         regmatch_t pmatch[2];
1340
1341         if (!s3_regexec_wrap(&code_regex, body_copy, 2, pmatch, 0)) {
1342             code = find_regex_substring(body_copy, pmatch[1]);
1343         }
1344         if (!s3_regexec_wrap(&details_regex, body_copy, 2, pmatch, 0)) {
1345             details = find_regex_substring(body_copy, pmatch[1]);
1346         }
1347         if (code && details) {
1348             hdl->last_message = g_strdup_printf("%s (%s)", details, code);
1349         } else if (code) {
1350             hdl->last_message = g_strdup_printf("(%s)", code);
1351         } else if (details) {
1352             hdl->last_message = g_strdup_printf("%s", details);
1353         } else {
1354             hdl->last_message = NULL;
1355         }
1356         g_free(code);
1357         g_free(details);
1358         g_free(body_copy);
1359         return FALSE;
1360     } else if (!hdl->content_type ||
1361                !g_str_equal(hdl->content_type, "application/xml")) {
1362         return FALSE;
1363     }
1364
1365     /* run the parser over it */
1366     ctxt = g_markup_parse_context_new(&parser, 0, (gpointer)&thunk, NULL);
1367     if (!g_markup_parse_context_parse(ctxt, body, body_len, &err)) {
1368             if (hdl->last_message) g_free(hdl->last_message);
1369             hdl->last_message = g_strdup(err->message);
1370             goto cleanup;
1371     }
1372
1373     if (!g_markup_parse_context_end_parse(ctxt, &err)) {
1374             if (hdl->last_message) g_free(hdl->last_message);
1375             hdl->last_message = g_strdup(err->message);
1376             goto cleanup;
1377     }
1378
1379     g_markup_parse_context_free(ctxt);
1380     ctxt = NULL;
1381
1382     if (hdl->s3_api == S3_API_SWIFT_2) {
1383         if (!hdl->x_auth_token && thunk.token_id) {
1384             hdl->x_auth_token = thunk.token_id;
1385             thunk.token_id = NULL;
1386         }
1387         if (!hdl->x_storage_url && thunk.service_public_url) {
1388             hdl->x_storage_url = thunk.service_public_url;
1389             thunk.service_public_url = NULL;
1390         }
1391     }
1392
1393     if (thunk.expires > 0) {
1394         hdl->expires = thunk.expires;
1395     }
1396 parsing_done:
1397     if (thunk.error_name) {
1398         hdl->last_s3_error_code = s3_error_code_from_name(thunk.error_name);
1399         g_free(thunk.error_name);
1400         thunk.error_name = NULL;
1401     }
1402
1403     if (thunk.message) {
1404         g_free(hdl->last_message);
1405         if (thunk.details) {
1406             hdl->last_message = g_strdup_printf("%s: %s", thunk.message,
1407                                                           thunk.details);
1408             amfree(thunk.message);
1409             amfree(thunk.details);
1410         } else {
1411             hdl->last_message = thunk.message;
1412             thunk.message = NULL; /* steal the reference to the string */
1413         }
1414     }
1415
1416 cleanup:
1417     g_free(thunk.text);
1418     g_free(thunk.message);
1419     g_free(thunk.error_name);
1420     g_free(thunk.token_id);
1421     g_free(thunk.service_public_url);
1422     g_free(thunk.service_type);
1423     return FALSE;
1424 }
1425
1426 /* a CURLOPT_READFUNCTION to read data from a buffer. */
1427 size_t
1428 s3_buffer_read_func(void *ptr, size_t size, size_t nmemb, void * stream)
1429 {
1430     CurlBuffer *data = stream;
1431     guint bytes_desired = (guint) size * nmemb;
1432
1433     /* check the number of bytes remaining, just to be safe */
1434     if (bytes_desired > data->buffer_len - data->buffer_pos)
1435         bytes_desired = data->buffer_len - data->buffer_pos;
1436
1437     memcpy((char *)ptr, data->buffer + data->buffer_pos, bytes_desired);
1438     data->buffer_pos += bytes_desired;
1439
1440     return bytes_desired;
1441 }
1442
1443 size_t
1444 s3_buffer_size_func(void *stream)
1445 {
1446     CurlBuffer *data = stream;
1447     return data->buffer_len;
1448 }
1449
1450 GByteArray*
1451 s3_buffer_md5_func(void *stream)
1452 {
1453     CurlBuffer *data = stream;
1454     GByteArray req_body_gba = {(guint8 *)data->buffer, data->buffer_len};
1455
1456     return s3_compute_md5_hash(&req_body_gba);
1457 }
1458
1459 void
1460 s3_buffer_reset_func(void *stream)
1461 {
1462     CurlBuffer *data = stream;
1463     data->buffer_pos = 0;
1464 }
1465
1466 /* a CURLOPT_WRITEFUNCTION to write data to a buffer. */
1467 size_t
1468 s3_buffer_write_func(void *ptr, size_t size, size_t nmemb, void *stream)
1469 {
1470     CurlBuffer * data = stream;
1471     guint new_bytes = (guint) size * nmemb;
1472     guint bytes_needed = data->buffer_pos + new_bytes;
1473
1474     /* error out if the new size is greater than the maximum allowed */
1475     if (data->max_buffer_size && bytes_needed > data->max_buffer_size)
1476         return 0;
1477
1478     /* reallocate if necessary. We use exponential sizing to make this
1479      * happen less often. */
1480     if (bytes_needed > data->buffer_len) {
1481         guint new_size = MAX(bytes_needed, data->buffer_len * 2);
1482         if (data->max_buffer_size) {
1483             new_size = MIN(new_size, data->max_buffer_size);
1484         }
1485         data->buffer = g_realloc(data->buffer, new_size);
1486         data->buffer_len = new_size;
1487     }
1488     if (!data->buffer)
1489         return 0; /* returning zero signals an error to libcurl */
1490
1491     /* actually copy the data to the buffer */
1492     memcpy(data->buffer + data->buffer_pos, ptr, new_bytes);
1493     data->buffer_pos += new_bytes;
1494
1495     /* signal success to curl */
1496     return new_bytes;
1497 }
1498
1499 /* a CURLOPT_READFUNCTION that writes nothing. */
1500 size_t
1501 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)
1502 {
1503     return 0;
1504 }
1505
1506 size_t
1507 s3_empty_size_func(G_GNUC_UNUSED void *stream)
1508 {
1509     return 0;
1510 }
1511
1512 GByteArray*
1513 s3_empty_md5_func(G_GNUC_UNUSED void *stream)
1514 {
1515     static const GByteArray empty = {(guint8 *) "", 0};
1516
1517     return s3_compute_md5_hash(&empty);
1518 }
1519
1520 /* a CURLOPT_WRITEFUNCTION to write data that just counts data.
1521  * s3_write_data should be NULL or a pointer to an gint64.
1522  */
1523 size_t
1524 s3_counter_write_func(G_GNUC_UNUSED void *ptr, size_t size, size_t nmemb, void *stream)
1525 {
1526     gint64 *count = (gint64*) stream, inc = nmemb*size;
1527
1528     if (count) *count += inc;
1529     return inc;
1530 }
1531
1532 void
1533 s3_counter_reset_func(void *stream)
1534 {
1535     gint64 *count = (gint64*) stream;
1536
1537     if (count) *count = 0;
1538 }
1539
1540 #ifdef _WIN32
1541 /* a CURLOPT_READFUNCTION to read data from a file. */
1542 size_t
1543 s3_file_read_func(void *ptr, size_t size, size_t nmemb, void * stream)
1544 {
1545     HANDLE *hFile = (HANDLE *) stream;
1546     DWORD bytes_read;
1547
1548     ReadFile(hFile, ptr, (DWORD) size*nmemb, &bytes_read, NULL);
1549     return bytes_read;
1550 }
1551
1552 size_t
1553 s3_file_size_func(void *stream)
1554 {
1555     HANDLE *hFile = (HANDLE *) stream;
1556     DWORD size = GetFileSize(hFile, NULL);
1557
1558     if (INVALID_FILE_SIZE == size) {
1559         return -1;
1560     } else {
1561         return size;
1562     }
1563 }
1564
1565 GByteArray*
1566 s3_file_md5_func(void *stream)
1567 {
1568 #define S3_MD5_BUF_SIZE (10*1024)
1569     HANDLE *hFile = (HANDLE *) stream;
1570     guint8 buf[S3_MD5_BUF_SIZE];
1571     DWORD bytes_read;
1572     MD5_CTX md5_ctx;
1573     GByteArray *ret = NULL;
1574
1575     g_assert(INVALID_SET_FILE_POINTER != SetFilePointer(hFile, 0, NULL, FILE_BEGIN));
1576
1577     ret = g_byte_array_sized_new(S3_MD5_HASH_BYTE_LEN);
1578     g_byte_array_set_size(ret, S3_MD5_HASH_BYTE_LEN);
1579     MD5_Init(&md5_ctx);
1580
1581     while (ReadFile(hFile, buf, S3_MD5_BUF_SIZE, &bytes_read, NULL)) {
1582         MD5_Update(&md5_ctx, buf, bytes_read);
1583     }
1584     MD5_Final(ret->data, &md5_ctx);
1585
1586     g_assert(INVALID_SET_FILE_POINTER != SetFilePointer(hFile, 0, NULL, FILE_BEGIN));
1587     return ret;
1588 #undef S3_MD5_BUF_SIZE
1589 }
1590
1591 GByteArray*
1592 s3_file_reset_func(void *stream)
1593 {
1594     g_assert(INVALID_SET_FILE_POINTER != SetFilePointer(hFile, 0, NULL, FILE_BEGIN));
1595 }
1596
1597 /* a CURLOPT_WRITEFUNCTION to write data to a file. */
1598 size_t
1599 s3_file_write_func(void *ptr, size_t size, size_t nmemb, void *stream)
1600 {
1601     HANDLE *hFile = (HANDLE *) stream;
1602     DWORD bytes_written;
1603
1604     WriteFile(hFile, ptr, (DWORD) size*nmemb, &bytes_written, NULL);
1605     return bytes_written;
1606 }
1607 #endif
1608
1609 static int
1610 curl_debug_message(CURL *curl G_GNUC_UNUSED,
1611            curl_infotype type,
1612            char *s,
1613            size_t len,
1614            void *unused G_GNUC_UNUSED)
1615 {
1616     char *lineprefix;
1617     char *message;
1618     char **lines, **line;
1619     size_t i;
1620
1621     switch (type) {
1622     case CURLINFO_TEXT:
1623         lineprefix="";
1624         break;
1625
1626     case CURLINFO_HEADER_IN:
1627         lineprefix="Hdr In: ";
1628         break;
1629
1630     case CURLINFO_HEADER_OUT:
1631         lineprefix="Hdr Out: ";
1632         break;
1633
1634     case CURLINFO_DATA_IN:
1635         if (len > 3000) return 0;
1636         for (i=0;i<len;i++) {
1637             if (!g_ascii_isprint(s[i])) {
1638                 return 0;
1639             }
1640         }
1641         lineprefix="Data In: ";
1642         break;
1643
1644     case CURLINFO_DATA_OUT:
1645         if (len > 3000) return 0;
1646         for (i=0;i<len;i++) {
1647             if (!g_ascii_isprint(s[i])) {
1648                 return 0;
1649             }
1650         }
1651         lineprefix="Data Out: ";
1652         break;
1653
1654     default:
1655         /* ignore data in/out -- nobody wants to see that in the
1656          * debug logs! */
1657         return 0;
1658     }
1659
1660     /* split the input into lines */
1661     message = g_strndup(s, (gsize) len);
1662     lines = g_strsplit(message, "\n", -1);
1663     g_free(message);
1664
1665     for (line = lines; *line; line++) {
1666         if (**line == '\0') continue; /* skip blank lines */
1667         g_debug("%s%s", lineprefix, *line);
1668     }
1669     g_strfreev(lines);
1670
1671     return 0;
1672 }
1673
1674 static s3_result_t
1675 perform_request(S3Handle *hdl,
1676                 const char *verb,
1677                 const char *bucket,
1678                 const char *key,
1679                 const char *subresource,
1680                 const char *query,
1681                 const char *content_type,
1682                 const char *project_id,
1683                 s3_read_func read_func,
1684                 s3_reset_func read_reset_func,
1685                 s3_size_func size_func,
1686                 s3_md5_func md5_func,
1687                 gpointer read_data,
1688                 s3_write_func write_func,
1689                 s3_reset_func write_reset_func,
1690                 gpointer write_data,
1691                 s3_progress_func progress_func,
1692                 gpointer progress_data,
1693                 const result_handling_t *result_handling)
1694 {
1695     char *url = NULL;
1696     s3_result_t result = S3_RESULT_FAIL; /* assume the worst.. */
1697     CURLcode curl_code = CURLE_OK;
1698     char curl_error_buffer[CURL_ERROR_SIZE] = "";
1699     struct curl_slist *headers = NULL;
1700     /* Set S3Internal Data */
1701     S3InternalData int_writedata = {{NULL, 0, 0, MAX_ERROR_RESPONSE_LEN}, NULL, NULL, NULL, FALSE, FALSE, NULL, hdl};
1702     gboolean should_retry;
1703     guint retries = 0;
1704     gulong backoff = EXPONENTIAL_BACKOFF_START_USEC;
1705     /* corresponds to PUT, HEAD, GET, and POST */
1706     int curlopt_upload = 0, curlopt_nobody = 0, curlopt_httpget = 0, curlopt_post = 0;
1707     /* do we want to examine the headers */
1708     const char *curlopt_customrequest = NULL;
1709     /* for MD5 calculation */
1710     GByteArray *md5_hash = NULL;
1711     gchar *md5_hash_hex = NULL, *md5_hash_b64 = NULL;
1712     size_t request_body_size = 0;
1713
1714     g_assert(hdl != NULL && hdl->curl != NULL);
1715
1716     if (hdl->s3_api == S3_API_OAUTH2 && !hdl->getting_oauth2_access_token &&
1717         (!hdl->access_token || hdl->expires < time(NULL))) {
1718         result = oauth2_get_access_token(hdl);
1719         if (!result) {
1720             g_debug("oauth2_get_access_token returned %d", result);
1721             return result;
1722         }
1723     } else if (hdl->s3_api == S3_API_SWIFT_2 && !hdl->getting_swift_2_token &&
1724                (!hdl->x_auth_token || hdl->expires < time(NULL))) {
1725         result = get_openstack_swift_api_v2_setting(hdl);
1726         if (!result) {
1727             g_debug("get_openstack_swift_api_v2_setting returned %d", result);
1728             return result;
1729         }
1730     }
1731
1732     s3_reset(hdl);
1733
1734     url = build_url(hdl, bucket, key, subresource, query);
1735     if (!url) goto cleanup;
1736
1737     /* libcurl may behave strangely if these are not set correctly */
1738     if (!strncmp(verb, "PUT", 4)) {
1739         curlopt_upload = 1;
1740     } else if (!strncmp(verb, "GET", 4)) {
1741         curlopt_httpget = 1;
1742     } else if (!strncmp(verb, "POST", 5)) {
1743         curlopt_post = 1;
1744     } else if (!strncmp(verb, "HEAD", 5)) {
1745         curlopt_nobody = 1;
1746     } else {
1747         curlopt_customrequest = verb;
1748     }
1749
1750     if (size_func) {
1751         request_body_size = size_func(read_data);
1752     }
1753     if (md5_func) {
1754
1755         md5_hash = md5_func(read_data);
1756         if (md5_hash) {
1757             md5_hash_b64 = s3_base64_encode(md5_hash);
1758             md5_hash_hex = s3_hex_encode(md5_hash);
1759             g_byte_array_free(md5_hash, TRUE);
1760         }
1761     }
1762     if (!read_func) {
1763         /* Curl will use fread() otherwise */
1764         read_func = s3_empty_read_func;
1765     }
1766
1767     if (write_func) {
1768         int_writedata.write_func = write_func;
1769         int_writedata.reset_func = write_reset_func;
1770         int_writedata.write_data = write_data;
1771     } else {
1772         /* Curl will use fwrite() otherwise */
1773         int_writedata.write_func = s3_counter_write_func;
1774         int_writedata.reset_func = s3_counter_reset_func;
1775         int_writedata.write_data = NULL;
1776     }
1777
1778     while (1) {
1779         /* reset things */
1780         if (headers) {
1781             curl_slist_free_all(headers);
1782         }
1783         curl_error_buffer[0] = '\0';
1784         if (read_reset_func) {
1785             read_reset_func(read_data);
1786         }
1787         /* calls write_reset_func */
1788         s3_internal_reset_func(&int_writedata);
1789
1790         /* set up the request */
1791         headers = authenticate_request(hdl, verb, bucket, key, subresource,
1792             md5_hash_b64, content_type, request_body_size, project_id);
1793
1794         if (hdl->ca_info) {
1795             if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_CAINFO, hdl->ca_info)))
1796                 goto curl_error;
1797         }
1798
1799         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_VERBOSE, hdl->verbose)))
1800             goto curl_error;
1801         if (hdl->verbose) {
1802             if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_DEBUGFUNCTION,
1803                               curl_debug_message)))
1804                 goto curl_error;
1805         }
1806         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_ERRORBUFFER,
1807                                           curl_error_buffer)))
1808             goto curl_error;
1809         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_NOPROGRESS, 1)))
1810             goto curl_error;
1811         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_FOLLOWLOCATION, 1)))
1812             goto curl_error;
1813         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_URL, url)))
1814             goto curl_error;
1815         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_HTTPHEADER,
1816                                           headers)))
1817             goto curl_error;
1818         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_WRITEFUNCTION, s3_internal_write_func)))
1819             goto curl_error;
1820         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_WRITEDATA, &int_writedata)))
1821             goto curl_error;
1822         /* Note: we always have to set this apparently, for consistent "end of header" detection */
1823         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_HEADERFUNCTION, s3_internal_header_func)))
1824             goto curl_error;
1825         /* Note: if set, CURLOPT_HEADERDATA seems to also be used for CURLOPT_WRITEDATA ? */
1826         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_HEADERDATA, &int_writedata)))
1827             goto curl_error;
1828         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_PROGRESSFUNCTION, progress_func)))
1829             goto curl_error;
1830         if (progress_func) {
1831             if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_NOPROGRESS,0)))
1832                 goto curl_error;
1833         }
1834         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_PROGRESSDATA, progress_data)))
1835             goto curl_error;
1836
1837 /* CURLOPT_INFILESIZE_LARGE added in 7.11.0 */
1838 #if LIBCURL_VERSION_NUM >= 0x070b00
1839         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)request_body_size)))
1840             goto curl_error;
1841 #else
1842         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_INFILESIZE, (long)request_body_size)))
1843             goto curl_error;
1844 #endif
1845 /* CURLOPT_POSTFIELDSIZE_LARGE added in 7.11.1 */
1846 #if LIBCURL_VERSION_NUM >= 0x070b01
1847         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)request_body_size)))
1848             goto curl_error;
1849 #else
1850         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_POSTFIELDSIZE, (long)request_body_size)))
1851             goto curl_error;
1852 #endif
1853
1854 /* CURLOPT_MAX_{RECV,SEND}_SPEED_LARGE added in 7.15.5 */
1855 #if LIBCURL_VERSION_NUM >= 0x070f05
1856         if (s3_curl_throttling_compat()) {
1857             if (hdl->max_send_speed)
1858                 if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_MAX_SEND_SPEED_LARGE, (curl_off_t)hdl->max_send_speed)))
1859                     goto curl_error;
1860
1861             if (hdl->max_recv_speed)
1862                 if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t)hdl->max_recv_speed)))
1863                     goto curl_error;
1864         }
1865 #endif
1866
1867         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_HTTPGET, curlopt_httpget)))
1868             goto curl_error;
1869         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_UPLOAD, curlopt_upload)))
1870             goto curl_error;
1871         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_POST, curlopt_post)))
1872             goto curl_error;
1873         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_NOBODY, curlopt_nobody)))
1874             goto curl_error;
1875         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_CUSTOMREQUEST,
1876                                           curlopt_customrequest)))
1877             goto curl_error;
1878
1879
1880         if (curlopt_upload || curlopt_post) {
1881             if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_READFUNCTION, read_func)))
1882                 goto curl_error;
1883             if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_READDATA, read_data)))
1884                 goto curl_error;
1885         } else {
1886             /* Clear request_body options. */
1887             if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_READFUNCTION,
1888                                               NULL)))
1889                 goto curl_error;
1890             if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_READDATA,
1891                                               NULL)))
1892                 goto curl_error;
1893         }
1894         if (hdl->proxy) {
1895             if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_PROXY,
1896                                               hdl->proxy)))
1897                 goto curl_error;
1898         }
1899
1900         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_FRESH_CONNECT,
1901                 (long)(hdl->reuse_connection? 0 : 1)))) {
1902             goto curl_error;
1903         }
1904         if ((curl_code = curl_easy_setopt(hdl->curl, CURLOPT_FORBID_REUSE,
1905                 (long)(hdl->reuse_connection? 0 : 1)))) {
1906             goto curl_error;
1907         }
1908
1909         /* Perform the request */
1910         curl_code = curl_easy_perform(hdl->curl);
1911
1912
1913         /* interpret the response into hdl->last* */
1914     curl_error: /* (label for short-circuiting the curl_easy_perform call) */
1915         should_retry = interpret_response(hdl, curl_code, curl_error_buffer,
1916             int_writedata.resp_buf.buffer, int_writedata.resp_buf.buffer_pos, int_writedata.etag, md5_hash_hex);
1917
1918         if (hdl->s3_api == S3_API_OAUTH2 &&
1919             hdl->last_response_code == 401 &&
1920             hdl->last_s3_error_code == S3_ERROR_AuthenticationRequired) {
1921             should_retry = oauth2_get_access_token(hdl);
1922         }
1923         /* and, unless we know we need to retry, see what we're to do now */
1924         if (!should_retry) {
1925             result = lookup_result(result_handling, hdl->last_response_code,
1926                                    hdl->last_s3_error_code, hdl->last_curl_code);
1927
1928             /* break out of the while(1) unless we're retrying */
1929             if (result != S3_RESULT_RETRY)
1930                 break;
1931         }
1932
1933         if (retries >= EXPONENTIAL_BACKOFF_MAX_RETRIES) {
1934             /* we're out of retries, so annotate hdl->last_message appropriately and bail
1935              * out. */
1936             char *m = g_strdup_printf("Too many retries; last message was '%s'", hdl->last_message);
1937             if (hdl->last_message) g_free(hdl->last_message);
1938             hdl->last_message = m;
1939             result = S3_RESULT_FAIL;
1940             break;
1941         }
1942
1943         g_usleep(backoff);
1944         retries++;
1945         backoff *= EXPONENTIAL_BACKOFF_BASE;
1946     }
1947
1948     if (result != S3_RESULT_OK) {
1949         g_debug(_("%s %s failed with %d/%s"), verb, url,
1950                 hdl->last_response_code,
1951                 s3_error_name_from_code(hdl->last_s3_error_code));
1952     }
1953
1954 cleanup:
1955     g_free(url);
1956     if (headers) curl_slist_free_all(headers);
1957     g_free(md5_hash_b64);
1958     g_free(md5_hash_hex);
1959
1960     /* we don't deallocate the response body -- we keep it for later */
1961     hdl->last_response_body = int_writedata.resp_buf.buffer;
1962     hdl->last_response_body_size = int_writedata.resp_buf.buffer_pos;
1963     hdl->last_num_retries = retries;
1964
1965     return result;
1966 }
1967
1968
1969 static size_t
1970 s3_internal_write_func(void *ptr, size_t size, size_t nmemb, void * stream)
1971 {
1972     S3InternalData *data = (S3InternalData *) stream;
1973     size_t bytes_saved;
1974
1975     if (!data->headers_done)
1976         return size*nmemb;
1977
1978     /* call write on internal buffer (if not full) */
1979     if (data->int_write_done) {
1980         bytes_saved = 0;
1981     } else {
1982         bytes_saved = s3_buffer_write_func(ptr, size, nmemb, &data->resp_buf);
1983         if (!bytes_saved) {
1984             data->int_write_done = TRUE;
1985         }
1986     }
1987     /* call write on user buffer */
1988     if (data->write_func) {
1989         return data->write_func(ptr, size, nmemb, data->write_data);
1990     } else {
1991         return bytes_saved;
1992     }
1993 }
1994
1995 static void
1996 s3_internal_reset_func(void * stream)
1997 {
1998     S3InternalData *data = (S3InternalData *) stream;
1999
2000     s3_buffer_reset_func(&data->resp_buf);
2001     data->headers_done = FALSE;
2002     data->int_write_done = FALSE;
2003     data->etag = NULL;
2004     if (data->reset_func) {
2005         data->reset_func(data->write_data);
2006     }
2007 }
2008
2009 static size_t
2010 s3_internal_header_func(void *ptr, size_t size, size_t nmemb, void * stream)
2011 {
2012     static const char *final_header = "\r\n";
2013     time_t remote_time_in_sec,local_time;
2014     char *header;
2015     regmatch_t pmatch[2];
2016     S3InternalData *data = (S3InternalData *) stream;
2017
2018     header = g_strndup((gchar *) ptr, (gsize) size*nmemb);
2019
2020     if (header[strlen(header)-1] == '\n')
2021         header[strlen(header)-1] = '\0';
2022     if (header[strlen(header)-1] == '\r')
2023         header[strlen(header)-1] = '\0';
2024     if (!s3_regexec_wrap(&etag_regex, header, 2, pmatch, 0))
2025         data->etag = find_regex_substring(header, pmatch[1]);
2026     if (!s3_regexec_wrap(&x_auth_token_regex, header, 2, pmatch, 0))
2027         data->hdl->x_auth_token = find_regex_substring(header, pmatch[1]);
2028
2029     if (!s3_regexec_wrap(&x_storage_url_regex, header, 2, pmatch, 0))
2030         data->hdl->x_storage_url = find_regex_substring(header, pmatch[1]);
2031
2032     if (!s3_regexec_wrap(&content_type_regex, header, 2, pmatch, 0))
2033        data->hdl->content_type = find_regex_substring(header, pmatch[1]);
2034
2035     if (strlen(header) == 0)
2036         data->headers_done = TRUE;
2037     if (g_str_equal(final_header, header))
2038         data->headers_done = TRUE;
2039     if (g_str_equal("\n", header))
2040         data->headers_done = TRUE;
2041
2042     /* If date header is found */
2043     if (!s3_regexec_wrap(&date_sync_regex, header, 2, pmatch, 0)){
2044         char *date = find_regex_substring(header, pmatch[1]);
2045
2046         /* Remote time is always in GMT: RFC 2616 */
2047         /* both curl_getdate and time operate in UTC, so no timezone math is necessary */
2048         if ( (remote_time_in_sec = curl_getdate(date, NULL)) < 0 ){
2049             g_debug("Error: Conversion of remote time to seconds failed.");
2050             data->hdl->time_offset_with_s3 = 0;
2051         }else{
2052             local_time = time(NULL);
2053             /* Offset time */
2054             data->hdl->time_offset_with_s3 = remote_time_in_sec - local_time;
2055
2056             if (data->hdl->verbose)
2057                 g_debug("Time Offset (remote - local) :%ld",(long)data->hdl->time_offset_with_s3);
2058         }
2059
2060         g_free(date);
2061     }
2062
2063     g_free(header);
2064     return size*nmemb;
2065 }
2066
2067 static gboolean
2068 compile_regexes(void)
2069 {
2070 #ifdef HAVE_REGEX_H
2071
2072   /* using POSIX regular expressions */
2073   struct {const char * str; int flags; regex_t *regex;} regexes[] = {
2074         {"<Code>[[:space:]]*([^<]*)[[:space:]]*</Code>", REG_EXTENDED | REG_ICASE, &error_name_regex},
2075         {"^ETag:[[:space:]]*\"([^\"]+)\"[[:space:]]*$", REG_EXTENDED | REG_ICASE | REG_NEWLINE, &etag_regex},
2076         {"^X-Auth-Token:[[:space:]]*([^ ]+)[[:space:]]*$", REG_EXTENDED | REG_ICASE | REG_NEWLINE, &x_auth_token_regex},
2077         {"^X-Storage-Url:[[:space:]]*([^ ]+)[[:space:]]*$", REG_EXTENDED | REG_ICASE | REG_NEWLINE, &x_storage_url_regex},
2078         {"^Content-Type:[[:space:]]*([^ ;]+).*$", REG_EXTENDED | REG_ICASE | REG_NEWLINE, &content_type_regex},
2079         {"<Message>[[:space:]]*([^<]*)[[:space:]]*</Message>", REG_EXTENDED | REG_ICASE, &message_regex},
2080         {"^[a-z0-9](-*[a-z0-9]){2,62}$", REG_EXTENDED | REG_NOSUB, &subdomain_regex},
2081         {"(/>)|(>([^<]*)</LocationConstraint>)", REG_EXTENDED | REG_ICASE, &location_con_regex},
2082         {"^Date:(.*)\r",REG_EXTENDED | REG_ICASE | REG_NEWLINE, &date_sync_regex},
2083         {"\"access_token\" : \"([^\"]*)\",", REG_EXTENDED | REG_ICASE | REG_NEWLINE, &access_token_regex},
2084         {"\"expires_in\" : (.*)", REG_EXTENDED | REG_ICASE | REG_NEWLINE, &expires_in_regex},
2085         {"\"details\": \"([^\"]*)\",", REG_EXTENDED | REG_ICASE | REG_NEWLINE, &details_regex},
2086         {"\"code\": (.*),", REG_EXTENDED | REG_ICASE | REG_NEWLINE, &code_regex},
2087         {NULL, 0, NULL}
2088     };
2089     char regmessage[1024];
2090     int i;
2091     int reg_result;
2092
2093     for (i = 0; regexes[i].str; i++) {
2094         reg_result = regcomp(regexes[i].regex, regexes[i].str, regexes[i].flags);
2095         if (reg_result != 0) {
2096             regerror(reg_result, regexes[i].regex, regmessage, sizeof(regmessage));
2097             g_error(_("Regex error: %s"), regmessage);
2098             return FALSE;
2099         }
2100     }
2101 #else /* ! HAVE_REGEX_H */
2102   /* using PCRE via GLib */
2103   struct {const char * str; int flags; regex_t *regex;} regexes[] = {
2104         {"<Code>\\s*([^<]*)\\s*</Code>",
2105          G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
2106          &error_name_regex},
2107         {"^ETag:\\s*\"([^\"]+)\"\\s*$",
2108          G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
2109          &etag_regex},
2110         {"^X-Auth-Token:\\s*([^ ]+)\\s*$",
2111          G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
2112          &x_auth_token_regex},
2113         {"^X-Storage-Url:\\s*([^ ]+)\\s*$",
2114          G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
2115          &x_storage_url_regex},
2116         {"^Content-Type:\\s*([^ ]+)\\s*$",
2117          G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
2118          &content_type_regex},
2119         {"<Message>\\s*([^<]*)\\s*</Message>",
2120          G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
2121          &message_regex},
2122         {"^[a-z0-9]((-*[a-z0-9])|(\\.[a-z0-9])){2,62}$",
2123          G_REGEX_OPTIMIZE | G_REGEX_NO_AUTO_CAPTURE,
2124          &subdomain_regex},
2125         {"(/>)|(>([^<]*)</LocationConstraint>)",
2126          G_REGEX_CASELESS,
2127          &location_con_regex},
2128         {"^Date:(.*)\\r",
2129          G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
2130          &date_sync_regex},
2131         {"\"access_token\" : \"([^\"]*)\"",
2132          G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
2133          &access_token_regex},
2134         {"\"expires_n\" : (.*)",
2135          G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
2136          &expires_in_regex},
2137         {"\"details\" : \"([^\"]*)\"",
2138          G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
2139          &details_regex},
2140         {"\"code\" : (.*)",
2141          G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
2142          &code_regex},
2143         {NULL, 0, NULL}
2144   };
2145   int i;
2146   GError *err = NULL;
2147
2148   for (i = 0; regexes[i].str; i++) {
2149       *(regexes[i].regex) = g_regex_new(regexes[i].str, regexes[i].flags, 0, &err);
2150       if (err) {
2151           g_error(_("Regex error: %s"), err->message);
2152           g_error_free(err);
2153           return FALSE;
2154       }
2155   }
2156 #endif
2157     return TRUE;
2158 }
2159
2160 /*
2161  * Public function implementations
2162  */
2163
2164 #if (GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 31))
2165 # pragma GCC diagnostic push
2166 # pragma GCC diagnostic ignored "-Wmissing-field-initializers"
2167 #endif
2168 gboolean s3_init(void)
2169 {
2170     static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
2171     static gboolean init = FALSE, ret;
2172
2173     /* n.b. curl_global_init is called in common-src/glib-util.c:glib_init() */
2174
2175     g_static_mutex_lock (&mutex);
2176     if (!init) {
2177         ret = compile_regexes();
2178         init = TRUE;
2179     }
2180     g_static_mutex_unlock(&mutex);
2181     return ret;
2182 }
2183 #if (GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 31))
2184 # pragma GCC diagnostic pop
2185 #endif
2186
2187 gboolean
2188 s3_curl_location_compat(void)
2189 {
2190     curl_version_info_data *info;
2191
2192     info = curl_version_info(CURLVERSION_NOW);
2193     return info->version_num > 0x070a02;
2194 }
2195
2196 gboolean
2197 s3_bucket_location_compat(const char *bucket)
2198 {
2199     return !s3_regexec_wrap(&subdomain_regex, bucket, 0, NULL, 0);
2200 }
2201
2202 static gboolean
2203 get_openstack_swift_api_v1_setting(
2204         S3Handle *hdl)
2205 {
2206     s3_result_t result = S3_RESULT_FAIL;
2207     static result_handling_t result_handling[] = {
2208         { 200,  0,                    0, S3_RESULT_OK },
2209         RESULT_HANDLING_ALWAYS_RETRY,
2210         { 0, 0,                       0, /* default: */ S3_RESULT_FAIL  }
2211         };
2212
2213     s3_verbose(hdl, 1);
2214     result = perform_request(hdl, "GET", NULL, NULL, NULL, NULL, NULL, NULL,
2215                              NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2216                              NULL, NULL, result_handling);
2217
2218     return result == S3_RESULT_OK;
2219 }
2220
2221 static gboolean
2222 get_openstack_swift_api_v2_setting(
2223         S3Handle *hdl)
2224 {
2225     s3_result_t result = S3_RESULT_FAIL;
2226     static result_handling_t result_handling[] = {
2227         { 200,  0,                    0, S3_RESULT_OK },
2228         RESULT_HANDLING_ALWAYS_RETRY,
2229         { 0, 0,                       0, /* default: */ S3_RESULT_FAIL  }
2230         };
2231
2232     CurlBuffer buf = {NULL, 0, 0, 0};
2233     GString *body = g_string_new("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
2234     if (hdl->username && hdl->password) {
2235         g_string_append_printf(body, "<auth xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://docs.openstack.org/identity/api/v2.0\"");
2236     } else {
2237         g_string_append_printf(body, "<auth xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://www.hp.com/identity/api/ext/HP-IDM/v1.0\"");
2238     }
2239
2240     if (hdl->tenant_id) {
2241         g_string_append_printf(body, " tenantId=\"%s\"", hdl->tenant_id);
2242     }
2243     if (hdl->tenant_name) {
2244         g_string_append_printf(body, " tenantName=\"%s\"", hdl->tenant_name);
2245     }
2246     g_string_append(body, ">");
2247     if (hdl->username && hdl->password) {
2248         g_string_append_printf(body, "<passwordCredentials username=\"%s\" password=\"%s\"/>", hdl->username, hdl->password);
2249     } else {
2250         g_string_append_printf(body, "<apiAccessKeyCredentials accessKey=\"%s\" secretKey=\"%s\"/>", hdl->access_key, hdl->secret_key);
2251     }
2252     g_string_append(body, "</auth>");
2253
2254     buf.buffer = g_string_free(body, FALSE);
2255     buf.buffer_len = strlen(buf.buffer);
2256     s3_verbose(hdl, 1);
2257     hdl->getting_swift_2_token = 1;
2258     g_free(hdl->x_storage_url);
2259     hdl->x_storage_url = NULL;
2260     result = perform_request(hdl, "POST", NULL, NULL, NULL, NULL,
2261                              "application/xml", NULL,
2262                              S3_BUFFER_READ_FUNCS, &buf,
2263                              NULL, NULL, NULL,
2264                              NULL, NULL, result_handling);
2265     hdl->getting_swift_2_token = 0;
2266
2267     return result == S3_RESULT_OK;
2268 }
2269
2270 S3Handle *
2271 s3_open(const char *access_key,
2272         const char *secret_key,
2273         const char *swift_account_id,
2274         const char *swift_access_key,
2275         const char *host,
2276         const char *service_path,
2277         const gboolean use_subdomain,
2278         const char *user_token,
2279         const char *bucket_location,
2280         const char *storage_class,
2281         const char *ca_info,
2282         const char *server_side_encryption,
2283         const char *proxy,
2284         const S3_api s3_api,
2285         const char *username,
2286         const char *password,
2287         const char *tenant_id,
2288         const char *tenant_name,
2289         const char *client_id,
2290         const char *client_secret,
2291         const char *refresh_token,
2292         const gboolean reuse_connection)
2293 {
2294     S3Handle *hdl;
2295
2296     hdl = g_new0(S3Handle, 1);
2297     if (!hdl) goto error;
2298
2299     hdl->verbose = TRUE;
2300     hdl->use_ssl = s3_curl_supports_ssl();
2301     hdl->reuse_connection = reuse_connection;
2302
2303     if (s3_api == S3_API_S3) {
2304         g_assert(access_key);
2305         hdl->access_key = g_strdup(access_key);
2306         g_assert(secret_key);
2307         hdl->secret_key = g_strdup(secret_key);
2308     } else if (s3_api == S3_API_SWIFT_1) {
2309         g_assert(swift_account_id);
2310         hdl->swift_account_id = g_strdup(swift_account_id);
2311         g_assert(swift_access_key);
2312         hdl->swift_access_key = g_strdup(swift_access_key);
2313     } else if (s3_api == S3_API_SWIFT_2) {
2314         g_assert((username && password) || (access_key && secret_key));
2315         hdl->username = g_strdup(username);
2316         hdl->password = g_strdup(password);
2317         hdl->access_key = g_strdup(access_key);
2318         hdl->secret_key = g_strdup(secret_key);
2319         g_assert(tenant_id || tenant_name);
2320         hdl->tenant_id = g_strdup(tenant_id);
2321         hdl->tenant_name = g_strdup(tenant_name);
2322     } else if (s3_api == S3_API_OAUTH2) {
2323         hdl->client_id = g_strdup(client_id);
2324         hdl->client_secret = g_strdup(client_secret);
2325         hdl->refresh_token = g_strdup(refresh_token);
2326     }
2327
2328     /* NULL is okay */
2329     hdl->user_token = g_strdup(user_token);
2330
2331     /* NULL is okay */
2332     hdl->bucket_location = g_strdup(bucket_location);
2333
2334     /* NULL is ok */
2335     hdl->storage_class = g_strdup(storage_class);
2336
2337     /* NULL is ok */
2338     hdl->server_side_encryption = g_strdup(server_side_encryption);
2339
2340     /* NULL is ok */
2341     hdl->proxy = g_strdup(proxy);
2342
2343     /* NULL is okay */
2344     hdl->ca_info = g_strdup(ca_info);
2345
2346     if (!is_non_empty_string(host))
2347         host = "s3.amazonaws.com";
2348     hdl->host = g_ascii_strdown(host, -1);
2349     hdl->use_subdomain = use_subdomain ||
2350                          (strcmp(hdl->host, "s3.amazonaws.com") == 0 &&
2351                           is_non_empty_string(hdl->bucket_location));
2352     hdl->s3_api = s3_api;
2353     if (service_path) {
2354         if (strlen(service_path) == 0 ||
2355             (strlen(service_path) == 1 && service_path[0] == '/')) {
2356             hdl->service_path = NULL;
2357         } else if (service_path[0] != '/') {
2358             hdl->service_path = g_strdup_printf("/%s", service_path);
2359         } else {
2360             hdl->service_path = g_strdup(service_path);
2361         }
2362         if (hdl->service_path) {
2363             /* remove trailling / */
2364             size_t len = strlen(hdl->service_path) - 1;
2365             if (hdl->service_path[len] == '/')
2366                 hdl->service_path[len] = '\0';
2367         }
2368     } else {
2369         hdl->service_path = NULL;
2370     }
2371
2372     hdl->curl = curl_easy_init();
2373     if (!hdl->curl) goto error;
2374
2375     return hdl;
2376
2377 error:
2378     s3_free(hdl);
2379     return NULL;
2380 }
2381
2382 gboolean
2383 s3_open2(
2384     S3Handle *hdl)
2385 {
2386     gboolean ret = TRUE;
2387
2388     /* get the X-Storage-Url and X-Auth-Token */
2389     if (hdl->s3_api == S3_API_SWIFT_1) {
2390         ret = get_openstack_swift_api_v1_setting(hdl);
2391     } else if (hdl->s3_api == S3_API_SWIFT_2) {
2392         ret = get_openstack_swift_api_v2_setting(hdl);
2393     }
2394
2395     return ret;
2396 }
2397
2398 void
2399 s3_free(S3Handle *hdl)
2400 {
2401     s3_reset(hdl);
2402
2403     if (hdl) {
2404         g_free(hdl->access_key);
2405         g_free(hdl->secret_key);
2406         g_free(hdl->swift_account_id);
2407         g_free(hdl->swift_access_key);
2408         g_free(hdl->content_type);
2409         g_free(hdl->user_token);
2410         g_free(hdl->ca_info);
2411         g_free(hdl->proxy);
2412         g_free(hdl->username);
2413         g_free(hdl->password);
2414         g_free(hdl->tenant_id);
2415         g_free(hdl->tenant_name);
2416         g_free(hdl->client_id);
2417         g_free(hdl->client_secret);
2418         g_free(hdl->refresh_token);
2419         g_free(hdl->access_token);
2420         if (hdl->user_token) g_free(hdl->user_token);
2421         if (hdl->bucket_location) g_free(hdl->bucket_location);
2422         if (hdl->storage_class) g_free(hdl->storage_class);
2423         if (hdl->server_side_encryption) g_free(hdl->server_side_encryption);
2424         if (hdl->host) g_free(hdl->host);
2425         if (hdl->service_path) g_free(hdl->service_path);
2426         if (hdl->curl) curl_easy_cleanup(hdl->curl);
2427
2428         g_free(hdl);
2429     }
2430 }
2431
2432 void
2433 s3_reset(S3Handle *hdl)
2434 {
2435     if (hdl) {
2436         /* We don't call curl_easy_reset here, because doing that in curl
2437          * < 7.16 blanks the default CA certificate path, and there's no way
2438          * to get it back. */
2439         if (hdl->last_message) {
2440             g_free(hdl->last_message);
2441             hdl->last_message = NULL;
2442         }
2443
2444         hdl->last_response_code = 0;
2445         hdl->last_curl_code = 0;
2446         hdl->last_s3_error_code = 0;
2447         hdl->last_num_retries = 0;
2448
2449         if (hdl->last_response_body) {
2450             g_free(hdl->last_response_body);
2451             hdl->last_response_body = NULL;
2452         }
2453         if (hdl->content_type) {
2454             g_free(hdl->content_type);
2455             hdl->content_type = NULL;
2456         }
2457
2458         hdl->last_response_body_size = 0;
2459     }
2460 }
2461
2462 void
2463 s3_error(S3Handle *hdl,
2464          const char **message,
2465          guint *response_code,
2466          s3_error_code_t *s3_error_code,
2467          const char **s3_error_name,
2468          CURLcode *curl_code,
2469          guint *num_retries)
2470 {
2471     if (hdl) {
2472         if (message) *message = hdl->last_message;
2473         if (response_code) *response_code = hdl->last_response_code;
2474         if (s3_error_code) *s3_error_code = hdl->last_s3_error_code;
2475         if (s3_error_name) *s3_error_name = s3_error_name_from_code(hdl->last_s3_error_code);
2476         if (curl_code) *curl_code = hdl->last_curl_code;
2477         if (num_retries) *num_retries = hdl->last_num_retries;
2478     } else {
2479         /* no hdl? return something coherent, anyway */
2480         if (message) *message = "NULL S3Handle";
2481         if (response_code) *response_code = 0;
2482         if (s3_error_code) *s3_error_code = 0;
2483         if (s3_error_name) *s3_error_name = NULL;
2484         if (curl_code) *curl_code = 0;
2485         if (num_retries) *num_retries = 0;
2486     }
2487 }
2488
2489 void
2490 s3_verbose(S3Handle *hdl, gboolean verbose)
2491 {
2492     hdl->verbose = verbose;
2493 }
2494
2495 gboolean
2496 s3_set_max_send_speed(S3Handle *hdl, guint64 max_send_speed)
2497 {
2498     if (!s3_curl_throttling_compat())
2499         return FALSE;
2500
2501     hdl->max_send_speed = max_send_speed;
2502
2503     return TRUE;
2504 }
2505
2506 gboolean
2507 s3_set_max_recv_speed(S3Handle *hdl, guint64 max_recv_speed)
2508 {
2509     if (!s3_curl_throttling_compat())
2510         return FALSE;
2511
2512     hdl->max_recv_speed = max_recv_speed;
2513
2514     return TRUE;
2515 }
2516
2517 gboolean
2518 s3_use_ssl(S3Handle *hdl, gboolean use_ssl)
2519 {
2520     gboolean ret = TRUE;
2521     if (use_ssl & !s3_curl_supports_ssl()) {
2522         ret = FALSE;
2523     } else {
2524         hdl->use_ssl = use_ssl;
2525     }
2526     return ret;
2527 }
2528
2529 char *
2530 s3_strerror(S3Handle *hdl)
2531 {
2532     const char *message;
2533     guint response_code;
2534     const char *s3_error_name;
2535     CURLcode curl_code;
2536     guint num_retries;
2537
2538     char s3_info[256] = "";
2539     char response_info[16] = "";
2540     char curl_info[32] = "";
2541     char retries_info[32] = "";
2542
2543     s3_error(hdl, &message, &response_code, NULL, &s3_error_name, &curl_code, &num_retries);
2544
2545     if (!message)
2546         message = "Unknown S3 error";
2547     if (s3_error_name)
2548         g_snprintf(s3_info, sizeof(s3_info), " (%s)", s3_error_name);
2549     if (response_code)
2550         g_snprintf(response_info, sizeof(response_info), " (HTTP %d)", response_code);
2551     if (curl_code)
2552         g_snprintf(curl_info, sizeof(curl_info), " (CURLcode %d)", curl_code);
2553     if (num_retries)
2554         g_snprintf(retries_info, sizeof(retries_info), " (after %d retries)", num_retries);
2555
2556     return g_strdup_printf("%s%s%s%s%s", message, s3_info, curl_info, response_info, retries_info);
2557 }
2558
2559 /* Perform an upload. When this function returns, KEY and
2560  * BUFFER remain the responsibility of the caller.
2561  *
2562  * @param self: the s3 device
2563  * @param bucket: the bucket to which the upload should be made
2564  * @param key: the key to which the upload should be made
2565  * @param buffer: the data to be uploaded
2566  * @param buffer_len: the length of the data to upload
2567  * @returns: false if an error ocurred
2568  */
2569 gboolean
2570 s3_upload(S3Handle *hdl,
2571           const char *bucket,
2572           const char *key,
2573           s3_read_func read_func,
2574           s3_reset_func reset_func,
2575           s3_size_func size_func,
2576           s3_md5_func md5_func,
2577           gpointer read_data,
2578           s3_progress_func progress_func,
2579           gpointer progress_data)
2580 {
2581     s3_result_t result = S3_RESULT_FAIL;
2582     static result_handling_t result_handling[] = {
2583         { 200,  0, 0, S3_RESULT_OK },
2584         { 201,  0, 0, S3_RESULT_OK },
2585         RESULT_HANDLING_ALWAYS_RETRY,
2586         { 0,    0, 0, /* default: */ S3_RESULT_FAIL }
2587         };
2588
2589     g_assert(hdl != NULL);
2590
2591     result = perform_request(hdl, "PUT", bucket, key, NULL, NULL, NULL, NULL,
2592                  read_func, reset_func, size_func, md5_func, read_data,
2593                  NULL, NULL, NULL, progress_func, progress_data,
2594                  result_handling);
2595
2596     return result == S3_RESULT_OK;
2597 }
2598
2599
2600 /* Private structure for our "thunk", which tracks where the user is in the list
2601  * of keys. */
2602 struct list_keys_thunk {
2603     GSList *filename_list; /* all pending filenames */
2604
2605     gboolean in_contents; /* look for "key" entities in here */
2606     gboolean in_common_prefixes; /* look for "prefix" entities in here */
2607
2608     gboolean is_truncated;
2609     gchar *next_marker;
2610     guint64 size;
2611
2612     gboolean want_text;
2613
2614     gchar *text;
2615     gsize text_len;
2616 };
2617
2618 /* Functions for a SAX parser to parse the XML from Amazon */
2619
2620 static void
2621 list_start_element(GMarkupParseContext *context G_GNUC_UNUSED,
2622                    const gchar *element_name,
2623                    const gchar **attribute_names G_GNUC_UNUSED,
2624                    const gchar **attribute_values G_GNUC_UNUSED,
2625                    gpointer user_data,
2626                    GError **error G_GNUC_UNUSED)
2627 {
2628     struct list_keys_thunk *thunk = (struct list_keys_thunk *)user_data;
2629
2630     thunk->want_text = 0;
2631     if (g_ascii_strcasecmp(element_name, "contents") == 0 ||
2632         g_ascii_strcasecmp(element_name, "object") == 0) {
2633         thunk->in_contents = 1;
2634     } else if (g_ascii_strcasecmp(element_name, "commonprefixes") == 0) {
2635         thunk->in_common_prefixes = 1;
2636     } else if (g_ascii_strcasecmp(element_name, "prefix") == 0 && thunk->in_common_prefixes) {
2637         thunk->want_text = 1;
2638     } else if ((g_ascii_strcasecmp(element_name, "key") == 0 ||
2639                 g_ascii_strcasecmp(element_name, "name") == 0) &&
2640                thunk->in_contents) {
2641         thunk->want_text = 1;
2642     } else if ((g_ascii_strcasecmp(element_name, "size") == 0 ||
2643                 g_ascii_strcasecmp(element_name, "bytes") == 0) &&
2644                thunk->in_contents) {
2645         thunk->want_text = 1;
2646     } else if (g_ascii_strcasecmp(element_name, "istruncated")) {
2647         thunk->want_text = 1;
2648     } else if (g_ascii_strcasecmp(element_name, "nextmarker")) {
2649         thunk->want_text = 1;
2650     }
2651 }
2652
2653 static void
2654 list_end_element(GMarkupParseContext *context G_GNUC_UNUSED,
2655                  const gchar *element_name,
2656                  gpointer user_data,
2657                  GError **error G_GNUC_UNUSED)
2658 {
2659     struct list_keys_thunk *thunk = (struct list_keys_thunk *)user_data;
2660
2661     if (g_ascii_strcasecmp(element_name, "contents") == 0) {
2662         thunk->in_contents = 0;
2663     } else if (g_ascii_strcasecmp(element_name, "commonprefixes") == 0) {
2664         thunk->in_common_prefixes = 0;
2665     } else if ((g_ascii_strcasecmp(element_name, "key") == 0 ||
2666                 g_ascii_strcasecmp(element_name, "name") == 0) &&
2667                thunk->in_contents) {
2668         thunk->filename_list = g_slist_prepend(thunk->filename_list, thunk->text);
2669         if (thunk->is_truncated) {
2670             if (thunk->next_marker) g_free(thunk->next_marker);
2671             thunk->next_marker = g_strdup(thunk->text);
2672         }
2673         thunk->text = NULL;
2674     } else if ((g_ascii_strcasecmp(element_name, "size") == 0 ||
2675                 g_ascii_strcasecmp(element_name, "bytes") == 0) &&
2676                thunk->in_contents) {
2677         thunk->size += g_ascii_strtoull (thunk->text, NULL, 10);
2678         thunk->text = NULL;
2679     } else if (g_ascii_strcasecmp(element_name, "prefix") == 0 && thunk->in_common_prefixes) {
2680         thunk->filename_list = g_slist_prepend(thunk->filename_list, thunk->text);
2681         thunk->text = NULL;
2682     } else if (g_ascii_strcasecmp(element_name, "istruncated") == 0) {
2683         if (thunk->text && g_ascii_strncasecmp(thunk->text, "false", 5) != 0)
2684             thunk->is_truncated = TRUE;
2685     } else if (g_ascii_strcasecmp(element_name, "nextmarker") == 0) {
2686         if (thunk->next_marker) g_free(thunk->next_marker);
2687         thunk->next_marker = thunk->text;
2688         thunk->text = NULL;
2689     }
2690 }
2691
2692 static void
2693 list_text(GMarkupParseContext *context G_GNUC_UNUSED,
2694           const gchar *text,
2695           gsize text_len,
2696           gpointer user_data,
2697           GError **error G_GNUC_UNUSED)
2698 {
2699     struct list_keys_thunk *thunk = (struct list_keys_thunk *)user_data;
2700
2701     if (thunk->want_text) {
2702         if (thunk->text) g_free(thunk->text);
2703         thunk->text = g_strndup(text, text_len);
2704     }
2705 }
2706
2707 /* Perform a fetch from S3; several fetches may be involved in a
2708  * single listing operation */
2709 static s3_result_t
2710 list_fetch(S3Handle *hdl,
2711            const char *bucket,
2712            const char *prefix,
2713            const char *delimiter,
2714            const char *marker,
2715            const char *max_keys,
2716            CurlBuffer *buf)
2717 {
2718     s3_result_t result = S3_RESULT_FAIL;
2719     static result_handling_t result_handling[] = {
2720         { 200, 0, 0, S3_RESULT_OK },
2721         { 204, 0, 0, S3_RESULT_OK },
2722         RESULT_HANDLING_ALWAYS_RETRY,
2723         { 0,   0, 0, /* default: */ S3_RESULT_FAIL  }
2724         };
2725    const char* pos_parts[][2] = {
2726         {"prefix", prefix},
2727         {"delimiter", delimiter},
2728         {"marker", marker},
2729         {"max-keys", max_keys},
2730         {NULL, NULL}
2731         };
2732     char *esc_value;
2733     GString *query;
2734     guint i;
2735     gboolean have_prev_part = FALSE;
2736
2737     /* loop over possible parts to build query string */
2738     query = g_string_new("");
2739     for (i = 0; pos_parts[i][0]; i++) {
2740         if (pos_parts[i][1]) {
2741             const char *keyword;
2742             if (have_prev_part)
2743                 g_string_append(query, "&");
2744             else
2745                 have_prev_part = TRUE;
2746             esc_value = curl_escape(pos_parts[i][1], 0);
2747             keyword = pos_parts[i][0];
2748             if ((hdl->s3_api == S3_API_SWIFT_1 ||
2749                  hdl->s3_api == S3_API_SWIFT_2) &&
2750                 strcmp(keyword, "max-keys") == 0) {
2751                 keyword = "limit";
2752             }
2753             g_string_append_printf(query, "%s=%s", keyword, esc_value);
2754             curl_free(esc_value);
2755         }
2756     }
2757     if (hdl->s3_api == S3_API_SWIFT_1 || hdl->s3_api == S3_API_SWIFT_2) {
2758         if (have_prev_part)
2759             g_string_append(query, "&");
2760         g_string_append(query, "format=xml");
2761     }
2762
2763     /* and perform the request on that URI */
2764     result = perform_request(hdl, "GET", bucket, NULL, NULL, query->str, NULL,
2765                              NULL,
2766                              NULL, NULL, NULL, NULL, NULL,
2767                              S3_BUFFER_WRITE_FUNCS, buf, NULL, NULL,
2768                              result_handling);
2769
2770     if (query) g_string_free(query, TRUE);
2771
2772     return result;
2773 }
2774
2775 gboolean
2776 s3_list_keys(S3Handle *hdl,
2777               const char *bucket,
2778               const char *prefix,
2779               const char *delimiter,
2780               GSList **list,
2781               guint64 *total_size)
2782 {
2783     /*
2784      * max len of XML variables:
2785      * bucket: 255 bytes (p12 API Version 2006-03-01)
2786      * key: 1024 bytes (p15 API Version 2006-03-01)
2787      * size per key: 5GB bytes (p6 API Version 2006-03-01)
2788      * size of size 10 bytes (i.e. 10 decimal digits)
2789      * etag: 44 (observed+assumed)
2790      * owner ID: 64 (observed+assumed)
2791      * owner DisplayName: 255 (assumed)
2792      * StorageClass: const (p18 API Version 2006-03-01)
2793      */
2794     static const guint MAX_RESPONSE_LEN = 1000*2000;
2795     static const char *MAX_KEYS = "1000";
2796     struct list_keys_thunk thunk;
2797     GMarkupParseContext *ctxt = NULL;
2798     static GMarkupParser parser = { list_start_element, list_end_element, list_text, NULL, NULL };
2799     GError *err = NULL;
2800     s3_result_t result = S3_RESULT_FAIL;
2801     CurlBuffer buf = {NULL, 0, 0, MAX_RESPONSE_LEN};
2802
2803     g_assert(list);
2804     *list = NULL;
2805     thunk.filename_list = NULL;
2806     thunk.text = NULL;
2807     thunk.next_marker = NULL;
2808     thunk.size = 0;
2809
2810     /* Loop until S3 has given us the entire picture */
2811     do {
2812         s3_buffer_reset_func(&buf);
2813         /* get some data from S3 */
2814         result = list_fetch(hdl, bucket, prefix, delimiter, thunk.next_marker, MAX_KEYS, &buf);
2815         if (result != S3_RESULT_OK) goto cleanup;
2816         if (buf.buffer_pos == 0) goto cleanup; /* no body */
2817
2818         /* run the parser over it */
2819         thunk.in_contents = FALSE;
2820         thunk.in_common_prefixes = FALSE;
2821         thunk.is_truncated = FALSE;
2822         if (thunk.next_marker) g_free(thunk.next_marker);
2823         thunk.next_marker = NULL;
2824         thunk.want_text = FALSE;
2825
2826         ctxt = g_markup_parse_context_new(&parser, 0, (gpointer)&thunk, NULL);
2827
2828         if (!g_markup_parse_context_parse(ctxt, buf.buffer, buf.buffer_pos, &err)) {
2829             if (hdl->last_message) g_free(hdl->last_message);
2830             hdl->last_message = g_strdup(err->message);
2831             result = S3_RESULT_FAIL;
2832             goto cleanup;
2833         }
2834
2835         if (!g_markup_parse_context_end_parse(ctxt, &err)) {
2836             if (hdl->last_message) g_free(hdl->last_message);
2837             hdl->last_message = g_strdup(err->message);
2838             result = S3_RESULT_FAIL;
2839             goto cleanup;
2840         }
2841
2842         g_markup_parse_context_free(ctxt);
2843         ctxt = NULL;
2844     } while (thunk.next_marker);
2845
2846 cleanup:
2847     if (err) g_error_free(err);
2848     if (thunk.text) g_free(thunk.text);
2849     if (thunk.next_marker) g_free(thunk.next_marker);
2850     if (ctxt) g_markup_parse_context_free(ctxt);
2851     if (buf.buffer) g_free(buf.buffer);
2852
2853     if (result != S3_RESULT_OK) {
2854         g_slist_free(thunk.filename_list);
2855         return FALSE;
2856     } else {
2857         *list = thunk.filename_list;
2858         if(total_size) {
2859             *total_size = thunk.size;
2860         }
2861         return TRUE;
2862     }
2863 }
2864
2865 gboolean
2866 s3_read(S3Handle *hdl,
2867         const char *bucket,
2868         const char *key,
2869         s3_write_func write_func,
2870         s3_reset_func reset_func,
2871         gpointer write_data,
2872         s3_progress_func progress_func,
2873         gpointer progress_data)
2874 {
2875     s3_result_t result = S3_RESULT_FAIL;
2876     static result_handling_t result_handling[] = {
2877         { 200, 0, 0, S3_RESULT_OK },
2878         RESULT_HANDLING_ALWAYS_RETRY,
2879         { 0,   0, 0, /* default: */ S3_RESULT_FAIL  }
2880         };
2881
2882     g_assert(hdl != NULL);
2883     g_assert(write_func != NULL);
2884
2885     result = perform_request(hdl, "GET", bucket, key, NULL, NULL, NULL, NULL,
2886         NULL, NULL, NULL, NULL, NULL, write_func, reset_func, write_data,
2887         progress_func, progress_data, result_handling);
2888
2889     return result == S3_RESULT_OK;
2890 }
2891
2892 gboolean
2893 s3_delete(S3Handle *hdl,
2894           const char *bucket,
2895           const char *key)
2896 {
2897     s3_result_t result = S3_RESULT_FAIL;
2898     static result_handling_t result_handling[] = {
2899         { 204,  0,                     0, S3_RESULT_OK },
2900         { 404,  0,                     0, S3_RESULT_OK },
2901         { 404,  S3_ERROR_NoSuchBucket, 0, S3_RESULT_OK },
2902         RESULT_HANDLING_ALWAYS_RETRY,
2903         { 409,  0,                     0, S3_RESULT_OK },
2904         { 0,    0,                     0, /* default: */ S3_RESULT_FAIL  }
2905         };
2906
2907     g_assert(hdl != NULL);
2908
2909     result = perform_request(hdl, "DELETE", bucket, key, NULL, NULL, NULL, NULL,
2910                  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2911                  result_handling);
2912
2913     return result == S3_RESULT_OK;
2914 }
2915
2916 int
2917 s3_multi_delete(S3Handle *hdl,
2918                 const char *bucket,
2919                 const char **key)
2920 {
2921     GString *query;
2922     CurlBuffer data;
2923     s3_result_t result = S3_RESULT_FAIL;
2924     static result_handling_t result_handling[] = {
2925         { 200,  0,                     0, S3_RESULT_OK },
2926         { 204,  0,                     0, S3_RESULT_OK },
2927         { 400,  0,                     0, S3_RESULT_NOTIMPL },
2928         { 404,  S3_ERROR_NoSuchBucket, 0, S3_RESULT_OK },
2929         RESULT_HANDLING_ALWAYS_RETRY,
2930         { 0,    0,                     0, /* default: */ S3_RESULT_FAIL  }
2931         };
2932
2933     g_assert(hdl != NULL);
2934
2935     query = g_string_new(NULL);
2936     g_string_append(query, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
2937     g_string_append(query, "<Delete>\n");
2938     if (!hdl->verbose) {
2939         g_string_append(query, "  <Quiet>true</Quiet>\n");
2940     }
2941     while (*key != NULL) {
2942         g_string_append(query, "  <Object>\n");
2943         g_string_append(query, "    <Key>");
2944         g_string_append(query, *key);
2945         g_string_append(query, "</Key>\n");
2946         g_string_append(query, "  </Object>\n");
2947         key++;
2948     }
2949     g_string_append(query, "</Delete>\n");
2950
2951     data.buffer_len = query->len;
2952     data.buffer = query->str;
2953     data.buffer_pos = 0;
2954     data.max_buffer_size = data.buffer_len;
2955
2956     result = perform_request(hdl, "POST", bucket, NULL, "delete", NULL,
2957                  "application/xml", NULL,
2958                  s3_buffer_read_func, s3_buffer_reset_func,
2959                  s3_buffer_size_func, s3_buffer_md5_func,
2960                  &data, NULL, NULL, NULL, NULL, NULL,
2961                  result_handling);
2962
2963     g_string_free(query, TRUE);
2964     if (result == S3_RESULT_OK)
2965         return 1;
2966     else if (result == S3_RESULT_NOTIMPL)
2967         return 2;
2968     else
2969         return 0;
2970 }
2971
2972 gboolean
2973 s3_make_bucket(S3Handle *hdl,
2974                const char *bucket,
2975                const char *project_id)
2976 {
2977     char *body = NULL;
2978     s3_result_t result = S3_RESULT_FAIL;
2979     static result_handling_t result_handling[] = {
2980         { 200,  0,                    0, S3_RESULT_OK },
2981         { 201,  0,                    0, S3_RESULT_OK },
2982         { 202,  0,                    0, S3_RESULT_OK },
2983         { 204,  0,                    0, S3_RESULT_OK },
2984         { 404, S3_ERROR_NoSuchBucket, 0, S3_RESULT_RETRY },
2985         RESULT_HANDLING_ALWAYS_RETRY,
2986         { 0, 0,                       0, /* default: */ S3_RESULT_FAIL  }
2987         };
2988     regmatch_t pmatch[4];
2989     char *loc_end_open, *loc_content;
2990     CurlBuffer buf = {NULL, 0, 0, 0}, *ptr = NULL;
2991     s3_read_func read_func = NULL;
2992     s3_reset_func reset_func = NULL;
2993     s3_md5_func md5_func = NULL;
2994     s3_size_func size_func = NULL;
2995
2996     g_assert(hdl != NULL);
2997
2998     if (is_non_empty_string(hdl->bucket_location) &&
2999         0 != strcmp(AMAZON_WILDCARD_LOCATION, hdl->bucket_location)) {
3000         if (s3_bucket_location_compat(bucket)) {
3001             ptr = &buf;
3002             buf.buffer = g_strdup_printf(AMAZON_BUCKET_CONF_TEMPLATE,
3003                  g_str_equal(hdl->host, "gss.iijgio.com")?
3004                         " xmlns=\"http://acs.iijgio.com/doc/2006-03-01/\"":
3005                         "",
3006                 hdl->bucket_location);
3007             buf.buffer_len = (guint) strlen(buf.buffer);
3008             buf.buffer_pos = 0;
3009             buf.max_buffer_size = buf.buffer_len;
3010             read_func = s3_buffer_read_func;
3011             reset_func = s3_buffer_reset_func;
3012             size_func = s3_buffer_size_func;
3013             md5_func = s3_buffer_md5_func;
3014         } else {
3015             hdl->last_message = g_strdup_printf(_(
3016                 "Location constraint given for Amazon S3 bucket, "
3017                 "but the bucket name (%s) is not usable as a subdomain."), bucket);
3018             return FALSE;
3019         }
3020     }
3021
3022     result = perform_request(hdl, "PUT", bucket, NULL, NULL, NULL, NULL,
3023                  project_id,
3024                  read_func, reset_func, size_func, md5_func, ptr,
3025                  NULL, NULL, NULL, NULL, NULL, result_handling);
3026
3027    if (result == S3_RESULT_OK ||
3028        (result != S3_RESULT_OK &&
3029         hdl->last_s3_error_code == S3_ERROR_BucketAlreadyOwnedByYou)) {
3030         /* verify the that the location constraint on the existing bucket matches
3031          * the one that's configured.
3032          */
3033         if (is_non_empty_string(hdl->bucket_location)) {
3034             result = perform_request(hdl, "GET", bucket, NULL, "location", NULL, NULL, NULL,
3035                                      NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
3036                                      NULL, NULL, result_handling);
3037         } else {
3038             result = perform_request(hdl, "GET", bucket, NULL, NULL, NULL, NULL, NULL,
3039                                      NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
3040                                      NULL, NULL, result_handling);
3041         }
3042
3043         if (result == S3_RESULT_OK && is_non_empty_string(hdl->bucket_location)) {
3044             /* return to the default state of failure */
3045             result = S3_RESULT_FAIL;
3046
3047             if (body) g_free(body);
3048             /* use strndup to get a null-terminated string */
3049             body = g_strndup(hdl->last_response_body, hdl->last_response_body_size);
3050             if (!body) {
3051                 hdl->last_message = g_strdup(_("No body received for location request"));
3052                 goto cleanup;
3053             } else if ('\0' == body[0]) {
3054                 hdl->last_message = g_strdup(_("Empty body received for location request"));
3055                 goto cleanup;
3056             }
3057
3058             if (!s3_regexec_wrap(&location_con_regex, body, 4, pmatch, 0)) {
3059                 loc_end_open = find_regex_substring(body, pmatch[1]);
3060                 loc_content = find_regex_substring(body, pmatch[3]);
3061
3062                 /* The case of an empty string is special because XML allows
3063                  * "self-closing" tags
3064                  */
3065                 if (0 == strcmp(AMAZON_WILDCARD_LOCATION, hdl->bucket_location) &&
3066                     '/' != loc_end_open[0])
3067                     hdl->last_message = g_strdup(_("A wildcard location constraint is "
3068                         "configured, but the bucket has a non-empty location constraint"));
3069                 else if (strcmp(AMAZON_WILDCARD_LOCATION, hdl->bucket_location)?
3070                     strncmp(loc_content, hdl->bucket_location, strlen(hdl->bucket_location)) :
3071                     ('\0' != loc_content[0]))
3072                     hdl->last_message = g_strdup(_("The location constraint configured "
3073                         "does not match the constraint currently on the bucket"));
3074                 else
3075                     result = S3_RESULT_OK;
3076             } else {
3077                 hdl->last_message = g_strdup(_("Unexpected location response from Amazon S3"));
3078             }
3079         }
3080    }
3081
3082 cleanup:
3083     if (body) g_free(body);
3084
3085     return result == S3_RESULT_OK;
3086
3087 }
3088
3089 static s3_result_t
3090 oauth2_get_access_token(
3091     S3Handle *hdl)
3092 {
3093     GString *query;
3094     CurlBuffer data;
3095     s3_result_t result = S3_RESULT_FAIL;
3096     static result_handling_t result_handling[] = {
3097         { 200,  0,                    0, S3_RESULT_OK },
3098         { 204,  0,                    0, S3_RESULT_OK },
3099         RESULT_HANDLING_ALWAYS_RETRY,
3100         { 0, 0,                       0, /* default: */ S3_RESULT_FAIL  }
3101         };
3102     char *body;
3103     regmatch_t pmatch[2];
3104
3105     g_assert(hdl != NULL);
3106
3107     query = g_string_new(NULL);
3108     g_string_append(query, "client_id=");
3109     g_string_append(query, hdl->client_id);
3110     g_string_append(query, "&client_secret=");
3111     g_string_append(query, hdl->client_secret);
3112     g_string_append(query, "&refresh_token=");
3113     g_string_append(query, hdl->refresh_token);
3114     g_string_append(query, "&grant_type=refresh_token");
3115
3116     data.buffer_len = query->len;
3117     data.buffer = query->str;
3118     data.buffer_pos = 0;
3119     data.max_buffer_size = data.buffer_len;
3120
3121     hdl->x_storage_url = "https://accounts.google.com/o/oauth2/token";
3122     hdl->getting_oauth2_access_token = 1;
3123     result = perform_request(hdl, "POST", NULL, NULL, NULL, NULL,
3124                              "application/x-www-form-urlencoded", NULL,
3125                              s3_buffer_read_func, s3_buffer_reset_func,
3126                              s3_buffer_size_func, s3_buffer_md5_func,
3127                              &data, NULL, NULL, NULL, NULL, NULL,
3128                              result_handling);
3129     hdl->x_storage_url = NULL;
3130     hdl->getting_oauth2_access_token = 0;
3131
3132     /* use strndup to get a null-terminated string */
3133     body = g_strndup(hdl->last_response_body, hdl->last_response_body_size);
3134     if (!body) {
3135         hdl->last_message = g_strdup(_("No body received for location request"));
3136         goto cleanup;
3137     } else if ('\0' == body[0]) {
3138         hdl->last_message = g_strdup(_("Empty body received for location request"));
3139         goto cleanup;
3140     }
3141
3142     if (!s3_regexec_wrap(&access_token_regex, body, 2, pmatch, 0)) {
3143         hdl->access_token = find_regex_substring(body, pmatch[1]);
3144         hdl->x_auth_token = g_strdup(hdl->access_token);
3145     }
3146     if (!s3_regexec_wrap(&expires_in_regex, body, 2, pmatch, 0)) {
3147         char *expires_in = find_regex_substring(body, pmatch[1]);
3148         hdl->expires = time(NULL) + atoi(expires_in) - 600;
3149         g_free(expires_in);
3150     }
3151
3152 cleanup:
3153     g_free(body);
3154     return result == S3_RESULT_OK;
3155 }
3156
3157 gboolean
3158 s3_is_bucket_exists(S3Handle *hdl,
3159                     const char *bucket,
3160                     const char *project_id)
3161 {
3162     s3_result_t result = S3_RESULT_FAIL;
3163     char *query;
3164     static result_handling_t result_handling[] = {
3165         { 200,  0,                    0, S3_RESULT_OK },
3166         { 204,  0,                    0, S3_RESULT_OK },
3167         RESULT_HANDLING_ALWAYS_RETRY,
3168         { 0, 0,                       0, /* default: */ S3_RESULT_FAIL  }
3169         };
3170
3171     if (hdl->s3_api == S3_API_SWIFT_1 ||
3172         hdl->s3_api == S3_API_SWIFT_2) {
3173         query = "limit=1";
3174     } else {
3175         query = "max-keys=1";
3176     }
3177
3178     result = perform_request(hdl, "GET", bucket, NULL, NULL, query,
3179                              NULL, project_id,
3180                              NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
3181                              NULL, NULL, result_handling);
3182
3183     return result == S3_RESULT_OK;
3184 }
3185
3186 gboolean
3187 s3_delete_bucket(S3Handle *hdl,
3188                  const char *bucket)
3189 {
3190     return s3_delete(hdl, bucket, NULL);
3191 }