This is an automated email from the ASF dual-hosted git repository.

bharat pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 8587de7  HDDS-6096. S3 copy object fails for source key with special 
char (#2912)
8587de7 is described below

commit 8587de72f109b0d7354cc098cfd4ce8070308dd4
Author: Doroszlai, Attila <[email protected]>
AuthorDate: Mon Dec 13 20:28:11 2021 +0100

    HDDS-6096. S3 copy object fails for source key with special char (#2912)
---
 .../dist/src/main/smoketest/s3/objectcopy.robot    | 20 +++++-----
 .../dist/src/main/smoketest/s3/objectdelete.robot  | 32 ++++++++--------
 .../src/main/smoketest/s3/objectmultidelete.robot  | 24 ++++++------
 .../dist/src/main/smoketest/s3/objectputget.robot  | 42 ++++++++++-----------
 .../hadoop/ozone/security/AWSV4AuthValidator.java  | 10 -----
 .../hadoop/ozone/s3/endpoint/ObjectEndpoint.java   | 19 ++++++++--
 .../s3/signature/AuthorizationV4QueryParser.java   |  5 ++-
 .../ozone/s3/signature/StringToSignProducer.java   |  5 +--
 .../org/apache/hadoop/ozone/s3/util/S3Utils.java   | 44 ++++++++++++++++++++++
 .../hadoop/ozone/s3/endpoint/TestObjectPut.java    | 15 ++++----
 10 files changed, 131 insertions(+), 85 deletions(-)

diff --git a/hadoop-ozone/dist/src/main/smoketest/s3/objectcopy.robot 
b/hadoop-ozone/dist/src/main/smoketest/s3/objectcopy.robot
index 588ead7..21764d6 100644
--- a/hadoop-ozone/dist/src/main/smoketest/s3/objectcopy.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/s3/objectcopy.robot
@@ -37,28 +37,28 @@ Create Dest Bucket
 Copy Object Happy Scenario
     Run Keyword if    '${DESTBUCKET}' == 'generated1'    Create Dest Bucket
                         Execute                    date > /tmp/copyfile
-    ${result} =         Execute AWSS3ApiCli        put-object --bucket 
${BUCKET} --key ${PREFIX}/copyobject/f1 --body /tmp/copyfile
-    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${BUCKET} --prefix ${PREFIX}/copyobject/
+    ${result} =         Execute AWSS3ApiCli        put-object --bucket 
${BUCKET} --key ${PREFIX}/copyobject/key=value/f1 --body /tmp/copyfile
+    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${BUCKET} --prefix ${PREFIX}/copyobject/key=value/
                         Should contain             ${result}         f1
 
-    ${result} =         Execute AWSS3ApiCli        copy-object --bucket 
${DESTBUCKET} --key ${PREFIX}/copyobject/f1 --copy-source 
${BUCKET}/${PREFIX}/copyobject/f1
-    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${DESTBUCKET} --prefix ${PREFIX}/copyobject/
+    ${result} =         Execute AWSS3ApiCli        copy-object --bucket 
${DESTBUCKET} --key ${PREFIX}/copyobject/key=value/f1 --copy-source 
${BUCKET}/${PREFIX}/copyobject/key=value/f1
+    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${DESTBUCKET} --prefix ${PREFIX}/copyobject/key=value/
                         Should contain             ${result}         f1
     #copying again will not throw error
-    ${result} =         Execute AWSS3ApiCli        copy-object --bucket 
${DESTBUCKET} --key ${PREFIX}/copyobject/f1 --copy-source 
${BUCKET}/${PREFIX}/copyobject/f1
-    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${DESTBUCKET} --prefix ${PREFIX}/copyobject/
+    ${result} =         Execute AWSS3ApiCli        copy-object --bucket 
${DESTBUCKET} --key ${PREFIX}/copyobject/key=value/f1 --copy-source 
${BUCKET}/${PREFIX}/copyobject/key=value/f1
+    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${DESTBUCKET} --prefix ${PREFIX}/copyobject/key=value/
                         Should contain             ${result}         f1
 
 Copy Object Where Bucket is not available
-    ${result} =         Execute AWSS3APICli and checkrc        copy-object 
--bucket dfdfdfdfdfnonexistent --key ${PREFIX}/copyobject/f1 --copy-source 
${BUCKET}/${PREFIX}/copyobject/f1      255
+    ${result} =         Execute AWSS3APICli and checkrc        copy-object 
--bucket dfdfdfdfdfnonexistent --key ${PREFIX}/copyobject/key=value/f1 
--copy-source ${BUCKET}/${PREFIX}/copyobject/key=value/f1      255
                         Should contain             ${result}        
NoSuchBucket
-    ${result} =         Execute AWSS3APICli and checkrc        copy-object 
--bucket ${DESTBUCKET} --key ${PREFIX}/copyobject/f1 --copy-source 
dfdfdfdfdfnonexistent/${PREFIX}/copyobject/f1  255
+    ${result} =         Execute AWSS3APICli and checkrc        copy-object 
--bucket ${DESTBUCKET} --key ${PREFIX}/copyobject/key=value/f1 --copy-source 
dfdfdfdfdfnonexistent/${PREFIX}/copyobject/key=value/f1  255
                         Should contain             ${result}        
NoSuchBucket
 
 Copy Object Where both source and dest are same with change to storageclass
-     ${result} =         Execute AWSS3APICli        copy-object 
--storage-class REDUCED_REDUNDANCY --bucket ${DESTBUCKET} --key 
${PREFIX}/copyobject/f1 --copy-source ${DESTBUCKET}/${PREFIX}/copyobject/f1
+     ${result} =         Execute AWSS3APICli        copy-object 
--storage-class REDUCED_REDUNDANCY --bucket ${DESTBUCKET} --key 
${PREFIX}/copyobject/key=value/f1 --copy-source 
${DESTBUCKET}/${PREFIX}/copyobject/key=value/f1
                          Should contain             ${result}        ETag
 
 Copy Object Where Key not available
-    ${result} =         Execute AWSS3APICli and checkrc        copy-object 
--bucket ${DESTBUCKET} --key ${PREFIX}/copyobject/f1 --copy-source 
${BUCKET}/nonnonexistentkey       255
+    ${result} =         Execute AWSS3APICli and checkrc        copy-object 
--bucket ${DESTBUCKET} --key ${PREFIX}/copyobject/key=value/f1 --copy-source 
${BUCKET}/nonnonexistentkey       255
                         Should contain             ${result}        NoSuchKey
diff --git a/hadoop-ozone/dist/src/main/smoketest/s3/objectdelete.robot 
b/hadoop-ozone/dist/src/main/smoketest/s3/objectdelete.robot
index ce51731..73bf3da 100644
--- a/hadoop-ozone/dist/src/main/smoketest/s3/objectdelete.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/s3/objectdelete.robot
@@ -29,12 +29,12 @@ ${BUCKET}             generated
 *** Test Cases ***
 Delete file with s3api
                         Execute                    date > /tmp/testfile
-    ${result} =         Execute AWSS3ApiCli        put-object --bucket 
${BUCKET} --key ${PREFIX}/deletetestapi/f1 --body /tmp/testfile
-    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${BUCKET} --prefix ${PREFIX}/deletetestapi/
-                        Should contain             ${result}         
"${PREFIX}/deletetestapi/f1"
-    ${result} =         Execute AWSS3APICli        delete-object --bucket 
${BUCKET} --key ${PREFIX}/deletetestapi/f1
-    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${BUCKET} --prefix ${PREFIX}/deletetestapi/
-                        Should not contain         ${result}         
"${PREFIX}/deletetestapi/f1"
+    ${result} =         Execute AWSS3ApiCli        put-object --bucket 
${BUCKET} --key ${PREFIX}/deletetestapi/key=value/f1 --body /tmp/testfile
+    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${BUCKET} --prefix ${PREFIX}/deletetestapi/key=value/
+                        Should contain             ${result}         
"${PREFIX}/deletetestapi/key=value/f1"
+    ${result} =         Execute AWSS3APICli        delete-object --bucket 
${BUCKET} --key ${PREFIX}/deletetestapi/key=value/f1
+    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${BUCKET} --prefix ${PREFIX}/deletetestapi/key=value/
+                        Should not contain         ${result}         
"${PREFIX}/deletetestapi/key=value/f1"
 
 Delete file with s3api, file doesn't exist
     ${result} =         Execute AWSS3Cli           ls s3://${BUCKET}/
@@ -45,24 +45,24 @@ Delete file with s3api, file doesn't exist
 
 Delete dir with s3api
                         Execute                    date > /tmp/testfile
-    ${result} =         Execute AWSS3Cli           cp /tmp/testfile 
s3://${BUCKET}/${PREFIX}/deletetestapidir/f1
-    ${result} =         Execute AWSS3Cli           ls 
s3://${BUCKET}/${PREFIX}/deletetestapidir/
+    ${result} =         Execute AWSS3Cli           cp /tmp/testfile 
s3://${BUCKET}/${PREFIX}/deletetestapidir/key=value/f1
+    ${result} =         Execute AWSS3Cli           ls 
s3://${BUCKET}/${PREFIX}/deletetestapidir/key=value/
                         Should contain             ${result}         f1
-    ${result} =         Execute AWSS3APICli        delete-object --bucket 
${BUCKET} --key ${PREFIX}/deletetestapidir/
-    ${result} =         Execute AWSS3Cli           ls 
s3://${BUCKET}/${PREFIX}/deletetestapidir/
+    ${result} =         Execute AWSS3APICli        delete-object --bucket 
${BUCKET} --key ${PREFIX}/deletetestapidir/key=value/
+    ${result} =         Execute AWSS3Cli           ls 
s3://${BUCKET}/${PREFIX}/deletetestapidir/key=value/
                         Should contain             ${result}         f1
-    ${result} =         Execute AWSS3APICli        delete-object --bucket 
${BUCKET} --key ${PREFIX}/deletetestapidir/f1
+    ${result} =         Execute AWSS3APICli        delete-object --bucket 
${BUCKET} --key ${PREFIX}/deletetestapidir/key=value/f1
 
 
 Delete file with s3api, file doesn't exist, prefix of a real file
                         Execute                    date > /tmp/testfile
-    ${result} =         Execute AWSS3Cli           cp /tmp/testfile 
s3://${BUCKET}/${PREFIX}/deletetestapiprefix/filefile
-    ${result} =         Execute AWSS3Cli           ls 
s3://${BUCKET}/${PREFIX}/deletetestapiprefix/
+    ${result} =         Execute AWSS3Cli           cp /tmp/testfile 
s3://${BUCKET}/${PREFIX}/deletetestapiprefix/key=value/filefile
+    ${result} =         Execute AWSS3Cli           ls 
s3://${BUCKET}/${PREFIX}/deletetestapiprefix/key=value/
                         Should contain             ${result}         filefile
-    ${result} =         Execute AWSS3APICli        delete-object --bucket 
${BUCKET} --key ${PREFIX}/deletetestapiprefix/file
-    ${result} =         Execute AWSS3Cli           ls 
s3://${BUCKET}/${PREFIX}/deletetestapiprefix/
+    ${result} =         Execute AWSS3APICli        delete-object --bucket 
${BUCKET} --key ${PREFIX}/deletetestapiprefix/key=value/file
+    ${result} =         Execute AWSS3Cli           ls 
s3://${BUCKET}/${PREFIX}/deletetestapiprefix/key=value/
                         Should contain             ${result}         filefile
-    ${result} =         Execute AWSS3APICli        delete-object --bucket 
${BUCKET} --key ${PREFIX}/deletetestapiprefix/filefile
+    ${result} =         Execute AWSS3APICli        delete-object --bucket 
${BUCKET} --key ${PREFIX}/deletetestapiprefix/key=value/filefile
 
 
 
diff --git a/hadoop-ozone/dist/src/main/smoketest/s3/objectmultidelete.robot 
b/hadoop-ozone/dist/src/main/smoketest/s3/objectmultidelete.robot
index cc49129..37dc2d1 100644
--- a/hadoop-ozone/dist/src/main/smoketest/s3/objectmultidelete.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/s3/objectmultidelete.robot
@@ -30,20 +30,20 @@ ${BUCKET}             generated
 
 Delete file with multi delete
                         Execute                    date > /tmp/testfile
-    ${result} =         Execute AWSS3ApiCli        put-object --bucket 
${BUCKET} --key ${PREFIX}/multidelete/f1 --body /tmp/testfile
-    ${result} =         Execute AWSS3ApiCli        put-object --bucket 
${BUCKET} --key ${PREFIX}/multidelete/f2 --body /tmp/testfile
-    ${result} =         Execute AWSS3ApiCli        put-object --bucket 
${BUCKET} --key ${PREFIX}/multidelete/f3 --body /tmp/testfile
-    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${BUCKET} --prefix ${PREFIX}/multidelete/
-                        Should contain             ${result}         
${PREFIX}/multidelete/f1
-                        Should contain             ${result}         
${PREFIX}/multidelete/f2
-                        Should contain             ${result}         
${PREFIX}/multidelete/f3
+    ${result} =         Execute AWSS3ApiCli        put-object --bucket 
${BUCKET} --key ${PREFIX}/multidelete/key=value/f1 --body /tmp/testfile
+    ${result} =         Execute AWSS3ApiCli        put-object --bucket 
${BUCKET} --key ${PREFIX}/multidelete/key=value/f2 --body /tmp/testfile
+    ${result} =         Execute AWSS3ApiCli        put-object --bucket 
${BUCKET} --key ${PREFIX}/multidelete/key=value/f3 --body /tmp/testfile
+    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${BUCKET} --prefix ${PREFIX}/multidelete/key=value/
+                        Should contain             ${result}         
${PREFIX}/multidelete/key=value/f1
+                        Should contain             ${result}         
${PREFIX}/multidelete/key=value/f2
+                        Should contain             ${result}         
${PREFIX}/multidelete/key=value/f3
                         Should contain             ${result}         STANDARD
                         Should not contain         ${result}         
REDUCED_REDUNDANCY
-    ${result} =         Execute AWSS3APICli        delete-objects --bucket 
${BUCKET} --delete 
'Objects=[{Key=${PREFIX}/multidelete/f1},{Key=${PREFIX}/multidelete/f2},{Key=${PREFIX}/multidelete/f4}]'
+    ${result} =         Execute AWSS3APICli        delete-objects --bucket 
${BUCKET} --delete 
'Objects=[{Key=${PREFIX}/multidelete/key=value/f1},{Key=${PREFIX}/multidelete/key=value/f2},{Key=${PREFIX}/multidelete/key=value/f4}]'
                         Should not contain         ${result}         Error
-    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${BUCKET} --prefix ${PREFIX}/multidelete/
-                        Should not contain         ${result}         
${PREFIX}/multidelete/f1
-                        Should not contain         ${result}         
${PREFIX}/multidelete/f2
-                        Should contain             ${result}         
${PREFIX}/multidelete/f3
+    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${BUCKET} --prefix ${PREFIX}/multidelete/key=value/
+                        Should not contain         ${result}         
${PREFIX}/multidelete/key=value/f1
+                        Should not contain         ${result}         
${PREFIX}/multidelete/key=value/f2
+                        Should contain             ${result}         
${PREFIX}/multidelete/key=value/f3
                         Should contain             ${result}         STANDARD
                         Should not contain         ${result}         
REDUCED_REDUNDANCY
diff --git a/hadoop-ozone/dist/src/main/smoketest/s3/objectputget.robot 
b/hadoop-ozone/dist/src/main/smoketest/s3/objectputget.robot
index e1896e2..575e538 100644
--- a/hadoop-ozone/dist/src/main/smoketest/s3/objectputget.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/s3/objectputget.robot
@@ -31,22 +31,22 @@ ${BUCKET}             generated
 
 Put object to s3
                         Execute                    echo "Randomtext" > 
/tmp/testfile
-    ${result} =         Execute AWSS3ApiCli        put-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/f1 --body /tmp/testfile
-    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${BUCKET} --prefix ${PREFIX}/putobject/
+    ${result} =         Execute AWSS3ApiCli        put-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/key=value/f1 --body /tmp/testfile
+    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${BUCKET} --prefix ${PREFIX}/putobject/key=value/
                         Should contain             ${result}         f1
 
                         Execute                    touch -f /tmp/zerobyte
-    ${result} =         Execute AWSS3ApiCli        put-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/zerobyte --body /tmp/zerobyte
-    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${BUCKET} --prefix ${PREFIX}/putobject/
+    ${result} =         Execute AWSS3ApiCli        put-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/key=value/zerobyte --body /tmp/zerobyte
+    ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${BUCKET} --prefix ${PREFIX}/putobject/key=value/
                         Should contain             ${result}         zerobyte
 
 #This test depends on the previous test case. Can't be executes alone
 Get object from s3
-    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/f1 /tmp/testfile.result
+    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/key=value/f1 /tmp/testfile.result
     Compare files               /tmp/testfile              /tmp/testfile.result
 
 Get Partial object from s3 with both start and endoffset
-    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/f1 --range bytes=0-4 /tmp/testfile1.result
+    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/key=value/f1 --range bytes=0-4 
/tmp/testfile1.result
                                 Should contain             ${result}        
ContentRange
                                 Should contain             ${result}        
bytes 0-4/11
                                 Should contain             ${result}        
AcceptRanges
@@ -54,7 +54,7 @@ Get Partial object from s3 with both start and endoffset
     ${actualData} =             Execute                    cat 
/tmp/testfile1.result
                                 Should Be Equal            ${expectedData}     
       ${actualData}
 
-    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/f1 --range bytes=2-4 /tmp/testfile1.result1
+    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/key=value/f1 --range bytes=2-4 
/tmp/testfile1.result1
                                 Should contain             ${result}        
ContentRange
                                 Should contain             ${result}        
bytes 2-4/11
                                 Should contain             ${result}        
AcceptRanges
@@ -63,7 +63,7 @@ Get Partial object from s3 with both start and endoffset
                                 Should Be Equal            ${expectedData}     
       ${actualData}
 
 # end offset greater than file size and start with in file length
-    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/f1 --range bytes=2-1000 
/tmp/testfile1.result2
+    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/key=value/f1 --range bytes=2-1000 
/tmp/testfile1.result2
                                 Should contain             ${result}        
ContentRange
                                 Should contain             ${result}        
bytes 2-10/11
                                 Should contain             ${result}        
AcceptRanges
@@ -72,12 +72,12 @@ Get Partial object from s3 with both start and endoffset
                                 Should Be Equal            ${expectedData}     
       ${actualData}
 
 Get Partial object from s3 with both start and endoffset(start offset and 
endoffset is greater than file size)
-    ${result} =                 Execute AWSS3APICli and checkrc        
get-object --bucket ${BUCKET} --key ${PREFIX}/putobject/f1 --range 
bytes=10000-10000 /tmp/testfile2.result   255
+    ${result} =                 Execute AWSS3APICli and checkrc        
get-object --bucket ${BUCKET} --key ${PREFIX}/putobject/key=value/f1 --range 
bytes=10000-10000 /tmp/testfile2.result   255
                                 Should contain             ${result}        
InvalidRange
 
 
 Get Partial object from s3 with both start and endoffset(end offset is greater 
than file size)
-    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/f1 --range bytes=0-10000 
/tmp/testfile2.result
+    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/key=value/f1 --range bytes=0-10000 
/tmp/testfile2.result
                                 Should contain             ${result}        
ContentRange
                                 Should contain             ${result}        
bytes 0-10/11
                                 Should contain             ${result}        
AcceptRanges
@@ -86,7 +86,7 @@ Get Partial object from s3 with both start and endoffset(end 
offset is greater t
                                 Should Be Equal            ${expectedData}     
       ${actualData}
 
 Get Partial object from s3 with only start offset
-    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/f1 --range bytes=0- /tmp/testfile3.result
+    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/key=value/f1 --range bytes=0- 
/tmp/testfile3.result
                                 Should contain             ${result}        
ContentRange
                                 Should contain             ${result}        
bytes 0-10/11
                                 Should contain             ${result}        
AcceptRanges
@@ -95,7 +95,7 @@ Get Partial object from s3 with only start offset
                                 Should Be Equal            ${expectedData}     
       ${actualData}
 
 Get Partial object from s3 with both start and endoffset which are equal
-    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/f1 --range bytes=0-0 /tmp/testfile4.result
+    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/key=value/f1 --range bytes=0-0 
/tmp/testfile4.result
                                 Should contain             ${result}        
ContentRange
                                 Should contain             ${result}        
bytes 0-0/11
                                 Should contain             ${result}        
AcceptRanges
@@ -103,7 +103,7 @@ Get Partial object from s3 with both start and endoffset 
which are equal
     ${actualData} =             Execute                    cat 
/tmp/testfile4.result
                                 Should Be Equal            ${expectedData}     
       ${actualData}
 
-    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/f1 --range bytes=4-4 /tmp/testfile5.result
+    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/key=value/f1 --range bytes=4-4 
/tmp/testfile5.result
                                 Should contain             ${result}        
ContentRange
                                 Should contain             ${result}        
bytes 4-4/11
                                 Should contain             ${result}        
AcceptRanges
@@ -112,7 +112,7 @@ Get Partial object from s3 with both start and endoffset 
which are equal
                                 Should Be Equal            ${expectedData}     
       ${actualData}
 
 Get Partial object from s3 to get last n bytes
-    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/f1 --range bytes=-4 /tmp/testfile6.result
+    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/key=value/f1 --range bytes=-4 
/tmp/testfile6.result
                                 Should contain             ${result}        
ContentRange
                                 Should contain             ${result}        
bytes 7-10/11
                                 Should contain             ${result}        
AcceptRanges
@@ -121,7 +121,7 @@ Get Partial object from s3 to get last n bytes
                                 Should Be Equal            ${expectedData}     
       ${actualData}
 
 # if end is greater than file length, returns whole file
-    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/f1 --range bytes=-10000 
/tmp/testfile7.result
+    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/key=value/f1 --range bytes=-10000 
/tmp/testfile7.result
                                 Should contain             ${result}        
ContentRange
                                 Should contain             ${result}        
bytes 0-10/11
                                 Should contain             ${result}        
AcceptRanges
@@ -130,14 +130,14 @@ Get Partial object from s3 to get last n bytes
                                 Should Be Equal            ${expectedData}     
       ${actualData}
 
 Incorrect values for end and start offset
-    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/f1 --range bytes=-11-10000 
/tmp/testfile8.result
+    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/key=value/f1 --range bytes=-11-10000 
/tmp/testfile8.result
                                 Should not contain         ${result}        
ContentRange
                                 Should contain             ${result}        
AcceptRanges
     ${expectedData} =           Execute                    cat /tmp/testfile
     ${actualData} =             Execute                    cat 
/tmp/testfile8.result
                                 Should Be Equal            ${expectedData}     
       ${actualData}
 
-    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/f1 --range bytes=11-8 /tmp/testfile9.result
+    ${result} =                 Execute AWSS3ApiCli        get-object --bucket 
${BUCKET} --key ${PREFIX}/putobject/key=value/f1 --range bytes=11-8 
/tmp/testfile9.result
                                 Should not contain         ${result}        
ContentRange
                                 Should contain             ${result}        
AcceptRanges
     ${expectedData} =           Execute                    cat /tmp/testfile
@@ -145,11 +145,11 @@ Incorrect values for end and start offset
                                 Should Be Equal            ${expectedData}     
       ${actualData}
 
 Zero byte file
-    ${result} =                 Execute AWSS3APICli and checkrc        
get-object --bucket ${BUCKET} --key ${PREFIX}/putobject/zerobyte --range 
bytes=0-0 /tmp/testfile2.result   255
+    ${result} =                 Execute AWSS3APICli and checkrc        
get-object --bucket ${BUCKET} --key ${PREFIX}/putobject/key=value/zerobyte 
--range bytes=0-0 /tmp/testfile2.result   255
                                 Should contain             ${result}        
InvalidRange
 
-    ${result} =                 Execute AWSS3APICli and checkrc        
get-object --bucket ${BUCKET} --key ${PREFIX}/putobject/zerobyte --range 
bytes=0-1 /tmp/testfile2.result   255
+    ${result} =                 Execute AWSS3APICli and checkrc        
get-object --bucket ${BUCKET} --key ${PREFIX}/putobject/key=value/zerobyte 
--range bytes=0-1 /tmp/testfile2.result   255
                                 Should contain             ${result}        
InvalidRange
 
-    ${result} =                 Execute AWSS3APICli and checkrc        
get-object --bucket ${BUCKET} --key ${PREFIX}/putobject/zerobyte --range 
bytes=0-10000 /tmp/testfile2.result   255
-                                Should contain             ${result}        
InvalidRange
\ No newline at end of file
+    ${result} =                 Execute AWSS3APICli and checkrc        
get-object --bucket ${BUCKET} --key ${PREFIX}/putobject/key=value/zerobyte 
--range bytes=0-10000 /tmp/testfile2.result   255
+                                Should contain             ${result}        
InvalidRange
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/AWSV4AuthValidator.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/AWSV4AuthValidator.java
index ca0bc18..1b526e7 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/AWSV4AuthValidator.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/AWSV4AuthValidator.java
@@ -24,8 +24,6 @@ import org.slf4j.LoggerFactory;
 
 import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
 import java.security.GeneralSecurityException;
 import java.security.MessageDigest;
@@ -45,14 +43,6 @@ final class AWSV4AuthValidator {
   private AWSV4AuthValidator() {
   }
 
-  private static String urlDecode(String str) {
-    try {
-      return URLDecoder.decode(str, StandardCharsets.UTF_8.name());
-    } catch (UnsupportedEncodingException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
   public static String hash(String payload) throws NoSuchAlgorithmException {
     MessageDigest md = MessageDigest.getInstance("SHA-256");
     md.update(payload.getBytes(StandardCharsets.UTF_8));
diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
index 52b9c49..d2a45b1 100644
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
@@ -41,6 +41,7 @@ import javax.ws.rs.core.StreamingOutput;
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
 import java.text.ParseException;
 import java.time.Instant;
 import java.time.ZoneId;
@@ -90,6 +91,7 @@ import static 
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ENABLE_FILESYSTEM
 import static 
org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_CLIENT_BUFFER_SIZE_DEFAULT;
 import static 
org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_CLIENT_BUFFER_SIZE_KEY;
 import static 
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.ENTITY_TOO_SMALL;
+import static 
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.INVALID_ARGUMENT;
 import static 
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.INVALID_REQUEST;
 import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NO_SUCH_UPLOAD;
 import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.PRECOND_FAILED;
@@ -102,6 +104,8 @@ import static 
org.apache.hadoop.ozone.s3.util.S3Consts.COPY_SOURCE_IF_UNMODIFIED
 import static org.apache.hadoop.ozone.s3.util.S3Consts.RANGE_HEADER;
 import static 
org.apache.hadoop.ozone.s3.util.S3Consts.RANGE_HEADER_SUPPORTED_UNIT;
 import static org.apache.hadoop.ozone.s3.util.S3Consts.STORAGE_CLASS_HEADER;
+import static org.apache.hadoop.ozone.s3.util.S3Utils.urlDecode;
+
 import org.apache.http.HttpStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -828,14 +832,21 @@ public class ObjectEndpoint extends EndpointBase {
     }
     int pos = header.indexOf('/');
     if (pos == -1) {
-      OS3Exception ex = S3ErrorTable.newError(S3ErrorTable
-          .INVALID_ARGUMENT, header);
+      OS3Exception ex = S3ErrorTable.newError(INVALID_ARGUMENT, header);
       ex.setErrorMessage("Copy Source must mention the source bucket and " +
           "key: sourcebucket/sourcekey");
       throw ex;
     }
 
-    return Pair.of(header.substring(0, pos), header.substring(pos + 1));
+    try {
+      String bucket = header.substring(0, pos);
+      String key = urlDecode(header.substring(pos + 1));
+      return Pair.of(bucket, key);
+    } catch (UnsupportedEncodingException e) {
+      OS3Exception ex = S3ErrorTable.newError(INVALID_ARGUMENT, header);
+      ex.setErrorMessage("Copy Source header could not be url-decoded");
+      throw ex;
+    }
   }
 
   private static S3StorageType toS3StorageType(String storageType)
@@ -843,7 +854,7 @@ public class ObjectEndpoint extends EndpointBase {
     try {
       return S3StorageType.valueOf(storageType);
     } catch (IllegalArgumentException ex) {
-      throw S3ErrorTable.newError(S3ErrorTable.INVALID_ARGUMENT,
+      throw S3ErrorTable.newError(INVALID_ARGUMENT,
           storageType);
     }
   }
diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4QueryParser.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4QueryParser.java
index fbf4594..40f9542 100644
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4QueryParser.java
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4QueryParser.java
@@ -18,7 +18,6 @@
 package org.apache.hadoop.ozone.s3.signature;
 
 import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
 import java.time.ZonedDateTime;
 import java.util.Map;
 
@@ -27,6 +26,8 @@ import 
org.apache.hadoop.ozone.s3.signature.SignatureInfo.Version;
 
 import com.google.common.annotations.VisibleForTesting;
 import static java.time.temporal.ChronoUnit.SECONDS;
+import static org.apache.hadoop.ozone.s3.util.S3Utils.urlDecode;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -63,7 +64,7 @@ public class AuthorizationV4QueryParser implements 
SignatureParser {
     Credential credential =
         null;
     try {
-      credential = new Credential(URLDecoder.decode(rawCredential, "UTF-8"));
+      credential = new Credential(urlDecode(rawCredential));
     } catch (UnsupportedEncodingException e) {
       throw new IllegalArgumentException(
           "X-Amz-Credential is not proper URL encoded");
diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java
index 3202a96..9dfa13e 100644
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java
@@ -23,7 +23,6 @@ import java.io.UnsupportedEncodingException;
 import java.net.InetAddress;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.net.URLEncoder;
 import java.net.UnknownHostException;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
@@ -42,6 +41,7 @@ import java.util.regex.Pattern;
 
 import org.apache.hadoop.ozone.s3.exception.OS3Exception;
 import 
org.apache.hadoop.ozone.s3.signature.AWSSignatureProcessor.LowerCaseKeyStringMap;
+import org.apache.hadoop.ozone.s3.util.S3Utils;
 import org.apache.hadoop.util.StringUtils;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -247,8 +247,7 @@ public final class StringToSignProducer {
 
   private static String urlEncode(String str) {
     try {
-
-      return URLEncoder.encode(str, UTF_8.name())
+      return S3Utils.urlEncode(str)
           .replaceAll("\\+", "%20")
           .replaceAll("%7E", "~");
     } catch (UnsupportedEncodingException e) {
diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Utils.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Utils.java
new file mode 100644
index 0000000..c9e16d1
--- /dev/null
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Utils.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.ozone.s3.util;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * Utilities.
+ */
+public final class S3Utils {
+
+  public static String urlDecode(String str)
+      throws UnsupportedEncodingException {
+    return URLDecoder.decode(str, UTF_8.name());
+  }
+
+  public static String urlEncode(String str)
+      throws UnsupportedEncodingException {
+    return URLEncoder.encode(str, UTF_8.name());
+  }
+
+  private S3Utils() {
+    // no instances
+  }
+}
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
index 95b1244..cf44854 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
@@ -43,6 +43,7 @@ import org.mockito.Mockito;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.hadoop.ozone.s3.util.S3Consts.COPY_SOURCE_HEADER;
 import static org.apache.hadoop.ozone.s3.util.S3Consts.STORAGE_CLASS_HEADER;
+import static org.apache.hadoop.ozone.s3.util.S3Utils.urlEncode;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.when;
@@ -53,9 +54,9 @@ import static org.mockito.Mockito.when;
 public class TestObjectPut {
   public static final String CONTENT = "0123456789";
   private String bucketName = "b1";
-  private String keyName = "key1";
+  private String keyName = "key=value/1";
   private String destBucket = "b2";
-  private String destkey = "key2";
+  private String destkey = "key=value/2";
   private String nonexist = "nonexist";
   private OzoneClient clientStub;
   private ObjectEndpoint objectEndpoint;
@@ -152,7 +153,7 @@ public class TestObjectPut {
 
     // Add copy header, and then call put
     when(headers.getHeaderString(COPY_SOURCE_HEADER)).thenReturn(
-        bucketName  + "/" + keyName);
+        bucketName  + "/" + urlEncode(keyName));
 
     response = objectEndpoint.put(destBucket, destkey, CONTENT.length(), 1,
         null, body);
@@ -178,7 +179,7 @@ public class TestObjectPut {
     // source bucket not found
     try {
       when(headers.getHeaderString(COPY_SOURCE_HEADER)).thenReturn(
-          nonexist + "/"  + keyName);
+          nonexist + "/"  + urlEncode(keyName));
       objectEndpoint.put(destBucket, destkey, CONTENT.length(), 1, null,
           body);
       fail("test copy object failed");
@@ -189,7 +190,7 @@ public class TestObjectPut {
     // dest bucket not found
     try {
       when(headers.getHeaderString(COPY_SOURCE_HEADER)).thenReturn(
-          bucketName + "/" + keyName);
+          bucketName + "/" + urlEncode(keyName));
       objectEndpoint.put(nonexist, destkey, CONTENT.length(), 1, null, body);
       fail("test copy object failed");
     } catch (OS3Exception ex) {
@@ -199,7 +200,7 @@ public class TestObjectPut {
     //Both source and dest bucket not found
     try {
       when(headers.getHeaderString(COPY_SOURCE_HEADER)).thenReturn(
-          nonexist + "/" + keyName);
+          nonexist + "/" + urlEncode(keyName));
       objectEndpoint.put(nonexist, destkey, CONTENT.length(), 1, null, body);
       fail("test copy object failed");
     } catch (OS3Exception ex) {
@@ -209,7 +210,7 @@ public class TestObjectPut {
     // source key not found
     try {
       when(headers.getHeaderString(COPY_SOURCE_HEADER)).thenReturn(
-          bucketName + "/" + nonexist);
+          bucketName + "/" + urlEncode(nonexist));
       objectEndpoint.put("nonexistent", keyName, CONTENT.length(), 1,
           null, body);
       fail("test copy object failed");

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to