@@ -2703,6 +2703,68 @@ describe('S3 Protocol', () => {
27032703 expect ( listedKeys ) . toEqual ( expect . arrayContaining ( [ sourceKey , destinationKey ] ) )
27042704 } )
27052705
2706+ it ( 'can copy objects when partially encoded CopySource keeps raw ? and # in the key' , async ( ) => {
2707+ const bucketName = await createBucket ( client )
2708+ const sourceKey = `copy-src-reserved-${ randomUUID ( ) } -일이삼?x=1#frag.png`
2709+ const destinationKey = `copy-dst-reserved-${ randomUUID ( ) } -🙂.jpg`
2710+
2711+ await client . send (
2712+ new PutObjectCommand ( {
2713+ Bucket : bucketName ,
2714+ Key : sourceKey ,
2715+ Body : Buffer . alloc ( 1024 * 128 ) ,
2716+ } )
2717+ )
2718+
2719+ const copyObjectResp = await client . send (
2720+ new CopyObjectCommand ( {
2721+ Bucket : bucketName ,
2722+ Key : destinationKey ,
2723+ CopySource : encodeURI ( `${ bucketName } /${ sourceKey } ` ) ,
2724+ } )
2725+ )
2726+ expect ( copyObjectResp . $metadata . httpStatusCode ) . toBe ( 200 )
2727+
2728+ const listObjectsResp = await client . send (
2729+ new ListObjectsV2Command ( {
2730+ Bucket : bucketName ,
2731+ } )
2732+ )
2733+ const listedKeys = ( listObjectsResp . Contents || [ ] ) . map ( ( item ) => item . Key )
2734+ expect ( listedKeys ) . toEqual ( expect . arrayContaining ( [ sourceKey , destinationKey ] ) )
2735+ } )
2736+
2737+ it ( 'can copy objects using fully URL-encoded leading-slash CopySource' , async ( ) => {
2738+ const bucketName = await createBucket ( client )
2739+ const sourceKey = getUnicodeObjectName ( )
2740+ const destinationKey = `copy-dst-leading-encoded-${ randomUUID ( ) } -🙂.jpg`
2741+
2742+ await client . send (
2743+ new PutObjectCommand ( {
2744+ Bucket : bucketName ,
2745+ Key : sourceKey ,
2746+ Body : Buffer . alloc ( 1024 * 128 ) ,
2747+ } )
2748+ )
2749+
2750+ const copyObjectResp = await client . send (
2751+ new CopyObjectCommand ( {
2752+ Bucket : bucketName ,
2753+ Key : destinationKey ,
2754+ CopySource : encodeURIComponent ( `/${ bucketName } /${ sourceKey } ` ) ,
2755+ } )
2756+ )
2757+ expect ( copyObjectResp . $metadata . httpStatusCode ) . toBe ( 200 )
2758+
2759+ const listObjectsResp = await client . send (
2760+ new ListObjectsV2Command ( {
2761+ Bucket : bucketName ,
2762+ } )
2763+ )
2764+ const listedKeys = ( listObjectsResp . Contents || [ ] ) . map ( ( item ) => item . Key )
2765+ expect ( listedKeys ) . toEqual ( expect . arrayContaining ( [ sourceKey , destinationKey ] ) )
2766+ } )
2767+
27062768 it ( 'can upload part copy using Unicode keys in CopySource' , async ( ) => {
27072769 const bucketName = await createBucket ( client )
27082770 const sourceKey = `copy-part-src-${ randomUUID ( ) } -일이삼-🙂.jpg`
@@ -2779,6 +2841,44 @@ describe('S3 Protocol', () => {
27792841 expect ( listPartsResp . Parts ?. length ) . toBe ( 1 )
27802842 } )
27812843
2844+ it ( 'can upload part copy when partially encoded CopySource keeps raw ? and # in the key' , async ( ) => {
2845+ const bucketName = await createBucket ( client )
2846+ const sourceKey = `copy-part-src-reserved-${ randomUUID ( ) } -일이삼?x=1#frag.png`
2847+ const destinationKey = `copy-part-dst-reserved-${ randomUUID ( ) } -🙂.jpg`
2848+
2849+ await uploadFile ( client , bucketName , sourceKey , 8 )
2850+
2851+ const createMultiPartUpload = new CreateMultipartUploadCommand ( {
2852+ Bucket : bucketName ,
2853+ Key : destinationKey ,
2854+ ContentType : 'image/jpg' ,
2855+ CacheControl : 'max-age=2000' ,
2856+ } )
2857+ const createMultipartResp = await client . send ( createMultiPartUpload )
2858+ expect ( createMultipartResp . UploadId ) . toBeTruthy ( )
2859+
2860+ const uploadPartCopyResp = await client . send (
2861+ new UploadPartCopyCommand ( {
2862+ Bucket : bucketName ,
2863+ Key : destinationKey ,
2864+ UploadId : createMultipartResp . UploadId ,
2865+ PartNumber : 1 ,
2866+ CopySource : encodeURI ( `${ bucketName } /${ sourceKey } ` ) ,
2867+ CopySourceRange : 'bytes=0-4096' ,
2868+ } )
2869+ )
2870+ expect ( uploadPartCopyResp . CopyPartResult ?. ETag ) . toBeTruthy ( )
2871+
2872+ const listPartsResp = await client . send (
2873+ new ListPartsCommand ( {
2874+ Bucket : bucketName ,
2875+ Key : destinationKey ,
2876+ UploadId : createMultipartResp . UploadId ,
2877+ } )
2878+ )
2879+ expect ( listPartsResp . Parts ?. length ) . toBe ( 1 )
2880+ } )
2881+
27822882 it ( 'should not upload if the name contains invalid characters' , async ( ) => {
27832883 const bucketName = await createBucket ( client )
27842884 const invalidObjectName = getInvalidObjectName ( )
0 commit comments