diff options
author | Bryan Ischo <bryan@ischo.com> | 2008-07-11 12:13:40 +0000 |
---|---|---|
committer | Bryan Ischo <bryan@ischo.com> | 2008-07-11 12:13:40 +0000 |
commit | 037783a33a3c2f02307511bdadd4c85486217a40 (patch) | |
tree | 8f495fac334b8cba7027a73e90c366c7106b2341 | |
parent | c0535eabe25706904711f1453e724e659b93318c (diff) | |
download | ceph-libs3-037783a33a3c2f02307511bdadd4c85486217a40.tar.gz |
* More work in progress. Initial list_bucket support.
-rw-r--r-- | inc/libs3.h | 44 | ||||
-rw-r--r-- | inc/util.h | 12 | ||||
-rw-r--r-- | src/bucket.c | 332 | ||||
-rw-r--r-- | src/general.c | 1 | ||||
-rw-r--r-- | src/request.c | 66 | ||||
-rw-r--r-- | src/s3.c | 166 | ||||
-rw-r--r-- | src/service.c | 9 | ||||
-rw-r--r-- | src/util.c | 80 |
8 files changed, 606 insertions, 104 deletions
diff --git a/inc/libs3.h b/inc/libs3.h index 190cf8d..495fe0e 100644 --- a/inc/libs3.h +++ b/inc/libs3.h @@ -95,6 +95,7 @@ typedef enum S3StatusInvalidBucketNameCharacterSequence , S3StatusInvalidBucketNameTooShort , S3StatusInvalidBucketNameDotQuadNotation , + S3StatusQueryParamsTooLong , S3StatusFailedToCreateRequest , S3StatusFailedToInitializeRequest , S3StatusFailedToCreateRequestContext , @@ -326,9 +327,10 @@ typedef struct S3ResponseHeaders * This optional field provides the last modified time, relative to the * Unix epoch, of the contents. If this value is > 0, then the last * modified date of the contents are availableb as a number of seconds - * since the UNIX epoch. Note that this is second precision. + * since the UNIX epoch. Note that this is second precision; HTTP + * **/ - long lastModified; + time_t lastModified; /** * This is the number of user-provided metadata headers associated with * the resource. @@ -431,10 +433,9 @@ typedef struct S3BucketContext /** * This is a single entry supplied to the list bucket callback by a call to * S3_list_bucket. It identifies a single matching key from the list - * operation. All fields of this structure are non-optional and will be - * present. + * operation. **/ -typedef struct ListBucketContent +typedef struct S3ListBucketContent { /** * This is the next key in the list bucket results. @@ -442,15 +443,9 @@ typedef struct ListBucketContent const char *key; /** * This is the number of seconds since UNIX epoch of the last modified - * date of the object identified by the key. - **/ - int lastModifiedSeconds; - /** - * This is the number of microseconds within the second specified by - * lastModifiedSeconds of the last modified date of the object identified - * by the key. + * date of the object identified by the key. **/ - int lastModifiedMilliseconds; + time_t lastModified; /** * This gives a tag which gives a signature of the contents of the object. **/ @@ -459,7 +454,17 @@ typedef struct ListBucketContent * This is the size of the object **/ uint64_t size; -} ListBucketContent; + /** + * This is the ID of the owner of the key; it is present only if + * access permissions allow it to be viewed. + **/ + const char *ownerId; + /** + * This is the display name of the owner of the key; it is present only if + * access permissions allow it to be viewed. + **/ + const char *ownerDisplayName; +} S3ListBucketContent; /** @@ -614,15 +619,12 @@ typedef void (S3ResponseCompleteCallback)(S3Status status, * @param creationDateSeconds if < 0 indicates that no creation date was * supplied for the bucket; if > 0 indicates the number of seconds * since UNIX Epoch of the creation date of the bucket - * @param creationDateMilliseconds gives an offset from creationDateSeconds - * at which the bucket was created * @return S3Status??? **/ typedef S3Status (S3ListServiceCallback)(const char *ownerId, const char *ownerDisplayName, const char *bucketName, - int creationDateSeconds, - int creationDateMilliseconds, + time_t creationDateSeconds, void *callbackData); @@ -648,8 +650,10 @@ typedef S3Status (S3ListServiceCallback)(const char *ownerId, **/ typedef S3Status (S3ListBucketCallback)(int isTruncated, const char *nextMarker, - int contentsLength, - const ListBucketContent *contents, + int contentsCount, + const S3ListBucketContent *contents, + int commonPrefixesCount, + const char **commonPrefixes, void *callbackData); @@ -83,8 +83,16 @@ void mutex_destroy(struct S3Mutex *mutex); // Utilities ----------------------------------------------------------------- -// Returns 0 on success, nonzero on failure -int parseIso8601Time(const char *str, time_t *secondsReturn, int *millisReturn); +// URL-encodes a string from [src] into [dest]. [dest] must have at least +// 3x the number of characters that [source] has. At most [maxSrcSize] bytes +// from [src] are encoded; if more are present in [src], 0 is returned from +// urlEncode, else nonzero is returned. +int urlEncode(char *dest, const char *src, int maxSrcSize); + +// Returns < 0 on failure >= 0 on success +time_t parseIso8601Time(const char *str); + +uint64_t parseUnsignedInt(const char *str); #endif /* UTIL_H */ diff --git a/src/bucket.c b/src/bucket.c index 039bd2a..2daa271 100644 --- a/src/bucket.c +++ b/src/bucket.c @@ -374,9 +374,341 @@ void S3_delete_bucket(S3Protocol protocol, S3UriStyle uriStyle, } +// list bucket ---------------------------------------------------------------- + +typedef struct ListBucketContents +{ + string_buffer(key, 1024); + string_buffer(lastModified, 256); + string_buffer(eTag, 256); + string_buffer(size, 24); + string_buffer(ownerId, 256); + string_buffer(ownerDisplayName, 256); +} ListBucketContents; + + +static void initialize_list_bucket_contents(ListBucketContents *contents) +{ + string_buffer_initialize(contents->key); + string_buffer_initialize(contents->lastModified); + string_buffer_initialize(contents->eTag); + string_buffer_initialize(contents->size); + string_buffer_initialize(contents->ownerId); + string_buffer_initialize(contents->ownerDisplayName); +} + +#define MAX_CONTENTS 32 +#define MAX_COMMON_PREFIXES 8 + +typedef struct ListBucketData +{ + SimpleXml simpleXml; + + S3ResponseHeadersCallback *responseHeadersCallback; + S3ListBucketCallback *listBucketCallback; + S3ResponseCompleteCallback *responseCompleteCallback; + void *callbackData; + + string_buffer(isTruncated, 64); + string_buffer(nextMarker, 1024); + + // We read up to 32 Contents at a time + int contentsCount; + ListBucketContents contents[MAX_CONTENTS]; + + // We read up to 8 CommonPrefixes at a time + int commonPrefixesCount; + char commonPrefixes[MAX_COMMON_PREFIXES][1024]; + int commonPrefixLens[MAX_COMMON_PREFIXES]; +} ListBucketData; + + +static void initialize_list_bucket_data(ListBucketData *lbData) +{ + lbData->contentsCount = 0; + initialize_list_bucket_contents(lbData->contents); + lbData->commonPrefixesCount = 0; + lbData->commonPrefixes[0][0] = 0; + lbData->commonPrefixLens[0] = 0; +} + + +static S3Status make_list_bucket_callback(ListBucketData *lbData) +{ + int i; + + // Convert IsTruncated + int isTruncated = (!strcmp(lbData->isTruncated, "true") || + !strcmp(lbData->isTruncated, "1")) ? 1 : 0; + + // Convert the contents + S3ListBucketContent contents[lbData->contentsCount]; + + int contentsCount = lbData->contentsCount; + for (i = 0; i < contentsCount; i++) { + S3ListBucketContent *contentDest = &(contents[i]); + ListBucketContents *contentSrc = &(lbData->contents[i]); + contentDest->key = contentSrc->key; + contentDest->lastModified = parseIso8601Time(contentSrc->lastModified); + contentDest->eTag = contentSrc->eTag; + contentDest->size = parseUnsignedInt(contentSrc->size); + contentDest->ownerId = contentSrc->ownerId[0] ?contentSrc->ownerId : 0; + contentDest->ownerDisplayName = (contentSrc->ownerDisplayName[0] ? + contentSrc->ownerDisplayName : 0); + } + + // Make the common prefixes array + int commonPrefixesCount = lbData->commonPrefixesCount; + char *commonPrefixes[commonPrefixesCount]; + for (i = 0; i < commonPrefixesCount; i++) { + commonPrefixes[i] = lbData->commonPrefixes[i]; + } + + return (*(lbData->listBucketCallback)) + (isTruncated, lbData->nextMarker, + contentsCount, contents, commonPrefixesCount, + (const char **) commonPrefixes, lbData->callbackData); +} + + +static S3Status listBucketXmlCallback(const char *elementPath, const char *data, + int dataLen, void *callbackData) +{ + ListBucketData *lbData = (ListBucketData *) callbackData; + + int fit; + + if (data) { + if (!strcmp(elementPath, "ListBucketResult/IsTruncated")) { + string_buffer_append(lbData->isTruncated, data, dataLen, fit); + } + else if (!strcmp(elementPath, "ListBucketResult/NextMarker")) { + string_buffer_append(lbData->nextMarker, data, dataLen, fit); + } + else if (!strcmp(elementPath, "ListBucketResult/Contents/Key")) { + ListBucketContents *contents = + &(lbData->contents[lbData->contentsCount]); + string_buffer_append(contents->key, data, dataLen, fit); + } + else if (!strcmp(elementPath, + "ListBucketResult/Contents/LastModified")) { + ListBucketContents *contents = + &(lbData->contents[lbData->contentsCount]); + string_buffer_append(contents->lastModified, data, dataLen, fit); + } + else if (!strcmp(elementPath, "ListBucketResult/Contents/ETag")) { + ListBucketContents *contents = + &(lbData->contents[lbData->contentsCount]); + string_buffer_append(contents->eTag, data, dataLen, fit); + } + else if (!strcmp(elementPath, "ListBucketResult/Contents/Size")) { + ListBucketContents *contents = + &(lbData->contents[lbData->contentsCount]); + string_buffer_append(contents->size, data, dataLen, fit); + } + else if (!strcmp(elementPath, "ListBucketResult/Contents/Owner/ID")) { + ListBucketContents *contents = + &(lbData->contents[lbData->contentsCount]); + string_buffer_append(contents->ownerId, data, dataLen, fit); + } + else if (!strcmp(elementPath, + "ListBucketResult/Contents/Owner/DisplayName")) { + ListBucketContents *contents = + &(lbData->contents[lbData->contentsCount]); + string_buffer_append + (contents->ownerDisplayName, data, dataLen, fit); + } + else if (!strcmp(elementPath, "ListBucketResult/CommonPrefix/Prefix")) { + int which = lbData->commonPrefixesCount; + lbData->commonPrefixLens[which] += + snprintf(lbData->commonPrefixes[which], + sizeof(lbData->commonPrefixes[which]) - + lbData->commonPrefixLens[which] - 1, + "%.*s", dataLen, data); + } + } + else { + if (!strcmp(elementPath, "ListBucketResult/Contents")) { + // Finished a Contents + lbData->contentsCount++; + if (lbData->contentsCount == MAX_CONTENTS) { + // Make the callback + S3Status status = make_list_bucket_callback(lbData); + if (status != S3StatusOK) { + return status; + } + initialize_list_bucket_data(lbData); + } + else { + // Initialize the next one + initialize_list_bucket_contents + (&(lbData->contents[lbData->contentsCount])); + } + } + else if (!strcmp(elementPath, "ListBucketResult/CommonPrefix/Prefix")) { + // Finished a Prefix + lbData->commonPrefixesCount++; + if (lbData->commonPrefixesCount == MAX_COMMON_PREFIXES) { + // Make the callback + S3Status status = make_list_bucket_callback(lbData); + if (status != S3StatusOK) { + return status; + } + initialize_list_bucket_data(lbData); + } + else { + // Initialize the next one + lbData->commonPrefixes[lbData->commonPrefixesCount][0] = 0; + lbData->commonPrefixLens[lbData->commonPrefixesCount] = 0; + } + } + } + + return S3StatusOK; +} + + +static S3Status listBucketHeadersCallback + (const S3ResponseHeaders *responseHeaders, void *callbackData) +{ + ListBucketData *lbData = (ListBucketData *) callbackData; + + return (*(lbData->responseHeadersCallback)) + (responseHeaders, lbData->callbackData); +} + + +static int listBucketDataCallback(char *buffer, int bufferSize, + void *callbackData) +{ + ListBucketData *lbData = (ListBucketData *) callbackData; + + return ((simplexml_add(&(lbData->simpleXml), buffer, + bufferSize) == S3StatusOK) ? bufferSize : 0); +} + + +static void listBucketCompleteCallback(S3Status requestStatus, + int httpResponseCode, + const S3ErrorDetails *s3ErrorDetails, + void *callbackData) +{ + ListBucketData *lbData = (ListBucketData *) callbackData; + + // Make the callback if there is anything + if (lbData->contentsCount || lbData->commonPrefixesCount) { + make_list_bucket_callback(lbData); + } + + free(lbData); +} + + void S3_list_bucket(S3BucketContext *bucketContext, const char *prefix, const char *marker, const char *delimiter, int maxkeys, S3RequestContext *requestContext, S3ListBucketHandler *handler, void *callbackData) { + // Compose the query params + string_buffer(queryParams, 4096); + string_buffer_initialize(queryParams); + +#define safe_append(name, value) \ + do { \ + int fit; \ + string_buffer_append(queryParams, &sep, 1, fit); \ + if (!fit) { \ + (*(handler->responseHandler.completeCallback)) \ + (S3StatusQueryParamsTooLong, 0, 0, callbackData); \ + return; \ + } \ + string_buffer_append(queryParams, name "=", \ + sizeof(name "=") - 1, fit); \ + if (!fit) { \ + (*(handler->responseHandler.completeCallback)) \ + (S3StatusQueryParamsTooLong, 0, 0, callbackData); \ + return; \ + } \ + sep = '&'; \ + char encoded[3 * 1024]; \ + if (!urlEncode(encoded, value, 1024)) { \ + (*(handler->responseHandler.completeCallback)) \ + (S3StatusQueryParamsTooLong, 0, 0, callbackData); \ + } \ + string_buffer_append(queryParams, encoded, strlen(encoded), \ + fit); \ + if (!fit) { \ + (*(handler->responseHandler.completeCallback)) \ + (S3StatusQueryParamsTooLong, 0, 0, callbackData); \ + return; \ + } \ + } while (0) + + + char sep = '?'; + if (prefix) { + safe_append("prefix", prefix); + } + if (marker) { + safe_append("marker", marker); + } + if (delimiter) { + safe_append("delimiter", delimiter); + } + if (maxkeys) { + char maxKeysString[64]; + snprintf(maxKeysString, sizeof(maxKeysString), "%d", maxkeys); + safe_append("max-keys", maxKeysString); + } + + ListBucketData *lbData = (ListBucketData *) malloc(sizeof(ListBucketData)); + + if (!lbData) { + (*(handler->responseHandler.completeCallback)) + (S3StatusOutOfMemory, 0, 0, callbackData); + return; + } + + S3Status status = simplexml_initialize + (&(lbData->simpleXml), &listBucketXmlCallback, lbData); + if (status != S3StatusOK) { + free(lbData); + (*(handler->responseHandler.completeCallback)) + (status, 0, 0, callbackData); + return; + } + + lbData->responseHeadersCallback = handler->responseHandler.headersCallback; + lbData->listBucketCallback = handler->listBucketCallback; + lbData->responseCompleteCallback = + handler->responseHandler.completeCallback; + lbData->callbackData = callbackData; + + string_buffer_initialize(lbData->isTruncated); + string_buffer_initialize(lbData->nextMarker); + initialize_list_bucket_data(lbData); + + // Set up the RequestParams + RequestParams params = + { + HttpRequestTypeGET, // httpRequestType + bucketContext->protocol, // protocol + bucketContext->uriStyle, // uriStyle + bucketContext->bucketName, // bucketName + 0, // key + queryParams[0] ? queryParams : 0, // queryParams + 0, // subResource + bucketContext->accessKeyId, // accessKeyId + bucketContext->secretAccessKey, // secretAccessKey + 0, // requestHeaders + &listBucketHeadersCallback, // headersCallback + 0, // toS3Callback + 0, // toS3CallbackTotalSize + &listBucketDataCallback, // fromS3Callback + &listBucketCompleteCallback, // completeCallback + lbData // callbackData + }; + + // Perform the request + request_perform(¶ms, requestContext); } diff --git a/src/general.c b/src/general.c index 25ae0ed..1a7ced5 100644 --- a/src/general.c +++ b/src/general.c @@ -180,6 +180,7 @@ const char *S3_get_status_name(S3Status status) handlecase(InvalidBucketNameCharacterSequence); handlecase(InvalidBucketNameTooShort); handlecase(InvalidBucketNameDotQuadNotation); + handlecase(QueryParamsTooLong); handlecase(FailedToCreateRequest); handlecase(FailedToInitializeRequest); handlecase(FailedToCreateRequestContext); diff --git a/src/request.c b/src/request.c index 53b9257..e7568af 100644 --- a/src/request.c +++ b/src/request.c @@ -47,9 +47,6 @@ static Request *requestStackG[REQUEST_STACK_SIZE]; static int requestStackCountG; -static const char *urlSafeG = "-_.!~*'()/"; -static const char *hexG = "0123456789ABCDEF"; - typedef struct RequestComputedValues { @@ -405,41 +402,8 @@ static S3Status compose_standard_headers(const RequestParams *params, static S3Status encode_key(const RequestParams *params, RequestComputedValues *values) { - const char *key = params->key; - char *buffer = values->urlEncodedKey; - int len = 0; - - if (key) while (*key) { - if (++len > S3_MAX_KEY_SIZE) { - return S3StatusKeyTooLong; - } - const char *urlsafe = urlSafeG; - int isurlsafe = 0; - while (*urlsafe) { - if (*urlsafe == *key) { - isurlsafe = 1; - break; - } - urlsafe++; - } - if (isurlsafe || isalnum(*key)) { - *buffer++ = *key++; - } - else if (*key == ' ') { - *buffer++ = '+'; - key++; - } - else { - *buffer++ = '%'; - *buffer++ = hexG[*key / 16]; - *buffer++ = hexG[*key % 16]; - key++; - } - } - - *buffer = 0; - - return S3StatusOK; + return (urlEncode(values->urlEncodedKey, params->key, S3_MAX_KEY_SIZE) ? + S3StatusOK : S3StatusUriTooLong); } @@ -699,19 +663,20 @@ static S3Status compose_uri(const RequestParams *params, Request *request) if (params->key && params->key[0]) { uri_append("%s", params->key); - - if (params->queryParams) { - uri_append("%s", params->queryParams); - } - else if (params->subResource && params->subResource[0]) { - uri_append("%s", params->subResource); - } } else { uri_append("%s", "/"); - if (params->subResource) { - uri_append("%s", params->subResource); - } + } + + if (params->subResource) { + uri_append("%s", params->subResource); + } + + if (params->queryParams) { + uri_append("%s", params->queryParams); + } + else if (params->subResource && params->subResource[0]) { + uri_append("%s", params->subResource); } return S3StatusOK; @@ -734,7 +699,7 @@ static S3Status setup_curl(Request *request, // Debugging only // curl_easy_setopt_safe(CURLOPT_VERBOSE, 1); - // Always set header callback and data + // Set header callback and data curl_easy_setopt_safe(CURLOPT_HEADERDATA, request); curl_easy_setopt_safe(CURLOPT_HEADERFUNCTION, &curl_header_func); @@ -748,7 +713,8 @@ static S3Status setup_curl(Request *request, curl_easy_setopt_safe(CURLOPT_WRITEFUNCTION, &curl_write_func); curl_easy_setopt_safe(CURLOPT_WRITEDATA, request); - // Ask curl to parse the Last-Modified header + // 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. @@ -62,6 +62,14 @@ static const S3ErrorDetails *errorG = 0; #define LOCATION_CONSTRAINT_PREFIX_LEN (sizeof(LOCATION_CONSTRAINT_PREFIX) - 1) #define CANNED_ACL_PREFIX "cannedAcl=" #define CANNED_ACL_PREFIX_LEN (sizeof(CANNED_ACL_PREFIX) - 1) +#define PREFIX_PREFIX "prefix=" +#define PREFIX_PREFIX_LEN (sizeof(PREFIX_PREFIX) - 1) +#define MARKER_PREFIX "marker=" +#define MARKER_PREFIX_LEN (sizeof(MARKER_PREFIX) - 1) +#define DELIMITER_PREFIX "delimiter=" +#define DELIMITER_PREFIX_LEN (sizeof(DELIMITER_PREFIX) - 1) +#define MAXKEYS_PREFIX "maxkeys=" +#define MAXKEYS_PREFIX_LEN (sizeof(MAXKEYS_PREFIX) - 1) // libs3 mutex stuff --------------------------------------------------------- @@ -229,7 +237,7 @@ static S3Status responseHeadersCallback(const S3ResponseHeaders *headers, print_nonnull("Server", server); print_nonnull("ETag", eTag); if (headers->lastModified > 0) { - char timebuf[1024]; + char timebuf[256]; // localtime is not thread-safe but we don't care here. xxx note - // localtime doesn't seem to actually do anything, 0 locatime of 0 // returns EST Unix epoch, it should return the NZST equivalent ... @@ -266,9 +274,7 @@ static void responseCompleteCallback(S3Status status, int httpResponseCode, static S3Status listServiceCallback(const char *ownerId, const char *ownerDisplayName, const char *bucketName, - int creationDateSeconds, - int creationDateMilliseconds, - void *callbackData) + time_t creationDate, void *callbackData) { static int ownerPrinted = 0; @@ -279,16 +285,13 @@ static S3Status listServiceCallback(const char *ownerId, } printf("Bucket Name: %s\n", bucketName); - if (creationDateSeconds >= 0) { - char fmtbuf[256]; - snprintf(fmtbuf, sizeof(fmtbuf), "%%Y/%%m/%%d %%H:%%M:%%S.%03d %%Z", - creationDateMilliseconds); - char timebuf[1024]; - time_t d = (time_t) creationDateSeconds; + if (creationDate >= 0) { + char timebuf[256]; // localtime is not thread-safe but we don't care here. xxx note - // localtime doesn't seem to actually do anything, 0 locatime of 0 // returns EST Unix epoch, it should return the NZST equivalent ... - strftime(timebuf, sizeof(timebuf), fmtbuf, localtime(&d)); + strftime(timebuf, sizeof(timebuf), "%Y/%m/%d %H:%M:%S %Z", + localtime(&creationDate)); printf("Creation Date: %s\n", timebuf); } @@ -376,7 +379,6 @@ static void test_bucket(int argc, char **argv, int optind) // create bucket ------------------------------------------------------------- - static void create_bucket(int argc, char **argv, int optind) { if (optind == argc) { @@ -440,10 +442,8 @@ static void create_bucket(int argc, char **argv, int optind) // delete bucket ------------------------------------------------------------- - static void delete_bucket(int argc, char **argv, int optind) { - // delete bucket if (optind == argc) { fprintf(stderr, "ERROR: Missing parameter: bucket\n"); usageExit(); @@ -469,6 +469,142 @@ static void delete_bucket(int argc, char **argv, int optind) } +// list bucket --------------------------------------------------------------- + +typedef struct list_bucket_callback_data +{ + int isTruncated; + char nextMarker[1024]; +} list_bucket_callback_data; + +static S3Status listBucketCallback(int isTruncated, const char *nextMarker, + int contentsCount, + const S3ListBucketContent *contents, + int commonPrefixesCount, + const char **commonPrefixes, + void *callbackData) +{ + list_bucket_callback_data *data = + (list_bucket_callback_data *) callbackData; + + data->isTruncated = isTruncated; + // This is tricky. S3 doesn't return the NextMarker if there is no + // delimiter. Why, I don't know, since it's still useful for paging + // through results. We want NextMarker to be the last content in the + // list, so set it to that if necessary. + if (!nextMarker && contentsCount) { + nextMarker = contents[contentsCount - 1].key; + } + if (nextMarker) { + snprintf(data->nextMarker, sizeof(data->nextMarker), "%s", nextMarker); + } + else { + data->nextMarker[0] = 0; + } + + int i; + for (i = 0; i < contentsCount; i++) { + const S3ListBucketContent *content = &(contents[i]); + printf("\nKey: %s\n", content->key); + char timebuf[256]; + // localtime is not thread-safe but we don't care here. xxx note - + // localtime doesn't seem to actually do anything, 0 locatime of 0 + // returns EST Unix epoch, it should return the NZST equivalent ... + strftime(timebuf, sizeof(timebuf), "%Y/%m/%d %H:%M:%S %Z", + localtime(&(content->lastModified))); + printf("Last Modified: %s\n", timebuf); + printf("ETag: %s\n", content->eTag); + printf("Size: %llu\n", content->size); + if (content->ownerId) { + printf("Owner ID: %s\n", content->ownerId); + } + if (content->ownerDisplayName) { + printf("Owner Display Name: %s\n", content->ownerDisplayName); + } + } + + for (i = 0; i < commonPrefixesCount; i++) { + printf("\nCommon Prefix: %s\n", commonPrefixes[i]); + } + + return S3StatusOK; +} + + +static void list_bucket(int argc, char **argv, int optind) +{ + if (optind == argc) { + fprintf(stderr, "ERROR: Missing parameter: bucket\n"); + usageExit(); + } + + const char *bucketName = argv[optind++]; + + const char *prefix = 0, *marker = 0, *delimiter = 0; + int maxkeys = 0; + while (optind < argc) { + char *param = argv[optind++]; + if (!strncmp(param, PREFIX_PREFIX, PREFIX_PREFIX_LEN)) { + prefix = &(param[PREFIX_PREFIX_LEN]); + } + else if (!strncmp(param, MARKER_PREFIX, MARKER_PREFIX_LEN)) { + marker = &(param[MARKER_PREFIX_LEN]); + } + else if (!strncmp(param, DELIMITER_PREFIX, DELIMITER_PREFIX_LEN)) { + delimiter = &(param[DELIMITER_PREFIX_LEN]); + } + else if (!strncmp(param, MAXKEYS_PREFIX, MAXKEYS_PREFIX_LEN)) { + char *mk = &(param[MAXKEYS_PREFIX_LEN]); + while (*mk) { + if (!isdigit(*mk)) { + fprintf(stderr, "ERROR: Nondigit in maxkeys " + "parameter: %c\n", *mk); + usageExit(); + } + maxkeys *= 10; + maxkeys += (*mk - '0'); + } + } + else { + fprintf(stderr, "ERROR: Unknown param: %s\n", param); + usageExit(); + } + } + + S3_init(); + + S3BucketContext bucketContext = + { + bucketName, + protocolG, + uriStyleG, + accessKeyIdG, + secretAccessKeyG + }; + + S3ListBucketHandler listBucketHandler = + { + { &responseHeadersCallback, &responseCompleteCallback }, + &listBucketCallback + }; + + list_bucket_callback_data data; + + do { + data.isTruncated = 0; + S3_list_bucket(&bucketContext, prefix, marker, delimiter, maxkeys, + 0, &listBucketHandler, &data); + if (statusG != S3StatusOK) { + printError(); + break; + } + marker = data.nextMarker; + } while (data.isTruncated); + + S3_deinitialize(); +} + + // main ---------------------------------------------------------------------- int main(int argc, char **argv) @@ -524,7 +660,7 @@ int main(int argc, char **argv) list_service(); } else { - // list bucket + list_bucket(argc, argv, optind); } } else if (!strcmp(command, "test")) { diff --git a/src/service.c b/src/service.c index 7a0dd79..0dc344e 100644 --- a/src/service.c +++ b/src/service.c @@ -72,18 +72,13 @@ static S3Status xmlCallback(const char *elementPath, const char *data, } else { if (!strcmp(elementPath, "ListAllMyBucketsResult/Buckets/Bucket")) { - // Convert the date - time_t creationDate; - int millis; // Parse date. Assume ISO-8601 date format. - int hasCreationDate = parseIso8601Time(cbData->creationDate, - &creationDate, &millis); + time_t creationDate = parseIso8601Time(cbData->creationDate); // Make the callback - a bucket just finished S3Status status = (*(cbData->listServiceCallback)) (cbData->ownerId, cbData->ownerDisplayName, - cbData->bucketName, hasCreationDate ? creationDate : -1, - hasCreationDate ? millis : 0, cbData->callbackData); + cbData->bucketName, creationDate, cbData->callbackData); string_buffer_initialize(cbData->ownerId); string_buffer_initialize(cbData->ownerDisplayName); @@ -26,6 +26,10 @@ #include <string.h> #include "util.h" +static const char *urlSafeG = "-_.!~*'()/"; +static const char *hexG = "0123456789ABCDEF"; + + // Convenience utility for making the code look nicer. Tests a string // against a format; only the characters specified in the format are // checked (i.e. if the string is longer than the format, the string still @@ -33,7 +37,7 @@ // d - is a digit // anything else - is that character // Returns 0 if the string checks out, nonzero if it does not. -int checkString(const char *str, const char *format) +static int checkString(const char *str, const char *format) { while (*format) { if (*format == 'd') { @@ -51,11 +55,49 @@ int checkString(const char *str, const char *format) } -int parseIso8601Time(const char *str, time_t *secondsReturn, int *millisReturn) +int urlEncode(char *dest, const char *src, int maxSrcSize) +{ + int len = 0; + + if (src) while (*src) { + if (++len > maxSrcSize) { + return 0; + } + const char *urlsafe = urlSafeG; + int isurlsafe = 0; + while (*urlsafe) { + if (*urlsafe == *src) { + isurlsafe = 1; + break; + } + urlsafe++; + } + if (isurlsafe || isalnum(*src)) { + *dest++ = *src++; + } + else if (*src == ' ') { + *dest++ = '+'; + src++; + } + else { + *dest++ = '%'; + *dest++ = hexG[*src / 16]; + *dest++ = hexG[*src % 16]; + src++; + } + } + + *dest = 0; + + return 1; +} + + +time_t parseIso8601Time(const char *str) { // Check to make sure that it has a valid format if (checkString(str, "dddd-dd-ddTdd:dd:dd")) { - return 0; + return -1; } #define nextnum() (((*str - '0') * 10) + (*(str + 1) - '0')) @@ -86,26 +128,44 @@ int parseIso8601Time(const char *str, time_t *secondsReturn, int *millisReturn) stm.tm_isdst = -1; - *secondsReturn = mktime(&stm); + time_t ret = mktime(&stm); - *millisReturn = 0; + // Skip the millis if (*str == '.') { str++; while (isdigit(*str)) { - *millisReturn *= 10; - *millisReturn += *str++ - '0'; + str++; } } - else if (checkString(str, "-dd:dd") || checkString(str, "+dd:dd")) { + + if (checkString(str, "-dd:dd") || checkString(str, "+dd:dd")) { int sign = (*str++ == '-') ? -1 : 1; int hours = nextnum(); str += 3; int minutes = nextnum(); - *secondsReturn += (-sign * (((hours * 60) + minutes) * 60)); + ret += (-sign * (((hours * 60) + minutes) * 60)); } // Else it should be Z to be a conformant time string, but we just assume // that it is rather than enforcing that - return 1; + return ret; +} + + +uint64_t parseUnsignedInt(const char *str) +{ + // Skip whitespace + while (isblank(*str)) { + str++; + } + + uint64_t ret = 0; + + while (isdigit(*str)) { + ret *= 10; + ret += (*str++ - '0'); + } + + return ret; } |