diff --git a/include/aws/s3/private/s3_request_messages.h b/include/aws/s3/private/s3_request_messages.h index b33df3a89..32f0c80cd 100644 --- a/include/aws/s3/private/s3_request_messages.h +++ b/include/aws/s3/private/s3_request_messages.h @@ -5,7 +5,7 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ - +#include "aws/s3/s3.h" #include #include #include @@ -17,46 +17,59 @@ struct aws_byte_cursor; struct aws_string; struct aws_array_list; -enum aws_s3_copy_http_message_flags { - AWS_S3_COPY_MESSAGE_INCLUDE_SSE = 0x00000001, - /* For multipart upload complete and abort, only host and two payer related headers are needed */ - AWS_S3_COPY_MESSAGE_MULTIPART_UPLOAD_OPS = 0x00000002, - /* For ranged put, acl should not be there */ - AWS_S3_COPY_MESSAGE_WITHOUT_ACL = 0x00000004, -}; +AWS_EXTERN_C_BEGIN + +AWS_S3_API +struct aws_http_message *aws_s3_message_util_copy_http_message( + struct aws_allocator *allocator, + struct aws_http_message *message, + const struct aws_byte_cursor *excluded_headers_arrays, + size_t excluded_headers_size); + +AWS_S3_API +struct aws_input_stream *aws_s3_message_util_assign_body( + struct aws_allocator *allocator, + struct aws_byte_buf *byte_buf, + struct aws_http_message *out_message); /* Create an HTTP request for an S3 Get Object Request, using the original request as a basis. If multipart is not * needed, part_number and part_size can be 0. */ +AWS_S3_API struct aws_http_message *aws_s3_get_object_message_new( struct aws_allocator *allocator, struct aws_http_message *base_message, uint32_t part_number, - size_t part_size, - bool has_range); + size_t part_size); -/* Create an HTTP request for an S3 Put Object request, using the original request as a basis. Creates and assigns a - * body stream using the passed in buffer. If multipart is not needed, part number and upload_id can be 0 and NULL, - * respectively. */ -struct aws_http_message *aws_s3_put_object_message_new( +AWS_S3_API +int aws_s3_message_util_set_multipart_request_path( struct aws_allocator *allocator, - struct aws_http_message *base_message, - struct aws_byte_buf *buffer, + const struct aws_string *upload_id, uint32_t part_number, - const struct aws_string *upload_id); + bool append_uploads_suffix, + struct aws_http_message *message); /* Create an HTTP request for an S3 Create-Multipart-Upload request. */ +AWS_S3_API struct aws_http_message *aws_s3_create_multipart_upload_message_new( struct aws_allocator *allocator, struct aws_http_message *base_message); -/* Given a response body from a multipart upload, try to extract the upload id. */ -struct aws_string *aws_s3_create_multipart_upload_get_upload_id( +/* Create an HTTP request for an S3 Put Object request, using the original request as a basis. Creates and assigns a + * body stream using the passed in buffer. If multipart is not needed, part number and upload_id can be 0 and NULL, + * respectively. */ +AWS_S3_API +struct aws_http_message *aws_s3_upload_part_message_new( struct aws_allocator *allocator, - struct aws_byte_cursor *response_body); + struct aws_http_message *base_message, + struct aws_byte_buf *buffer, + uint32_t part_number, + const struct aws_string *upload_id); /* Create an HTTP request for an S3 Complete-Multipart-Upload request. Creates the necessary XML payload using the * passed in array list of ETags. (Each ETag is assumed to be an aws_string*) Buffer passed in will be used to store * said XML payload, which will be used as the body. */ +AWS_S3_API struct aws_http_message *aws_s3_complete_multipart_message_new( struct aws_allocator *allocator, struct aws_http_message *base_message, @@ -64,20 +77,36 @@ struct aws_http_message *aws_s3_complete_multipart_message_new( const struct aws_string *upload_id, const struct aws_array_list *etags); +AWS_S3_API struct aws_http_message *aws_s3_abort_multipart_upload_message_new( struct aws_allocator *allocator, struct aws_http_message *base_message, const struct aws_string *upload_id); -/* TODO: maybe set a list of the headers we want */ -struct aws_http_message *aws_s3_message_util_copy_http_message( - struct aws_allocator *allocator, - struct aws_http_message *message, - uint32_t flags); +AWS_S3_API +extern const struct aws_byte_cursor g_s3_create_multipart_upload_excluded_headers[]; -struct aws_input_stream *aws_s3_message_util_assign_body( - struct aws_allocator *allocator, - struct aws_byte_buf *byte_buf, - struct aws_http_message *out_message); +AWS_S3_API +extern const size_t g_s3_create_multipart_upload_excluded_headers_count; + +AWS_S3_API +extern const struct aws_byte_cursor g_s3_upload_part_excluded_headers[]; + +AWS_S3_API +extern const size_t g_s3_upload_part_excluded_headers_count; + +AWS_S3_API +extern const struct aws_byte_cursor g_s3_complete_multipart_upload_excluded_headers[]; + +AWS_S3_API +extern const size_t g_s3_complete_multipart_upload_excluded_headers_count; + +AWS_S3_API +extern const struct aws_byte_cursor g_s3_abort_multipart_upload_excluded_headers[]; + +AWS_S3_API +extern const size_t g_s3_abort_multipart_upload_excluded_headers_count; + +AWS_EXTERN_C_END #endif /* AWS_S3_REQUEST_H */ diff --git a/include/aws/s3/private/s3_util.h b/include/aws/s3/private/s3_util.h index e1b16586f..9ff7a7208 100644 --- a/include/aws/s3/private/s3_util.h +++ b/include/aws/s3/private/s3_util.h @@ -39,14 +39,6 @@ struct aws_cached_signing_config_aws { struct aws_signing_config_aws config; }; -extern const struct aws_byte_cursor g_s3_service_name; -extern const struct aws_byte_cursor g_range_header_name; -extern const struct aws_byte_cursor g_content_range_header_name; -extern const struct aws_byte_cursor g_accept_ranges_header_name; -extern const struct aws_byte_cursor g_post_method; -extern const struct aws_byte_cursor g_delete_method; -extern const uint32_t g_s3_max_num_upload_parts; - AWS_EXTERN_C_BEGIN AWS_S3_API @@ -76,6 +68,27 @@ extern const struct aws_byte_cursor g_etag_header_name; AWS_S3_API extern const size_t g_s3_min_upload_part_size; +AWS_S3_API +extern const struct aws_byte_cursor g_s3_service_name; + +AWS_S3_API +extern const struct aws_byte_cursor g_range_header_name; + +AWS_S3_API +extern const struct aws_byte_cursor g_content_range_header_name; + +AWS_S3_API +extern const struct aws_byte_cursor g_accept_ranges_header_name; + +AWS_S3_API +extern const struct aws_byte_cursor g_post_method; + +AWS_S3_API +extern const struct aws_byte_cursor g_delete_method; + +AWS_S3_API +extern const uint32_t g_s3_max_num_upload_parts; + struct aws_cached_signing_config_aws *aws_cached_signing_config_new( struct aws_allocator *allocator, const struct aws_signing_config_aws *signing_config); diff --git a/source/s3_auto_ranged_get.c b/source/s3_auto_ranged_get.c index e7f633aff..59413682c 100644 --- a/source/s3_auto_ranged_get.c +++ b/source/s3_auto_ranged_get.c @@ -200,7 +200,7 @@ static bool s_s3_auto_ranged_get_update( request = aws_s3_request_new( meta_request, AWS_S3_AUTO_RANGE_GET_REQUEST_TYPE_PART_WITHOUT_RANGE, - 1, + 0, AWS_S3_REQUEST_DESC_RECORD_RESPONSE_HEADERS); auto_ranged_get->synced_data.get_without_range_sent = true; @@ -297,11 +297,7 @@ static int s_s3_auto_ranged_get_prepare_request( /* Generate a new ranged get request based on the original message. */ struct aws_http_message *message = aws_s3_get_object_message_new( - meta_request->allocator, - meta_request->initial_request_message, - request->part_number, - meta_request->part_size, - request->request_tag != AWS_S3_AUTO_RANGE_GET_REQUEST_TYPE_PART_WITHOUT_RANGE); + meta_request->allocator, meta_request->initial_request_message, request->part_number, meta_request->part_size); if (message == NULL) { AWS_LOGF_ERROR( @@ -344,7 +340,9 @@ static void s_s3_auto_ranged_get_request_finished( uint32_t num_parts = 0; - if (error_code == AWS_ERROR_SUCCESS && request->part_number == 1) { + /* Check if this was the first part and if it was successful. For a ranged-get request, the first part number will + * be 1. For an empty file request, the part number will be 0.*/ + if (error_code == AWS_ERROR_SUCCESS && request->part_number <= 1) { uint64_t total_object_size = 0; if (request->request_tag == AWS_S3_AUTO_RANGE_GET_REQUEST_TYPE_PART) { struct aws_byte_cursor content_range_header_value; diff --git a/source/s3_auto_ranged_put.c b/source/s3_auto_ranged_put.c index 69858d9c4..699de1664 100644 --- a/source/s3_auto_ranged_put.c +++ b/source/s3_auto_ranged_put.c @@ -368,7 +368,7 @@ static int s_s3_auto_ranged_put_prepare_request( } /* Create a new put-object message to upload a part. */ - message = aws_s3_put_object_message_new( + message = aws_s3_upload_part_message_new( meta_request->allocator, meta_request->initial_request_message, &request->request_body, diff --git a/source/s3_default_meta_request.c b/source/s3_default_meta_request.c index c20b171cf..72ac9a891 100644 --- a/source/s3_default_meta_request.c +++ b/source/s3_default_meta_request.c @@ -226,8 +226,8 @@ static int s_s3_meta_request_default_prepare_request( } } - struct aws_http_message *message = aws_s3_message_util_copy_http_message( - meta_request->allocator, meta_request->initial_request_message, AWS_S3_COPY_MESSAGE_INCLUDE_SSE); + struct aws_http_message *message = + aws_s3_message_util_copy_http_message(meta_request->allocator, meta_request->initial_request_message, NULL, 0); aws_s3_message_util_assign_body(meta_request->allocator, &request->request_body, message); diff --git a/source/s3_meta_request.c b/source/s3_meta_request.c index 24c276057..51a703a24 100644 --- a/source/s3_meta_request.c +++ b/source/s3_meta_request.c @@ -571,12 +571,21 @@ static void s_s3_meta_request_request_on_signed( aws_apply_signing_result_to_http_request(request->send_data.message, meta_request->allocator, signing_result)) { error_code = aws_last_error_or_unknown(); + goto finish; } finish: if (error_code != AWS_ERROR_SUCCESS) { + + AWS_LOGF_ERROR( + AWS_LS_S3_META_REQUEST, + "id=%p Meta request could not sign TTP request due to error code %d (%s)", + (void *)meta_request, + error_code, + aws_error_str(error_code)); + aws_s3_meta_request_lock_synced_data(meta_request); aws_s3_meta_request_set_fail_synced(meta_request, request, error_code); aws_s3_meta_request_unlock_synced_data(meta_request); diff --git a/source/s3_request_messages.c b/source/s3_request_messages.c index c0324dcf4..739c35c49 100644 --- a/source/s3_request_messages.c +++ b/source/s3_request_messages.c @@ -14,38 +14,128 @@ #include #include -static int s_s3_message_util_set_multipart_request_path( - struct aws_allocator *allocator, - const struct aws_string *upload_id, - uint32_t part_number, - struct aws_http_message *message); +const struct aws_byte_cursor g_s3_create_multipart_upload_excluded_headers[] = { + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Length"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-MD5"), +}; + +const size_t g_s3_create_multipart_upload_excluded_headers_count = + AWS_ARRAY_SIZE(g_s3_create_multipart_upload_excluded_headers); + +const struct aws_byte_cursor g_s3_upload_part_excluded_headers[] = { + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-acl"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Cache-Control"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Disposition"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Encoding"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Language"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Length"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-MD5"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Type"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Expires"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-grant-full-control"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-grant-read"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-grant-read-acp"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-grant-write-acp"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-storage-class"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-website-redirect-location"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-aws-kms-key-id"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-context"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-bucket-key-enabled"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-tagging"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-object-lock-mode"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-object-lock-retain-until-date"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-object-lock-legal-hold"), +}; + +const size_t g_s3_upload_part_excluded_headers_count = AWS_ARRAY_SIZE(g_s3_upload_part_excluded_headers); + +const struct aws_byte_cursor g_s3_complete_multipart_upload_excluded_headers[] = { + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-acl"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Cache-Control"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Disposition"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Encoding"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Language"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Length"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-MD5"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Type"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Expires"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-grant-full-control"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-grant-read"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-grant-read-acp"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-grant-write-acp"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-storage-class"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-website-redirect-location"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-customer-algorithm"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-customer-key"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-customer-key-MD5"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-aws-kms-key-id"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-context"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-bucket-key-enabled"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-tagging"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-object-lock-mode"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-object-lock-retain-until-date"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-object-lock-legal-hold"), +}; + +const size_t g_s3_complete_multipart_upload_excluded_headers_count = + AWS_ARRAY_SIZE(g_s3_complete_multipart_upload_excluded_headers); + +const struct aws_byte_cursor g_s3_abort_multipart_upload_excluded_headers[] = { + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-acl"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Cache-Control"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Disposition"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Encoding"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Language"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Length"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-MD5"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Type"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Expires"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-grant-full-control"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-grant-read"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-grant-read-acp"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-grant-write-acp"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-storage-class"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-website-redirect-location"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-customer-algorithm"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-customer-key"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-customer-key-MD5"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-aws-kms-key-id"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-context"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-bucket-key-enabled"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-tagging"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-object-lock-mode"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-object-lock-retain-until-date"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-object-lock-legal-hold"), +}; + +const size_t g_s3_abort_multipart_upload_excluded_headers_count = + AWS_ARRAY_SIZE(g_s3_abort_multipart_upload_excluded_headers); static int s_s3_message_util_add_content_range_header( uint64_t part_index, uint64_t part_size, struct aws_http_message *out_message); -static int s_s3_create_multipart_set_up_request_path(struct aws_allocator *allocator, struct aws_http_message *message); - /* Create a new get object request from an existing get object request. Currently just adds an optional ranged header. */ struct aws_http_message *aws_s3_get_object_message_new( struct aws_allocator *allocator, struct aws_http_message *base_message, uint32_t part_number, - size_t part_size, - bool has_range) { + size_t part_size) { AWS_PRECONDITION(allocator); AWS_PRECONDITION(base_message); - struct aws_http_message *message = - aws_s3_message_util_copy_http_message(allocator, base_message, AWS_S3_COPY_MESSAGE_INCLUDE_SSE); + struct aws_http_message *message = aws_s3_message_util_copy_http_message(allocator, base_message, NULL, 0); if (message == NULL) { return NULL; } - if (part_number > 0 && has_range) { + if (part_number > 0) { if (s_s3_message_util_add_content_range_header(part_number - 1, part_size, message)) { goto error_clean_up; } @@ -63,38 +153,36 @@ struct aws_http_message *aws_s3_get_object_message_new( return NULL; } -/* Create a new put object request from an existing put object request. Currently just optionally adds part information - * for a multipart upload. */ -struct aws_http_message *aws_s3_put_object_message_new( +/* Creates a create-multipart-upload request from a given put objet request. */ +struct aws_http_message *aws_s3_create_multipart_upload_message_new( struct aws_allocator *allocator, - struct aws_http_message *base_message, - struct aws_byte_buf *buffer, - uint32_t part_number, - const struct aws_string *upload_id) { + struct aws_http_message *base_message) { AWS_PRECONDITION(allocator); - AWS_PRECONDITION(base_message); /* For multipart upload, sse related headers should only be shown in create-multipart request */ - uint32_t flag = part_number > 0 ? AWS_S3_COPY_MESSAGE_WITHOUT_ACL : 0; - struct aws_http_message *message = aws_s3_message_util_copy_http_message(allocator, base_message, flag); + struct aws_http_message *message = aws_s3_message_util_copy_http_message( + allocator, + base_message, + g_s3_create_multipart_upload_excluded_headers, + AWS_ARRAY_SIZE(g_s3_create_multipart_upload_excluded_headers)); if (message == NULL) { goto error_clean_up; } - if (part_number > 0) { + if (aws_s3_message_util_set_multipart_request_path(allocator, NULL, 0, true, message)) { + goto error_clean_up; + } - if (s_s3_message_util_set_multipart_request_path(allocator, upload_id, part_number, message)) { - goto error_clean_up; - } + struct aws_http_headers *headers = aws_http_message_get_headers(message); - if (buffer != NULL) { - if (aws_s3_message_util_assign_body(allocator, buffer, message) == NULL) { - goto error_clean_up; - } - } + if (headers == NULL) { + goto error_clean_up; } + aws_http_message_set_request_method(message, g_post_method); + aws_http_message_set_body_stream(message, NULL); + return message; error_clean_up: @@ -107,39 +195,35 @@ struct aws_http_message *aws_s3_put_object_message_new( return NULL; } -/* Creates a create-multipart-upload request from a given put objet request. */ -struct aws_http_message *aws_s3_create_multipart_upload_message_new( +/* Create a new put object request from an existing put object request. Currently just optionally adds part information + * for a multipart upload. */ +struct aws_http_message *aws_s3_upload_part_message_new( struct aws_allocator *allocator, - struct aws_http_message *base_message) { + struct aws_http_message *base_message, + struct aws_byte_buf *buffer, + uint32_t part_number, + const struct aws_string *upload_id) { AWS_PRECONDITION(allocator); + AWS_PRECONDITION(base_message); + AWS_PRECONDITION(part_number > 0); - /* For multipart upload, sse related headers should only be shown in create-multipart request */ - struct aws_http_message *message = - aws_s3_message_util_copy_http_message(allocator, base_message, AWS_S3_COPY_MESSAGE_INCLUDE_SSE); - struct aws_http_headers *headers = NULL; + struct aws_http_message *message = aws_s3_message_util_copy_http_message( + allocator, base_message, g_s3_upload_part_excluded_headers, AWS_ARRAY_SIZE(g_s3_upload_part_excluded_headers)); if (message == NULL) { goto error_clean_up; } - if (s_s3_create_multipart_set_up_request_path(allocator, message)) { - goto error_clean_up; - } - - headers = aws_http_message_get_headers(message); - - if (headers == NULL) { + if (aws_s3_message_util_set_multipart_request_path(allocator, upload_id, part_number, false, message)) { goto error_clean_up; } - if (aws_http_headers_erase(headers, g_content_length_header_name)) { - goto error_clean_up; + if (buffer != NULL) { + if (aws_s3_message_util_assign_body(allocator, buffer, message) == NULL) { + goto error_clean_up; + } } - aws_http_message_set_request_method(message, g_post_method); - - aws_http_message_set_body_stream(message, NULL); - return message; error_clean_up: @@ -182,16 +266,19 @@ struct aws_http_message *aws_s3_complete_multipart_message_new( AWS_PRECONDITION(upload_id); AWS_PRECONDITION(etags); - /* For multipart upload, sse related headers should only be shown in create-multipart request */ - struct aws_http_message *message = - aws_s3_message_util_copy_http_message(allocator, base_message, AWS_S3_COPY_MESSAGE_MULTIPART_UPLOAD_OPS); + struct aws_http_message *message = aws_s3_message_util_copy_http_message( + allocator, + base_message, + g_s3_complete_multipart_upload_excluded_headers, + AWS_ARRAY_SIZE(g_s3_complete_multipart_upload_excluded_headers)); + struct aws_http_headers *headers = NULL; if (message == NULL) { goto error_clean_up; } - if (s_s3_message_util_set_multipart_request_path(allocator, upload_id, 0, message)) { + if (aws_s3_message_util_set_multipart_request_path(allocator, upload_id, 0, false, message)) { goto error_clean_up; } @@ -272,10 +359,14 @@ struct aws_http_message *aws_s3_abort_multipart_upload_message_new( struct aws_allocator *allocator, struct aws_http_message *base_message, const struct aws_string *upload_id) { - struct aws_http_message *message = - aws_s3_message_util_copy_http_message(allocator, base_message, AWS_S3_COPY_MESSAGE_MULTIPART_UPLOAD_OPS); - if (s_s3_message_util_set_multipart_request_path(allocator, upload_id, 0, message)) { + struct aws_http_message *message = aws_s3_message_util_copy_http_message( + allocator, + base_message, + g_s3_abort_multipart_upload_excluded_headers, + AWS_ARRAY_SIZE(g_s3_abort_multipart_upload_excluded_headers)); + + if (aws_s3_message_util_set_multipart_request_path(allocator, upload_id, 0, false, message)) { goto error_clean_up; } aws_http_message_set_request_method(message, g_delete_method); @@ -294,50 +385,6 @@ struct aws_http_message *aws_s3_abort_multipart_upload_message_new( return NULL; } -/* Sets up the request path for a create-multipart upload request. */ -static int s_s3_create_multipart_set_up_request_path( - struct aws_allocator *allocator, - struct aws_http_message *message) { - AWS_PRECONDITION(allocator); - AWS_PRECONDITION(message); - - const struct aws_byte_cursor request_path_suffix = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("?uploads"); - - struct aws_byte_cursor request_path; - - if (aws_http_message_get_request_path(message, &request_path)) { - return AWS_OP_ERR; - } - - struct aws_byte_buf request_path_buf; - - if (aws_byte_buf_init(&request_path_buf, allocator, request_path.len + request_path_suffix.len)) { - return AWS_OP_ERR; - } - - if (aws_byte_buf_append(&request_path_buf, &request_path)) { - goto error_clean_request_path_buf; - } - - if (aws_byte_buf_append(&request_path_buf, &request_path_suffix)) { - goto error_clean_request_path_buf; - } - - struct aws_byte_cursor new_request_path = aws_byte_cursor_from_buf(&request_path_buf); - - if (aws_http_message_set_request_path(message, new_request_path)) { - goto error_clean_request_path_buf; - } - - aws_byte_buf_clean_up(&request_path_buf); - return AWS_OP_SUCCESS; - -error_clean_request_path_buf: - - aws_byte_buf_clean_up(&request_path_buf); - return AWS_OP_ERR; -} - /* Assign a buffer to an HTTP message, creating a stream and setting the content-length header */ struct aws_input_stream *aws_s3_message_util_assign_body( struct aws_allocator *allocator, @@ -384,14 +431,12 @@ struct aws_input_stream *aws_s3_message_util_assign_body( struct aws_http_message *aws_s3_message_util_copy_http_message( struct aws_allocator *allocator, struct aws_http_message *base_message, - uint32_t flags) { + const struct aws_byte_cursor *excluded_header_array, + size_t excluded_header_array_size) { AWS_PRECONDITION(allocator); AWS_PRECONDITION(base_message); struct aws_http_message *message = aws_http_message_new_request(allocator); - uint32_t sse_included = (flags & AWS_S3_COPY_MESSAGE_INCLUDE_SSE) != 0; - uint32_t multipart_upload_ops = (flags & AWS_S3_COPY_MESSAGE_MULTIPART_UPLOAD_OPS) != 0; - uint32_t no_acl = (flags & AWS_S3_COPY_MESSAGE_WITHOUT_ACL) != 0; if (message == NULL) { return NULL; @@ -424,25 +469,19 @@ struct aws_http_message *aws_s3_message_util_copy_http_message( goto error_clean_up; } - if (multipart_upload_ops) { - if (aws_byte_cursor_eq_c_str_ignore_case(&header.name, "host") || - aws_byte_cursor_eq_c_str_ignore_case(&header.name, "x-amz-request-payer") || - aws_byte_cursor_eq_c_str_ignore_case(&header.name, "x-amz-expected-bucket-owner") || - aws_byte_cursor_eq_ignore_case(&header.name, &g_user_agent_header_name)) { - if (aws_http_message_add_header(message, header)) { - goto error_clean_up; + if (excluded_header_array && excluded_header_array_size > 0) { + bool exclude_header = false; + + for (size_t exclude_index = 0; exclude_index < excluded_header_array_size; ++exclude_index) { + if (aws_byte_cursor_eq_ignore_case(&header.name, &excluded_header_array[exclude_index])) { + exclude_header = true; + break; } } - continue; - } - - /* For SSE upload, the sse related headers should only be shown in the create_multipart_upload.*/ - if (!sse_included && aws_byte_cursor_eq_c_str_ignore_case(&header.name, "x-amz-server-side-encryption")) { - continue; - } - if (no_acl && aws_byte_cursor_eq_ignore_case(&header.name, &g_acl_header_name)) { - continue; + if (exclude_header) { + continue; + } } if (aws_http_message_add_header(message, header)) { @@ -482,6 +521,13 @@ static int s_s3_message_util_add_content_range_header( range_header.name = g_range_header_name; range_header.value = aws_byte_cursor_from_c_str(range_value_buffer); + struct aws_http_headers *headers = aws_http_message_get_headers(out_message); + AWS_ASSERT(headers != NULL); + + int erase_result = aws_http_headers_erase(headers, range_header.name); + AWS_ASSERT(erase_result == AWS_OP_SUCCESS || aws_last_error() == AWS_ERROR_HTTP_HEADER_NOT_FOUND) + (void)erase_result; + if (aws_http_message_add_header(out_message, range_header)) { return AWS_OP_ERR; } @@ -490,15 +536,17 @@ static int s_s3_message_util_add_content_range_header( } /* Handle setting up the multipart request path for a message. */ -/* TODO Should be a more compact way of writing this. */ -static int s_s3_message_util_set_multipart_request_path( +int aws_s3_message_util_set_multipart_request_path( struct aws_allocator *allocator, const struct aws_string *upload_id, uint32_t part_number, + bool append_uploads_suffix, struct aws_http_message *message) { const struct aws_byte_cursor question_mark = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("?"); const struct aws_byte_cursor ampersand = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("&"); + + const struct aws_byte_cursor uploads_suffix = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("uploads"); const struct aws_byte_cursor part_number_arg = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("partNumber="); const struct aws_byte_cursor upload_id_arg = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("uploadId="); @@ -517,8 +565,18 @@ static int s_s3_message_util_set_multipart_request_path( goto error_clean_up; } + bool has_existing_query_parameters = false; + + for (size_t i = 0; i < request_path.len; ++i) { + if (request_path.ptr[i] == '?') { + has_existing_query_parameters = true; + break; + } + } + if (part_number > 0) { - if (aws_byte_buf_append_dynamic(&request_path_buf, &question_mark)) { + if (aws_byte_buf_append_dynamic( + &request_path_buf, has_existing_query_parameters ? &ersand : &question_mark)) { goto error_clean_up; } @@ -534,17 +592,16 @@ static int s_s3_message_util_set_multipart_request_path( if (aws_byte_buf_append_dynamic(&request_path_buf, &part_number_cursor)) { goto error_clean_up; } + + has_existing_query_parameters = true; } if (upload_id != NULL) { struct aws_byte_cursor upload_id_cursor = aws_byte_cursor_from_string(upload_id); - if (part_number > 0) { - if (aws_byte_buf_append_dynamic(&request_path_buf, &ersand)) { - goto error_clean_up; - } - } else if (aws_byte_buf_append_dynamic(&request_path_buf, &question_mark)) { + if (aws_byte_buf_append_dynamic( + &request_path_buf, has_existing_query_parameters ? &ersand : &question_mark)) { goto error_clean_up; } @@ -555,6 +612,21 @@ static int s_s3_message_util_set_multipart_request_path( if (aws_byte_buf_append_dynamic(&request_path_buf, &upload_id_cursor)) { goto error_clean_up; } + + has_existing_query_parameters = true; + } + + if (append_uploads_suffix) { + if (aws_byte_buf_append_dynamic( + &request_path_buf, has_existing_query_parameters ? &ersand : &question_mark)) { + goto error_clean_up; + } + + if (aws_byte_buf_append_dynamic(&request_path_buf, &uploads_suffix)) { + goto error_clean_up; + } + + has_existing_query_parameters = true; } struct aws_byte_cursor new_request_path = aws_byte_cursor_from_buf(&request_path_buf); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5ff094a4c..ac10e2d86 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,6 +5,15 @@ file(GLOB TEST_SRC "*.c") file(GLOB TEST_HDRS "*.h") file(GLOB TESTS ${TEST_HDRS} ${TEST_SRC}) +add_test_case(test_s3_copy_http_message) +add_test_case(test_s3_message_util_assign_body) +add_test_case(test_s3_get_object_message_new) +add_test_case(test_s3_set_multipart_request_path) +add_test_case(test_s3_create_multipart_upload_message_new) +add_test_case(test_s3_upload_part_message_new) +add_test_case(test_s3_complete_multipart_message_new) +add_test_case(test_s3_abort_multipart_upload_message_new) + add_net_test_case(test_s3_client_create_destroy) add_net_test_case(test_s3_client_max_active_connections_override) add_test_case(test_s3_client_get_max_active_connections) @@ -18,6 +27,7 @@ add_test_case(test_s3_client_update_connections_request_assign) add_test_case(test_s3_client_update_connections_too_many_conns) add_test_case(test_s3_client_update_connections_finish_result) add_test_case(test_s3_client_update_connections_clean_up) + add_net_test_case(test_s3_vip_create_destroy) add_net_test_case(test_s3_client_add_remove_vips) add_net_test_case(test_s3_client_resolve_vips) diff --git a/tests/s3_request_messages_tests.c b/tests/s3_request_messages_tests.c new file mode 100644 index 000000000..3b26ba035 --- /dev/null +++ b/tests/s3_request_messages_tests.c @@ -0,0 +1,936 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include "aws/s3/private/s3_client_impl.h" +#include "aws/s3/private/s3_meta_request_impl.h" +#include "aws/s3/private/s3_request_messages.h" +#include "aws/s3/private/s3_util.h" +#include "s3_tester.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct aws_http_header get_object_test_headers[] = { + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Host"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("HostValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("If-Match"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("If-MatchValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("If-Modified-Since"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("If-Modified-SinceValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("If-None-Match"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("If-None-MatchValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Range"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("RangeValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-customer-algorithm"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-customer-algorithmValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-customer-key"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-customer-keyValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-customer-key-MD5"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-customer-key-MD5Value"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-request-payer"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-request-payerValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-expected-bucket-owner"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-expected-bucket-ownerValue"), + }, +}; + +static const struct aws_http_header s_put_object_test_headers[] = { + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-acl"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ACLValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Cache-Control"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("CacheControlValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Disposition"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ContentDispositionValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Encoding"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ContentEncodingValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Language"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ContentLanguageValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Length"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ContentLengthValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-MD5"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ContentMD5Value"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Type"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ContentTypeValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Expires"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ExpiresValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-grant-full-control"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("GrantFullControlValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-grant-read"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("GrantReadValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-grant-read-acp"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("GrantReadACPValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-grant-write-acp"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("GrantWriteACPValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ServerSideEncryptionValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-storage-class"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("StorageClassValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-website-redirect-location"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("WebsiteRedirectLocationValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-customer-algorithm"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("SSECustomerAlgorithmValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-customer-key"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("SSECustomerKeyValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-customer-key-MD5"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("SSECustomerKeyMD5Value"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-aws-kms-key-id"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("SSEKMSKeyIdValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-context"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("SSEKMSEncryptionContextValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-server-side-encryption-bucket-key-enabled"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("BucketKeyEnabledValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-request-payer"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("RequestPayerValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-tagging"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("TaggingValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-object-lock-mode"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ObjectLockModeValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-object-lock-retain-until-date"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ObjectLockRetainUntilDateValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-object-lock-legal-hold"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ObjectLockLegalHoldStatusValue"), + }, + { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-expected-bucket-owner"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ExpectedBucketOwnerValue"), + }, +}; + +static int s_fill_byte_buf(struct aws_byte_buf *buffer, struct aws_allocator *allocator, size_t buffer_size) { + ASSERT_TRUE(buffer != NULL); + ASSERT_TRUE(allocator != NULL); + ASSERT_TRUE(buffer_size > 0); + + ASSERT_SUCCESS(aws_byte_buf_init(buffer, allocator, buffer_size)); + + srand(0); + + for (size_t i = 0; i < buffer_size; ++i) { + const char single_char = (char)(rand() % (int)('z' - 'a') + (int)'a'); + + struct aws_byte_cursor single_char_cursor = { + .ptr = (uint8_t *)&single_char, + .len = 1, + }; + + ASSERT_SUCCESS(aws_byte_buf_append(buffer, &single_char_cursor)); + } + + return AWS_OP_SUCCESS; +} + +static int s_test_http_headers_match( + struct aws_allocator *allocator, + struct aws_http_message *message0, + struct aws_http_message *message1, + + /* Headers that we know are in message0, but should NOT be in message1 */ + const struct aws_byte_cursor *excluded_message0_headers, + size_t excluded_message0_headers_count, + + /* Headers in message1 that are okay to be in message1 even if they are in the excluded list or are not in + message0.*/ + const struct aws_byte_cursor *message1_header_exceptions, + size_t message1_header_exceptions_count) { + ASSERT_TRUE(message0 != NULL); + ASSERT_TRUE(message1 != NULL); + ASSERT_TRUE(excluded_message0_headers != NULL || excluded_message0_headers_count == 0); + ASSERT_TRUE(message1_header_exceptions != NULL || message1_header_exceptions_count == 0); + + struct aws_http_headers *message0_headers = aws_http_message_get_headers(message0); + ASSERT_TRUE(message0_headers != NULL); + + struct aws_http_headers *message1_headers = aws_http_message_get_headers(message1); + ASSERT_TRUE(message1_headers != NULL); + + struct aws_http_headers *expected_message0_headers = aws_http_headers_new(allocator); + + /* Copy message1 headers to expected_message0_headers. With upcoming adds/removes, it should transform back into + * message0. + */ + for (size_t i = 0; i < aws_http_headers_count(message1_headers); ++i) { + struct aws_http_header message1_header; + AWS_ZERO_STRUCT(message1_header); + ASSERT_SUCCESS(aws_http_headers_get_index(message1_headers, i, &message1_header)); + ASSERT_SUCCESS(aws_http_headers_add(expected_message0_headers, message1_header.name, message1_header.value)); + } + + /* Go through all of the headers that were originally removed from message1 after it was copied from message0. */ + for (size_t i = 0; i < excluded_message0_headers_count; ++i) { + const struct aws_byte_cursor *excluded_header_name = &excluded_message0_headers[i]; + + bool header_existance_is_valid = false; + + /* If the heaer is in the exception list, it's okay for message1 to have. (It may have been re-added.) */ + for (size_t j = 0; j < message1_header_exceptions_count; ++j) { + if (aws_byte_cursor_eq(excluded_header_name, &message1_header_exceptions[j])) { + header_existance_is_valid = true; + break; + } + } + + /* Try to get the header from message1. */ + struct aws_byte_cursor message1_header_value; + AWS_ZERO_STRUCT(message1_header_value); + int result = aws_http_headers_get(message1_headers, *excluded_header_name, &message1_header_value); + + if (header_existance_is_valid) { + + /* If this header is allowed to exist in message1, then we don't need to assert on its existance or + * non-existance. But we do want to erase it from the expected_message0_headers, since its value may be + * different from that in message0. */ + if (result == AWS_OP_SUCCESS) { + ASSERT_SUCCESS(aws_http_headers_erase(expected_message0_headers, *excluded_header_name)); + } + + } else { + /* In this case, message1 should not have the header. */ + ASSERT_TRUE(result == AWS_OP_ERR && aws_last_error() == AWS_ERROR_HTTP_HEADER_NOT_FOUND); + } + + /* At this point, expected_message0_headers should not have the excluded header in it. Add a copy of the header + * from message0 to expected_message0_headers to further transform it toward being a copy of message0 headers. + */ + struct aws_byte_cursor message0_header_value; + AWS_ZERO_STRUCT(message0_header_value); + if (aws_http_headers_get(message0_headers, *excluded_header_name, &message0_header_value) == AWS_OP_SUCCESS) { + ASSERT_SUCCESS( + aws_http_headers_add(expected_message0_headers, *excluded_header_name, message0_header_value)); + } + } + + /* message0_headers should now match expected_message0_headers */ + { + ASSERT_TRUE(aws_http_headers_count(message0_headers) == aws_http_headers_count(expected_message0_headers)); + + for (size_t i = 0; i < aws_http_headers_count(message0_headers); ++i) { + struct aws_http_header message0_header; + AWS_ZERO_STRUCT(message0_header); + ASSERT_SUCCESS(aws_http_headers_get_index(message0_headers, i, &message0_header)); + + struct aws_byte_cursor expected_message0_header_value; + AWS_ZERO_STRUCT(expected_message0_header_value); + ASSERT_SUCCESS( + aws_http_headers_get(expected_message0_headers, message0_header.name, &expected_message0_header_value)); + + ASSERT_TRUE(aws_byte_cursor_eq(&message0_header.value, &expected_message0_header_value)); + } + } + + aws_http_headers_release(expected_message0_headers); + + return AWS_OP_SUCCESS; +} + +static int s_test_http_messages_match( + struct aws_allocator *allocator, + struct aws_http_message *message0, + struct aws_http_message *message1, + const struct aws_byte_cursor *excluded_headers, + size_t excluded_headers_count) { + ASSERT_TRUE(message0 != NULL); + ASSERT_TRUE(message1 != NULL); + ASSERT_TRUE(excluded_headers != NULL || excluded_headers_count == 0); + + struct aws_byte_cursor request_path; + AWS_ZERO_STRUCT(request_path); + ASSERT_SUCCESS(aws_http_message_get_request_path(message0, &request_path)); + + struct aws_byte_cursor copied_request_path; + AWS_ZERO_STRUCT(copied_request_path); + ASSERT_SUCCESS(aws_http_message_get_request_path(message1, &copied_request_path)); + + ASSERT_TRUE(aws_byte_cursor_eq(&request_path, &copied_request_path)); + + struct aws_byte_cursor request_method; + AWS_ZERO_STRUCT(request_method); + ASSERT_SUCCESS(aws_http_message_get_request_method(message0, &request_method)); + + struct aws_byte_cursor copied_request_method; + AWS_ZERO_STRUCT(copied_request_method); + ASSERT_SUCCESS(aws_http_message_get_request_method(message1, &copied_request_method)); + + ASSERT_TRUE(aws_byte_cursor_eq(&request_method, &copied_request_method)); + + ASSERT_SUCCESS( + s_test_http_headers_match(allocator, message0, message1, excluded_headers, excluded_headers_count, NULL, 0)); + + return AWS_OP_SUCCESS; +} + +static int s_test_http_message_request_path( + struct aws_http_message *message, + const struct aws_byte_cursor *request_path) { + + struct aws_byte_cursor message_request_path; + AWS_ZERO_STRUCT(message_request_path); + ASSERT_SUCCESS(aws_http_message_get_request_path(message, &message_request_path)); + + ASSERT_TRUE(aws_byte_cursor_eq(&message_request_path, request_path)); + + return AWS_OP_SUCCESS; +} + +static int s_test_http_message_request_method(struct aws_http_message *message, const char *method) { + + struct aws_byte_cursor message_request_method; + AWS_ZERO_STRUCT(message_request_method); + ASSERT_SUCCESS(aws_http_message_get_request_method(message, &message_request_method)); + + struct aws_byte_cursor method_cursor = aws_byte_cursor_from_c_str(method); + + ASSERT_TRUE(aws_byte_cursor_eq(&message_request_method, &method_cursor)); + + return AWS_OP_SUCCESS; +} + +static int s_test_http_message_body_stream( + struct aws_allocator *allocator, + struct aws_http_message *derived_message, + struct aws_byte_buf *expected_stream_contents) { + ASSERT_TRUE(derived_message != NULL); + ASSERT_TRUE(expected_stream_contents != NULL); + + struct aws_http_headers *headers = aws_http_message_get_headers(derived_message); + ASSERT_TRUE(headers != NULL); + + struct aws_input_stream *body_stream = aws_http_message_get_body_stream(derived_message); + ASSERT_TRUE(body_stream != NULL); + + /* Check for the content length header. */ + struct aws_byte_cursor content_length_header_value; + AWS_ZERO_STRUCT(content_length_header_value); + ASSERT_SUCCESS(aws_http_headers_get(headers, g_content_length_header_name, &content_length_header_value)); + + struct aws_string *content_length_header_str = aws_string_new_from_cursor(allocator, &content_length_header_value); + uint32_t content_length = (uint32_t)atoi((const char *)content_length_header_str->bytes); + ASSERT_TRUE(content_length == (uint32_t)expected_stream_contents->len); + aws_string_destroy(content_length_header_str); + + /* Check that the stream data is equal to the original buffer data. */ + struct aws_byte_buf stream_read_buffer; + ASSERT_SUCCESS(aws_byte_buf_init(&stream_read_buffer, allocator, expected_stream_contents->len)); + ASSERT_SUCCESS(aws_input_stream_read(body_stream, &stream_read_buffer)); + ASSERT_TRUE(aws_byte_buf_eq(expected_stream_contents, &stream_read_buffer)); + aws_byte_buf_clean_up(&stream_read_buffer); + + /* There should be no data left in the stream. */ + struct aws_byte_buf stream_overread_buffer; + ASSERT_SUCCESS(aws_byte_buf_init(&stream_overread_buffer, allocator, expected_stream_contents->len)); + ASSERT_SUCCESS(aws_input_stream_read(body_stream, &stream_overread_buffer)); + ASSERT_TRUE(stream_overread_buffer.len == 0); + aws_byte_buf_clean_up(&stream_overread_buffer); + + return AWS_OP_SUCCESS; +} + +int s_create_get_object_message( + struct aws_allocator *allocator, + const struct aws_byte_cursor *path, + struct aws_http_message **out_message) { + ASSERT_TRUE(out_message != NULL); + ASSERT_TRUE(*out_message == NULL); + + struct aws_http_message *message = aws_http_message_new_request(allocator); + ASSERT_TRUE(message != NULL); + + ASSERT_SUCCESS(aws_http_message_set_request_path(message, *path)); + ASSERT_SUCCESS(aws_http_message_set_request_method(message, aws_byte_cursor_from_c_str("GET"))); + + for (size_t i = 0; i < AWS_ARRAY_SIZE(get_object_test_headers); ++i) { + ASSERT_SUCCESS(aws_http_message_add_header(message, get_object_test_headers[i])); + } + + *out_message = message; + + return AWS_OP_SUCCESS; +} + +int s_create_put_object_message( + struct aws_allocator *allocator, + const struct aws_byte_cursor *path, + struct aws_http_message **out_message) { + ASSERT_TRUE(out_message != NULL); + ASSERT_TRUE(*out_message == NULL); + + struct aws_http_message *message = aws_http_message_new_request(allocator); + ASSERT_TRUE(message != NULL); + + ASSERT_SUCCESS(aws_http_message_set_request_path(message, *path)); + ASSERT_SUCCESS(aws_http_message_set_request_method(message, aws_byte_cursor_from_c_str("PUT"))); + + for (size_t i = 0; i < AWS_ARRAY_SIZE(s_put_object_test_headers); ++i) { + ASSERT_SUCCESS(aws_http_message_add_header(message, s_put_object_test_headers[i])); + } + + *out_message = message; + + return AWS_OP_SUCCESS; + ; +} + +AWS_TEST_CASE(test_s3_copy_http_message, s_test_s3_copy_http_message) +static int s_test_s3_copy_http_message(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + const struct aws_byte_cursor request_method = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("RequestMethod"); + const struct aws_byte_cursor request_path = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("RequestPath"); + + const struct aws_http_header included_header = { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("IncludedHeader"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("IncludedHeaderValue"), + }; + + const struct aws_http_header excluded_header = { + .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ExcludedHeader"), + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ExcludedHeaderValue"), + }; + + struct aws_http_message *message = aws_http_message_new_request(allocator); + ASSERT_TRUE(message != NULL); + ASSERT_SUCCESS(aws_http_message_set_request_method(message, request_method)); + ASSERT_SUCCESS(aws_http_message_set_request_path(message, request_path)); + + struct aws_http_headers *message_headers = aws_http_message_get_headers(message); + ASSERT_TRUE(message != NULL); + ASSERT_SUCCESS(aws_http_headers_add(message_headers, included_header.name, included_header.value)); + ASSERT_SUCCESS(aws_http_headers_add(message_headers, excluded_header.name, excluded_header.value)); + + struct aws_http_message *copied_message = + aws_s3_message_util_copy_http_message(allocator, message, &excluded_header.name, 1); + ASSERT_TRUE(copied_message != NULL); + + ASSERT_SUCCESS(s_test_http_messages_match(allocator, message, copied_message, &excluded_header.name, 1)); + + aws_http_message_release(copied_message); + aws_http_message_release(message); + + return 0; +} + +AWS_TEST_CASE(test_s3_message_util_assign_body, s_test_s3_message_util_assign_body) +static int s_test_s3_message_util_assign_body(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_http_message *message = aws_http_message_new_request(allocator); + + const size_t test_buffer_size = 42; + struct aws_byte_buf test_buffer; + ASSERT_SUCCESS(s_fill_byte_buf(&test_buffer, allocator, test_buffer_size)); + + struct aws_input_stream *input_stream = aws_s3_message_util_assign_body(allocator, &test_buffer, message); + ASSERT_TRUE(input_stream != NULL); + + ASSERT_TRUE(aws_http_message_get_body_stream(message) == input_stream); + ASSERT_SUCCESS(s_test_http_message_body_stream(allocator, message, &test_buffer)); + + aws_input_stream_destroy(input_stream); + aws_byte_buf_clean_up(&test_buffer); + aws_http_message_release(message); + + return 0; +} + +AWS_TEST_CASE(test_s3_get_object_message_new, s_test_s3_get_object_message_new) +static int s_test_s3_get_object_message_new(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + const struct aws_byte_cursor test_path = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath"); + + struct aws_http_message *original_message = NULL; + ASSERT_SUCCESS(s_create_get_object_message(allocator, &test_path, &original_message)); + ASSERT_TRUE(original_message != NULL); + + { + struct aws_http_message *get_object_message = aws_s3_get_object_message_new(allocator, original_message, 0, 0); + ASSERT_TRUE(get_object_message != NULL); + + ASSERT_SUCCESS(s_test_http_messages_match(allocator, original_message, get_object_message, NULL, 0)); + + aws_http_message_release(get_object_message); + } + + { + const uint32_t part_number = 2; + const size_t part_size = 42; + + char expected_range_value_buffer[128] = "bytes=42-83"; + struct aws_byte_cursor expected_range_value_cursor = aws_byte_cursor_from_c_str(expected_range_value_buffer); + + struct aws_http_message *get_object_message = + aws_s3_get_object_message_new(allocator, original_message, part_number, part_size); + ASSERT_TRUE(get_object_message != NULL); + + struct aws_http_headers *headers = aws_http_message_get_headers(get_object_message); + ASSERT_TRUE(headers != NULL); + + struct aws_byte_cursor range_header_value; + AWS_ZERO_STRUCT(range_header_value); + ASSERT_SUCCESS(aws_http_headers_get(headers, g_range_header_name, &range_header_value)); + + ASSERT_TRUE(aws_byte_cursor_eq(&range_header_value, &expected_range_value_cursor)); + + s_test_http_message_request_method(get_object_message, "GET"); + + aws_http_message_release(get_object_message); + } + + aws_http_message_release(original_message); + + return 0; +} + +AWS_TEST_CASE(test_s3_set_multipart_request_path, s_test_s3_set_multipart_request_path) +static int s_test_s3_set_multipart_request_path(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + +#define TEST_PATH "/TestPath" +#define TEST_PATH_WITH_PARAMS "/TestPath?arg=value" +#define UPLOAD_ID "test_upload_id" +#define UPLOAD_ID_PARAM "uploadId=test_upload_id" +#define PART_NUMBER 4 +#define UPLOADS_PARAM "uploads" + + const struct aws_byte_cursor test_path = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL(TEST_PATH); + const struct aws_byte_cursor test_path_with_params = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL(TEST_PATH_WITH_PARAMS); + + struct aws_byte_cursor test_path_permutations[] = { + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath?uploads"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath?partNumber=4"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath?partNumber=4&uploads"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath?uploadId=test_upload_id"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath?uploadId=test_upload_id&uploads"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath?partNumber=4&uploadId=test_upload_id"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath?partNumber=4&uploadId=test_upload_id&uploads"), + }; + + struct aws_byte_cursor test_path_with_params_permutations[] = { + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath?arg=value"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath?arg=value&uploads"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath?arg=value&partNumber=4"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath?arg=value&partNumber=4&uploads"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath?arg=value&uploadId=test_upload_id"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath?arg=value&uploadId=test_upload_id&uploads"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath?arg=value&partNumber=4&uploadId=test_upload_id"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath?arg=value&partNumber=4&uploadId=test_upload_id&uploads"), + }; + + const uint32_t num_permutations = 8; + + for (uint32_t i = 0; i < num_permutations; ++i) { + struct aws_string *upload_id = NULL; + uint32_t part_number = 0; + bool uploads_param = false; + + if (i & 0x4) { + upload_id = aws_string_new_from_c_str(allocator, UPLOAD_ID); + } + + if (i & 0x2) { + part_number = PART_NUMBER; + } + + if (i & 0x1) { + uploads_param = true; + } + + { + struct aws_http_message *message = NULL; + ASSERT_SUCCESS(s_create_put_object_message(allocator, &test_path, &message)); + + ASSERT_SUCCESS(aws_s3_message_util_set_multipart_request_path( + allocator, upload_id, part_number, uploads_param, message)); + + ASSERT_SUCCESS(s_test_http_message_request_path(message, &test_path_permutations[i])); + + aws_http_message_release(message); + } + + { + struct aws_http_message *message_with_params = NULL; + ASSERT_SUCCESS(s_create_put_object_message(allocator, &test_path_with_params, &message_with_params)); + + ASSERT_SUCCESS(aws_s3_message_util_set_multipart_request_path( + allocator, upload_id, part_number, uploads_param, message_with_params)); + + ASSERT_SUCCESS( + s_test_http_message_request_path(message_with_params, &test_path_with_params_permutations[i])); + + aws_http_message_release(message_with_params); + } + + aws_string_destroy(upload_id); + } + +#undef TEST_PATH +#undef TEST_PATH_WITH_PARAMS +#undef UPLOAD_ID +#undef UPLOAD_ID_PARAM +#undef PART_NUMBER +#undef UPLOADS_PARAM + + return 0; +} + +AWS_TEST_CASE(test_s3_create_multipart_upload_message_new, s_test_s3_create_multipart_upload_message_new) +static int s_test_s3_create_multipart_upload_message_new(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_byte_cursor path = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath"); + struct aws_byte_cursor expected_create_path = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/TestPath?uploads"); + + struct aws_http_message *original_message = NULL; + ASSERT_SUCCESS(s_create_put_object_message(allocator, &path, &original_message)); + ASSERT_TRUE(original_message != NULL); + + struct aws_http_message *create_multipart_upload_message = + aws_s3_create_multipart_upload_message_new(allocator, original_message); + ASSERT_TRUE(create_multipart_upload_message != NULL); + + ASSERT_SUCCESS(s_test_http_message_request_method(create_multipart_upload_message, "POST")); + ASSERT_SUCCESS(s_test_http_message_request_path(create_multipart_upload_message, &expected_create_path)); + ASSERT_SUCCESS(s_test_http_headers_match( + allocator, + original_message, + create_multipart_upload_message, + g_s3_create_multipart_upload_excluded_headers, + g_s3_create_multipart_upload_excluded_headers_count, + NULL, + 0)); + + aws_http_message_release(create_multipart_upload_message); + aws_http_message_release(original_message); + + return 0; +} + +AWS_TEST_CASE(test_s3_upload_part_message_new, s_test_s3_upload_part_message_new) +static int s_test_s3_upload_part_message_new(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + +#define STRINGIFY_HELPER(x) #x +#define STRINGIFY(x) STRINGIFY_HELPER(x) +#define TEST_PATH "/TestPath" +#define UPLOAD_ID "test_upload_id" +#define PART_NUMBER 4 +#define PART_NUMBER_STR "?partNumber=" STRINGIFY(PART_NUMBER) +#define EXPECTED_UPLOAD_PART_PATH TEST_PATH PART_NUMBER_STR "&uploadId=" UPLOAD_ID + + const struct aws_byte_cursor header_exclude_exceptions[] = { + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Length"), + }; + + struct aws_byte_cursor path = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL(TEST_PATH); + struct aws_byte_cursor expected_create_path = aws_byte_cursor_from_c_str(EXPECTED_UPLOAD_PART_PATH); + + struct aws_http_message *original_message = NULL; + ASSERT_SUCCESS(s_create_put_object_message(allocator, &path, &original_message)); + ASSERT_TRUE(original_message != NULL); + + const size_t part_buffer_size = 42; + struct aws_byte_buf part_buffer; + AWS_ZERO_STRUCT(part_buffer); + s_fill_byte_buf(&part_buffer, allocator, part_buffer_size); + + struct aws_string *upload_id = aws_string_new_from_c_str(allocator, UPLOAD_ID); + + struct aws_http_message *upload_part_message = + aws_s3_upload_part_message_new(allocator, original_message, &part_buffer, PART_NUMBER, upload_id); + ASSERT_TRUE(upload_part_message != NULL); + + ASSERT_SUCCESS(s_test_http_message_request_method(upload_part_message, "PUT")); + ASSERT_SUCCESS(s_test_http_message_request_path(upload_part_message, &expected_create_path)); + ASSERT_SUCCESS(s_test_http_headers_match( + allocator, + original_message, + upload_part_message, + g_s3_upload_part_excluded_headers, + g_s3_upload_part_excluded_headers_count, + header_exclude_exceptions, + AWS_ARRAY_SIZE(header_exclude_exceptions))); + + ASSERT_SUCCESS(s_test_http_message_body_stream(allocator, upload_part_message, &part_buffer)); + + aws_string_destroy(upload_id); + aws_byte_buf_clean_up(&part_buffer); + + aws_input_stream_destroy(aws_http_message_get_body_stream(upload_part_message)); + aws_http_message_release(upload_part_message); + aws_http_message_release(original_message); + +#undef STRINGIFY_HELPER +#undef STRINGIFY +#undef TEST_PATH +#undef UPLOAD_ID +#undef PART_NUMBER +#undef PART_NUMBER_STR +#undef EXPECTED_UPLOAD_PART_PATH + + return 0; +} + +struct complete_multipart_upload_xml_test_data { + struct aws_byte_cursor etag_value; + struct aws_byte_cursor part_number_value; + bool found_etag; + bool found_part_number; +}; + +static bool s_complete_multipart_upload_traverse_xml_node( + struct aws_xml_parser *parser, + struct aws_xml_node *node, + void *user_data) { + + const struct aws_byte_cursor complete_multipar_upload_tag_name = + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("CompleteMultipartUpload"); + const struct aws_byte_cursor part_tag_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Part"); + const struct aws_byte_cursor etag_tag_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ETag"); + const struct aws_byte_cursor part_number_tag_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("PartNumber"); + + struct aws_byte_cursor node_name; + AWS_ZERO_STRUCT(node_name); + + bool keep_traversing = false; + struct complete_multipart_upload_xml_test_data *test_data = user_data; + + /* If we can't get the name of the node, stop traversing. */ + if (aws_xml_node_get_name(node, &node_name)) { + /* Couldn't get the tag name, so nothing to do but stop traversing. */ + } else if (aws_byte_cursor_eq(&node_name, &complete_multipar_upload_tag_name)) { + aws_xml_node_traverse(parser, node, s_complete_multipart_upload_traverse_xml_node, user_data); + } else if (aws_byte_cursor_eq(&node_name, &part_tag_name)) { + aws_xml_node_traverse(parser, node, s_complete_multipart_upload_traverse_xml_node, user_data); + } else if (aws_byte_cursor_eq(&node_name, &etag_tag_name)) { + + struct aws_byte_cursor node_body; + AWS_ZERO_STRUCT(node_body); + if (aws_xml_node_as_body(parser, node, &node_body)) { + goto finish; + } + + test_data->found_etag = aws_byte_cursor_eq(&node_body, &test_data->etag_value); + keep_traversing = true; + } else if (aws_byte_cursor_eq(&node_name, &part_number_tag_name)) { + + struct aws_byte_cursor node_body; + AWS_ZERO_STRUCT(node_body); + aws_xml_node_as_body(parser, node, &node_body); + + test_data->found_part_number = aws_byte_cursor_eq(&node_body, &test_data->part_number_value); + keep_traversing = true; + } + +finish: + return keep_traversing; +} + +AWS_TEST_CASE(test_s3_complete_multipart_message_new, s_test_s3_complete_multipart_message_new) +static int s_test_s3_complete_multipart_message_new(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + +#define TEST_PATH "/TestPath" +#define UPLOAD_ID "test_upload_id" +#define EXPECTED_UPLOAD_PART_PATH TEST_PATH "?uploadId=" UPLOAD_ID +#define ETAG_VALUE "etag_value" + + struct aws_array_list etags; + ASSERT_SUCCESS(aws_array_list_init_dynamic(&etags, allocator, 1, sizeof(struct aws_string *))); + struct aws_string *etag = aws_string_new_from_c_str(allocator, ETAG_VALUE); + ASSERT_SUCCESS(aws_array_list_push_back(&etags, &etag)); + + const struct aws_byte_cursor header_exclude_exceptions[] = { + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Length"), + }; + + struct aws_byte_cursor path = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL(TEST_PATH); + struct aws_byte_cursor expected_create_path = aws_byte_cursor_from_c_str(EXPECTED_UPLOAD_PART_PATH); + + struct aws_http_message *original_message = NULL; + ASSERT_SUCCESS(s_create_put_object_message(allocator, &path, &original_message)); + ASSERT_TRUE(original_message != NULL); + + struct aws_string *upload_id = aws_string_new_from_c_str(allocator, UPLOAD_ID); + + struct aws_byte_buf body_buffer; + aws_byte_buf_init(&body_buffer, allocator, 64); + + struct aws_http_message *complete_multipart_message = + aws_s3_complete_multipart_message_new(allocator, original_message, &body_buffer, upload_id, &etags); + + ASSERT_SUCCESS(s_test_http_message_request_method(complete_multipart_message, "POST")); + ASSERT_SUCCESS(s_test_http_message_request_path(complete_multipart_message, &expected_create_path)); + ASSERT_SUCCESS(s_test_http_headers_match( + allocator, + original_message, + complete_multipart_message, + g_s3_complete_multipart_upload_excluded_headers, + g_s3_complete_multipart_upload_excluded_headers_count, + header_exclude_exceptions, + AWS_ARRAY_SIZE(header_exclude_exceptions))); + + { + struct aws_xml_parser_options parser_options = { + .doc = aws_byte_cursor_from_buf(&body_buffer), + }; + + struct aws_xml_parser *parser = aws_xml_parser_new(allocator, &parser_options); + + struct complete_multipart_upload_xml_test_data xml_user_data = { + .etag_value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL(ETAG_VALUE), + .part_number_value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("1"), + .found_etag = false, + .found_part_number = false, + }; + + ASSERT_SUCCESS( + aws_xml_parser_parse(parser, s_complete_multipart_upload_traverse_xml_node, (void *)&xml_user_data)); + aws_xml_parser_destroy(parser); + + ASSERT_TRUE(xml_user_data.found_etag); + ASSERT_TRUE(xml_user_data.found_part_number); + } + + aws_byte_buf_clean_up(&body_buffer); + aws_string_destroy(upload_id); + + aws_input_stream_destroy(aws_http_message_get_body_stream(complete_multipart_message)); + aws_http_message_release(complete_multipart_message); + aws_http_message_release(original_message); + + aws_string_destroy(etag); + aws_array_list_clean_up(&etags); + +#undef TEST_PATH +#undef UPLOAD_ID +#undef EXPECTED_UPLOAD_PART_PATH +#undef ETAG_VALUE + + return 0; +} + +AWS_TEST_CASE(test_s3_abort_multipart_upload_message_new, s_test_s3_abort_multipart_upload_message_newt) +static int s_test_s3_abort_multipart_upload_message_newt(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + +#define TEST_PATH "/TestPath" +#define UPLOAD_ID "test_upload_id" +#define EXPECTED_UPLOAD_PART_PATH TEST_PATH "?uploadId=" UPLOAD_ID + + struct aws_byte_cursor path = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL(TEST_PATH); + struct aws_byte_cursor expected_create_path = aws_byte_cursor_from_c_str(EXPECTED_UPLOAD_PART_PATH); + + struct aws_http_message *original_message = NULL; + ASSERT_SUCCESS(s_create_put_object_message(allocator, &path, &original_message)); + ASSERT_TRUE(original_message != NULL); + + struct aws_string *upload_id = aws_string_new_from_c_str(allocator, UPLOAD_ID); + + struct aws_http_message *abort_upload_message = + aws_s3_abort_multipart_upload_message_new(allocator, original_message, upload_id); + ASSERT_TRUE(abort_upload_message != NULL); + + ASSERT_SUCCESS(s_test_http_message_request_method(abort_upload_message, "DELETE")); + ASSERT_SUCCESS(s_test_http_message_request_path(abort_upload_message, &expected_create_path)); + ASSERT_SUCCESS(s_test_http_headers_match( + allocator, + original_message, + abort_upload_message, + g_s3_abort_multipart_upload_excluded_headers, + g_s3_abort_multipart_upload_excluded_headers_count, + NULL, + 0)); + + aws_string_destroy(upload_id); + + aws_http_message_release(abort_upload_message); + aws_http_message_release(original_message); + +#undef TEST_PATH +#undef UPLOAD_ID +#undef EXPECTED_UPLOAD_PART_PATH + + return 0; +} diff --git a/tests/s3_tester.c b/tests/s3_tester.c index 13b9868ee..08516826a 100644 --- a/tests/s3_tester.c +++ b/tests/s3_tester.c @@ -1151,9 +1151,7 @@ int aws_s3_tester_send_meta_request_with_options( if (options->put_options.invalid_request) { /* make a invalid request */ - struct aws_http_headers *headers = aws_http_message_get_headers(message); - aws_http_headers_add( - headers, aws_byte_cursor_from_c_str("Content-MD5"), aws_byte_cursor_from_c_str("something")); + aws_http_message_set_request_path(message, aws_byte_cursor_from_c_str("invalid_path")); } meta_request_options.message = message;