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
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
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
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
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
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
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
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
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
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
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