diff options
Diffstat (limited to 'src/request.c')
-rw-r--r-- | src/request.c | 2282 |
1 files changed, 1141 insertions, 1141 deletions
diff --git a/src/request.c b/src/request.c index 94a36bd..1b9e386 100644 --- a/src/request.c +++ b/src/request.c @@ -49,60 +49,60 @@ static int requestStackCountG; typedef struct RequestComputedValues { - // All x-amz- headers, in normalized form (i.e. NAME: VALUE, no other ws) - char *amzHeaders[S3_MAX_METADATA_COUNT + 2]; // + 2 for acl and date + // All x-amz- headers, in normalized form (i.e. NAME: VALUE, no other ws) + char *amzHeaders[S3_MAX_METADATA_COUNT + 2]; // + 2 for acl and date - // The number of x-amz- headers - int amzHeadersCount; + // The number of x-amz- headers + int amzHeadersCount; - // Storage for amzHeaders (the +256 is for x-amz-acl and x-amz-date) - char amzHeadersRaw[COMPACTED_METADATA_BUFFER_SIZE + 256 + 1]; + // Storage for amzHeaders (the +256 is for x-amz-acl and x-amz-date) + char amzHeadersRaw[COMPACTED_METADATA_BUFFER_SIZE + 256 + 1]; - // Canonicalized x-amz- headers - string_multibuffer(canonicalizedAmzHeaders, - COMPACTED_METADATA_BUFFER_SIZE + 256 + 1); + // Canonicalized x-amz- headers + string_multibuffer(canonicalizedAmzHeaders, + COMPACTED_METADATA_BUFFER_SIZE + 256 + 1); - // URL-Encoded key - char urlEncodedKey[MAX_URLENCODED_KEY_SIZE + 1]; + // URL-Encoded key + char urlEncodedKey[MAX_URLENCODED_KEY_SIZE + 1]; - // Canonicalized resource - char canonicalizedResource[MAX_CANONICALIZED_RESOURCE_SIZE + 1]; + // Canonicalized resource + char canonicalizedResource[MAX_CANONICALIZED_RESOURCE_SIZE + 1]; - // Cache-Control header (or empty) - char cacheControlHeader[128]; + // Cache-Control header (or empty) + char cacheControlHeader[128]; - // Content-Type header (or empty) - char contentTypeHeader[128]; + // Content-Type header (or empty) + char contentTypeHeader[128]; - // Content-MD5 header (or empty) - char md5Header[128]; + // Content-MD5 header (or empty) + char md5Header[128]; - // Content-Disposition header (or empty) - char contentDispositionHeader[128]; + // Content-Disposition header (or empty) + char contentDispositionHeader[128]; - // Content-Encoding header (or empty) - char contentEncodingHeader[128]; + // Content-Encoding header (or empty) + char contentEncodingHeader[128]; - // Expires header (or empty) - char expiresHeader[128]; + // Expires header (or empty) + char expiresHeader[128]; - // If-Modified-Since header - char ifModifiedSinceHeader[128]; + // If-Modified-Since header + char ifModifiedSinceHeader[128]; - // If-Unmodified-Since header - char ifUnmodifiedSinceHeader[128]; + // If-Unmodified-Since header + char ifUnmodifiedSinceHeader[128]; - // If-Match header - char ifMatchHeader[128]; + // If-Match header + char ifMatchHeader[128]; - // If-None-Match header - char ifNoneMatchHeader[128]; + // If-None-Match header + char ifNoneMatchHeader[128]; - // Range header - char rangeHeader[128]; + // Range header + char rangeHeader[128]; - // Authorization header - char authorizationHeader[128]; + // Authorization header + char authorizationHeader[128]; } RequestComputedValues; @@ -112,126 +112,126 @@ typedef struct RequestComputedValues // zero on failure. static void request_headers_done(Request *request) { - if (request->propertiesCallbackMade) { - return; - } - - request->propertiesCallbackMade = 1; - - // Get the http response code - long httpResponseCode; - request->httpResponseCode = 0; - if (curl_easy_getinfo(request->curl, CURLINFO_RESPONSE_CODE, - &httpResponseCode) != CURLE_OK) { - // Not able to get the HTTP response code - error - request->status = S3StatusInternalError; - return; - } - else { - request->httpResponseCode = httpResponseCode; - } - - response_headers_handler_done(&(request->responseHeadersHandler), - request->curl); - - // Only make the callback if it was a successful request; otherwise we're - // returning information about the error response itself - if (request->propertiesCallback && - (request->httpResponseCode >= 200) && - (request->httpResponseCode <= 299)) { - request->status = (*(request->propertiesCallback)) - (&(request->responseHeadersHandler.responseProperties), - request->callbackData); - } + if (request->propertiesCallbackMade) { + return; + } + + request->propertiesCallbackMade = 1; + + // Get the http response code + long httpResponseCode; + request->httpResponseCode = 0; + if (curl_easy_getinfo(request->curl, CURLINFO_RESPONSE_CODE, + &httpResponseCode) != CURLE_OK) { + // Not able to get the HTTP response code - error + request->status = S3StatusInternalError; + return; + } + else { + request->httpResponseCode = httpResponseCode; + } + + response_headers_handler_done(&(request->responseHeadersHandler), + request->curl); + + // Only make the callback if it was a successful request; otherwise we're + // returning information about the error response itself + if (request->propertiesCallback && + (request->httpResponseCode >= 200) && + (request->httpResponseCode <= 299)) { + request->status = (*(request->propertiesCallback)) + (&(request->responseHeadersHandler.responseProperties), + request->callbackData); + } } static size_t curl_header_func(void *ptr, size_t size, size_t nmemb, - void *data) + void *data) { - Request *request = (Request *) data; + Request *request = (Request *) data; - int len = size * nmemb; + int len = size * nmemb; - response_headers_handler_add - (&(request->responseHeadersHandler), (char *) ptr, len); + response_headers_handler_add + (&(request->responseHeadersHandler), (char *) ptr, len); - return len; + return len; } static size_t curl_read_func(void *ptr, size_t size, size_t nmemb, void *data) { - Request *request = (Request *) data; - - int len = size * nmemb; - - request_headers_done(request); - - if (request->status != S3StatusOK) { - return CURL_READFUNC_ABORT; - } - - // If there is no data callback, or the data callback has already returned - // contentLength bytes, return 0; - if (!request->toS3Callback || !request->toS3CallbackBytesRemaining) { - return 0; - } - - // Don't tell the callback that we are willing to accept more data than we - // really are - if (len > request->toS3CallbackBytesRemaining) { - len = request->toS3CallbackBytesRemaining; - } - - // Otherwise, make the data callback - int ret = (*(request->toS3Callback)) - (len, (char *) ptr, request->callbackData); - if (ret < 0) { - request->status = S3StatusAbortedByCallback; - return CURL_READFUNC_ABORT; - } - else { - if (ret > request->toS3CallbackBytesRemaining) { - ret = request->toS3CallbackBytesRemaining; - } - request->toS3CallbackBytesRemaining -= ret; - return ret; - } + Request *request = (Request *) data; + + int len = size * nmemb; + + request_headers_done(request); + + if (request->status != S3StatusOK) { + return CURL_READFUNC_ABORT; + } + + // If there is no data callback, or the data callback has already returned + // contentLength bytes, return 0; + if (!request->toS3Callback || !request->toS3CallbackBytesRemaining) { + return 0; + } + + // Don't tell the callback that we are willing to accept more data than we + // really are + if (len > request->toS3CallbackBytesRemaining) { + len = request->toS3CallbackBytesRemaining; + } + + // Otherwise, make the data callback + int ret = (*(request->toS3Callback)) + (len, (char *) ptr, request->callbackData); + if (ret < 0) { + request->status = S3StatusAbortedByCallback; + return CURL_READFUNC_ABORT; + } + else { + if (ret > request->toS3CallbackBytesRemaining) { + ret = request->toS3CallbackBytesRemaining; + } + request->toS3CallbackBytesRemaining -= ret; + return ret; + } } static size_t curl_write_func(void *ptr, size_t size, size_t nmemb, - void *data) + void *data) { - Request *request = (Request *) data; - - int len = size * nmemb; - - request_headers_done(request); - - if (request->status != S3StatusOK) { - return 0; - } - - // On HTTP error, we expect to parse an HTTP error response - if ((request->httpResponseCode < 200) || - (request->httpResponseCode > 299)) { - request->status = error_parser_add - (&(request->errorParser), (char *) ptr, len); - } - // If there was a callback registered, make it - else if (request->fromS3Callback) { - request->status = (*(request->fromS3Callback)) - (len, (char *) ptr, request->callbackData); - } - // Else, consider this an error - S3 has sent back data when it was not - // expected - else { - request->status = S3StatusInternalError; - } - - return ((request->status == S3StatusOK) ? len : 0); + Request *request = (Request *) data; + + int len = size * nmemb; + + request_headers_done(request); + + if (request->status != S3StatusOK) { + return 0; + } + + // On HTTP error, we expect to parse an HTTP error response + if ((request->httpResponseCode < 200) || + (request->httpResponseCode > 299)) { + request->status = error_parser_add + (&(request->errorParser), (char *) ptr, len); + } + // If there was a callback registered, make it + else if (request->fromS3Callback) { + request->status = (*(request->fromS3Callback)) + (len, (char *) ptr, request->callbackData); + } + // Else, consider this an error - S3 has sent back data when it was not + // expected + else { + request->status = S3StatusInternalError; + } + + return ((request->status == S3StatusOK) ? len : 0); } @@ -240,279 +240,279 @@ static size_t curl_write_func(void *ptr, size_t size, size_t nmemb, // them such that they all look exactly like this: // x-amz-meta-${NAME}: ${VALUE} // It also adds the x-amz-acl, x-amz-copy-source, and x-amz-metadata-directive -// headers if necessary, and always adds the x-amz-date header. It copies the +// headers if necessary, and always adds the x-amz-date header. It copies the // raw string values into params->amzHeadersRaw, and creates an array of // string pointers representing these headers in params->amzHeaders (and also // sets params->amzHeadersCount to be the count of the total number of x-amz- // headers thus created). static S3Status compose_amz_headers(const RequestParams *params, - RequestComputedValues *values) + RequestComputedValues *values) { - const S3PutProperties *properties = params->putProperties; - - values->amzHeadersCount = 0; - values->amzHeadersRaw[0] = 0; - int len = 0; - - // Append a header to amzHeaders, trimming whitespace from the end. - // Does NOT trim whitespace from the beginning. -#define headers_append(isNewHeader, format, ...) \ - do { \ - if (isNewHeader) { \ - values->amzHeaders[values->amzHeadersCount++] = \ - &(values->amzHeadersRaw[len]); \ - } \ - len += snprintf(&(values->amzHeadersRaw[len]), \ - sizeof(values->amzHeadersRaw) - len, \ - format, __VA_ARGS__); \ - if (len >= (int) sizeof(values->amzHeadersRaw)) { \ - return S3StatusMetaDataHeadersTooLong; \ - } \ - while ((len > 0) && (values->amzHeadersRaw[len - 1] == ' ')) { \ - len--; \ - } \ - values->amzHeadersRaw[len++] = 0; \ - } while (0) - -#define header_name_tolower_copy(str, l) \ - do { \ - values->amzHeaders[values->amzHeadersCount++] = \ - &(values->amzHeadersRaw[len]); \ - if ((len + l) >= (int) sizeof(values->amzHeadersRaw)) { \ - return S3StatusMetaDataHeadersTooLong; \ - } \ - int todo = l; \ - while (todo--) { \ - if ((*(str) >= 'A') && (*(str) <= 'Z')) { \ - values->amzHeadersRaw[len++] = 'a' + (*(str) - 'A'); \ - } \ - else { \ - values->amzHeadersRaw[len++] = *(str); \ - } \ - (str)++; \ - } \ - } while (0) - - // Check and copy in the x-amz-meta headers - if (properties) { - int i; - for (i = 0; i < properties->metaDataCount; i++) { - const S3NameValue *property = &(properties->metaData[i]); - char headerName[S3_MAX_METADATA_SIZE - sizeof(": v")]; - int l = snprintf(headerName, sizeof(headerName), - S3_METADATA_HEADER_NAME_PREFIX "%s", - property->name); - char *hn = headerName; - header_name_tolower_copy(hn, l); - // Copy in the value - headers_append(0, ": %s", property->value); - } - - // Add the x-amz-acl header, if necessary - const char *cannedAclString; - switch (params->putProperties->cannedAcl) { - case S3CannedAclPrivate: - cannedAclString = 0; - break; - case S3CannedAclPublicRead: - cannedAclString = "public-read"; - break; - case S3CannedAclPublicReadWrite: - cannedAclString = "public-read-write"; - break; - default: // S3CannedAclAuthenticatedRead - cannedAclString = "authenticated-read"; - break; - } - if (cannedAclString) { - headers_append(1, "x-amz-acl: %s", cannedAclString); - } - } - - // Add the x-amz-date header - time_t now = time(NULL); - char date[64]; - strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now)); - headers_append(1, "x-amz-date: %s", date); - - if (params->httpRequestType == HttpRequestTypeCOPY) { - // Add the x-amz-copy-source header - if (params->copySourceBucketName && params->copySourceBucketName[0] && - params->copySourceKey && params->copySourceKey[0]) { - headers_append(1, "x-amz-copy-source: /%s/%s", - params->copySourceBucketName, - params->copySourceKey); - } - // And the x-amz-metadata-directive header - if (params->putProperties) { - headers_append(1, "%s", "x-amz-metadata-directive: REPLACE"); - } - } - - return S3StatusOK; + const S3PutProperties *properties = params->putProperties; + + values->amzHeadersCount = 0; + values->amzHeadersRaw[0] = 0; + int len = 0; + + // Append a header to amzHeaders, trimming whitespace from the end. + // Does NOT trim whitespace from the beginning. +#define headers_append(isNewHeader, format, ...) \ + do { \ + if (isNewHeader) { \ + values->amzHeaders[values->amzHeadersCount++] = \ + &(values->amzHeadersRaw[len]); \ + } \ + len += snprintf(&(values->amzHeadersRaw[len]), \ + sizeof(values->amzHeadersRaw) - len, \ + format, __VA_ARGS__); \ + if (len >= (int) sizeof(values->amzHeadersRaw)) { \ + return S3StatusMetaDataHeadersTooLong; \ + } \ + while ((len > 0) && (values->amzHeadersRaw[len - 1] == ' ')) { \ + len--; \ + } \ + values->amzHeadersRaw[len++] = 0; \ + } while (0) + +#define header_name_tolower_copy(str, l) \ + do { \ + values->amzHeaders[values->amzHeadersCount++] = \ + &(values->amzHeadersRaw[len]); \ + if ((len + l) >= (int) sizeof(values->amzHeadersRaw)) { \ + return S3StatusMetaDataHeadersTooLong; \ + } \ + int todo = l; \ + while (todo--) { \ + if ((*(str) >= 'A') && (*(str) <= 'Z')) { \ + values->amzHeadersRaw[len++] = 'a' + (*(str) - 'A'); \ + } \ + else { \ + values->amzHeadersRaw[len++] = *(str); \ + } \ + (str)++; \ + } \ + } while (0) + + // Check and copy in the x-amz-meta headers + if (properties) { + int i; + for (i = 0; i < properties->metaDataCount; i++) { + const S3NameValue *property = &(properties->metaData[i]); + char headerName[S3_MAX_METADATA_SIZE - sizeof(": v")]; + int l = snprintf(headerName, sizeof(headerName), + S3_METADATA_HEADER_NAME_PREFIX "%s", + property->name); + char *hn = headerName; + header_name_tolower_copy(hn, l); + // Copy in the value + headers_append(0, ": %s", property->value); + } + + // Add the x-amz-acl header, if necessary + const char *cannedAclString; + switch (params->putProperties->cannedAcl) { + case S3CannedAclPrivate: + cannedAclString = 0; + break; + case S3CannedAclPublicRead: + cannedAclString = "public-read"; + break; + case S3CannedAclPublicReadWrite: + cannedAclString = "public-read-write"; + break; + default: // S3CannedAclAuthenticatedRead + cannedAclString = "authenticated-read"; + break; + } + if (cannedAclString) { + headers_append(1, "x-amz-acl: %s", cannedAclString); + } + } + + // Add the x-amz-date header + time_t now = time(NULL); + char date[64]; + strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now)); + headers_append(1, "x-amz-date: %s", date); + + if (params->httpRequestType == HttpRequestTypeCOPY) { + // Add the x-amz-copy-source header + if (params->copySourceBucketName && params->copySourceBucketName[0] && + params->copySourceKey && params->copySourceKey[0]) { + headers_append(1, "x-amz-copy-source: /%s/%s", + params->copySourceBucketName, + params->copySourceKey); + } + // And the x-amz-metadata-directive header + if (params->putProperties) { + headers_append(1, "%s", "x-amz-metadata-directive: REPLACE"); + } + } + + return S3StatusOK; } // Composes the other headers static S3Status compose_standard_headers(const RequestParams *params, - RequestComputedValues *values) + RequestComputedValues *values) { -#define do_put_header(fmt, sourceField, destField, badError, tooLongError) \ - do { \ - if (params->putProperties && \ - params->putProperties-> sourceField && \ - params->putProperties-> sourceField[0]) { \ - /* Skip whitespace at beginning of val */ \ - const char *val = params->putProperties-> sourceField; \ - while (*val && isblank(*val)) { \ - val++; \ - } \ - if (!*val) { \ - return badError; \ - } \ - /* Compose header, make sure it all fit */ \ - int len = snprintf(values-> destField, \ - sizeof(values-> destField), fmt, val); \ - if (len >= (int) sizeof(values-> destField)) { \ - return tooLongError; \ - } \ - /* Now remove the whitespace at the end */ \ - while (isblank(values-> destField[len])) { \ - len--; \ - } \ - values-> destField[len] = 0; \ - } \ - else { \ - values-> destField[0] = 0; \ - } \ - } while (0) - -#define do_get_header(fmt, sourceField, destField, badError, tooLongError) \ - do { \ - if (params->getConditions && \ - params->getConditions-> sourceField && \ - params->getConditions-> sourceField[0]) { \ - /* Skip whitespace at beginning of val */ \ - const char *val = params->getConditions-> sourceField; \ - while (*val && isblank(*val)) { \ - val++; \ - } \ - if (!*val) { \ - return badError; \ - } \ - /* Compose header, make sure it all fit */ \ - int len = snprintf(values-> destField, \ - sizeof(values-> destField), fmt, val); \ - if (len >= (int) sizeof(values-> destField)) { \ - return tooLongError; \ - } \ - /* Now remove the whitespace at the end */ \ - while (isblank(values-> destField[len])) { \ - len--; \ - } \ - values-> destField[len] = 0; \ - } \ - else { \ - values-> destField[0] = 0; \ - } \ - } while (0) - - // Cache-Control - do_put_header("Cache-Control: %s", cacheControl, cacheControlHeader, - S3StatusBadCacheControl, S3StatusCacheControlTooLong); - - // ContentType - do_put_header("Content-Type: %s", contentType, contentTypeHeader, - S3StatusBadContentType, S3StatusContentTypeTooLong); - - // MD5 - do_put_header("Content-MD5: %s", md5, md5Header, S3StatusBadMD5, - S3StatusMD5TooLong); - - // Content-Disposition - do_put_header("Content-Disposition: attachment; filename=\"%s\"", - contentDispositionFilename, contentDispositionHeader, - S3StatusBadContentDispositionFilename, - S3StatusContentDispositionFilenameTooLong); - - // ContentEncoding - do_put_header("Content-Encoding: %s", contentEncoding, - contentEncodingHeader, S3StatusBadContentEncoding, - S3StatusContentEncodingTooLong); - - // Expires - if (params->putProperties && (params->putProperties->expires >= 0)) { - time_t t = (time_t) params->putProperties->expires; - strftime(values->expiresHeader, sizeof(values->expiresHeader), - "Expires: %a, %d %b %Y %H:%M:%S UTC", gmtime(&t)); - } - else { - values->expiresHeader[0] = 0; - } - - // If-Modified-Since - if (params->getConditions && - (params->getConditions->ifModifiedSince >= 0)) { - time_t t = (time_t) params->getConditions->ifModifiedSince; - strftime(values->ifModifiedSinceHeader, - sizeof(values->ifModifiedSinceHeader), - "If-Modified-Since: %a, %d %b %Y %H:%M:%S UTC", gmtime(&t)); - } - else { - values->ifModifiedSinceHeader[0] = 0; - } - - // If-Unmodified-Since header - if (params->getConditions && - (params->getConditions->ifNotModifiedSince >= 0)) { - time_t t = (time_t) params->getConditions->ifNotModifiedSince; - strftime(values->ifUnmodifiedSinceHeader, - sizeof(values->ifUnmodifiedSinceHeader), - "If-Unmodified-Since: %a, %d %b %Y %H:%M:%S UTC", gmtime(&t)); - } - else { - values->ifUnmodifiedSinceHeader[0] = 0; - } - - // If-Match header - do_get_header("If-Match: %s", ifMatchETag, ifMatchHeader, - S3StatusBadIfMatchETag, S3StatusIfMatchETagTooLong); - - // If-None-Match header - do_get_header("If-None-Match: %s", ifNotMatchETag, ifNoneMatchHeader, - S3StatusBadIfNotMatchETag, - S3StatusIfNotMatchETagTooLong); - - // Range header - if (params->startByte || params->byteCount) { - if (params->byteCount) { - snprintf(values->rangeHeader, sizeof(values->rangeHeader), - "Range: bytes=%llu-%llu", - (unsigned long long) params->startByte, - (unsigned long long) (params->startByte + - params->byteCount - 1)); - } - else { - snprintf(values->rangeHeader, sizeof(values->rangeHeader), - "Range: bytes=%llu-", - (unsigned long long) params->startByte); - } - } - else { - values->rangeHeader[0] = 0; - } - - return S3StatusOK; +#define do_put_header(fmt, sourceField, destField, badError, tooLongError) \ + do { \ + if (params->putProperties && \ + params->putProperties-> sourceField && \ + params->putProperties-> sourceField[0]) { \ + /* Skip whitespace at beginning of val */ \ + const char *val = params->putProperties-> sourceField; \ + while (*val && isblank(*val)) { \ + val++; \ + } \ + if (!*val) { \ + return badError; \ + } \ + /* Compose header, make sure it all fit */ \ + int len = snprintf(values-> destField, \ + sizeof(values-> destField), fmt, val); \ + if (len >= (int) sizeof(values-> destField)) { \ + return tooLongError; \ + } \ + /* Now remove the whitespace at the end */ \ + while (isblank(values-> destField[len])) { \ + len--; \ + } \ + values-> destField[len] = 0; \ + } \ + else { \ + values-> destField[0] = 0; \ + } \ + } while (0) + +#define do_get_header(fmt, sourceField, destField, badError, tooLongError) \ + do { \ + if (params->getConditions && \ + params->getConditions-> sourceField && \ + params->getConditions-> sourceField[0]) { \ + /* Skip whitespace at beginning of val */ \ + const char *val = params->getConditions-> sourceField; \ + while (*val && isblank(*val)) { \ + val++; \ + } \ + if (!*val) { \ + return badError; \ + } \ + /* Compose header, make sure it all fit */ \ + int len = snprintf(values-> destField, \ + sizeof(values-> destField), fmt, val); \ + if (len >= (int) sizeof(values-> destField)) { \ + return tooLongError; \ + } \ + /* Now remove the whitespace at the end */ \ + while (isblank(values-> destField[len])) { \ + len--; \ + } \ + values-> destField[len] = 0; \ + } \ + else { \ + values-> destField[0] = 0; \ + } \ + } while (0) + + // Cache-Control + do_put_header("Cache-Control: %s", cacheControl, cacheControlHeader, + S3StatusBadCacheControl, S3StatusCacheControlTooLong); + + // ContentType + do_put_header("Content-Type: %s", contentType, contentTypeHeader, + S3StatusBadContentType, S3StatusContentTypeTooLong); + + // MD5 + do_put_header("Content-MD5: %s", md5, md5Header, S3StatusBadMD5, + S3StatusMD5TooLong); + + // Content-Disposition + do_put_header("Content-Disposition: attachment; filename=\"%s\"", + contentDispositionFilename, contentDispositionHeader, + S3StatusBadContentDispositionFilename, + S3StatusContentDispositionFilenameTooLong); + + // ContentEncoding + do_put_header("Content-Encoding: %s", contentEncoding, + contentEncodingHeader, S3StatusBadContentEncoding, + S3StatusContentEncodingTooLong); + + // Expires + if (params->putProperties && (params->putProperties->expires >= 0)) { + time_t t = (time_t) params->putProperties->expires; + strftime(values->expiresHeader, sizeof(values->expiresHeader), + "Expires: %a, %d %b %Y %H:%M:%S UTC", gmtime(&t)); + } + else { + values->expiresHeader[0] = 0; + } + + // If-Modified-Since + if (params->getConditions && + (params->getConditions->ifModifiedSince >= 0)) { + time_t t = (time_t) params->getConditions->ifModifiedSince; + strftime(values->ifModifiedSinceHeader, + sizeof(values->ifModifiedSinceHeader), + "If-Modified-Since: %a, %d %b %Y %H:%M:%S UTC", gmtime(&t)); + } + else { + values->ifModifiedSinceHeader[0] = 0; + } + + // If-Unmodified-Since header + if (params->getConditions && + (params->getConditions->ifNotModifiedSince >= 0)) { + time_t t = (time_t) params->getConditions->ifNotModifiedSince; + strftime(values->ifUnmodifiedSinceHeader, + sizeof(values->ifUnmodifiedSinceHeader), + "If-Unmodified-Since: %a, %d %b %Y %H:%M:%S UTC", gmtime(&t)); + } + else { + values->ifUnmodifiedSinceHeader[0] = 0; + } + + // If-Match header + do_get_header("If-Match: %s", ifMatchETag, ifMatchHeader, + S3StatusBadIfMatchETag, S3StatusIfMatchETagTooLong); + + // If-None-Match header + do_get_header("If-None-Match: %s", ifNotMatchETag, ifNoneMatchHeader, + S3StatusBadIfNotMatchETag, + S3StatusIfNotMatchETagTooLong); + + // Range header + if (params->startByte || params->byteCount) { + if (params->byteCount) { + snprintf(values->rangeHeader, sizeof(values->rangeHeader), + "Range: bytes=%llu-%llu", + (unsigned long long) params->startByte, + (unsigned long long) (params->startByte + + params->byteCount - 1)); + } + else { + snprintf(values->rangeHeader, sizeof(values->rangeHeader), + "Range: bytes=%llu-", + (unsigned long long) params->startByte); + } + } + else { + values->rangeHeader[0] = 0; + } + + return S3StatusOK; } // URL encodes the params->key value into params->urlEncodedKey static S3Status encode_key(const RequestParams *params, - RequestComputedValues *values) + RequestComputedValues *values) { - return (urlEncode(values->urlEncodedKey, params->key, S3_MAX_KEY_SIZE) ? - S3StatusOK : S3StatusUriTooLong); + return (urlEncode(values->urlEncodedKey, params->key, S3_MAX_KEY_SIZE) ? + S3StatusOK : S3StatusUriTooLong); } @@ -521,857 +521,857 @@ static S3Status encode_key(const RequestParams *params, // before header2 alphabetically, false if not static int headerle(const char *header1, const char *header2) { - while (1) { - if (*header1 == ':') { - return (*header2 == ':'); - } - else if (*header2 == ':') { - return 0; - } - else if (*header2 < *header1) { - return 0; - } - else if (*header2 > *header1) { - return 1; - } - header1++, header2++; - } + while (1) { + if (*header1 == ':') { + return (*header2 == ':'); + } + else if (*header2 == ':') { + return 0; + } + else if (*header2 < *header1) { + return 0; + } + else if (*header2 > *header1) { + return 1; + } + header1++, header2++; + } } -// Replace this with merge sort eventually, it's the best stable sort. But +// Replace this with merge sort eventually, it's the best stable sort. But // since typically the number of elements being sorted is small, it doesn't // matter that much which sort is used, and gnome sort is the world's simplest -// stable sort. Added a slight twist to the standard gnome_sort - don't go -// forward +1, go forward to the last highest index considered. This saves +// stable sort. Added a slight twist to the standard gnome_sort - don't go +// forward +1, go forward to the last highest index considered. This saves // all the string comparisons that would be done "going forward", and thus // only does the necessary string comparisons to move values back into their // sorted position. static void header_gnome_sort(const char **headers, int size) { - int i = 0, last_highest = 0; - - while (i < size) { - if ((i == 0) || headerle(headers[i - 1], headers[i])) { - i = ++last_highest; - } - else { - const char *tmp = headers[i]; - headers[i] = headers[i - 1]; - headers[--i] = tmp; - } - } + int i = 0, last_highest = 0; + + while (i < size) { + if ((i == 0) || headerle(headers[i - 1], headers[i])) { + i = ++last_highest; + } + else { + const char *tmp = headers[i]; + headers[i] = headers[i - 1]; + headers[--i] = tmp; + } + } } // Canonicalizes the x-amz- headers into the canonicalizedAmzHeaders buffer static void canonicalize_amz_headers(RequestComputedValues *values) { - // Make a copy of the headers that will be sorted - const char *sortedHeaders[S3_MAX_METADATA_COUNT]; - - memcpy(sortedHeaders, values->amzHeaders, - (values->amzHeadersCount * sizeof(sortedHeaders[0]))); - - // Now sort these - header_gnome_sort(sortedHeaders, values->amzHeadersCount); - - // Now copy this sorted list into the buffer, all the while: - // - folding repeated headers into single lines, and - // - folding multiple lines - // - removing the space after the colon - int lastHeaderLen = 0, i; - char *buffer = values->canonicalizedAmzHeaders; - for (i = 0; i < values->amzHeadersCount; i++) { - const char *header = sortedHeaders[i]; - const char *c = header; - // If the header names are the same, append the next value - if ((i > 0) && - !strncmp(header, sortedHeaders[i - 1], lastHeaderLen)) { - // Replacing the previous newline with a comma - *(buffer - 1) = ','; - // Skip the header name and space - c += (lastHeaderLen + 1); - } - // Else this is a new header - else { - // Copy in everything up to the space in the ": " - while (*c != ' ') { - *buffer++ = *c++; - } - // Save the header len since it's a new header - lastHeaderLen = c - header; - // Skip the space - c++; - } - // Now copy in the value, folding the lines - while (*c) { - // If c points to a \r\n[whitespace] sequence, then fold - // this newline out - if ((*c == '\r') && (*(c + 1) == '\n') && isblank(*(c + 2))) { - c += 3; - while (isblank(*c)) { - c++; - } - // Also, what has most recently been copied into buffer amy - // have been whitespace, and since we're folding whitespace - // out around this newline sequence, back buffer up over - // any whitespace it contains - while (isblank(*(buffer - 1))) { - buffer--; - } - continue; - } - *buffer++ = *c++; - } - // Finally, add the newline - *buffer++ = '\n'; - } - - // Terminate the buffer - *buffer = 0; + // Make a copy of the headers that will be sorted + const char *sortedHeaders[S3_MAX_METADATA_COUNT]; + + memcpy(sortedHeaders, values->amzHeaders, + (values->amzHeadersCount * sizeof(sortedHeaders[0]))); + + // Now sort these + header_gnome_sort(sortedHeaders, values->amzHeadersCount); + + // Now copy this sorted list into the buffer, all the while: + // - folding repeated headers into single lines, and + // - folding multiple lines + // - removing the space after the colon + int lastHeaderLen = 0, i; + char *buffer = values->canonicalizedAmzHeaders; + for (i = 0; i < values->amzHeadersCount; i++) { + const char *header = sortedHeaders[i]; + const char *c = header; + // If the header names are the same, append the next value + if ((i > 0) && + !strncmp(header, sortedHeaders[i - 1], lastHeaderLen)) { + // Replacing the previous newline with a comma + *(buffer - 1) = ','; + // Skip the header name and space + c += (lastHeaderLen + 1); + } + // Else this is a new header + else { + // Copy in everything up to the space in the ": " + while (*c != ' ') { + *buffer++ = *c++; + } + // Save the header len since it's a new header + lastHeaderLen = c - header; + // Skip the space + c++; + } + // Now copy in the value, folding the lines + while (*c) { + // If c points to a \r\n[whitespace] sequence, then fold + // this newline out + if ((*c == '\r') && (*(c + 1) == '\n') && isblank(*(c + 2))) { + c += 3; + while (isblank(*c)) { + c++; + } + // Also, what has most recently been copied into buffer amy + // have been whitespace, and since we're folding whitespace + // out around this newline sequence, back buffer up over + // any whitespace it contains + while (isblank(*(buffer - 1))) { + buffer--; + } + continue; + } + *buffer++ = *c++; + } + // Finally, add the newline + *buffer++ = '\n'; + } + + // Terminate the buffer + *buffer = 0; } // Canonicalizes the resource into params->canonicalizedResource static void canonicalize_resource(const char *bucketName, - const char *subResource, - const char *urlEncodedKey, - char *buffer) + const char *subResource, + const char *urlEncodedKey, + char *buffer) { - int len = 0; + int len = 0; - *buffer = 0; + *buffer = 0; #define append(str) len += sprintf(&(buffer[len]), "%s", str) - if (bucketName && bucketName[0]) { - buffer[len++] = '/'; - append(bucketName); - } + if (bucketName && bucketName[0]) { + buffer[len++] = '/'; + append(bucketName); + } - append("/"); + append("/"); - if (urlEncodedKey && urlEncodedKey[0]) { - append(urlEncodedKey); - } + if (urlEncodedKey && urlEncodedKey[0]) { + append(urlEncodedKey); + } - if (subResource && subResource[0]) { - append("?"); - append(subResource); - } + if (subResource && subResource[0]) { + append("?"); + append(subResource); + } } // Convert an HttpRequestType to an HTTP Verb string static const char *http_request_type_to_verb(HttpRequestType requestType) { - switch (requestType) { - case HttpRequestTypeGET: - return "GET"; - case HttpRequestTypeHEAD: - return "HEAD"; - case HttpRequestTypePUT: - case HttpRequestTypeCOPY: - return "PUT"; - default: // HttpRequestTypeDELETE - return "DELETE"; - } + switch (requestType) { + case HttpRequestTypeGET: + return "GET"; + case HttpRequestTypeHEAD: + return "HEAD"; + case HttpRequestTypePUT: + case HttpRequestTypeCOPY: + return "PUT"; + default: // HttpRequestTypeDELETE + return "DELETE"; + } } // Composes the Authorization header for the request static S3Status compose_auth_header(const RequestParams *params, - RequestComputedValues *values) + RequestComputedValues *values) { - // We allow for: - // 17 bytes for HTTP-Verb + \n - // 129 bytes for Content-MD5 + \n - // 129 bytes for Content-Type + \n - // 1 byte for empty Date + \n - // CanonicalizedAmzHeaders & CanonicalizedResource - char signbuf[17 + 129 + 129 + 1 + - (sizeof(values->canonicalizedAmzHeaders) - 1) + - (sizeof(values->canonicalizedResource) - 1) + 1]; - int len = 0; - -#define signbuf_append(format, ...) \ - len += snprintf(&(signbuf[len]), sizeof(signbuf) - len, \ - format, __VA_ARGS__) - - signbuf_append - ("%s\n", http_request_type_to_verb(params->httpRequestType)); - - // For MD5 and Content-Type, use the value in the actual header, because - // it's already been trimmed - signbuf_append("%s\n", values->md5Header[0] ? - &(values->md5Header[sizeof("Content-MD5: ") - 1]) : ""); - - signbuf_append - ("%s\n", values->contentTypeHeader[0] ? - &(values->contentTypeHeader[sizeof("Content-Type: ") - 1]) : ""); - - signbuf_append("%s", "\n"); // Date - we always use x-amz-date - - signbuf_append("%s", values->canonicalizedAmzHeaders); - - signbuf_append("%s", values->canonicalizedResource); - - // Generate an HMAC-SHA-1 of the signbuf - unsigned char hmac[20]; - - HMAC_SHA1(hmac, (unsigned char *) params->bucketContext.secretAccessKey, - strlen(params->bucketContext.secretAccessKey), - (unsigned char *) signbuf, len); - - // Now base-64 encode the results - char b64[((20 + 1) * 4) / 3]; - int b64Len = base64Encode(hmac, 20, b64); - - snprintf(values->authorizationHeader, sizeof(values->authorizationHeader), - "Authorization: AWS %s:%.*s", params->bucketContext.accessKeyId, - b64Len, b64); - - return S3StatusOK; + // We allow for: + // 17 bytes for HTTP-Verb + \n + // 129 bytes for Content-MD5 + \n + // 129 bytes for Content-Type + \n + // 1 byte for empty Date + \n + // CanonicalizedAmzHeaders & CanonicalizedResource + char signbuf[17 + 129 + 129 + 1 + + (sizeof(values->canonicalizedAmzHeaders) - 1) + + (sizeof(values->canonicalizedResource) - 1) + 1]; + int len = 0; + +#define signbuf_append(format, ...) \ + len += snprintf(&(signbuf[len]), sizeof(signbuf) - len, \ + format, __VA_ARGS__) + + signbuf_append + ("%s\n", http_request_type_to_verb(params->httpRequestType)); + + // For MD5 and Content-Type, use the value in the actual header, because + // it's already been trimmed + signbuf_append("%s\n", values->md5Header[0] ? + &(values->md5Header[sizeof("Content-MD5: ") - 1]) : ""); + + signbuf_append + ("%s\n", values->contentTypeHeader[0] ? + &(values->contentTypeHeader[sizeof("Content-Type: ") - 1]) : ""); + + signbuf_append("%s", "\n"); // Date - we always use x-amz-date + + signbuf_append("%s", values->canonicalizedAmzHeaders); + + signbuf_append("%s", values->canonicalizedResource); + + // Generate an HMAC-SHA-1 of the signbuf + unsigned char hmac[20]; + + HMAC_SHA1(hmac, (unsigned char *) params->bucketContext.secretAccessKey, + strlen(params->bucketContext.secretAccessKey), + (unsigned char *) signbuf, len); + + // Now base-64 encode the results + char b64[((20 + 1) * 4) / 3]; + int b64Len = base64Encode(hmac, 20, b64); + + snprintf(values->authorizationHeader, sizeof(values->authorizationHeader), + "Authorization: AWS %s:%.*s", params->bucketContext.accessKeyId, + b64Len, b64); + + return S3StatusOK; } // Compose the URI to use for the request given the request parameters static S3Status compose_uri(char *buffer, int bufferSize, - const S3BucketContext *bucketContext, - const char *urlEncodedKey, - const char *subResource, const char *queryParams) + const S3BucketContext *bucketContext, + const char *urlEncodedKey, + const char *subResource, const char *queryParams) { - int len = 0; - -#define uri_append(fmt, ...) \ - do { \ - len += snprintf(&(buffer[len]), bufferSize - len, fmt, __VA_ARGS__); \ - if (len >= bufferSize) { \ - return S3StatusUriTooLong; \ - } \ - } while (0) - - uri_append("http%s://", - (bucketContext->protocol == S3ProtocolHTTP) ? "" : "s"); - - if (bucketContext->bucketName && - bucketContext->bucketName[0]) { - if (bucketContext->uriStyle == S3UriStyleVirtualHost) { - uri_append("%s.s3.amazonaws.com", bucketContext->bucketName); - } - else { - uri_append("s3.amazonaws.com/%s", bucketContext->bucketName); - } - } - else { - uri_append("%s", "s3.amazonaws.com"); - } - - uri_append("%s", "/"); - - uri_append("%s", urlEncodedKey); - - if (subResource && subResource[0]) { - uri_append("?%s", subResource); - } - - if (queryParams) { - uri_append("%s%s", (subResource && subResource[0]) ? "&" : "?", - queryParams); - } - - return S3StatusOK; + int len = 0; + +#define uri_append(fmt, ...) \ + do { \ + len += snprintf(&(buffer[len]), bufferSize - len, fmt, __VA_ARGS__); \ + if (len >= bufferSize) { \ + return S3StatusUriTooLong; \ + } \ + } while (0) + + uri_append("http%s://", + (bucketContext->protocol == S3ProtocolHTTP) ? "" : "s"); + + if (bucketContext->bucketName && + bucketContext->bucketName[0]) { + if (bucketContext->uriStyle == S3UriStyleVirtualHost) { + uri_append("%s.s3.amazonaws.com", bucketContext->bucketName); + } + else { + uri_append("s3.amazonaws.com/%s", bucketContext->bucketName); + } + } + else { + uri_append("%s", "s3.amazonaws.com"); + } + + uri_append("%s", "/"); + + uri_append("%s", urlEncodedKey); + + if (subResource && subResource[0]) { + uri_append("?%s", subResource); + } + + if (queryParams) { + uri_append("%s%s", (subResource && subResource[0]) ? "&" : "?", + queryParams); + } + + return S3StatusOK; } // Sets up the curl handle given the completely computed RequestParams static S3Status setup_curl(Request *request, - const RequestParams *params, - const RequestComputedValues *values) + const RequestParams *params, + const RequestComputedValues *values) { - CURLcode status; - -#define curl_easy_setopt_safe(opt, val) \ - if ((status = curl_easy_setopt \ - (request->curl, opt, val)) != CURLE_OK) { \ - return S3StatusFailedToInitializeRequest; \ - } - - // Debugging only - // curl_easy_setopt_safe(CURLOPT_VERBOSE, 1); - - // Set private data to request for the benefit of S3RequestContext - curl_easy_setopt_safe(CURLOPT_PRIVATE, request); - - // Set header callback and data - curl_easy_setopt_safe(CURLOPT_HEADERDATA, request); - curl_easy_setopt_safe(CURLOPT_HEADERFUNCTION, &curl_header_func); - - // Set read callback, data, and readSize - curl_easy_setopt_safe(CURLOPT_READFUNCTION, &curl_read_func); - curl_easy_setopt_safe(CURLOPT_READDATA, request); - - // Set write callback and data - curl_easy_setopt_safe(CURLOPT_WRITEFUNCTION, &curl_write_func); - curl_easy_setopt_safe(CURLOPT_WRITEDATA, request); - - // Ask curl to parse the Last-Modified header. This is easier than - // parsing it ourselves. - curl_easy_setopt_safe(CURLOPT_FILETIME, 1); - - // Curl docs suggest that this is necessary for multithreaded code. - // However, it also points out that DNS timeouts will not be honored - // during DNS lookup, which can be worked around by using the c-ares - // library, which we do not do yet. - curl_easy_setopt_safe(CURLOPT_NOSIGNAL, 1); - - // Turn off Curl's built-in progress meter - curl_easy_setopt_safe(CURLOPT_NOPROGRESS, 1); - - // xxx todo - support setting the proxy for Curl to use (can't use https - // for proxies though) - - // xxx todo - support setting the network interface for Curl to use - - // I think this is useful - we don't need interactive performance, we need - // to complete large operations quickly - curl_easy_setopt_safe(CURLOPT_TCP_NODELAY, 1); - - // Don't use Curl's 'netrc' feature - curl_easy_setopt_safe(CURLOPT_NETRC, CURL_NETRC_IGNORED); - - // Don't verify S3's certificate, there are known to be issues with - // them sometimes - // xxx todo - support an option for verifying the S3 CA (default false) - curl_easy_setopt_safe(CURLOPT_SSL_VERIFYPEER, 0); - - // Follow any redirection directives that S3 sends - curl_easy_setopt_safe(CURLOPT_FOLLOWLOCATION, 1); - - // A safety valve in case S3 goes bananas with redirects - curl_easy_setopt_safe(CURLOPT_MAXREDIRS, 10); - - // Set the User-Agent; maybe Amazon will track these? - curl_easy_setopt_safe(CURLOPT_USERAGENT, userAgentG); - - // Set the low speed limit and time; we abort transfers that stay at - // less than 1K per second for more than 15 seconds. - // xxx todo - make these configurable - // xxx todo - allow configurable max send and receive speed - curl_easy_setopt_safe(CURLOPT_LOW_SPEED_LIMIT, 1024); - curl_easy_setopt_safe(CURLOPT_LOW_SPEED_TIME, 15); - - // Append standard headers -#define append_standard_header(fieldName) \ - if (values-> fieldName [0]) { \ - request->headers = curl_slist_append(request->headers, \ - values-> fieldName); \ - } - - // Would use CURLOPT_INFILESIZE_LARGE, but it is buggy in libcurl - if (params->httpRequestType == HttpRequestTypePUT) { - char header[256]; - snprintf(header, sizeof(header), "Content-Length: %llu", - (unsigned long long) params->toS3CallbackTotalSize); - request->headers = curl_slist_append(request->headers, header); - request->headers = curl_slist_append(request->headers, - "Transfer-Encoding:"); - } - else if (params->httpRequestType == HttpRequestTypeCOPY) { - request->headers = curl_slist_append(request->headers, - "Transfer-Encoding:"); - } - - append_standard_header(cacheControlHeader); - append_standard_header(contentTypeHeader); - append_standard_header(md5Header); - append_standard_header(contentDispositionHeader); - append_standard_header(contentEncodingHeader); - append_standard_header(expiresHeader); - append_standard_header(ifModifiedSinceHeader); - append_standard_header(ifUnmodifiedSinceHeader); - append_standard_header(ifMatchHeader); - append_standard_header(ifNoneMatchHeader); - append_standard_header(rangeHeader); - append_standard_header(authorizationHeader); - - // Append x-amz- headers - int i; - for (i = 0; i < values->amzHeadersCount; i++) { - request->headers = - curl_slist_append(request->headers, values->amzHeaders[i]); - } - - // Set the HTTP headers - curl_easy_setopt_safe(CURLOPT_HTTPHEADER, request->headers); - - // Set URI - curl_easy_setopt_safe(CURLOPT_URL, request->uri); - - // Set request type. - switch (params->httpRequestType) { - case HttpRequestTypeHEAD: - curl_easy_setopt_safe(CURLOPT_NOBODY, 1); - break; - case HttpRequestTypePUT: - case HttpRequestTypeCOPY: - curl_easy_setopt_safe(CURLOPT_UPLOAD, 1); - break; - case HttpRequestTypeDELETE: - curl_easy_setopt_safe(CURLOPT_CUSTOMREQUEST, "DELETE"); - break; - default: // HttpRequestTypeGET - break; - } - - return S3StatusOK; + CURLcode status; + +#define curl_easy_setopt_safe(opt, val) \ + if ((status = curl_easy_setopt \ + (request->curl, opt, val)) != CURLE_OK) { \ + return S3StatusFailedToInitializeRequest; \ + } + + // Debugging only + // curl_easy_setopt_safe(CURLOPT_VERBOSE, 1); + + // Set private data to request for the benefit of S3RequestContext + curl_easy_setopt_safe(CURLOPT_PRIVATE, request); + + // Set header callback and data + curl_easy_setopt_safe(CURLOPT_HEADERDATA, request); + curl_easy_setopt_safe(CURLOPT_HEADERFUNCTION, &curl_header_func); + + // Set read callback, data, and readSize + curl_easy_setopt_safe(CURLOPT_READFUNCTION, &curl_read_func); + curl_easy_setopt_safe(CURLOPT_READDATA, request); + + // Set write callback and data + curl_easy_setopt_safe(CURLOPT_WRITEFUNCTION, &curl_write_func); + curl_easy_setopt_safe(CURLOPT_WRITEDATA, request); + + // Ask curl to parse the Last-Modified header. This is easier than + // parsing it ourselves. + curl_easy_setopt_safe(CURLOPT_FILETIME, 1); + + // Curl docs suggest that this is necessary for multithreaded code. + // However, it also points out that DNS timeouts will not be honored + // during DNS lookup, which can be worked around by using the c-ares + // library, which we do not do yet. + curl_easy_setopt_safe(CURLOPT_NOSIGNAL, 1); + + // Turn off Curl's built-in progress meter + curl_easy_setopt_safe(CURLOPT_NOPROGRESS, 1); + + // xxx todo - support setting the proxy for Curl to use (can't use https + // for proxies though) + + // xxx todo - support setting the network interface for Curl to use + + // I think this is useful - we don't need interactive performance, we need + // to complete large operations quickly + curl_easy_setopt_safe(CURLOPT_TCP_NODELAY, 1); + + // Don't use Curl's 'netrc' feature + curl_easy_setopt_safe(CURLOPT_NETRC, CURL_NETRC_IGNORED); + + // Don't verify S3's certificate, there are known to be issues with + // them sometimes + // xxx todo - support an option for verifying the S3 CA (default false) + curl_easy_setopt_safe(CURLOPT_SSL_VERIFYPEER, 0); + + // Follow any redirection directives that S3 sends + curl_easy_setopt_safe(CURLOPT_FOLLOWLOCATION, 1); + + // A safety valve in case S3 goes bananas with redirects + curl_easy_setopt_safe(CURLOPT_MAXREDIRS, 10); + + // Set the User-Agent; maybe Amazon will track these? + curl_easy_setopt_safe(CURLOPT_USERAGENT, userAgentG); + + // Set the low speed limit and time; we abort transfers that stay at + // less than 1K per second for more than 15 seconds. + // xxx todo - make these configurable + // xxx todo - allow configurable max send and receive speed + curl_easy_setopt_safe(CURLOPT_LOW_SPEED_LIMIT, 1024); + curl_easy_setopt_safe(CURLOPT_LOW_SPEED_TIME, 15); + + // Append standard headers +#define append_standard_header(fieldName) \ + if (values-> fieldName [0]) { \ + request->headers = curl_slist_append(request->headers, \ + values-> fieldName); \ + } + + // Would use CURLOPT_INFILESIZE_LARGE, but it is buggy in libcurl + if (params->httpRequestType == HttpRequestTypePUT) { + char header[256]; + snprintf(header, sizeof(header), "Content-Length: %llu", + (unsigned long long) params->toS3CallbackTotalSize); + request->headers = curl_slist_append(request->headers, header); + request->headers = curl_slist_append(request->headers, + "Transfer-Encoding:"); + } + else if (params->httpRequestType == HttpRequestTypeCOPY) { + request->headers = curl_slist_append(request->headers, + "Transfer-Encoding:"); + } + + append_standard_header(cacheControlHeader); + append_standard_header(contentTypeHeader); + append_standard_header(md5Header); + append_standard_header(contentDispositionHeader); + append_standard_header(contentEncodingHeader); + append_standard_header(expiresHeader); + append_standard_header(ifModifiedSinceHeader); + append_standard_header(ifUnmodifiedSinceHeader); + append_standard_header(ifMatchHeader); + append_standard_header(ifNoneMatchHeader); + append_standard_header(rangeHeader); + append_standard_header(authorizationHeader); + + // Append x-amz- headers + int i; + for (i = 0; i < values->amzHeadersCount; i++) { + request->headers = + curl_slist_append(request->headers, values->amzHeaders[i]); + } + + // Set the HTTP headers + curl_easy_setopt_safe(CURLOPT_HTTPHEADER, request->headers); + + // Set URI + curl_easy_setopt_safe(CURLOPT_URL, request->uri); + + // Set request type. + switch (params->httpRequestType) { + case HttpRequestTypeHEAD: + curl_easy_setopt_safe(CURLOPT_NOBODY, 1); + break; + case HttpRequestTypePUT: + case HttpRequestTypeCOPY: + curl_easy_setopt_safe(CURLOPT_UPLOAD, 1); + break; + case HttpRequestTypeDELETE: + curl_easy_setopt_safe(CURLOPT_CUSTOMREQUEST, "DELETE"); + break; + default: // HttpRequestTypeGET + break; + } + + return S3StatusOK; } static void request_deinitialize(Request *request) { - if (request->headers) { - curl_slist_free_all(request->headers); - } - - error_parser_deinitialize(&(request->errorParser)); - - // curl_easy_reset prevents connections from being re-used for some - // reason. This makes HTTP Keep-Alive meaningless and is very bad for - // performance. But it is necessary to allow curl to work properly. - // xxx todo figure out why - curl_easy_reset(request->curl); + if (request->headers) { + curl_slist_free_all(request->headers); + } + + error_parser_deinitialize(&(request->errorParser)); + + // curl_easy_reset prevents connections from being re-used for some + // reason. This makes HTTP Keep-Alive meaningless and is very bad for + // performance. But it is necessary to allow curl to work properly. + // xxx todo figure out why + curl_easy_reset(request->curl); } static S3Status request_get(const RequestParams *params, - const RequestComputedValues *values, - Request **reqReturn) + const RequestComputedValues *values, + Request **reqReturn) { - Request *request = 0; - - // Try to get one from the request stack. We hold the lock for the - // shortest time possible here. - pthread_mutex_lock(&requestStackMutexG); - - if (requestStackCountG) { - request = requestStackG[--requestStackCountG]; - } - - pthread_mutex_unlock(&requestStackMutexG); - - // If we got one, deinitialize it for re-use - if (request) { - request_deinitialize(request); - } - // Else there wasn't one available in the request stack, so create one - else { - if (!(request = (Request *) malloc(sizeof(Request)))) { - return S3StatusOutOfMemory; - } - if (!(request->curl = curl_easy_init())) { - free(request); - return S3StatusFailedToInitializeRequest; - } - } - - // Initialize the request - request->prev = 0; - request->next = 0; - - // Request status is initialized to no error, will be updated whenever - // an error occurs - request->status = S3StatusOK; - - S3Status status; - - // Start out with no headers - request->headers = 0; - - // Compute the URL - if ((status = compose_uri - (request->uri, sizeof(request->uri), - &(params->bucketContext), values->urlEncodedKey, - params->subResource, params->queryParams)) != S3StatusOK) { - curl_easy_cleanup(request->curl); - free(request); - return status; - } - - // Set all of the curl handle options - if ((status = setup_curl(request, params, values)) != S3StatusOK) { - curl_easy_cleanup(request->curl); - free(request); - return status; - } - - request->propertiesCallback = params->propertiesCallback; - - request->toS3Callback = params->toS3Callback; - - request->toS3CallbackBytesRemaining = params->toS3CallbackTotalSize; - - request->fromS3Callback = params->fromS3Callback; - - request->completeCallback = params->completeCallback; - - request->callbackData = params->callbackData; - - response_headers_handler_initialize(&(request->responseHeadersHandler)); - - request->propertiesCallbackMade = 0; - - error_parser_initialize(&(request->errorParser)); - - *reqReturn = request; - - return S3StatusOK; + Request *request = 0; + + // Try to get one from the request stack. We hold the lock for the + // shortest time possible here. + pthread_mutex_lock(&requestStackMutexG); + + if (requestStackCountG) { + request = requestStackG[--requestStackCountG]; + } + + pthread_mutex_unlock(&requestStackMutexG); + + // If we got one, deinitialize it for re-use + if (request) { + request_deinitialize(request); + } + // Else there wasn't one available in the request stack, so create one + else { + if (!(request = (Request *) malloc(sizeof(Request)))) { + return S3StatusOutOfMemory; + } + if (!(request->curl = curl_easy_init())) { + free(request); + return S3StatusFailedToInitializeRequest; + } + } + + // Initialize the request + request->prev = 0; + request->next = 0; + + // Request status is initialized to no error, will be updated whenever + // an error occurs + request->status = S3StatusOK; + + S3Status status; + + // Start out with no headers + request->headers = 0; + + // Compute the URL + if ((status = compose_uri + (request->uri, sizeof(request->uri), + &(params->bucketContext), values->urlEncodedKey, + params->subResource, params->queryParams)) != S3StatusOK) { + curl_easy_cleanup(request->curl); + free(request); + return status; + } + + // Set all of the curl handle options + if ((status = setup_curl(request, params, values)) != S3StatusOK) { + curl_easy_cleanup(request->curl); + free(request); + return status; + } + + request->propertiesCallback = params->propertiesCallback; + + request->toS3Callback = params->toS3Callback; + + request->toS3CallbackBytesRemaining = params->toS3CallbackTotalSize; + + request->fromS3Callback = params->fromS3Callback; + + request->completeCallback = params->completeCallback; + + request->callbackData = params->callbackData; + + response_headers_handler_initialize(&(request->responseHeadersHandler)); + + request->propertiesCallbackMade = 0; + + error_parser_initialize(&(request->errorParser)); + + *reqReturn = request; + + return S3StatusOK; } static void request_destroy(Request *request) { - request_deinitialize(request); - curl_easy_cleanup(request->curl); - free(request); + request_deinitialize(request); + curl_easy_cleanup(request->curl); + free(request); } static void request_release(Request *request) { - pthread_mutex_lock(&requestStackMutexG); - - // If the request stack is full, destroy this one - if (requestStackCountG == REQUEST_STACK_SIZE) { - pthread_mutex_unlock(&requestStackMutexG); - request_destroy(request); - } - // Else put this one at the front of the request stack; we do this because - // we want the most-recently-used curl handle to be re-used on the next - // request, to maximize our chances of re-using a TCP connection before it - // times out - else { - requestStackG[requestStackCountG++] = request; - pthread_mutex_unlock(&requestStackMutexG); - } + pthread_mutex_lock(&requestStackMutexG); + + // If the request stack is full, destroy this one + if (requestStackCountG == REQUEST_STACK_SIZE) { + pthread_mutex_unlock(&requestStackMutexG); + request_destroy(request); + } + // Else put this one at the front of the request stack; we do this because + // we want the most-recently-used curl handle to be re-used on the next + // request, to maximize our chances of re-using a TCP connection before it + // times out + else { + requestStackG[requestStackCountG++] = request; + pthread_mutex_unlock(&requestStackMutexG); + } } S3Status request_api_initialize(const char *userAgentInfo, int flags) { - if (curl_global_init(CURL_GLOBAL_ALL & - ~((flags & S3_INIT_WINSOCK) ? 0 : CURL_GLOBAL_WIN32)) - != CURLE_OK) { - return S3StatusInternalError; - } - - pthread_mutex_init(&requestStackMutexG, 0); - - requestStackCountG = 0; - - if (!userAgentInfo || !*userAgentInfo) { - userAgentInfo = "Unknown"; - } - - char platform[96]; - struct utsname utsn; - if (uname(&utsn)) { - strncpy(platform, "Unknown", sizeof(platform)); - // Because strncpy doesn't always zero terminate - platform[sizeof(platform) - 1] = 0; - } - else { - snprintf(platform, sizeof(platform), "%s%s%s", utsn.sysname, - utsn.machine[0] ? " " : "", utsn.machine); - } - - snprintf(userAgentG, sizeof(userAgentG), - "Mozilla/4.0 (Compatible; %s; libs3 %s.%s; %s)", - userAgentInfo, LIBS3_VER_MAJOR, LIBS3_VER_MINOR, platform); - - return S3StatusOK; + if (curl_global_init(CURL_GLOBAL_ALL & + ~((flags & S3_INIT_WINSOCK) ? 0 : CURL_GLOBAL_WIN32)) + != CURLE_OK) { + return S3StatusInternalError; + } + + pthread_mutex_init(&requestStackMutexG, 0); + + requestStackCountG = 0; + + if (!userAgentInfo || !*userAgentInfo) { + userAgentInfo = "Unknown"; + } + + char platform[96]; + struct utsname utsn; + if (uname(&utsn)) { + strncpy(platform, "Unknown", sizeof(platform)); + // Because strncpy doesn't always zero terminate + platform[sizeof(platform) - 1] = 0; + } + else { + snprintf(platform, sizeof(platform), "%s%s%s", utsn.sysname, + utsn.machine[0] ? " " : "", utsn.machine); + } + + snprintf(userAgentG, sizeof(userAgentG), + "Mozilla/4.0 (Compatible; %s; libs3 %s.%s; %s)", + userAgentInfo, LIBS3_VER_MAJOR, LIBS3_VER_MINOR, platform); + + return S3StatusOK; } void request_api_deinitialize() { - pthread_mutex_destroy(&requestStackMutexG); + pthread_mutex_destroy(&requestStackMutexG); - while (requestStackCountG--) { - request_destroy(requestStackG[requestStackCountG]); - } + while (requestStackCountG--) { + request_destroy(requestStackG[requestStackCountG]); + } } void request_perform(const RequestParams *params, S3RequestContext *context) { - Request *request; - S3Status status; - -#define return_status(status) \ - (*(params->completeCallback))(status, 0, params->callbackData); \ - return - - // These will hold the computed values - RequestComputedValues computed; - - // Validate the bucket name - if (params->bucketContext.bucketName && - ((status = S3_validate_bucket_name - (params->bucketContext.bucketName, - params->bucketContext.uriStyle)) != S3StatusOK)) { - return_status(status); - } - - // Compose the amz headers - if ((status = compose_amz_headers(params, &computed)) != S3StatusOK) { - return_status(status); - } - - // Compose standard headers - if ((status = compose_standard_headers - (params, &computed)) != S3StatusOK) { - return_status(status); - } - - // URL encode the key - if ((status = encode_key(params, &computed)) != S3StatusOK) { - return_status(status); - } - - // Compute the canonicalized amz headers - canonicalize_amz_headers(&computed); - - // Compute the canonicalized resource - canonicalize_resource(params->bucketContext.bucketName, - params->subResource, computed.urlEncodedKey, - computed.canonicalizedResource); - - // Compose Authorization header - if ((status = compose_auth_header(params, &computed)) != S3StatusOK) { - return_status(status); - } - - // Get an initialized Request structure now - if ((status = request_get(params, &computed, &request)) != S3StatusOK) { - return_status(status); - } - - // If a RequestContext was provided, add the request to the curl multi - if (context) { - CURLMcode code = curl_multi_add_handle(context->curlm, request->curl); - if (code == CURLM_OK) { - if (context->requests) { - request->prev = context->requests->prev; - request->next = context->requests; - context->requests->prev->next = request; - context->requests->prev = request; - } - else { - context->requests = request->next = request->prev = request; - } - } - else { - if (request->status == S3StatusOK) { - request->status = (code == CURLM_OUT_OF_MEMORY) ? - S3StatusOutOfMemory : S3StatusInternalError; - } - request_finish(request); - } - } - // Else, perform the request immediately - else { - CURLcode code = curl_easy_perform(request->curl); - if ((code != CURLE_OK) && (request->status == S3StatusOK)) { - request->status = request_curl_code_to_status(code); - } - // Finish the request, ensuring that all callbacks have been made, and - // also releases the request - request_finish(request); - } + Request *request; + S3Status status; + +#define return_status(status) \ + (*(params->completeCallback))(status, 0, params->callbackData); \ + return + + // These will hold the computed values + RequestComputedValues computed; + + // Validate the bucket name + if (params->bucketContext.bucketName && + ((status = S3_validate_bucket_name + (params->bucketContext.bucketName, + params->bucketContext.uriStyle)) != S3StatusOK)) { + return_status(status); + } + + // Compose the amz headers + if ((status = compose_amz_headers(params, &computed)) != S3StatusOK) { + return_status(status); + } + + // Compose standard headers + if ((status = compose_standard_headers + (params, &computed)) != S3StatusOK) { + return_status(status); + } + + // URL encode the key + if ((status = encode_key(params, &computed)) != S3StatusOK) { + return_status(status); + } + + // Compute the canonicalized amz headers + canonicalize_amz_headers(&computed); + + // Compute the canonicalized resource + canonicalize_resource(params->bucketContext.bucketName, + params->subResource, computed.urlEncodedKey, + computed.canonicalizedResource); + + // Compose Authorization header + if ((status = compose_auth_header(params, &computed)) != S3StatusOK) { + return_status(status); + } + + // Get an initialized Request structure now + if ((status = request_get(params, &computed, &request)) != S3StatusOK) { + return_status(status); + } + + // If a RequestContext was provided, add the request to the curl multi + if (context) { + CURLMcode code = curl_multi_add_handle(context->curlm, request->curl); + if (code == CURLM_OK) { + if (context->requests) { + request->prev = context->requests->prev; + request->next = context->requests; + context->requests->prev->next = request; + context->requests->prev = request; + } + else { + context->requests = request->next = request->prev = request; + } + } + else { + if (request->status == S3StatusOK) { + request->status = (code == CURLM_OUT_OF_MEMORY) ? + S3StatusOutOfMemory : S3StatusInternalError; + } + request_finish(request); + } + } + // Else, perform the request immediately + else { + CURLcode code = curl_easy_perform(request->curl); + if ((code != CURLE_OK) && (request->status == S3StatusOK)) { + request->status = request_curl_code_to_status(code); + } + // Finish the request, ensuring that all callbacks have been made, and + // also releases the request + request_finish(request); + } } void request_finish(Request *request) { - // If we haven't detected this already, we now know that the headers are - // definitely done being read in - request_headers_done(request); - - // If there was no error processing the request, then possibly there was - // an S3 error parsed, which should be converted into the request status - if (request->status == S3StatusOK) { - error_parser_convert_status(&(request->errorParser), - &(request->status)); - // If there still was no error recorded, then it is possible that - // there was in fact an error but that there was no error XML - // detailing the error - if ((request->status == S3StatusOK) && - ((request->httpResponseCode < 200) || - (request->httpResponseCode > 299))) { - switch (request->httpResponseCode) { - case 0: - // This happens if the request never got any HTTP response - // headers at all, we call this a ConnectionFailed error - request->status = S3StatusConnectionFailed; - break; - case 100: // Some versions of libcurl erroneously set HTTP - // status to this - break; - case 301: - request->status = S3StatusErrorPermanentRedirect; - break; - case 307: - request->status = S3StatusHttpErrorMovedTemporarily; - break; - case 400: - request->status = S3StatusHttpErrorBadRequest; - break; - case 403: - request->status = S3StatusHttpErrorForbidden; - break; - case 404: - request->status = S3StatusHttpErrorNotFound; - break; - case 405: - request->status = S3StatusErrorMethodNotAllowed; - break; - case 409: - request->status = S3StatusHttpErrorConflict; - break; - case 411: - request->status = S3StatusErrorMissingContentLength; - break; - case 412: - request->status = S3StatusErrorPreconditionFailed; - break; - case 416: - request->status = S3StatusErrorInvalidRange; - break; - case 500: - request->status = S3StatusErrorInternalError; - break; - case 501: - request->status = S3StatusErrorNotImplemented; - break; - case 503: - request->status = S3StatusErrorSlowDown; - break; - default: - request->status = S3StatusHttpErrorUnknown; - break; - } - } - } - - (*(request->completeCallback)) - (request->status, &(request->errorParser.s3ErrorDetails), - request->callbackData); - - request_release(request); + // If we haven't detected this already, we now know that the headers are + // definitely done being read in + request_headers_done(request); + + // If there was no error processing the request, then possibly there was + // an S3 error parsed, which should be converted into the request status + if (request->status == S3StatusOK) { + error_parser_convert_status(&(request->errorParser), + &(request->status)); + // If there still was no error recorded, then it is possible that + // there was in fact an error but that there was no error XML + // detailing the error + if ((request->status == S3StatusOK) && + ((request->httpResponseCode < 200) || + (request->httpResponseCode > 299))) { + switch (request->httpResponseCode) { + case 0: + // This happens if the request never got any HTTP response + // headers at all, we call this a ConnectionFailed error + request->status = S3StatusConnectionFailed; + break; + case 100: // Some versions of libcurl erroneously set HTTP + // status to this + break; + case 301: + request->status = S3StatusErrorPermanentRedirect; + break; + case 307: + request->status = S3StatusHttpErrorMovedTemporarily; + break; + case 400: + request->status = S3StatusHttpErrorBadRequest; + break; + case 403: + request->status = S3StatusHttpErrorForbidden; + break; + case 404: + request->status = S3StatusHttpErrorNotFound; + break; + case 405: + request->status = S3StatusErrorMethodNotAllowed; + break; + case 409: + request->status = S3StatusHttpErrorConflict; + break; + case 411: + request->status = S3StatusErrorMissingContentLength; + break; + case 412: + request->status = S3StatusErrorPreconditionFailed; + break; + case 416: + request->status = S3StatusErrorInvalidRange; + break; + case 500: + request->status = S3StatusErrorInternalError; + break; + case 501: + request->status = S3StatusErrorNotImplemented; + break; + case 503: + request->status = S3StatusErrorSlowDown; + break; + default: + request->status = S3StatusHttpErrorUnknown; + break; + } + } + } + + (*(request->completeCallback)) + (request->status, &(request->errorParser.s3ErrorDetails), + request->callbackData); + + request_release(request); } S3Status request_curl_code_to_status(CURLcode code) { - switch (code) { - case CURLE_OUT_OF_MEMORY: - return S3StatusOutOfMemory; - case CURLE_COULDNT_RESOLVE_PROXY: - case CURLE_COULDNT_RESOLVE_HOST: - return S3StatusNameLookupError; - case CURLE_COULDNT_CONNECT: - return S3StatusFailedToConnect; - case CURLE_WRITE_ERROR: - case CURLE_OPERATION_TIMEDOUT: - return S3StatusConnectionFailed; - case CURLE_PARTIAL_FILE: - return S3StatusOK; - case CURLE_SSL_CACERT: - return S3StatusServerFailedVerification; - default: - return S3StatusInternalError; - } + switch (code) { + case CURLE_OUT_OF_MEMORY: + return S3StatusOutOfMemory; + case CURLE_COULDNT_RESOLVE_PROXY: + case CURLE_COULDNT_RESOLVE_HOST: + return S3StatusNameLookupError; + case CURLE_COULDNT_CONNECT: + return S3StatusFailedToConnect; + case CURLE_WRITE_ERROR: + case CURLE_OPERATION_TIMEDOUT: + return S3StatusConnectionFailed; + case CURLE_PARTIAL_FILE: + return S3StatusOK; + case CURLE_SSL_CACERT: + return S3StatusServerFailedVerification; + default: + return S3StatusInternalError; + } } S3Status S3_generate_authenticated_query_string - (char *buffer, const S3BucketContext *bucketContext, - const char *key, int64_t expires, const char *resource) + (char *buffer, const S3BucketContext *bucketContext, + const char *key, int64_t expires, const char *resource) { #define MAX_EXPIRES (((int64_t) 1 << 31) - 1) - // S3 seems to only accept expiration dates up to the number of seconds - // representably by a signed 32-bit integer - if (expires < 0) { - expires = MAX_EXPIRES; - } - else if (expires > MAX_EXPIRES) { - expires = MAX_EXPIRES; - } - - // xxx todo: rework this so that it can be incorporated into shared code - // with request_perform(). It's really unfortunate that this code is not - // shared with request_perform(). - - // URL encode the key - char urlEncodedKey[S3_MAX_KEY_SIZE * 3]; - if (key) { - urlEncode(urlEncodedKey, key, strlen(key)); - } - else { - urlEncodedKey[0] = 0; - } - - // Compute canonicalized resource - char canonicalizedResource[MAX_CANONICALIZED_RESOURCE_SIZE]; - canonicalize_resource(bucketContext->bucketName, resource, urlEncodedKey, - canonicalizedResource); - - // We allow for: - // 17 bytes for HTTP-Verb + \n - // 1 byte for empty Content-MD5 + \n - // 1 byte for empty Content-Type + \n - // 20 bytes for Expires + \n - // 0 bytes for CanonicalizedAmzHeaders - // CanonicalizedResource - char signbuf[17 + 1 + 1 + 1 + 20 + sizeof(canonicalizedResource) + 1]; - int len = 0; - -#define signbuf_append(format, ...) \ - len += snprintf(&(signbuf[len]), sizeof(signbuf) - len, \ - format, __VA_ARGS__) - - signbuf_append("%s\n", "GET"); // HTTP-Verb - signbuf_append("%s\n", ""); // Content-MD5 - signbuf_append("%s\n", ""); // Content-Type - signbuf_append("%llu\n", (unsigned long long) expires); - signbuf_append("%s", canonicalizedResource); - - // Generate an HMAC-SHA-1 of the signbuf - unsigned char hmac[20]; - - HMAC_SHA1(hmac, (unsigned char *) bucketContext->secretAccessKey, - strlen(bucketContext->secretAccessKey), - (unsigned char *) signbuf, len); - - // Now base-64 encode the results - char b64[((20 + 1) * 4) / 3]; - int b64Len = base64Encode(hmac, 20, b64); - - // Now urlEncode that - char signature[sizeof(b64) * 3]; - urlEncode(signature, b64, b64Len); - - // Finally, compose the uri, with params: - // ?AWSAccessKeyId=xxx[&Expires=]&Signature=xxx - char queryParams[sizeof("AWSAccessKeyId=") + 20 + - sizeof("&Expires=") + 20 + - sizeof("&Signature=") + sizeof(signature) + 1]; - - sprintf(queryParams, "AWSAccessKeyId=%s&Expires=%ld&Signature=%s", - bucketContext->accessKeyId, (long) expires, signature); - - return compose_uri(buffer, S3_MAX_AUTHENTICATED_QUERY_STRING_SIZE, - bucketContext, urlEncodedKey, resource, queryParams); + // S3 seems to only accept expiration dates up to the number of seconds + // representably by a signed 32-bit integer + if (expires < 0) { + expires = MAX_EXPIRES; + } + else if (expires > MAX_EXPIRES) { + expires = MAX_EXPIRES; + } + + // xxx todo: rework this so that it can be incorporated into shared code + // with request_perform(). It's really unfortunate that this code is not + // shared with request_perform(). + + // URL encode the key + char urlEncodedKey[S3_MAX_KEY_SIZE * 3]; + if (key) { + urlEncode(urlEncodedKey, key, strlen(key)); + } + else { + urlEncodedKey[0] = 0; + } + + // Compute canonicalized resource + char canonicalizedResource[MAX_CANONICALIZED_RESOURCE_SIZE]; + canonicalize_resource(bucketContext->bucketName, resource, urlEncodedKey, + canonicalizedResource); + + // We allow for: + // 17 bytes for HTTP-Verb + \n + // 1 byte for empty Content-MD5 + \n + // 1 byte for empty Content-Type + \n + // 20 bytes for Expires + \n + // 0 bytes for CanonicalizedAmzHeaders + // CanonicalizedResource + char signbuf[17 + 1 + 1 + 1 + 20 + sizeof(canonicalizedResource) + 1]; + int len = 0; + +#define signbuf_append(format, ...) \ + len += snprintf(&(signbuf[len]), sizeof(signbuf) - len, \ + format, __VA_ARGS__) + + signbuf_append("%s\n", "GET"); // HTTP-Verb + signbuf_append("%s\n", ""); // Content-MD5 + signbuf_append("%s\n", ""); // Content-Type + signbuf_append("%llu\n", (unsigned long long) expires); + signbuf_append("%s", canonicalizedResource); + + // Generate an HMAC-SHA-1 of the signbuf + unsigned char hmac[20]; + + HMAC_SHA1(hmac, (unsigned char *) bucketContext->secretAccessKey, + strlen(bucketContext->secretAccessKey), + (unsigned char *) signbuf, len); + + // Now base-64 encode the results + char b64[((20 + 1) * 4) / 3]; + int b64Len = base64Encode(hmac, 20, b64); + + // Now urlEncode that + char signature[sizeof(b64) * 3]; + urlEncode(signature, b64, b64Len); + + // Finally, compose the uri, with params: + // ?AWSAccessKeyId=xxx[&Expires=]&Signature=xxx + char queryParams[sizeof("AWSAccessKeyId=") + 20 + + sizeof("&Expires=") + 20 + + sizeof("&Signature=") + sizeof(signature) + 1]; + + sprintf(queryParams, "AWSAccessKeyId=%s&Expires=%ld&Signature=%s", + bucketContext->accessKeyId, (long) expires, signature); + + return compose_uri(buffer, S3_MAX_AUTHENTICATED_QUERY_STRING_SIZE, + bucketContext, urlEncodedKey, resource, queryParams); } |