From 7c2a2e410edcf14dd5b55ece6bd464b3683990f4 Mon Sep 17 00:00:00 2001 From: "Bala.FA" Date: Mon, 15 Jul 2019 18:29:34 +0530 Subject: [PATCH] fix: handle SSE in copyObject() properly and have one source code. Added new version of copyObject(). Note: this method makes source and destination argument swapping correctly to denote on which bucket and object the request is sent. ``` public void copyObject(String bucketName, String objectName, Map headerMap, ServerSideEncryption sse, String srcBucketName, String srcObjectName, ServerSideEncryption srcSse, CopyConditions copyConditions) ``` --- api/src/main/java/io/minio/MinioClient.java | 191 ++++++++++++++------ examples/CopyObject.java | 4 +- examples/CopyObjectEncrypted.java | 4 +- examples/CopyObjectEncryptedKms.java | 6 +- examples/CopyObjectEncryptedS3.java | 6 +- examples/CopyObjectMetadata.java | 4 +- functional/FunctionalTest.java | 26 +-- 7 files changed, 157 insertions(+), 84 deletions(-) diff --git a/api/src/main/java/io/minio/MinioClient.java b/api/src/main/java/io/minio/MinioClient.java index 192404932..6db6b5ea3 100755 --- a/api/src/main/java/io/minio/MinioClient.java +++ b/api/src/main/java/io/minio/MinioClient.java @@ -1300,6 +1300,19 @@ private void checkReadRequestSse(ServerSideEncryption sse) throws InvalidArgumen } } + private void checkWriteRequestSse(ServerSideEncryption sse) throws InvalidArgumentException { + if (sse == null) { + return; + } + + if (!this.baseUrl.isHttps()) { + if (sse.getType() == ServerSideEncryption.Type.SSE_C || sse.getType() == ServerSideEncryption.Type.SSE_KMS) { + throw new InvalidArgumentException(sse.getType().name() + + " operations must be performed over a secure connection."); + } + } + } + /** * Executes GET method for given request parameters. * @@ -2002,13 +2015,15 @@ public void getObject(String bucketName, String objectName, ServerSideEncryption * @throws XmlPullParserException upon parsing response xml * @throws InvalidArgumentException upon invalid value is passed to a method. * @throws InvalidResponseException upon a non-xml response from server + * + * @deprecated As of release 6.1 */ + @Deprecated public void copyObject(String bucketName, String objectName, String destBucketName) throws InvalidKeyException, InvalidBucketNameException, NoSuchAlgorithmException, InsufficientDataException, NoResponseException, ErrorResponseException, InternalException, IOException, XmlPullParserException, InvalidArgumentException, InvalidResponseException { - - copyObject(bucketName, objectName, destBucketName, null, null, null); + copyObject(destBucketName, objectName, null, null, bucketName, objectName, null, null); } /** @@ -2045,13 +2060,18 @@ public void copyObject(String bucketName, String objectName, String destBucketNa * @throws XmlPullParserException upon parsing response xml * @throws InvalidArgumentException upon invalid value is passed to a method. * @throws InvalidResponseException upon a non-xml response from server + * + * @deprecated As of release 6.1 */ + @Deprecated public void copyObject(String bucketName, String objectName, String destBucketName, String destObjectName) throws InvalidKeyException, InvalidBucketNameException, NoSuchAlgorithmException, InsufficientDataException, NoResponseException, ErrorResponseException, InternalException, IOException, XmlPullParserException, InvalidArgumentException, InvalidResponseException { - - copyObject(bucketName, objectName, destBucketName, destObjectName, null, null); + if (destObjectName == null) { + destObjectName = objectName; + } + copyObject(destBucketName, destObjectName, null, null, bucketName, objectName, null, null); } /** @@ -2090,14 +2110,15 @@ public void copyObject(String bucketName, String objectName, String destBucketNa * @throws XmlPullParserException upon parsing response xml * @throws InvalidArgumentException upon invalid value is passed to a method. * @throws InvalidResponseException upon a non-xml response from server + * + * @deprecated As of release 6.1 */ - public void copyObject(String bucketName, String objectName, String destBucketName, - CopyConditions copyConditions) + @Deprecated + public void copyObject(String bucketName, String objectName, String destBucketName, CopyConditions copyConditions) throws InvalidKeyException, InvalidBucketNameException, NoSuchAlgorithmException, InsufficientDataException, NoResponseException, ErrorResponseException, InternalException, IOException, XmlPullParserException, InvalidArgumentException, InvalidResponseException { - - copyObject(bucketName, objectName, destBucketName, null, copyConditions, null); + copyObject(destBucketName, objectName, null, null, bucketName, objectName, null, copyConditions); } /** @@ -2139,14 +2160,19 @@ public void copyObject(String bucketName, String objectName, String destBucketNa * @throws XmlPullParserException upon parsing response xml * @throws InvalidArgumentException upon invalid value is passed to a method. * @throws InvalidResponseException upon a non-xml response from server + * + * @deprecated As of release 6.1 */ - public void copyObject(String bucketName, String objectName, String destBucketName, - String destObjectName, CopyConditions copyConditions) + @Deprecated + public void copyObject(String bucketName, String objectName, String destBucketName, String destObjectName, + CopyConditions copyConditions) throws InvalidKeyException, InvalidBucketNameException, NoSuchAlgorithmException, InsufficientDataException, NoResponseException, ErrorResponseException, InternalException, IOException, XmlPullParserException, InvalidArgumentException, InvalidResponseException { - - copyObject(bucketName, objectName, destBucketName, destObjectName, copyConditions, null); + if (destObjectName == null) { + destObjectName = objectName; + } + copyObject(destBucketName, destObjectName, null, null, bucketName, objectName, null, copyConditions); } /** @@ -2192,30 +2218,19 @@ public void copyObject(String bucketName, String objectName, String destBucketNa * @throws XmlPullParserException upon parsing response xml * @throws InvalidArgumentException upon invalid value is passed to a method. * @throws InvalidResponseException upon a non-xml response from server + * + * @deprecated As of release 6.1 */ + @Deprecated public void copyObject(String bucketName, String objectName, ServerSideEncryption sseSource, String destBucketName, String destObjectName, CopyConditions copyConditions, ServerSideEncryption sseTarget) throws InvalidKeyException, InvalidBucketNameException, NoSuchAlgorithmException, InsufficientDataException, NoResponseException, ErrorResponseException, InternalException, IOException, XmlPullParserException, InvalidArgumentException, InvalidResponseException { - - if ((sseTarget.getType() == ServerSideEncryption.Type.SSE_C) && (!this.baseUrl.isHttps())) { - throw new InvalidArgumentException("SSE_C operations must be performed over a secure connection."); - } else if ((sseTarget.getType() == (ServerSideEncryption.Type.SSE_KMS)) && (!this.baseUrl.isHttps())) { - throw new InvalidArgumentException("SSE_KMS operations must be performed over a secure connection."); - } - Map headerTarget = new HashMap<>(); - Map headers = new HashMap<>(); - sseTarget.marshal(headerTarget); - if (sseTarget.getType() == ServerSideEncryption.Type.SSE_C) { - Map headerSource = new HashMap<>(); - sseSource.marshal(headerSource); - headers.putAll(headerSource); - headers.putAll(headerTarget); - } else { - headers.putAll(headerTarget); + if (destObjectName == null) { + destObjectName = objectName; } - copyObject(bucketName, objectName, destBucketName, destObjectName, copyConditions, headers); + copyObject(destBucketName, destObjectName, null, sseTarget, bucketName, objectName, sseSource, copyConditions); } /** @@ -2260,49 +2275,114 @@ public void copyObject(String bucketName, String objectName, ServerSideEncryptio * @throws InternalException upon internal library error * @throws InvalidArgumentException upon invalid value is passed to a method. * @throws InvalidResponseException upon a non-xml response from server + * + * @deprecated As of release 6.1 */ + @Deprecated public void copyObject(String bucketName, String objectName, String destBucketName, String destObjectName, CopyConditions copyConditions, Map metadata) throws InvalidKeyException, InvalidBucketNameException, NoSuchAlgorithmException, InsufficientDataException, NoResponseException, ErrorResponseException, InternalException, IOException, XmlPullParserException, InvalidArgumentException, InvalidResponseException { + if (destObjectName == null) { + destObjectName = objectName; + } + copyObject(destBucketName, destObjectName, metadata, null, bucketName, objectName, null, copyConditions); + } + + /** + * Copy a source object into a new object with the provided name in the provided bucket. + * optionally can take a key value CopyConditions and server side encryption as well for + * conditionally attempting copyObject. + * + *

+ * Example:
+ * + *
+   * {@code minioClient.copyObject("my-bucketname", "my-objectname", headers, sse, "my-srcbucketname",
+   * "my-srcobjname", srcSse, copyConditions);}
+   * 
+ * + * @param bucketName + * Destination bucket name. + * @param objectName + * Destination object name. + * @param headerMap + * Destination object custom metadata. + * @param sse + * Server side encryption of destination object. + * @param srcBucketName + * Source bucket name. + * @param srcObjectName + * Source object name. + * @param srcSse + * Server side encryption of source object. + * @param copyConditions + * CopyConditions object with collection of supported CopyObject conditions. + * + * @throws InvalidBucketNameException upon invalid bucket name is given + * @throws NoSuchAlgorithmException + * upon requested algorithm was not found during signature calculation + * @throws InsufficientDataException upon getting EOFException while reading given + * InputStream even before reading given length + * @throws IOException upon connection error + * @throws InvalidKeyException + * upon an invalid access key or secret key + * @throws NoResponseException upon no response from server + * @throws XmlPullParserException upon parsing response xml + * @throws ErrorResponseException upon unsuccessful execution + * @throws InternalException upon internal library error + * @throws InvalidArgumentException upon invalid value is passed to a method. + * @throws InvalidResponseException upon a non-xml response from server + */ + public void copyObject(String bucketName, String objectName, Map headerMap, ServerSideEncryption sse, + String srcBucketName, String srcObjectName, ServerSideEncryption srcSse, + CopyConditions copyConditions) + throws InvalidKeyException, InvalidBucketNameException, NoSuchAlgorithmException, InsufficientDataException, + NoResponseException, ErrorResponseException, InternalException, IOException, XmlPullParserException, + InvalidArgumentException, InvalidResponseException { if (bucketName == null) { - throw new InvalidArgumentException("Source bucket name cannot be empty"); + throw new InvalidArgumentException("bucket name cannot be empty"); } + if (objectName == null) { - throw new InvalidArgumentException("Source object name cannot be empty"); + throw new InvalidArgumentException("object name cannot be empty"); } - if (destBucketName == null) { - throw new InvalidArgumentException("Destination bucket name cannot be empty"); + + checkWriteRequestSse(sse); + + if (srcBucketName == null) { + throw new InvalidArgumentException("Source bucket name cannot be empty"); + } + + // Source object name is optional, if empty default to object name. + if (srcObjectName == null) { + srcObjectName = objectName; } - // Escape source object path. - String sourceObjectPath = S3Escaper.encodePath(bucketName + "/" + objectName); + checkReadRequestSse(srcSse); - // Destination object name is optional, if empty default to source object name. - if (destObjectName == null) { - destObjectName = objectName; + if (headerMap == null) { + headerMap = new HashMap<>(); } - Map headerMap = new HashMap<>(); + headerMap.put("x-amz-copy-source", S3Escaper.encodePath(srcBucketName + "/" + srcObjectName)); - // Set the object source - headerMap.put("x-amz-copy-source", sourceObjectPath); + if (sse != null) { + sse.marshal(headerMap); + } - // If no conditions available, skip addition else add the conditions to the header - if (copyConditions != null) { - headerMap.putAll(copyConditions.getConditions()); + if (srcSse != null) { + srcSse.marshal(headerMap); } - // Set metadata on the destination of object. - if (metadata != null) { - headerMap.putAll(metadata); + if (copyConditions != null) { + headerMap.putAll(copyConditions.getConditions()); } - HttpResponse response = executePut(destBucketName, destObjectName, headerMap, - null, "", 0); + HttpResponse response = executePut(bucketName, objectName, headerMap, null, "", 0); // For now ignore the copyObjectResult, just read and parse it. CopyObjectResult result = new CopyObjectResult(); @@ -4285,23 +4365,14 @@ private void putObject(String bucketName, String objectName, Long size, Object d } if (sse != null) { - if (sse.getType() == ServerSideEncryption.Type.SSE_C && !this.baseUrl.isHttps()) { - throw new InvalidArgumentException("SSE_C operations must be performed over a secure connection."); - } else if (sse.getType() == ServerSideEncryption.Type.SSE_KMS && !this.baseUrl.isHttps()) { - throw new InvalidArgumentException("SSE_KMS operations must be performed over a secure connection."); - } - + checkWriteRequestSse(sse); // The correct approach is see.marshal() needs to accept boolean isHttps and do above checks inside. sse.marshal(headerMap); } if (size <= MIN_MULTIPART_SIZE) { - // Single put object. - if (sse != null) { - sse.marshal(headerMap); - } - putObject(bucketName, objectName, data, size.intValue(),headerMap , null, 0); + putObject(bucketName, objectName, data, size.intValue(), headerMap, null, 0); return; } diff --git a/examples/CopyObject.java b/examples/CopyObject.java index 210c2aef3..75f1b1c3c 100644 --- a/examples/CopyObject.java +++ b/examples/CopyObject.java @@ -70,8 +70,8 @@ public static void main(String[] args) bais.close(); System.out.println("my-objectname is uploaded successfully"); - minioClient.copyObject("my-bucketname", "my-objectname", "my-destbucketname", - "my-objectname-copy"); + minioClient.copyObject("my-destbucketname", "my-objectname-copy", null, null, + "my-bucketname", "my-objectname", null, null); System.out.println("my-objectname-copy copied to my-destbucketname successfully"); } catch (MinioException e) { System.out.println("Error occurred: " + e); diff --git a/examples/CopyObjectEncrypted.java b/examples/CopyObjectEncrypted.java index acfa4b432..ce89cec00 100644 --- a/examples/CopyObjectEncrypted.java +++ b/examples/CopyObjectEncrypted.java @@ -64,8 +64,8 @@ public static void main(String[] args) minioClient.putObject("my-bucketname", "my-objectname", bais, Long.valueOf(bais.available()), null, ssePut, null); System.out.println("my-objectname is uploaded successfully"); - minioClient.copyObject("my-bucketname", "my-objectname", sseSource, "my-destbucketname", - "my-objectname-copy", null, sseTarget); + minioClient.copyObject("my-destbucketname", "my-objectname-copy", null, sseTarget, + "my-bucketname", "my-objectname", sseSource, null); bais.close(); diff --git a/examples/CopyObjectEncryptedKms.java b/examples/CopyObjectEncryptedKms.java index be762a818..3b51341f6 100644 --- a/examples/CopyObjectEncryptedKms.java +++ b/examples/CopyObjectEncryptedKms.java @@ -61,8 +61,8 @@ public static void main(String[] args) bais.close(); System.out.println("my-objectname is uploaded successfully"); - minioClient.copyObject("my-bucketname", "my-objectname", null, "my-destbucketname", - "my-objectname-copy", null, sse); + minioClient.copyObject("my-destbucketname", "my-objectname-copy", null, sse, + "my-bucketname", "my-objectname", null, null); System.out.println("my-objectname-copy copied to my-destbucketname successfully"); } catch (MinioException e) { @@ -70,4 +70,4 @@ public static void main(String[] args) } System.out.println("finished"); } -} \ No newline at end of file +} diff --git a/examples/CopyObjectEncryptedS3.java b/examples/CopyObjectEncryptedS3.java index ee92ceec8..9fd491583 100644 --- a/examples/CopyObjectEncryptedS3.java +++ b/examples/CopyObjectEncryptedS3.java @@ -55,12 +55,12 @@ public static void main(String[] args) bais.close(); System.out.println("my-objectname is uploaded successfully"); - minioClient.copyObject("my-bucketname", "my-objectname", null, "my-destbucketname", - "my-objectname-copy", null, sse); + minioClient.copyObject("my-destbucketname", "my-objectname-copy", null, sse, + "my-bucketname", "my-objectname", null, null); System.out.println("my-objectname-copy copied to my-destbucketname successfully"); } catch (MinioException e) { System.out.println("Error occurred: " + e); } System.out.println("finished"); } -} \ No newline at end of file +} diff --git a/examples/CopyObjectMetadata.java b/examples/CopyObjectMetadata.java index bf89e9370..84707970a 100644 --- a/examples/CopyObjectMetadata.java +++ b/examples/CopyObjectMetadata.java @@ -79,8 +79,8 @@ public static void main(String[] args) Map metadata = new HashMap<>(); metadata.put("Content-Type", "application/javascript"); - minioClient.copyObject("my-bucketname", "my-objectname", "my-destbucketname", - "my-objectname-copy", copyConditions, metadata); + minioClient.copyObject("my-destbucketname", "my-objectname-copy", metadata, null, + "my-bucketname", "my-objectname", null, copyConditions); System.out.println("my-objectname-copy copied to my-destbucketname successfully"); } catch (MinioException e) { System.out.println("Error occurred: " + e); diff --git a/functional/FunctionalTest.java b/functional/FunctionalTest.java index 1bdc55823..c421576bd 100644 --- a/functional/FunctionalTest.java +++ b/functional/FunctionalTest.java @@ -2010,7 +2010,7 @@ public static void copyObject_test1() throws Exception { String destBucketName = getRandomName(); client.makeBucket(destBucketName); - client.copyObject(bucketName, objectName, destBucketName); + client.copyObject(destBucketName, objectName, null, null, bucketName, null, null, null); client.getObject(destBucketName, objectName).close(); client.removeObject(bucketName, objectName); @@ -2048,7 +2048,7 @@ public static void copyObject_test2() throws Exception { invalidETag.setMatchETag("TestETag"); try { - client.copyObject(bucketName, objectName, destBucketName, invalidETag); + client.copyObject(destBucketName, objectName, null, null, bucketName, null, null, invalidETag); } catch (ErrorResponseException e) { if (!e.errorResponse().code().equals("PreconditionFailed")) { throw e; @@ -2094,7 +2094,7 @@ public static void copyObject_test3() throws Exception { copyConditions.setMatchETag(stat.etag()); // File should be copied as ETag set in copyConditions matches object's ETag. - client.copyObject(bucketName, objectName, destBucketName, copyConditions); + client.copyObject(destBucketName, objectName, null, null, bucketName, null, null, copyConditions); client.getObject(destBucketName, objectName).close(); client.removeObject(bucketName, objectName); @@ -2136,7 +2136,7 @@ public static void copyObject_test4() throws Exception { // File should be copied as ETag set in copyConditions doesn't match object's // ETag. - client.copyObject(bucketName, objectName, destBucketName, copyConditions); + client.copyObject(destBucketName, objectName, null, null, bucketName, null, null, copyConditions); client.getObject(destBucketName, objectName).close(); client.removeObject(bucketName, objectName); @@ -2179,7 +2179,7 @@ public static void copyObject_test5() throws Exception { matchingETagNone.setMatchETagNone(stat.etag()); try { - client.copyObject(bucketName, objectName, destBucketName, matchingETagNone); + client.copyObject(destBucketName, objectName, null, null, bucketName, null, null, matchingETagNone); } catch (ErrorResponseException e) { // File should not be copied as ETag set in copyConditions matches object's // ETag. @@ -2228,7 +2228,7 @@ public static void copyObject_test6() throws Exception { modifiedDateCondition.setModified(dateRepresentation); // File should be copied as object was modified after the set date. - client.copyObject(bucketName, objectName, destBucketName, modifiedDateCondition); + client.copyObject(destBucketName, objectName, null, null, bucketName, null, null, modifiedDateCondition); client.getObject(destBucketName, objectName).close(); client.removeObject(bucketName, objectName); @@ -2272,7 +2272,7 @@ public static void copyObject_test7() throws Exception { invalidUnmodifiedCondition.setUnmodified(dateRepresentation); try { - client.copyObject(bucketName, objectName, destBucketName, invalidUnmodifiedCondition); + client.copyObject(destBucketName, objectName, null, null, bucketName, null, null, invalidUnmodifiedCondition); } catch (ErrorResponseException e) { // File should not be copied as object was modified after date set in // copyConditions. @@ -2323,7 +2323,8 @@ public static void copyObject_test8() throws Exception { Map metadata = new HashMap<>(); metadata.put("Content-Type", customContentType); - client.copyObject(bucketName, objectName, destBucketName, objectName, copyConditions, metadata); + client.copyObject(destBucketName, objectName, metadata, null, + bucketName, objectName, null, copyConditions); ObjectStat objectStat = client.statObject(destBucketName, objectName); if (!customContentType.equals(objectStat.contentType())) { @@ -2369,7 +2370,8 @@ public static void copyObject_test9() throws Exception { CopyConditions copyConditions = new CopyConditions(); copyConditions.setReplaceMetadataDirective(); - client.copyObject(bucketName, objectName, bucketName, objectName, copyConditions, new HashMap()); + client.copyObject(bucketName, objectName, new HashMap(), null, + bucketName, objectName, null, copyConditions); ObjectStat objectStat = client.statObject(bucketName, objectName); if (objectStat.httpHeaders().containsKey("X-Amz-Meta-Test")) { throw new Exception("expected user-defined metadata has been removed"); @@ -2422,7 +2424,7 @@ public static void copyObject_test10() throws Exception { CopyConditions copyConditions = new CopyConditions(); copyConditions.setReplaceMetadataDirective(); - client.copyObject(bucketName, objectName, sseSource, bucketName, objectName, copyConditions, sseTarget); + client.copyObject(bucketName, objectName, null, sseTarget, bucketName, objectName, sseSource, copyConditions); ObjectStat objectStat = client.statObject(bucketName, objectName, sseTarget); client.removeObject(bucketName, objectName); @@ -2463,7 +2465,7 @@ public static void copyObject_test11() throws Exception { CopyConditions copyConditions = new CopyConditions(); copyConditions.setReplaceMetadataDirective(); - client.copyObject(bucketName, objectName, null, bucketName, objectName, copyConditions, sse); + client.copyObject(bucketName, objectName, null, sse, bucketName, objectName, null, copyConditions); ObjectStat objectStat = client.statObject(bucketName, objectName); if (objectStat.httpHeaders().containsKey("X-Amz-Meta-Test")) { throw new Exception("expected user-defined metadata has been removed"); @@ -2515,7 +2517,7 @@ public static void copyObject_test12() throws Exception { CopyConditions copyConditions = new CopyConditions(); copyConditions.setReplaceMetadataDirective(); - client.copyObject(bucketName, objectName, null, bucketName, objectName, copyConditions, sse); + client.copyObject(bucketName, objectName, null, sse, bucketName, objectName, null, copyConditions); ObjectStat objectStat = client.statObject(bucketName, objectName); if (objectStat.httpHeaders().containsKey("X-Amz-Meta-Test")) { throw new Exception("expected user-defined metadata has been removed");