Include libs3 sources in the BlueSky tree.
[bluesky.git] / libs3-1.4 / src / general.c
diff --git a/libs3-1.4/src/general.c b/libs3-1.4/src/general.c
new file mode 100644 (file)
index 0000000..861c289
--- /dev/null
@@ -0,0 +1,475 @@
+/** **************************************************************************
+ * general.c
+ * 
+ * Copyright 2008 Bryan Ischo <bryan@ischo.com>
+ * 
+ * This file is part of libs3.
+ * 
+ * libs3 is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3 of the License.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this library and its programs with the
+ * OpenSSL library, and distribute linked combinations including the two.
+ *
+ * libs3 is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License version 3
+ * along with libs3, in a file named COPYING.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ ************************************************************************** **/
+
+#include <ctype.h>
+#include <string.h>
+#include "request.h"
+#include "simplexml.h"
+#include "util.h"
+
+static int initializeCountG = 0;
+
+S3Status S3_initialize(const char *userAgentInfo, int flags)
+{
+    if (initializeCountG++) {
+        return S3StatusOK;
+    }
+
+    return request_api_initialize(userAgentInfo, flags);
+}
+
+
+void S3_deinitialize()
+{
+    if (--initializeCountG) {
+        return;
+    }
+
+    request_api_deinitialize();
+}
+
+const char *S3_get_status_name(S3Status status)
+{
+    switch (status) {
+#define handlecase(s)                           \
+        case S3Status##s:                       \
+            return #s
+
+        handlecase(OK);
+        handlecase(InternalError);
+        handlecase(OutOfMemory);
+        handlecase(Interrupted);
+        handlecase(InvalidBucketNameTooLong);
+        handlecase(InvalidBucketNameFirstCharacter);
+        handlecase(InvalidBucketNameCharacter);
+        handlecase(InvalidBucketNameCharacterSequence);
+        handlecase(InvalidBucketNameTooShort);
+        handlecase(InvalidBucketNameDotQuadNotation);
+        handlecase(QueryParamsTooLong);
+        handlecase(FailedToInitializeRequest);
+        handlecase(MetaDataHeadersTooLong);
+        handlecase(BadMetaData);
+        handlecase(BadContentType);
+        handlecase(ContentTypeTooLong);
+        handlecase(BadMD5);
+        handlecase(MD5TooLong);
+        handlecase(BadCacheControl);
+        handlecase(CacheControlTooLong);
+        handlecase(BadContentDispositionFilename);
+        handlecase(ContentDispositionFilenameTooLong);
+        handlecase(BadContentEncoding);
+        handlecase(ContentEncodingTooLong);
+        handlecase(BadIfMatchETag);
+        handlecase(IfMatchETagTooLong);
+        handlecase(BadIfNotMatchETag);
+        handlecase(IfNotMatchETagTooLong);
+        handlecase(HeadersTooLong);
+        handlecase(KeyTooLong);
+        handlecase(UriTooLong);
+        handlecase(XmlParseFailure);
+        handlecase(EmailAddressTooLong);
+        handlecase(UserIdTooLong);
+        handlecase(UserDisplayNameTooLong);
+        handlecase(GroupUriTooLong);
+        handlecase(PermissionTooLong);
+        handlecase(TargetBucketTooLong);
+        handlecase(TargetPrefixTooLong);
+        handlecase(TooManyGrants);
+        handlecase(BadGrantee);
+        handlecase(BadPermission);
+        handlecase(XmlDocumentTooLarge);
+        handlecase(NameLookupError);
+        handlecase(FailedToConnect);
+        handlecase(ServerFailedVerification);
+        handlecase(ConnectionFailed);
+        handlecase(AbortedByCallback);
+        handlecase(ErrorAccessDenied);
+        handlecase(ErrorAccountProblem);
+        handlecase(ErrorAmbiguousGrantByEmailAddress);
+        handlecase(ErrorBadDigest);
+        handlecase(ErrorBucketAlreadyExists);
+        handlecase(ErrorBucketAlreadyOwnedByYou);
+        handlecase(ErrorBucketNotEmpty);
+        handlecase(ErrorCredentialsNotSupported);
+        handlecase(ErrorCrossLocationLoggingProhibited);
+        handlecase(ErrorEntityTooSmall);
+        handlecase(ErrorEntityTooLarge);
+        handlecase(ErrorExpiredToken);
+        handlecase(ErrorIncompleteBody);
+        handlecase(ErrorIncorrectNumberOfFilesInPostRequest);
+        handlecase(ErrorInlineDataTooLarge);
+        handlecase(ErrorInternalError);
+        handlecase(ErrorInvalidAccessKeyId);
+        handlecase(ErrorInvalidAddressingHeader);
+        handlecase(ErrorInvalidArgument);
+        handlecase(ErrorInvalidBucketName);
+        handlecase(ErrorInvalidDigest);
+        handlecase(ErrorInvalidLocationConstraint);
+        handlecase(ErrorInvalidPayer);
+        handlecase(ErrorInvalidPolicyDocument);
+        handlecase(ErrorInvalidRange);
+        handlecase(ErrorInvalidSecurity);
+        handlecase(ErrorInvalidSOAPRequest);
+        handlecase(ErrorInvalidStorageClass);
+        handlecase(ErrorInvalidTargetBucketForLogging);
+        handlecase(ErrorInvalidToken);
+        handlecase(ErrorInvalidURI);
+        handlecase(ErrorKeyTooLong);
+        handlecase(ErrorMalformedACLError);
+        handlecase(ErrorMalformedXML);
+        handlecase(ErrorMaxMessageLengthExceeded);
+        handlecase(ErrorMaxPostPreDataLengthExceededError);
+        handlecase(ErrorMetadataTooLarge);
+        handlecase(ErrorMethodNotAllowed);
+        handlecase(ErrorMissingAttachment);
+        handlecase(ErrorMissingContentLength);
+        handlecase(ErrorMissingSecurityElement);
+        handlecase(ErrorMissingSecurityHeader);
+        handlecase(ErrorNoLoggingStatusForKey);
+        handlecase(ErrorNoSuchBucket);
+        handlecase(ErrorNoSuchKey);
+        handlecase(ErrorNotImplemented);
+        handlecase(ErrorNotSignedUp);
+        handlecase(ErrorOperationAborted);
+        handlecase(ErrorPermanentRedirect);
+        handlecase(ErrorPreconditionFailed);
+        handlecase(ErrorRedirect);
+        handlecase(ErrorRequestIsNotMultiPartContent);
+        handlecase(ErrorRequestTimeout);
+        handlecase(ErrorRequestTimeTooSkewed);
+        handlecase(ErrorRequestTorrentOfBucketError);
+        handlecase(ErrorSignatureDoesNotMatch);
+        handlecase(ErrorSlowDown);
+        handlecase(ErrorTemporaryRedirect);
+        handlecase(ErrorTokenRefreshRequired);
+        handlecase(ErrorTooManyBuckets);
+        handlecase(ErrorUnexpectedContent);
+        handlecase(ErrorUnresolvableGrantByEmailAddress);
+        handlecase(ErrorUserKeyMustBeSpecified);
+        handlecase(ErrorUnknown);    
+        handlecase(HttpErrorMovedTemporarily);
+        handlecase(HttpErrorBadRequest);
+        handlecase(HttpErrorForbidden);
+        handlecase(HttpErrorNotFound);
+        handlecase(HttpErrorConflict);
+        handlecase(HttpErrorUnknown);
+    }
+
+    return "Unknown";
+}
+
+
+S3Status S3_validate_bucket_name(const char *bucketName, S3UriStyle uriStyle)
+{
+    int virtualHostStyle = (uriStyle == S3UriStyleVirtualHost);
+    int len = 0, maxlen = virtualHostStyle ? 63 : 255;
+    const char *b = bucketName;
+
+    int hasDot = 0;
+    int hasNonDigit = 0;
+
+    while (*b) {
+        if (len == maxlen) {
+            return S3StatusInvalidBucketNameTooLong;
+        }
+        else if (isalpha(*b)) {
+            len++, b++;
+            hasNonDigit = 1;
+        }
+        else if (isdigit(*b)) {
+            len++, b++;
+        }
+        else if (len == 0) {
+            return S3StatusInvalidBucketNameFirstCharacter;
+        }
+        else if (*b == '_') {
+            /* Virtual host style bucket names cannot have underscores */
+            if (virtualHostStyle) {
+                return S3StatusInvalidBucketNameCharacter;
+            }
+            len++, b++;
+            hasNonDigit = 1;
+        }
+        else if (*b == '-') {
+            /* Virtual host style bucket names cannot have .- */
+            if (virtualHostStyle && (b > bucketName) && (*(b - 1) == '.')) {
+                return S3StatusInvalidBucketNameCharacterSequence;
+            }
+            len++, b++;
+            hasNonDigit = 1;
+        }
+        else if (*b == '.') {
+            /* Virtual host style bucket names cannot have -. */
+            if (virtualHostStyle && (b > bucketName) && (*(b - 1) == '-')) {
+                return S3StatusInvalidBucketNameCharacterSequence;
+            }
+            len++, b++;
+            hasDot = 1;
+        }
+        else {
+            return S3StatusInvalidBucketNameCharacter;
+        }
+    }
+
+    if (len < 3) {
+        return S3StatusInvalidBucketNameTooShort;
+    }
+
+    /* It's not clear from Amazon's documentation exactly what 'IP address
+       style' means.  In its strictest sense, it could mean 'could be a valid
+       IP address', which would mean that 255.255.255.255 would be invalid,
+       wherase 256.256.256.256 would be valid.  Or it could mean 'has 4 sets
+       of digits separated by dots'.  Who knows.  Let's just be really
+       conservative here: if it has any dots, and no non-digit characters,
+       then we reject it */
+    if (hasDot && !hasNonDigit) {
+        return S3StatusInvalidBucketNameDotQuadNotation;
+    }
+
+    return S3StatusOK;
+}
+
+
+typedef struct ConvertAclData
+{
+    char *ownerId;
+    int ownerIdLen;
+    char *ownerDisplayName;
+    int ownerDisplayNameLen;
+    int *aclGrantCountReturn;
+    S3AclGrant *aclGrants;
+
+    string_buffer(emailAddress, S3_MAX_GRANTEE_EMAIL_ADDRESS_SIZE);
+    string_buffer(userId, S3_MAX_GRANTEE_USER_ID_SIZE);
+    string_buffer(userDisplayName, S3_MAX_GRANTEE_DISPLAY_NAME_SIZE);
+    string_buffer(groupUri, 128);
+    string_buffer(permission, 32);
+} ConvertAclData;
+
+
+static S3Status convertAclXmlCallback(const char *elementPath,
+                                      const char *data, int dataLen,
+                                      void *callbackData)
+{
+    ConvertAclData *caData = (ConvertAclData *) callbackData;
+
+    int fit;
+
+    if (data) {
+        if (!strcmp(elementPath, "AccessControlPolicy/Owner/ID")) {
+            caData->ownerIdLen += 
+                snprintf(&(caData->ownerId[caData->ownerIdLen]),
+                         S3_MAX_GRANTEE_USER_ID_SIZE - caData->ownerIdLen - 1,
+                         "%.*s", dataLen, data);
+            if (caData->ownerIdLen >= S3_MAX_GRANTEE_USER_ID_SIZE) {
+                return S3StatusUserIdTooLong;
+            }
+        }
+        else if (!strcmp(elementPath, "AccessControlPolicy/Owner/"
+                         "DisplayName")) {
+            caData->ownerDisplayNameLen += 
+                snprintf(&(caData->ownerDisplayName
+                           [caData->ownerDisplayNameLen]),
+                         S3_MAX_GRANTEE_DISPLAY_NAME_SIZE -
+                         caData->ownerDisplayNameLen - 1, 
+                         "%.*s", dataLen, data);
+            if (caData->ownerDisplayNameLen >= 
+                S3_MAX_GRANTEE_DISPLAY_NAME_SIZE) {
+                return S3StatusUserDisplayNameTooLong;
+            }
+        }
+        else if (!strcmp(elementPath, 
+                    "AccessControlPolicy/AccessControlList/Grant/"
+                    "Grantee/EmailAddress")) {
+            // AmazonCustomerByEmail
+            string_buffer_append(caData->emailAddress, data, dataLen, fit);
+            if (!fit) {
+                return S3StatusEmailAddressTooLong;
+            }
+        }
+        else if (!strcmp(elementPath,
+                         "AccessControlPolicy/AccessControlList/Grant/"
+                         "Grantee/ID")) {
+            // CanonicalUser
+            string_buffer_append(caData->userId, data, dataLen, fit);
+            if (!fit) {
+                return S3StatusUserIdTooLong;
+            }
+        }
+        else if (!strcmp(elementPath,
+                         "AccessControlPolicy/AccessControlList/Grant/"
+                         "Grantee/DisplayName")) {
+            // CanonicalUser
+            string_buffer_append(caData->userDisplayName, data, dataLen, fit);
+            if (!fit) {
+                return S3StatusUserDisplayNameTooLong;
+            }
+        }
+        else if (!strcmp(elementPath,
+                         "AccessControlPolicy/AccessControlList/Grant/"
+                         "Grantee/URI")) {
+            // Group
+            string_buffer_append(caData->groupUri, data, dataLen, fit);
+            if (!fit) {
+                return S3StatusGroupUriTooLong;
+            }
+        }
+        else if (!strcmp(elementPath,
+                         "AccessControlPolicy/AccessControlList/Grant/"
+                         "Permission")) {
+            // Permission
+            string_buffer_append(caData->permission, data, dataLen, fit);
+            if (!fit) {
+                return S3StatusPermissionTooLong;
+            }
+        }
+    }
+    else {
+        if (!strcmp(elementPath, "AccessControlPolicy/AccessControlList/"
+                    "Grant")) {
+            // A grant has just been completed; so add the next S3AclGrant
+            // based on the values read
+            if (*(caData->aclGrantCountReturn) == S3_MAX_ACL_GRANT_COUNT) {
+                return S3StatusTooManyGrants;
+            }
+
+            S3AclGrant *grant = &(caData->aclGrants
+                                  [*(caData->aclGrantCountReturn)]);
+
+            if (caData->emailAddress[0]) {
+                grant->granteeType = S3GranteeTypeAmazonCustomerByEmail;
+                strcpy(grant->grantee.amazonCustomerByEmail.emailAddress,
+                       caData->emailAddress);
+            }
+            else if (caData->userId[0] && caData->userDisplayName[0]) {
+                grant->granteeType = S3GranteeTypeCanonicalUser;
+                strcpy(grant->grantee.canonicalUser.id, caData->userId);
+                strcpy(grant->grantee.canonicalUser.displayName, 
+                       caData->userDisplayName);
+            }
+            else if (caData->groupUri[0]) {
+                if (!strcmp(caData->groupUri,
+                            "http://acs.amazonaws.com/groups/global/"
+                            "AuthenticatedUsers")) {
+                    grant->granteeType = S3GranteeTypeAllAwsUsers;
+                }
+                else if (!strcmp(caData->groupUri,
+                                 "http://acs.amazonaws.com/groups/global/"
+                                 "AllUsers")) {
+                    grant->granteeType = S3GranteeTypeAllUsers;
+                }
+                else if (!strcmp(caData->groupUri,
+                                 "http://acs.amazonaws.com/groups/s3/"
+                                 "LogDelivery")) {
+                    grant->granteeType = S3GranteeTypeLogDelivery;
+                }
+                else {
+                    return S3StatusBadGrantee;
+                }
+            }
+            else {
+                return S3StatusBadGrantee;
+            }
+
+            if (!strcmp(caData->permission, "READ")) {
+                grant->permission = S3PermissionRead;
+            }
+            else if (!strcmp(caData->permission, "WRITE")) {
+                grant->permission = S3PermissionWrite;
+            }
+            else if (!strcmp(caData->permission, "READ_ACP")) {
+                grant->permission = S3PermissionReadACP;
+            }
+            else if (!strcmp(caData->permission, "WRITE_ACP")) {
+                grant->permission = S3PermissionWriteACP;
+            }
+            else if (!strcmp(caData->permission, "FULL_CONTROL")) {
+                grant->permission = S3PermissionFullControl;
+            }
+            else {
+                return S3StatusBadPermission;
+            }
+
+            (*(caData->aclGrantCountReturn))++;
+
+            string_buffer_initialize(caData->emailAddress);
+            string_buffer_initialize(caData->userId);
+            string_buffer_initialize(caData->userDisplayName);
+            string_buffer_initialize(caData->groupUri);
+            string_buffer_initialize(caData->permission);
+        }
+    }
+
+    return S3StatusOK;
+}
+
+
+S3Status S3_convert_acl(char *aclXml, char *ownerId, char *ownerDisplayName,
+                        int *aclGrantCountReturn, S3AclGrant *aclGrants)
+{
+    ConvertAclData data;
+
+    data.ownerId = ownerId;
+    data.ownerIdLen = 0;
+    data.ownerId[0] = 0;
+    data.ownerDisplayName = ownerDisplayName;
+    data.ownerDisplayNameLen = 0;
+    data.ownerDisplayName[0] = 0;
+    data.aclGrantCountReturn = aclGrantCountReturn;
+    data.aclGrants = aclGrants;
+    *aclGrantCountReturn = 0;
+    string_buffer_initialize(data.emailAddress);
+    string_buffer_initialize(data.userId);
+    string_buffer_initialize(data.userDisplayName);
+    string_buffer_initialize(data.groupUri);
+    string_buffer_initialize(data.permission);
+
+    // Use a simplexml parser
+    SimpleXml simpleXml;
+    simplexml_initialize(&simpleXml, &convertAclXmlCallback, &data);
+
+    S3Status status = simplexml_add(&simpleXml, aclXml, strlen(aclXml));
+
+    simplexml_deinitialize(&simpleXml);
+                                          
+    return status;
+}
+
+
+int S3_status_is_retryable(S3Status status)
+{
+    switch (status) {
+    case S3StatusNameLookupError:
+    case S3StatusFailedToConnect:
+    case S3StatusConnectionFailed:
+    case S3StatusErrorInternalError:
+    case S3StatusErrorOperationAborted:
+    case S3StatusErrorRequestTimeout:
+        return 1;
+    default:
+        return 0;
+    }
+}