Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package aws-c-s3 for openSUSE:Factory checked in at 2026-06-19 16:38:01 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/aws-c-s3 (Old) and /work/SRC/openSUSE:Factory/.aws-c-s3.new.1956 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "aws-c-s3" Fri Jun 19 16:38:01 2026 rev:42 rq:1360559 version:0.12.6 Changes: -------- --- /work/SRC/openSUSE:Factory/aws-c-s3/aws-c-s3.changes 2026-06-15 19:53:11.787955128 +0200 +++ /work/SRC/openSUSE:Factory/.aws-c-s3.new.1956/aws-c-s3.changes 2026-06-19 17:13:41.226172467 +0200 @@ -1,0 +2,8 @@ +Mon Jun 15 09:49:09 UTC 2026 - John Paul Adrian Glaubitz <[email protected]> + +- Update to version 0.12.6 + * Support s2n-tls on macOS by @sfod in (#640) + * fix copy object mpu by @sbiscigl in (#643) + * CopyObject limitations by @TingDaoK in (#641) + +------------------------------------------------------------------- Old: ---- v0.12.5.tar.gz New: ---- v0.12.6.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ aws-c-s3.spec ++++++ --- /var/tmp/diff_new_pack.4luoqD/_old 2026-06-19 17:13:42.946231308 +0200 +++ /var/tmp/diff_new_pack.4luoqD/_new 2026-06-19 17:13:42.950231445 +0200 @@ -19,7 +19,7 @@ %define library_version 1.0.0 %define library_soversion 0unstable Name: aws-c-s3 -Version: 0.12.5 +Version: 0.12.6 Release: 0 Summary: AWS Cross-Platform, C99 wrapper for cryptography primitives License: Apache-2.0 ++++++ v0.12.5.tar.gz -> v0.12.6.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aws-c-s3-0.12.5/.github/workflows/ci.yml new/aws-c-s3-0.12.6/.github/workflows/ci.yml --- old/aws-c-s3-0.12.5/.github/workflows/ci.yml 2026-06-02 23:10:16.000000000 +0200 +++ new/aws-c-s3-0.12.6/.github/workflows/ci.yml 2026-06-11 20:12:01.000000000 +0200 @@ -228,8 +228,46 @@ source .venv/bin/activate python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" chmod a+x builder + ./builder build -p ${{ env.PACKAGE_NAME }} --cmake-extra=-DASSERT_LOCK_HELD=ON --cmake-extra=-DCMAKE_NO_SYSTEM_FROM_IMPORTED=ON + + macos-s2n: + runs-on: macos-14 # latest + env: + AWS_CRT_USE_NON_FIPS_TLS_13: 1 + steps: + - uses: aws-actions/configure-aws-credentials@v6 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - name: Checkout Sources + uses: actions/checkout@v6 + - name: Build ${{ env.PACKAGE_NAME }} + consumers + run: | + python3 -m venv .venv + source .venv/bin/activate + python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" + chmod a+x builder ./builder build -p ${{ env.PACKAGE_NAME }} --cmake-extra=-DASSERT_LOCK_HELD=ON + macos-x64-s2n: + runs-on: macos-14-large # latest + env: + AWS_CRT_USE_NON_FIPS_TLS_13: 1 + steps: + - uses: aws-actions/configure-aws-credentials@v6 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - name: Checkout Sources + uses: actions/checkout@v6 + - name: Build ${{ env.PACKAGE_NAME }} + consumers + run: | + python3 -m venv .venv + source .venv/bin/activate + python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" + chmod a+x builder + ./builder build -p ${{ env.PACKAGE_NAME }} --cmake-extra=-DASSERT_LOCK_HELD=ON --cmake-extra=-DCMAKE_NO_SYSTEM_FROM_IMPORTED=ON + # Test downstream repos. # This should not be required because we can run into a chicken and egg problem if there is a change that needs some fix in a downstream repo. downstream: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aws-c-s3-0.12.5/include/aws/s3/private/s3_request_messages.h new/aws-c-s3-0.12.6/include/aws/s3/private/s3_request_messages.h --- old/aws-c-s3-0.12.5/include/aws/s3/private/s3_request_messages.h 2026-06-02 23:10:16.000000000 +0200 +++ new/aws-c-s3-0.12.6/include/aws/s3/private/s3_request_messages.h 2026-06-11 20:12:01.000000000 +0200 @@ -122,7 +122,22 @@ /* Create an HTTP request for an S3 Complete-Multipart-Upload request. Creates the necessary XML payload using the * passed in array list of `struct aws_s3_mpu_part_info *`. Buffer passed in will be used to store - * said XML payload, which will be used as the body. */ + * said XML payload, which will be used as the body. The x-amz-mp-object-size header is taken from object_size; pass + * the known total object size, or 0 to omit the header (e.g. a streaming upload of unknown length). */ +AWS_S3_API +struct aws_http_message *aws_s3_complete_multipart_message_with_object_size_new( + struct aws_allocator *allocator, + struct aws_http_message *base_message, + struct aws_byte_buf *body_buffer, + const struct aws_string *upload_id, + const struct aws_array_list *parts, + const struct aws_s3_meta_request_checksum_config_storage *checksum_config, + uint64_t object_size); + +/* Convenience wrapper over aws_s3_complete_multipart_message_with_object_size_new() that derives the object size from + * base_message's Content-Length (0 if absent/unparseable). Suitable for the multipart upload path, where the base PUT + * carries the full object length; the copy path must call the _with_object_size_ variant directly because its base + * CopyObject is bodyless. */ AWS_S3_API struct aws_http_message *aws_s3_complete_multipart_message_new( struct aws_allocator *allocator, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aws-c-s3-0.12.5/include/aws/s3/s3_client.h new/aws-c-s3-0.12.6/include/aws/s3/s3_client.h --- old/aws-c-s3-0.12.5/include/aws/s3/s3_client.h 2026-06-02 23:10:16.000000000 +0200 +++ new/aws-c-s3-0.12.6/include/aws/s3/s3_client.h 2026-06-11 20:12:01.000000000 +0200 @@ -84,6 +84,11 @@ * source will not work * 3. source bucket is assumed to be in the same region as dest * 4. source bucket and dest bucket must both be either directory buckets or regular buckets. + * 5. on the multipart copy path, metadata, tags, and annotations are not + * automatically copied from the source object. The caller must set + * `x-amz-metadata-directive: REPLACE` and include all desired metadata + * headers and `x-amz-tagging` on the request. Single-part copies (< 1GB) + * are not affected as S3 CopyObject natively handles COPY directives. * * Provide the `meta_request_options.copy_source_uri` to bypass limitation 1 & 2. */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aws-c-s3-0.12.5/source/s3_copy_object.c new/aws-c-s3-0.12.6/source/s3_copy_object.c --- old/aws-c-s3-0.12.5/source/s3_copy_object.c 2026-06-02 23:10:16.000000000 +0200 +++ new/aws-c-s3-0.12.6/source/s3_copy_object.c 2026-06-11 20:12:01.000000000 +0200 @@ -489,14 +489,17 @@ aws_byte_buf_reset(&request->request_body, false); /* Build the message to complete our multipart upload, which includes a payload describing all of our - * completed parts. */ - message = aws_s3_complete_multipart_message_new( + * completed parts. The object size (discovered via the HEAD on the source) is passed explicitly so the + * x-amz-mp-object-size header matches what S3 computes from the copied parts; the base CopyObject request + * is bodyless, so its Content-Length cannot be used as the source of that value. */ + message = aws_s3_complete_multipart_message_with_object_size_new( meta_request->allocator, meta_request->initial_request_message, &request->request_body, copy_object->upload_id, ©_object->synced_data.part_list, - NULL); + NULL, + copy_object->synced_data.content_length); break; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aws-c-s3-0.12.5/source/s3_request_messages.c new/aws-c-s3-0.12.6/source/s3_request_messages.c --- old/aws-c-s3-0.12.5/source/s3_request_messages.c 2026-06-02 23:10:16.000000000 +0200 +++ new/aws-c-s3-0.12.6/source/s3_request_messages.c 2026-06-11 20:12:01.000000000 +0200 @@ -25,6 +25,13 @@ AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("if-none-match"), AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-create-session-mode"), AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-sdk-checksum-algorithm"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-metadata-directive"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-tagging-directive"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-object-annotation-directive"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-copy-source-if-match"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-copy-source-if-none-match"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-copy-source-if-modified-since"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-copy-source-if-unmodified-since"), }; const size_t g_s3_create_multipart_upload_excluded_headers_count = @@ -56,6 +63,9 @@ AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-object-lock-legal-hold"), AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("if-none-match"), AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-create-session-mode"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-metadata-directive"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-tagging-directive"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("x-amz-object-annotation-directive"), }; const size_t g_s3_upload_part_excluded_headers_count = AWS_ARRAY_SIZE(g_s3_upload_part_excluded_headers); @@ -678,13 +688,14 @@ static const struct aws_byte_cursor s_close_bracket = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL(">"); static const struct aws_byte_cursor s_close_bracket_new_line = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL(">\n"); /* Create a complete-multipart message, which includes an XML payload of all completed parts. */ -struct aws_http_message *aws_s3_complete_multipart_message_new( +struct aws_http_message *aws_s3_complete_multipart_message_with_object_size_new( struct aws_allocator *allocator, struct aws_http_message *base_message, struct aws_byte_buf *body_buffer, const struct aws_string *upload_id, const struct aws_array_list *parts, - const struct aws_s3_meta_request_checksum_config_storage *checksum_config) { + const struct aws_s3_meta_request_checksum_config_storage *checksum_config, + uint64_t object_size) { AWS_PRECONDITION(allocator); AWS_PRECONDITION(base_message); AWS_PRECONDITION(body_buffer); @@ -696,8 +707,6 @@ struct aws_http_message *message = NULL; bool set_checksums = checksum_config && (checksum_config->location != AWS_SCL_NONE || checksum_config->has_full_object_checksum); - const struct aws_http_headers *initial_message_headers = aws_http_message_get_headers(base_message); - AWS_ASSERT(initial_message_headers); if (set_checksums) { mpu_algorithm_checksum_name = aws_get_completed_part_name_from_checksum_algorithm(checksum_config->checksum_algorithm); @@ -750,11 +759,15 @@ goto error_clean_up; } } - struct aws_byte_cursor content_length_cursor; - if (aws_http_headers_get(initial_message_headers, g_content_length_header_name, &content_length_cursor) == - AWS_OP_SUCCESS) { - /* Set content-length from base message as x-amz-mp-object-size. */ - if (aws_http_headers_set(headers, aws_byte_cursor_from_c_str("x-amz-mp-object-size"), content_length_cursor)) { + /* Send the total object size as x-amz-mp-object-size so S3 can validate the assembled object. object_size of 0 + * means "unknown" (e.g. a streaming upload of unknown length); in that case the header is omitted, matching the + * prior behavior of skipping it when the base message had no Content-Length. */ + if (object_size > 0) { + char object_size_buffer[32]; + int object_size_len = snprintf(object_size_buffer, sizeof(object_size_buffer), "%" PRIu64, object_size); + struct aws_byte_cursor object_size_cursor = + aws_byte_cursor_from_array(object_size_buffer, (size_t)object_size_len); + if (aws_http_headers_set(headers, aws_byte_cursor_from_c_str("x-amz-mp-object-size"), object_size_cursor)) { goto error_clean_up; } } @@ -855,6 +868,32 @@ return NULL; } +struct aws_http_message *aws_s3_complete_multipart_message_new( + struct aws_allocator *allocator, + struct aws_http_message *base_message, + struct aws_byte_buf *body_buffer, + const struct aws_string *upload_id, + const struct aws_array_list *parts, + const struct aws_s3_meta_request_checksum_config_storage *checksum_config) { + + /* Derive the object size from the base message's Content-Length, as the multipart upload path expects. A missing + * or unparseable Content-Length (e.g. a streaming upload of unknown length) yields 0, which tells the delegate to + * omit the x-amz-mp-object-size header. */ + uint64_t object_size = 0; + struct aws_byte_cursor content_length_cursor; + const struct aws_http_headers *initial_message_headers = aws_http_message_get_headers(base_message); + if (initial_message_headers != NULL && + aws_http_headers_get(initial_message_headers, g_content_length_header_name, &content_length_cursor) == + AWS_OP_SUCCESS) { + if (aws_byte_cursor_utf8_parse_u64(content_length_cursor, &object_size)) { + object_size = 0; + } + } + + return aws_s3_complete_multipart_message_with_object_size_new( + allocator, base_message, body_buffer, upload_id, parts, checksum_config, object_size); +} + struct aws_http_message *aws_s3_abort_multipart_upload_message_new( struct aws_allocator *allocator, struct aws_http_message *base_message, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aws-c-s3-0.12.5/tests/CMakeLists.txt new/aws-c-s3-0.12.6/tests/CMakeLists.txt --- old/aws-c-s3-0.12.5/tests/CMakeLists.txt 2026-06-02 23:10:16.000000000 +0200 +++ new/aws-c-s3-0.12.6/tests/CMakeLists.txt 2026-06-11 20:12:01.000000000 +0200 @@ -344,6 +344,8 @@ add_net_test_case(test_s3_copy_source_prefixed_by_slash) add_net_test_case(test_s3_copy_invalid_source_uri) add_net_test_case(test_s3_copy_source_prefixed_by_slash_multipart) +add_net_test_case(test_s3_copy_object_with_replace_metadata_and_tagging) +add_net_test_case(test_s3_multipart_copy_object_with_replace_metadata_and_tagging) add_net_test_case(test_s3_put_pause_resume_happy_path) add_net_test_case(test_s3_put_pause_resume_all_parts_done) add_net_test_case(test_s3_put_pause_resume_invalid_resume_data) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aws-c-s3-0.12.5/tests/s3_data_plane_tests.c new/aws-c-s3-0.12.6/tests/s3_data_plane_tests.c --- old/aws-c-s3-0.12.5/tests/s3_data_plane_tests.c 2026-06-02 23:10:16.000000000 +0200 +++ new/aws-c-s3-0.12.6/tests/s3_data_plane_tests.c 2026-06-11 20:12:01.000000000 +0200 @@ -7820,6 +7820,253 @@ return AWS_OP_SUCCESS; } +/** + * Helper: copy object then verify metadata and tags on the destination. + * If use_replace: sets REPLACE directives with explicit metadata/tags on the request. + * If !use_replace: sets COPY directives (no metadata/tags on request). + * expect_metadata: whether HeadObject should have the metadata after copy. + */ +static int s_test_s3_copy_object_properties_helper( + struct aws_allocator *allocator, + struct aws_byte_cursor source_key, + struct aws_byte_cursor destination_key, + bool use_replace, + bool expect_metadata) { + + struct aws_s3_tester tester; + AWS_ZERO_STRUCT(tester); + ASSERT_SUCCESS(aws_s3_tester_init(allocator, &tester)); + + struct aws_byte_cursor source_bucket = g_test_bucket_name; + + char endpoint[1024]; + snprintf( + endpoint, + sizeof(endpoint), + "%.*s.s3.%s.amazonaws.com", + (int)source_bucket.len, + source_bucket.ptr, + g_test_s3_region.ptr); + + char copy_source_value[1024]; + snprintf( + copy_source_value, + sizeof(copy_source_value), + "%.*s/%.*s", + (int)source_bucket.len, + source_bucket.ptr, + (int)source_key.len, + source_key.ptr); + + char destination_path[512]; + snprintf(destination_path, sizeof(destination_path), "/%.*s", (int)destination_key.len, destination_key.ptr); + + /* Construct CopyObject request */ + struct aws_http_message *message = aws_http_message_new_request(allocator); + ASSERT_NOT_NULL(message); + ASSERT_SUCCESS(aws_http_message_set_request_path(message, aws_byte_cursor_from_c_str(destination_path))); + ASSERT_SUCCESS(aws_http_message_set_request_method(message, aws_http_method_put)); + + struct aws_http_headers *headers = aws_http_message_get_headers(message); + ASSERT_SUCCESS(aws_http_headers_add(headers, g_host_header_name, aws_byte_cursor_from_c_str(endpoint))); + ASSERT_SUCCESS(aws_http_headers_add( + headers, aws_byte_cursor_from_c_str("x-amz-copy-source"), aws_byte_cursor_from_c_str(copy_source_value))); + + if (use_replace) { + ASSERT_SUCCESS(aws_http_headers_add( + headers, aws_byte_cursor_from_c_str("x-amz-metadata-directive"), aws_byte_cursor_from_c_str("REPLACE"))); + ASSERT_SUCCESS(aws_http_headers_add( + headers, aws_byte_cursor_from_c_str("x-amz-meta-test-key"), aws_byte_cursor_from_c_str("test-value"))); + ASSERT_SUCCESS(aws_http_headers_add( + headers, aws_byte_cursor_from_c_str("Content-Type"), aws_byte_cursor_from_c_str("application/x-replaced"))); + ASSERT_SUCCESS(aws_http_headers_add( + headers, aws_byte_cursor_from_c_str("Content-Disposition"), aws_byte_cursor_from_c_str("attachment"))); + ASSERT_SUCCESS(aws_http_headers_add( + headers, aws_byte_cursor_from_c_str("Content-Language"), aws_byte_cursor_from_c_str("en-US"))); + ASSERT_SUCCESS(aws_http_headers_add( + headers, aws_byte_cursor_from_c_str("Cache-Control"), aws_byte_cursor_from_c_str("max-age=3600"))); + ASSERT_SUCCESS(aws_http_headers_add( + headers, aws_byte_cursor_from_c_str("x-amz-tagging-directive"), aws_byte_cursor_from_c_str("REPLACE"))); + ASSERT_SUCCESS(aws_http_headers_add( + headers, aws_byte_cursor_from_c_str("x-amz-tagging"), aws_byte_cursor_from_c_str("env=test&feature=copy"))); + } else { + ASSERT_SUCCESS(aws_http_headers_add( + headers, aws_byte_cursor_from_c_str("x-amz-metadata-directive"), aws_byte_cursor_from_c_str("COPY"))); + ASSERT_SUCCESS(aws_http_headers_add( + headers, aws_byte_cursor_from_c_str("x-amz-tagging-directive"), aws_byte_cursor_from_c_str("COPY"))); + } + + /* Execute the copy */ + struct aws_s3_meta_request_test_results copy_results; + aws_s3_meta_request_test_results_init(©_results, allocator); + + struct aws_s3_tester_meta_request_options options = { + .allocator = allocator, + .meta_request_type = AWS_S3_META_REQUEST_TYPE_COPY_OBJECT, + .message = message, + .validate_type = AWS_S3_TESTER_VALIDATE_TYPE_EXPECT_SUCCESS, + }; + + ASSERT_SUCCESS(aws_s3_tester_send_meta_request_with_options(&tester, &options, ©_results)); + ASSERT_INT_EQUALS(AWS_HTTP_STATUS_CODE_200_OK, copy_results.finished_response_status); + + aws_s3_meta_request_test_results_clean_up(©_results); + aws_http_message_release(message); + aws_s3_tester_clean_up(&tester); + + /* Verify destination metadata via HeadObject */ + struct aws_s3_tester head_tester; + AWS_ZERO_STRUCT(head_tester); + ASSERT_SUCCESS(aws_s3_tester_init(allocator, &head_tester)); + + struct aws_http_message *head_message = aws_http_message_new_request(allocator); + ASSERT_NOT_NULL(head_message); + ASSERT_SUCCESS(aws_http_message_set_request_method(head_message, aws_byte_cursor_from_c_str("HEAD"))); + ASSERT_SUCCESS(aws_http_message_set_request_path(head_message, aws_byte_cursor_from_c_str(destination_path))); + struct aws_http_headers *head_headers = aws_http_message_get_headers(head_message); + ASSERT_SUCCESS(aws_http_headers_add(head_headers, g_host_header_name, aws_byte_cursor_from_c_str(endpoint))); + + struct aws_s3_meta_request_test_results head_results; + aws_s3_meta_request_test_results_init(&head_results, allocator); + + struct aws_s3_tester_meta_request_options head_options = { + .allocator = allocator, + .meta_request_type = AWS_S3_META_REQUEST_TYPE_DEFAULT, + .message = head_message, + .validate_type = AWS_S3_TESTER_VALIDATE_TYPE_EXPECT_SUCCESS, + .default_type_options = + { + .mode = AWS_S3_TESTER_DEFAULT_TYPE_MODE_GET, + .operation_name = aws_byte_cursor_from_c_str("HeadObject"), + }, + }; + + ASSERT_SUCCESS(aws_s3_tester_send_meta_request_with_options(&head_tester, &head_options, &head_results)); + ASSERT_INT_EQUALS(AWS_HTTP_STATUS_CODE_200_OK, head_results.finished_response_status); + + if (expect_metadata) { + struct aws_byte_cursor meta_value; + ASSERT_SUCCESS(aws_http_headers_get( + head_results.response_headers, aws_byte_cursor_from_c_str("x-amz-meta-test-key"), &meta_value)); + ASSERT_TRUE(aws_byte_cursor_eq_c_str(&meta_value, "test-value")); + + struct aws_byte_cursor cache_value; + ASSERT_SUCCESS(aws_http_headers_get( + head_results.response_headers, aws_byte_cursor_from_c_str("Cache-Control"), &cache_value)); + ASSERT_TRUE(aws_byte_cursor_eq_c_str(&cache_value, "max-age=3600")); + + struct aws_byte_cursor content_type_value; + ASSERT_SUCCESS(aws_http_headers_get( + head_results.response_headers, aws_byte_cursor_from_c_str("Content-Type"), &content_type_value)); + ASSERT_TRUE(aws_byte_cursor_eq_c_str(&content_type_value, "application/x-replaced")); + } else { + /* Metadata NOT expected (documents MPU path limitation with COPY directive) */ + struct aws_byte_cursor meta_value; + ASSERT_TRUE( + aws_http_headers_get( + head_results.response_headers, aws_byte_cursor_from_c_str("x-amz-meta-test-key"), &meta_value) != + AWS_OP_SUCCESS); + } + + aws_s3_meta_request_test_results_clean_up(&head_results); + aws_http_message_release(head_message); + aws_s3_tester_clean_up(&head_tester); + + /* Verify tags */ + if (expect_metadata) { + struct aws_s3_tester tag_tester; + AWS_ZERO_STRUCT(tag_tester); + ASSERT_SUCCESS(aws_s3_tester_init(allocator, &tag_tester)); + + struct aws_http_message *tag_message = aws_http_message_new_request(allocator); + ASSERT_NOT_NULL(tag_message); + ASSERT_SUCCESS(aws_http_message_set_request_method(tag_message, aws_byte_cursor_from_c_str("GET"))); + + char tag_path[1536]; + snprintf(tag_path, sizeof(tag_path), "%s?tagging", destination_path); + ASSERT_SUCCESS(aws_http_message_set_request_path(tag_message, aws_byte_cursor_from_c_str(tag_path))); + + struct aws_http_headers *tag_req_headers = aws_http_message_get_headers(tag_message); + ASSERT_SUCCESS(aws_http_headers_add(tag_req_headers, g_host_header_name, aws_byte_cursor_from_c_str(endpoint))); + + struct aws_s3_meta_request_test_results tag_results; + aws_s3_meta_request_test_results_init(&tag_results, allocator); + + struct aws_s3_tester_meta_request_options tag_options = { + .allocator = allocator, + .meta_request_type = AWS_S3_META_REQUEST_TYPE_DEFAULT, + .message = tag_message, + .validate_type = AWS_S3_TESTER_VALIDATE_TYPE_EXPECT_SUCCESS, + .default_type_options = + { + .mode = AWS_S3_TESTER_DEFAULT_TYPE_MODE_GET, + .operation_name = aws_byte_cursor_from_c_str("GetObjectTagging"), + }, + }; + + ASSERT_SUCCESS(aws_s3_tester_send_meta_request_with_options(&tag_tester, &tag_options, &tag_results)); + ASSERT_INT_EQUALS(AWS_HTTP_STATUS_CODE_200_OK, tag_results.finished_response_status); + /* Body > 100 bytes = contains actual tags (empty TagSet is ~39 bytes) */ + ASSERT_TRUE(tag_results.received_body_size > 100); + + aws_s3_meta_request_test_results_clean_up(&tag_results); + aws_http_message_release(tag_message); + aws_s3_tester_clean_up(&tag_tester); + } + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + test_s3_copy_object_with_replace_metadata_and_tagging, + s_test_s3_copy_object_with_replace_metadata_and_tagging) +static int s_test_s3_copy_object_with_replace_metadata_and_tagging(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + /* REPLACE: copy with explicit metadata+tags, verify they arrive on destination */ + ASSERT_SUCCESS(s_test_s3_copy_object_properties_helper( + allocator, + aws_byte_cursor_from_c_str("pre-existing-1MB"), + aws_byte_cursor_from_c_str("copies/destination_1MB_replace_props"), + true /* use_replace */, + true /* expect_metadata */)); + + /* COPY directive on the result: single-part CopyObject natively handles it */ + ASSERT_SUCCESS(s_test_s3_copy_object_properties_helper( + allocator, + aws_byte_cursor_from_c_str("copies/destination_1MB_replace_props"), + aws_byte_cursor_from_c_str("copies/destination_1MB_copy_directive"), + false /* use_replace */, + true /* expect_metadata: single-part handles COPY natively */)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + test_s3_multipart_copy_object_with_replace_metadata_and_tagging, + s_test_s3_multipart_copy_object_with_replace_metadata_and_tagging) +static int s_test_s3_multipart_copy_object_with_replace_metadata_and_tagging( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + /* REPLACE: copy with explicit metadata+tags, verify they arrive on destination */ + ASSERT_SUCCESS(s_test_s3_copy_object_properties_helper( + allocator, + aws_byte_cursor_from_c_str("pre-existing-2GB"), + aws_byte_cursor_from_c_str("copies/destination_2GB_replace_props"), + true /* use_replace */, + true /* expect_metadata */)); + + /* COPY directive on the result: MPU path does NOT copy metadata (known limitation) */ + ASSERT_SUCCESS(s_test_s3_copy_object_properties_helper( + allocator, + aws_byte_cursor_from_c_str("copies/destination_2GB_replace_props"), + aws_byte_cursor_from_c_str("copies/destination_2GB_copy_directive"), + false /* use_replace */, + false /* expect_metadata: MPU path does NOT handle COPY directive */)); + + return AWS_OP_SUCCESS; +} + static int s_s3_get_object_mrap_helper(struct aws_allocator *allocator, bool multipart) { struct aws_s3_tester tester; AWS_ZERO_STRUCT(tester); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aws-c-s3-0.12.5/tests/s3_tester.c new/aws-c-s3-0.12.6/tests/s3_tester.c --- old/aws-c-s3-0.12.5/tests/s3_tester.c 2026-06-02 23:10:16.000000000 +0200 +++ new/aws-c-s3-0.12.6/tests/s3_tester.c 2026-06-11 20:12:01.000000000 +0200 @@ -2371,6 +2371,15 @@ goto error_clean_up_message; } + /* Copy Object request has a content length of 0 */ + struct aws_http_header content_length_header = { + .name = g_content_length_header_name, + .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("0"), + }; + if (aws_http_message_add_header(message, content_length_header)) { + goto error_clean_up_message; + } + struct aws_byte_buf copy_source_value_encoded; aws_byte_buf_init(©_source_value_encoded, allocator, 1024); aws_byte_buf_append_encoding_uri_path(©_source_value_encoded, &x_amz_source);
