diff --git a/include/aws/s3/private/s3_auto_ranged_get.h b/include/aws/s3/private/s3_auto_ranged_get.h index 1f2b6d2ab..000b86cf8 100644 --- a/include/aws/s3/private/s3_auto_ranged_get.h +++ b/include/aws/s3/private/s3_auto_ranged_get.h @@ -45,6 +45,9 @@ struct aws_s3_auto_ranged_get { } synced_data; uint32_t initial_message_has_range_header : 1; + uint32_t initial_message_has_if_match_header : 1; + + struct aws_string *etag; }; /* Creates a new auto-ranged get meta request. This will do multiple parallel ranged-gets when appropriate. */ diff --git a/include/aws/s3/private/s3_util.h b/include/aws/s3/private/s3_util.h index 7d4ea01e5..d699f8940 100644 --- a/include/aws/s3/private/s3_util.h +++ b/include/aws/s3/private/s3_util.h @@ -143,6 +143,8 @@ extern const struct aws_byte_cursor g_s3_service_name; AWS_S3_API extern const struct aws_byte_cursor g_range_header_name; +extern const struct aws_byte_cursor g_if_match_header_name; + AWS_S3_API extern const struct aws_byte_cursor g_content_range_header_name; diff --git a/include/aws/s3/s3.h b/include/aws/s3/s3.h index 19093c4f4..ff5aef5ae 100644 --- a/include/aws/s3/s3.h +++ b/include/aws/s3/s3.h @@ -33,6 +33,7 @@ enum aws_s3_errors { AWS_ERROR_S3_LIST_PARTS_PARSE_FAILED, AWS_ERROR_S3_RESUMED_PART_CHECKSUM_MISMATCH, AWS_ERROR_S3_RESUME_FAILED, + AWS_ERROR_S3_OBJECT_MODIFIED, AWS_ERROR_S3_END_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_S3_PACKAGE_ID) }; diff --git a/source/s3.c b/source/s3.c index 4b84f1d97..ab71355f0 100644 --- a/source/s3.c +++ b/source/s3.c @@ -34,6 +34,7 @@ static struct aws_error_info s_errors[] = { AWS_DEFINE_ERROR_INFO_S3(AWS_ERROR_S3_LIST_PARTS_PARSE_FAILED, "Failed to parse result from list parts"), AWS_DEFINE_ERROR_INFO_S3(AWS_ERROR_S3_RESUMED_PART_CHECKSUM_MISMATCH, "Checksum does not match previously uploaded part"), AWS_DEFINE_ERROR_INFO_S3(AWS_ERROR_S3_RESUME_FAILED, "Resuming request failed"), + AWS_DEFINE_ERROR_INFO_S3(AWS_ERROR_S3_OBJECT_MODIFIED, "The object modifed during download.") }; /* clang-format on */ diff --git a/source/s3_auto_ranged_get.c b/source/s3_auto_ranged_get.c index b15170437..e4771ab2c 100644 --- a/source/s3_auto_ranged_get.c +++ b/source/s3_auto_ranged_get.c @@ -96,6 +96,7 @@ struct aws_s3_meta_request *aws_s3_meta_request_auto_ranged_get_new( AWS_ASSERT(headers != NULL); auto_ranged_get->initial_message_has_range_header = aws_http_headers_has(headers, g_range_header_name); + auto_ranged_get->initial_message_has_if_match_header = aws_http_headers_has(headers, g_if_match_header_name); AWS_LOGF_DEBUG( AWS_LS_S3_META_REQUEST, "id=%p Created new Auto-Ranged Get Meta Request.", (void *)&auto_ranged_get->base); @@ -115,6 +116,7 @@ static void s_s3_meta_request_auto_ranged_get_destroy(struct aws_s3_meta_request AWS_PRECONDITION(meta_request->impl); struct aws_s3_auto_ranged_get *auto_ranged_get = meta_request->impl; + aws_string_destroy(auto_ranged_get->etag); aws_mem_release(meta_request->allocator, auto_ranged_get); } @@ -350,6 +352,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 = NULL; + struct aws_s3_auto_ranged_get *auto_ranged_get = meta_request->impl; switch (request->request_tag) { case AWS_S3_AUTO_RANGE_GET_REQUEST_TYPE_HEAD_OBJECT: @@ -382,6 +385,19 @@ static int s_s3_auto_ranged_get_prepare_request( if (meta_request->checksum_config.validate_response_checksum) { aws_http_headers_set(aws_http_message_get_headers(message), g_request_validation_mode, g_enabled); } + if (!auto_ranged_get->initial_message_has_if_match_header && auto_ranged_get->etag) { + /* Add the if_match to the request */ + AWS_LOGF_DEBUG( + AWS_LS_S3_META_REQUEST, + "id=%p: Added the If-Match header to request %p for part %d", + (void *)meta_request, + (void *)request, + request->part_number); + aws_http_headers_set( + aws_http_message_get_headers(message), + g_if_match_header_name, + aws_byte_cursor_from_string(auto_ranged_get->etag)); + } aws_s3_request_setup_send_data(request, message); aws_http_message_release(message); @@ -574,6 +590,24 @@ static void s_s3_auto_ranged_get_request_finished( goto update_synced_data; } + if (!request_failed && !auto_ranged_get->initial_message_has_if_match_header) { + AWS_ASSERT(auto_ranged_get->etag == NULL); + struct aws_byte_cursor etag_header_value; + + if (aws_http_headers_get(request->send_data.response_headers, g_etag_header_name, &etag_header_value)) { + aws_raise_error(AWS_ERROR_S3_MISSING_ETAG); + error_code = AWS_ERROR_S3_MISSING_ETAG; + goto update_synced_data; + } + + AWS_LOGF_TRACE( + AWS_LS_S3_META_REQUEST, + "id=%p Etag received for the meta request. value is: " PRInSTR "", + (void *)meta_request, + AWS_BYTE_CURSOR_PRI(etag_header_value)); + auto_ranged_get->etag = aws_string_new_from_cursor(auto_ranged_get->base.allocator, &etag_header_value); + } + /* If we were able to discover the object-range/content length successfully, then any error code that was passed * into this function is being handled and does not indicate an overall failure.*/ error_code = AWS_ERROR_SUCCESS; @@ -671,6 +705,12 @@ static void s_s3_auto_ranged_get_request_finished( } if (error_code != AWS_ERROR_SUCCESS) { + if (error_code == AWS_ERROR_S3_INVALID_RESPONSE_STATUS && + request->send_data.response_status == AWS_HTTP_STATUS_CODE_412_PRECONDITION_FAILED && + !auto_ranged_get->initial_message_has_if_match_header) { + /* Use more clear error code as we added the if-match header under the hood. */ + error_code = AWS_ERROR_S3_OBJECT_MODIFIED; + } aws_s3_meta_request_set_fail_synced(meta_request, request, error_code); if (error_code == AWS_ERROR_S3_RESPONSE_CHECKSUM_MISMATCH) { /* It's a mismatch of checksum, tell user that we validated the checksum and the algorithm we validated diff --git a/source/s3_request_messages.c b/source/s3_request_messages.c index ce860a322..d3787f997 100644 --- a/source/s3_request_messages.c +++ b/source/s3_request_messages.c @@ -220,7 +220,7 @@ static const struct aws_byte_cursor s_x_amz_meta_prefix = AWS_BYTE_CUR_INIT_FROM 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( +static int s_s3_message_util_add_range_header( uint64_t part_range_start, uint64_t part_range_end, struct aws_http_message *out_message); @@ -242,7 +242,7 @@ struct aws_http_message *aws_s3_ranged_get_object_message_new( return NULL; } - if (s_s3_message_util_add_content_range_header(range_start, range_end, message)) { + if (s_s3_message_util_add_range_header(range_start, range_end, message)) { goto error_clean_up; } @@ -995,8 +995,8 @@ int aws_s3_message_util_copy_headers( return AWS_OP_SUCCESS; } -/* Add a content-range header.*/ -static int s_s3_message_util_add_content_range_header( +/* Add a range header.*/ +static int s_s3_message_util_add_range_header( uint64_t part_range_start, uint64_t part_range_end, struct aws_http_message *out_message) { diff --git a/source/s3_util.c b/source/s3_util.c index f11d3a6d9..7ad5e987d 100644 --- a/source/s3_util.c +++ b/source/s3_util.c @@ -22,6 +22,7 @@ const struct aws_byte_cursor g_s3_client_version = AWS_BYTE_CUR_INIT_FROM_STRING const struct aws_byte_cursor g_s3_service_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("s3"); const struct aws_byte_cursor g_host_header_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Host"); const struct aws_byte_cursor g_range_header_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Range"); +const struct aws_byte_cursor g_if_match_header_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("If-Match"); const struct aws_byte_cursor g_etag_header_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ETag"); const struct aws_byte_cursor g_content_range_header_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Range"); const struct aws_byte_cursor g_content_type_header_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Type");