diff --git a/api/src/main/java/io/minio/Digest.java b/api/src/main/java/io/minio/Digest.java index 959c1e5d5..588b43074 100644 --- a/api/src/main/java/io/minio/Digest.java +++ b/api/src/main/java/io/minio/Digest.java @@ -26,6 +26,7 @@ import com.google.common.io.BaseEncoding; import io.minio.errors.InsufficientDataException; +import io.minio.errors.InternalException; /** @@ -42,84 +43,85 @@ private Digest() {} * Returns SHA-256 hash of given string. */ public static String sha256Hash(String string) throws NoSuchAlgorithmException { - return sha256Hash(string.getBytes(StandardCharsets.UTF_8)); - } - - - /** - * Returns SHA-256 hash of given byte array. - */ - public static String sha256Hash(byte[] data) throws NoSuchAlgorithmException { - return sha256Hash(data, data.length); - } - - - /** - * Returns SHA-256 hash string of given byte array and it's length. - */ - public static String sha256Hash(byte[] data, int length) throws NoSuchAlgorithmException { - MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); - - messageDigest.update(data, 0, length); - - return BaseEncoding.base16().encode(messageDigest.digest()).toLowerCase(); + byte[] data = string.getBytes(StandardCharsets.UTF_8); + MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256"); + sha256Digest.update((byte[]) data, 0, data.length); + return BaseEncoding.base16().encode(sha256Digest.digest()).toLowerCase(); } /** - * Returns SHA-256 of given input stream and it's length. + * Returns SHA-256 hash of given data and it's length. * - * @param inputStream Input stream whose type is either {@link RandomAccessFile} or {@link BufferedInputStream}. - * @param len Length of Input stream. + * @param data must be {@link RandomAccessFile}, {@link BufferedInputStream} or byte array. + * @param len length of data to be read for hash calculation. */ - public static String sha256Hash(Object inputStream, int len) - throws NoSuchAlgorithmException, IOException, InsufficientDataException { + public static String sha256Hash(Object data, int len) + throws NoSuchAlgorithmException, IOException, InsufficientDataException, InternalException { MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256"); - updateDigests(inputStream, len, sha256Digest, null); - return BaseEncoding.base16().encode(sha256Digest.digest()).toLowerCase(); - } - - /** - * Returns MD5 hash of given string. - */ - public static String md5Hash(String string) throws NoSuchAlgorithmException { - return md5Hash(string.getBytes(StandardCharsets.UTF_8)); - } + if (data instanceof BufferedInputStream || data instanceof RandomAccessFile) { + updateDigests(data, len, sha256Digest, null); + } else if (data instanceof byte[]) { + sha256Digest.update((byte[]) data, 0, len); + } else { + throw new InternalException("Unknown data source to calculate sha256 hash. This should not happen, " + + "please report this issue at https://github.com/minio/minio-java/issues"); + } - /** - * Returns MD5 hash of given byte array. - */ - public static String md5Hash(byte[] data) throws NoSuchAlgorithmException { - return md5Hash(data, data.length); + return BaseEncoding.base16().encode(sha256Digest.digest()).toLowerCase(); } /** - * Returns MD5 hash of given byte array and it's length. + * Returns SHA-256 and MD5 hashes of given data and it's length. + * + * @param data must be {@link RandomAccessFile}, {@link BufferedInputStream} or byte array. + * @param len length of data to be read for hash calculation. */ - public static String md5Hash(byte[] data, int length) throws NoSuchAlgorithmException { - MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + public static String[] sha256Md5Hashes(Object data, int len) + throws NoSuchAlgorithmException, IOException, InsufficientDataException, InternalException { + MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256"); + MessageDigest md5Digest = MessageDigest.getInstance("MD5"); - messageDigest.update(data, 0, length); + if (data instanceof BufferedInputStream || data instanceof RandomAccessFile) { + updateDigests(data, len, sha256Digest, md5Digest); + } else if (data instanceof byte[]) { + sha256Digest.update((byte[]) data, 0, len); + md5Digest.update((byte[]) data, 0, len); + } else { + throw new InternalException("Unknown data source to calculate sha256 hash. This should not happen, " + + "please report this issue at https://github.com/minio/minio-java/issues"); + } - return BaseEncoding.base64().encode(messageDigest.digest()); + return new String[]{BaseEncoding.base16().encode(sha256Digest.digest()).toLowerCase(), + BaseEncoding.base64().encode(md5Digest.digest())}; } /** - * Returns MD5 hash of given input stream and it's length. + * Returns MD5 hash of given data and it's length. * - * @param inputStream Input stream whose type is either {@link RandomAccessFile} or {@link BufferedInputStream}. - * @param len Length of Input stream. + * @param data must be {@link RandomAccessFile}, {@link BufferedInputStream} or byte array. + * @param len length of data to be read for hash calculation. */ - public static String md5Hash(Object inputStream, int len) - throws NoSuchAlgorithmException, IOException, InsufficientDataException { + public static String md5Hash(Object data, int len) + throws NoSuchAlgorithmException, IOException, InsufficientDataException, InternalException { MessageDigest md5Digest = MessageDigest.getInstance("MD5"); - updateDigests(inputStream, len, null, md5Digest); + + if (data instanceof BufferedInputStream || data instanceof RandomAccessFile) { + updateDigests(data, len, null, md5Digest); + } else if (data instanceof byte[]) { + md5Digest.update((byte[]) data, 0, len); + } else { + throw new InternalException("Unknown data source to calculate sha256 hash. This should not happen, " + + "please report this issue at https://github.com/minio/minio-java/issues"); + } + return BaseEncoding.base64().encode(md5Digest.digest()); } + /** * Updated MessageDigest with bytes read from file and stream. */ diff --git a/api/src/main/java/io/minio/MinioClient.java b/api/src/main/java/io/minio/MinioClient.java index ba10af7e9..1979c9a44 100755 --- a/api/src/main/java/io/minio/MinioClient.java +++ b/api/src/main/java/io/minio/MinioClient.java @@ -778,7 +778,8 @@ private Request createRequest(Method method, String bucketName, String objectNam String region, Map headerMap, Map queryParamMap, final String contentType, final Object body, final int length) - throws InvalidBucketNameException, NoSuchAlgorithmException, InsufficientDataException, IOException { + throws InvalidBucketNameException, NoSuchAlgorithmException, InsufficientDataException, IOException, + InternalException { if (bucketName == null && objectName != null) { throw new InvalidBucketNameException(NULL_STRING, "null bucket name for object '" + objectName + "'"); } @@ -896,40 +897,31 @@ public void writeTo(BufferedSink sink) throws IOException { // Fix issue #415: No need to compute sha256 if endpoint scheme is HTTPS. if (url.isHttps()) { sha256Hash = "UNSIGNED-PAYLOAD"; - if (body instanceof BufferedInputStream) { - md5Hash = Digest.md5Hash((BufferedInputStream) body, length); - } else if (body instanceof RandomAccessFile) { - md5Hash = Digest.md5Hash((RandomAccessFile) body, length); - } else if (body instanceof byte[]) { - byte[] data = (byte[]) body; - md5Hash = Digest.md5Hash(data, length); + if (body != null) { + md5Hash = Digest.md5Hash(body, length); } } else { - // Fix issue #567: Compute SHA256 hash only. - if (body == null) { - sha256Hash = Digest.sha256Hash(new byte[0]); - } else if (body instanceof BufferedInputStream) { - sha256Hash = Digest.sha256Hash((BufferedInputStream) body, length); - } else if (body instanceof RandomAccessFile) { - sha256Hash = Digest.sha256Hash((RandomAccessFile) body, length); - } else if (body instanceof byte[]) { - sha256Hash = Digest.sha256Hash((byte[]) body, length); + Object data = body; + int len = length; + if (data == null) { + data = new byte[0]; + len = 0; + } + + if (method == Method.POST && queryParamMap != null && queryParamMap.containsKey("delete")) { + // Fix issue #579: Treat 'Delete Multiple Objects' specially which requires MD5 hash. + String[] hashes = Digest.sha256Md5Hashes(data, len); + sha256Hash = hashes[0]; + md5Hash = hashes[1]; } else { - sha256Hash = Digest.sha256Hash(body.toString()); + // Fix issue #567: Compute SHA256 hash only. + sha256Hash = Digest.sha256Hash(data, len); } } } else { // Fix issue #567: Compute MD5 hash only of anonymous access. if (body != null) { - if (body instanceof BufferedInputStream) { - md5Hash = Digest.md5Hash((BufferedInputStream) body, length); - } else if (body instanceof RandomAccessFile) { - md5Hash = Digest.md5Hash((RandomAccessFile) body, length); - } else if (body instanceof byte[]) { - md5Hash = Digest.md5Hash((byte[]) body, length); - } else { - md5Hash = Digest.md5Hash(body.toString()); - } + md5Hash = Digest.md5Hash(body, length); } } @@ -2081,7 +2073,7 @@ public String presignedPutObject(String bucketName, String objectName, Integer e } String region = getRegion(bucketName); - Request request = createRequest(Method.PUT, bucketName, objectName, region, null, null, null, "", 0); + Request request = createRequest(Method.PUT, bucketName, objectName, region, null, null, null, new byte[0], 0); HttpUrl url = Signer.presignV4(request, region, accessKey, secretKey, expires); return url.toString(); }