diff --git a/api/src/main/java/io/minio/MinioClient.java b/api/src/main/java/io/minio/MinioClient.java index cc3ddbede..e9332cdb6 100644 --- a/api/src/main/java/io/minio/MinioClient.java +++ b/api/src/main/java/io/minio/MinioClient.java @@ -22,7 +22,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Strings; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; @@ -123,6 +122,7 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; +import okhttp3.Headers; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.Protocol; @@ -441,41 +441,36 @@ private String getHostHeader(HttpUrl url) { return url.host() + ":" + url.port(); } - /** Create HTTP request for given paramaters. */ - protected Request createRequest( - HttpUrl url, - Method method, - Multimap headerMap, - Object body, - int length, - Credentials creds) - throws InsufficientDataException, InternalException, IOException, NoSuchAlgorithmException { - Request.Builder requestBuilder = new Request.Builder(); - requestBuilder.url(url); + private Headers httpHeaders(Multimap headerMap) { + Headers.Builder builder = new Headers.Builder(); + if (headerMap == null) return builder.build(); - String contentType = null; - String contentEncoding = null; - if (headerMap != null) { - contentEncoding = + if (headerMap.containsKey("Content-Encoding")) { + builder.add( + "Content-Encoding", headerMap.get("Content-Encoding").stream() .distinct() .filter(encoding -> !encoding.isEmpty()) - .collect(Collectors.joining(",")); - for (Map.Entry entry : headerMap.entries()) { - if (entry.getKey().equals("Content-Type")) { - contentType = entry.getValue(); - } + .collect(Collectors.joining(","))); + } - if (!entry.getKey().equals("Content-Encoding")) { - requestBuilder.header(entry.getKey(), entry.getValue()); - } + for (Map.Entry entry : headerMap.entries()) { + if (!entry.getKey().equals("Content-Encoding")) { + builder.addUnsafeNonAscii(entry.getKey(), entry.getValue()); } } - if (!Strings.isNullOrEmpty(contentEncoding)) { - requestBuilder.header("Content-Encoding", contentEncoding); - } + return builder.build(); + } + + /** Create HTTP request for given paramaters. */ + protected Request createRequest( + HttpUrl url, Method method, Headers headers, Object body, int length, Credentials creds) + throws InsufficientDataException, InternalException, IOException, NoSuchAlgorithmException { + Request.Builder requestBuilder = new Request.Builder(); + requestBuilder.url(url); + if (headers != null) requestBuilder.headers(headers); requestBuilder.header("Host", getHostHeader(url)); // Disable default gzip compression by okhttp library. requestBuilder.header("Accept-Encoding", "identity"); @@ -526,6 +521,7 @@ protected Request createRequest( RequestBody requestBody = null; if (body != null) { + String contentType = (headers != null) ? headers.get("Content-Type") : null; if (body instanceof RandomAccessFile) { requestBody = new HttpRequestBody((RandomAccessFile) body, length, contentType); } else if (body instanceof BufferedInputStream) { @@ -568,7 +564,7 @@ protected Response execute( bucketName, objectName, getRegion(bucketName, region), - merge(args.extraHeaders(), headers), + httpHeaders(merge(args.extraHeaders(), headers)), merge(args.extraQueryParams(), queryParams), body, length); @@ -580,7 +576,7 @@ protected Response execute( String bucketName, String objectName, String region, - Multimap headerMap, + Headers headers, Multimap queryParamMap, Object body, int length) @@ -610,7 +606,7 @@ protected Response execute( HttpUrl url = buildUrl(method, bucketName, objectName, region, queryParamMap); Credentials creds = (provider == null) ? null : provider.fetch(); - Request request = createRequest(url, method, headerMap, body, length, creds); + Request request = createRequest(url, method, headers, body, length, creds); if (creds != null) { request = Signer.signV4S3( @@ -629,13 +625,12 @@ protected Response execute( encodedPath += "?" + encodedQuery; } this.traceStream.println(request.method() + " " + encodedPath + " HTTP/1.1"); - String headers = + this.traceStream.println( request .headers() .toString() .replaceAll("Signature=([0-9a-f]+)", "Signature=*REDACTED*") - .replaceAll("Credential=([^/]+)", "Credential=*REDACTED*"); - this.traceStream.println(headers); + .replaceAll("Credential=([^/]+)", "Credential=*REDACTED*")); if (traceRequestBody) { this.traceStream.println(new String((byte[]) body, StandardCharsets.UTF_8)); } @@ -2299,7 +2294,7 @@ public void makeBucket(MakeBucketArgs args) args.bucket(), null, region, - merge(args.extraHeaders(), headers), + httpHeaders(merge(args.extraHeaders(), headers)), args.extraQueryParams(), region.equals(US_EAST_1) ? null : new CreateBucketConfiguration(region), 0)) { @@ -4093,7 +4088,7 @@ protected AbortMultipartUploadResponse abortMultipartUpload( bucketName, objectName, getRegion(bucketName, region), - extraHeaders, + httpHeaders(extraHeaders), merge(extraQueryParams, newMultimap(UPLOAD_ID, uploadId)), null, 0)) { @@ -4145,7 +4140,7 @@ protected ObjectWriteResponse completeMultipartUpload( bucketName, objectName, getRegion(bucketName, region), - extraHeaders, + httpHeaders(extraHeaders), queryParams, new CompleteMultipartUpload(parts), 0)) { @@ -4235,7 +4230,7 @@ protected CreateMultipartUploadResponse createMultipartUpload( bucketName, objectName, getRegion(bucketName, region), - headersCopy, + httpHeaders(headersCopy), queryParams, null, 0)) { @@ -4296,7 +4291,7 @@ protected DeleteObjectsResponse deleteObjects( bucketName, null, getRegion(bucketName, region), - headers, + httpHeaders(headers), merge(extraQueryParams, newMultimap("delete", "")), new DeleteRequest(objectList, quiet), 0)) { @@ -4390,7 +4385,7 @@ protected ListObjectsV2Response listObjectsV2( bucketName, null, getRegion(bucketName, region), - extraHeaders, + httpHeaders(extraHeaders), queryParams, null, 0)) { @@ -4449,7 +4444,7 @@ protected ListObjectsV1Response listObjectsV1( bucketName, null, getRegion(bucketName, region), - extraHeaders, + httpHeaders(extraHeaders), queryParams, null, 0)) { @@ -4513,7 +4508,7 @@ protected ListObjectVersionsResponse listObjectVersions( bucketName, null, getRegion(bucketName, region), - extraHeaders, + httpHeaders(extraHeaders), queryParams, null, 0)) { @@ -4569,7 +4564,7 @@ protected ObjectWriteResponse putObject( bucketName, objectName, getRegion(bucketName, region), - headers, + httpHeaders(headers), extraQueryParams, data, length)) { @@ -4647,7 +4642,7 @@ protected ListMultipartUploadsResponse listMultipartUploads( bucketName, null, getRegion(bucketName, region), - extraHeaders, + httpHeaders(extraHeaders), queryParams, null, 0)) { @@ -4709,7 +4704,7 @@ protected ListPartsResponse listParts( bucketName, objectName, getRegion(bucketName, region), - extraHeaders, + httpHeaders(extraHeaders), queryParams, null, 0)) { @@ -4769,7 +4764,7 @@ protected UploadPartResponse uploadPart( bucketName, objectName, getRegion(bucketName, region), - extraHeaders, + httpHeaders(extraHeaders), merge( extraQueryParams, newMultimap("partNumber", Integer.toString(partNumber), UPLOAD_ID, uploadId)), @@ -4826,7 +4821,7 @@ protected UploadPartCopyResponse uploadPartCopy( bucketName, objectName, getRegion(bucketName, region), - headers, + httpHeaders(headers), merge( extraQueryParams, newMultimap("partNumber", Integer.toString(partNumber), "uploadId", uploadId)), diff --git a/functional/FunctionalTest.java b/functional/FunctionalTest.java index 58252b848..678c06382 100644 --- a/functional/FunctionalTest.java +++ b/functional/FunctionalTest.java @@ -877,6 +877,7 @@ public static void putObject() throws Exception { userMetadata.put("My-Project", "Project One"); userMetadata.put("My-header1", " a b c "); userMetadata.put("My-Header2", "\"a b c\""); + userMetadata.put("My-Unicode-Tag", "商品"); testPutObject( "[user metadata]",