1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"). 5 * You may not use this file except in compliance with the License. 6 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.services.s3.internal.handlers; 17 18 import static software.amazon.awssdk.utils.http.SdkHttpUtils.urlDecode; 19 20 import java.util.Collections; 21 import java.util.List; 22 import java.util.stream.Collectors; 23 import software.amazon.awssdk.annotations.SdkInternalApi; 24 import software.amazon.awssdk.core.SdkResponse; 25 import software.amazon.awssdk.core.interceptor.Context; 26 import software.amazon.awssdk.core.interceptor.ExecutionAttributes; 27 import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; 28 import software.amazon.awssdk.services.s3.model.CommonPrefix; 29 import software.amazon.awssdk.services.s3.model.EncodingType; 30 import software.amazon.awssdk.services.s3.model.ListMultipartUploadsResponse; 31 import software.amazon.awssdk.services.s3.model.ListObjectVersionsResponse; 32 import software.amazon.awssdk.services.s3.model.ListObjectsResponse; 33 import software.amazon.awssdk.services.s3.model.ListObjectsV2Response; 34 import software.amazon.awssdk.services.s3.model.MultipartUpload; 35 import software.amazon.awssdk.services.s3.model.ObjectVersion; 36 import software.amazon.awssdk.services.s3.model.S3Object; 37 38 /** 39 * Encoding type affects the following values in the response: 40 * <ul> 41 * <li>V1: Delimiter, Marker, Prefix, NextMarker, Key</li> 42 * <li>V2: Delimiter, Prefix, Key, and StartAfter</li> 43 * </ul> 44 * <p> 45 * See <a 46 * href="https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGET.html">https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGET.html</a> 47 * and <a 48 * href="https://docs.aws.amazon.com/AmazonS3/latest/API/v2-RESTBucketGET.html">https://docs.aws.amazon.com/AmazonS3/latest/API/v2-RESTBucketGET.html</a> 49 */ 50 @SdkInternalApi 51 public final class DecodeUrlEncodedResponseInterceptor implements ExecutionInterceptor { 52 53 @Override modifyResponse(Context.ModifyResponse context, ExecutionAttributes executionAttributes)54 public SdkResponse modifyResponse(Context.ModifyResponse context, 55 ExecutionAttributes executionAttributes) { 56 SdkResponse response = context.response(); 57 if (shouldHandle(response)) { 58 if (response instanceof ListObjectsResponse) { 59 return modifyListObjectsResponse((ListObjectsResponse) response); 60 } 61 62 if (response instanceof ListObjectsV2Response) { 63 return modifyListObjectsV2Response((ListObjectsV2Response) response); 64 } 65 66 if (response instanceof ListObjectVersionsResponse) { 67 return modifyListObjectVersionsResponse((ListObjectVersionsResponse) response); 68 } 69 70 if (response instanceof ListMultipartUploadsResponse) { 71 return modifyListMultipartUploadsResponse((ListMultipartUploadsResponse) response); 72 } 73 } 74 return response; 75 } 76 shouldHandle(SdkResponse sdkResponse)77 private static boolean shouldHandle(SdkResponse sdkResponse) { 78 return sdkResponse.getValueForField("EncodingType", String.class) 79 .map(et -> EncodingType.URL.toString().equals(et)) 80 .orElse(false); 81 } 82 83 // Elements to decode: Delimiter, Marker, Prefix, NextMarker, Key modifyListObjectsResponse(ListObjectsResponse response)84 private static SdkResponse modifyListObjectsResponse(ListObjectsResponse response) { 85 return response.toBuilder() 86 .delimiter(urlDecode(response.delimiter())) 87 .marker(urlDecode(response.marker())) 88 .prefix(urlDecode(response.prefix())) 89 .nextMarker(urlDecode(response.nextMarker())) 90 .contents(decodeContents(response.contents())) 91 .commonPrefixes(decodeCommonPrefixes(response.commonPrefixes())) 92 .build(); 93 } 94 95 // Elements to decode: Delimiter, Prefix, Key, and StartAfter modifyListObjectsV2Response(ListObjectsV2Response response)96 private static SdkResponse modifyListObjectsV2Response(ListObjectsV2Response response) { 97 return response.toBuilder() 98 .delimiter(urlDecode(response.delimiter())) 99 .prefix(urlDecode(response.prefix())) 100 .startAfter(urlDecode(response.startAfter())) 101 .contents(decodeContents(response.contents())) 102 .commonPrefixes(decodeCommonPrefixes(response.commonPrefixes())) 103 .build(); 104 } 105 106 // https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectVersions.html 107 // Elements to decode: Delimiter, KeyMarker, NextKeyMarker, Prefix modifyListObjectVersionsResponse(ListObjectVersionsResponse response)108 private SdkResponse modifyListObjectVersionsResponse(ListObjectVersionsResponse response) { 109 110 return response.toBuilder() 111 .prefix(urlDecode(response.prefix())) 112 .keyMarker(urlDecode(response.keyMarker())) 113 .delimiter(urlDecode(response.delimiter())) 114 .nextKeyMarker(urlDecode(response.nextKeyMarker())) 115 .commonPrefixes(decodeCommonPrefixes(response.commonPrefixes())) 116 .versions(decodeObjectVersions(response.versions())) 117 .build(); 118 } 119 120 // https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListMultipartUploads.html 121 // Elements to decode: Delimiter, KeyMarker, NextKeyMarker, Prefix, Key modifyListMultipartUploadsResponse(ListMultipartUploadsResponse response)122 private SdkResponse modifyListMultipartUploadsResponse(ListMultipartUploadsResponse response) { 123 return response.toBuilder() 124 .delimiter(urlDecode(response.delimiter())) 125 .keyMarker(urlDecode(response.keyMarker())) 126 .nextKeyMarker(urlDecode(response.nextKeyMarker())) 127 .prefix(urlDecode(response.prefix())) 128 .commonPrefixes(decodeCommonPrefixes(response.commonPrefixes())) 129 .uploads(decodeMultipartUpload(response.uploads())) 130 .build(); 131 132 } 133 decodeContents(List<S3Object> contents)134 private static List<S3Object> decodeContents(List<S3Object> contents) { 135 if (contents == null) { 136 return null; 137 } 138 return Collections.unmodifiableList(contents.stream() 139 .map(o -> o.toBuilder().key(urlDecode(o.key())).build()) 140 .collect(Collectors.toList())); 141 } 142 decodeObjectVersions(List<ObjectVersion> objectVersions)143 private static List<ObjectVersion> decodeObjectVersions(List<ObjectVersion> objectVersions) { 144 if (objectVersions == null) { 145 return null; 146 } 147 148 return Collections.unmodifiableList(objectVersions.stream() 149 .map(o -> o.toBuilder().key(urlDecode(o.key())).build()) 150 .collect(Collectors.toList())); 151 } 152 decodeCommonPrefixes(List<CommonPrefix> commonPrefixes)153 private static List<CommonPrefix> decodeCommonPrefixes(List<CommonPrefix> commonPrefixes) { 154 if (commonPrefixes == null) { 155 return null; 156 } 157 158 return Collections.unmodifiableList(commonPrefixes.stream() 159 .map(p -> p.toBuilder().prefix(urlDecode(p.prefix())).build()) 160 .collect(Collectors.toList())); 161 } 162 decodeMultipartUpload(List<MultipartUpload> multipartUploads)163 private static List<MultipartUpload> decodeMultipartUpload(List<MultipartUpload> multipartUploads) { 164 if (multipartUploads == null) { 165 return null; 166 } 167 168 return Collections.unmodifiableList(multipartUploads.stream() 169 .map(u -> u.toBuilder().key(urlDecode(u.key())).build()) 170 .collect(Collectors.toList())); 171 } 172 } 173