summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan Ischo <bryan@ischo.com>2008-07-11 12:13:40 +0000
committerBryan Ischo <bryan@ischo.com>2008-07-11 12:13:40 +0000
commit037783a33a3c2f02307511bdadd4c85486217a40 (patch)
tree8f495fac334b8cba7027a73e90c366c7106b2341
parentc0535eabe25706904711f1453e724e659b93318c (diff)
downloadceph-libs3-037783a33a3c2f02307511bdadd4c85486217a40.tar.gz
* More work in progress. Initial list_bucket support.
-rw-r--r--inc/libs3.h44
-rw-r--r--inc/util.h12
-rw-r--r--src/bucket.c332
-rw-r--r--src/general.c1
-rw-r--r--src/request.c66
-rw-r--r--src/s3.c166
-rw-r--r--src/service.c9
-rw-r--r--src/util.c80
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);
diff --git a/inc/util.h b/inc/util.h
index c56f3cc..5c6c865 100644
--- a/inc/util.h
+++ b/inc/util.h
@@ -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(&params, 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.
diff --git a/src/s3.c b/src/s3.c
index f2f5b81..07b65ce 100644
--- a/src/s3.c
+++ b/src/s3.c
@@ -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);
diff --git a/src/util.c b/src/util.c
index 19a4d90..72a19a2 100644
--- a/src/util.c
+++ b/src/util.c
@@ -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;
}