diff options
Diffstat (limited to 'vendor/github.com/aws/aws-sdk-go/service/s3/body_hash.go')
-rw-r--r-- | vendor/github.com/aws/aws-sdk-go/service/s3/body_hash.go | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/vendor/github.com/aws/aws-sdk-go/service/s3/body_hash.go b/vendor/github.com/aws/aws-sdk-go/service/s3/body_hash.go new file mode 100644 index 0000000..5c8ce5c --- /dev/null +++ b/vendor/github.com/aws/aws-sdk-go/service/s3/body_hash.go | |||
@@ -0,0 +1,249 @@ | |||
1 | package s3 | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "crypto/md5" | ||
6 | "crypto/sha256" | ||
7 | "encoding/base64" | ||
8 | "encoding/hex" | ||
9 | "fmt" | ||
10 | "hash" | ||
11 | "io" | ||
12 | |||
13 | "github.com/aws/aws-sdk-go/aws" | ||
14 | "github.com/aws/aws-sdk-go/aws/awserr" | ||
15 | "github.com/aws/aws-sdk-go/aws/request" | ||
16 | "github.com/aws/aws-sdk-go/internal/sdkio" | ||
17 | ) | ||
18 | |||
19 | const ( | ||
20 | contentMD5Header = "Content-Md5" | ||
21 | contentSha256Header = "X-Amz-Content-Sha256" | ||
22 | amzTeHeader = "X-Amz-Te" | ||
23 | amzTxEncodingHeader = "X-Amz-Transfer-Encoding" | ||
24 | |||
25 | appendMD5TxEncoding = "append-md5" | ||
26 | ) | ||
27 | |||
28 | // contentMD5 computes and sets the HTTP Content-MD5 header for requests that | ||
29 | // require it. | ||
30 | func contentMD5(r *request.Request) { | ||
31 | h := md5.New() | ||
32 | |||
33 | if !aws.IsReaderSeekable(r.Body) { | ||
34 | if r.Config.Logger != nil { | ||
35 | r.Config.Logger.Log(fmt.Sprintf( | ||
36 | "Unable to compute Content-MD5 for unseekable body, S3.%s", | ||
37 | r.Operation.Name)) | ||
38 | } | ||
39 | return | ||
40 | } | ||
41 | |||
42 | if _, err := copySeekableBody(h, r.Body); err != nil { | ||
43 | r.Error = awserr.New("ContentMD5", "failed to compute body MD5", err) | ||
44 | return | ||
45 | } | ||
46 | |||
47 | // encode the md5 checksum in base64 and set the request header. | ||
48 | v := base64.StdEncoding.EncodeToString(h.Sum(nil)) | ||
49 | r.HTTPRequest.Header.Set(contentMD5Header, v) | ||
50 | } | ||
51 | |||
52 | // computeBodyHashes will add Content MD5 and Content Sha256 hashes to the | ||
53 | // request. If the body is not seekable or S3DisableContentMD5Validation set | ||
54 | // this handler will be ignored. | ||
55 | func computeBodyHashes(r *request.Request) { | ||
56 | if aws.BoolValue(r.Config.S3DisableContentMD5Validation) { | ||
57 | return | ||
58 | } | ||
59 | if r.IsPresigned() { | ||
60 | return | ||
61 | } | ||
62 | if r.Error != nil || !aws.IsReaderSeekable(r.Body) { | ||
63 | return | ||
64 | } | ||
65 | |||
66 | var md5Hash, sha256Hash hash.Hash | ||
67 | hashers := make([]io.Writer, 0, 2) | ||
68 | |||
69 | // Determine upfront which hashes can be set without overriding user | ||
70 | // provide header data. | ||
71 | if v := r.HTTPRequest.Header.Get(contentMD5Header); len(v) == 0 { | ||
72 | md5Hash = md5.New() | ||
73 | hashers = append(hashers, md5Hash) | ||
74 | } | ||
75 | |||
76 | if v := r.HTTPRequest.Header.Get(contentSha256Header); len(v) == 0 { | ||
77 | sha256Hash = sha256.New() | ||
78 | hashers = append(hashers, sha256Hash) | ||
79 | } | ||
80 | |||
81 | // Create the destination writer based on the hashes that are not already | ||
82 | // provided by the user. | ||
83 | var dst io.Writer | ||
84 | switch len(hashers) { | ||
85 | case 0: | ||
86 | return | ||
87 | case 1: | ||
88 | dst = hashers[0] | ||
89 | default: | ||
90 | dst = io.MultiWriter(hashers...) | ||
91 | } | ||
92 | |||
93 | if _, err := copySeekableBody(dst, r.Body); err != nil { | ||
94 | r.Error = awserr.New("BodyHashError", "failed to compute body hashes", err) | ||
95 | return | ||
96 | } | ||
97 | |||
98 | // For the hashes created, set the associated headers that the user did not | ||
99 | // already provide. | ||
100 | if md5Hash != nil { | ||
101 | sum := make([]byte, md5.Size) | ||
102 | encoded := make([]byte, md5Base64EncLen) | ||
103 | |||
104 | base64.StdEncoding.Encode(encoded, md5Hash.Sum(sum[0:0])) | ||
105 | r.HTTPRequest.Header[contentMD5Header] = []string{string(encoded)} | ||
106 | } | ||
107 | |||
108 | if sha256Hash != nil { | ||
109 | encoded := make([]byte, sha256HexEncLen) | ||
110 | sum := make([]byte, sha256.Size) | ||
111 | |||
112 | hex.Encode(encoded, sha256Hash.Sum(sum[0:0])) | ||
113 | r.HTTPRequest.Header[contentSha256Header] = []string{string(encoded)} | ||
114 | } | ||
115 | } | ||
116 | |||
117 | const ( | ||
118 | md5Base64EncLen = (md5.Size + 2) / 3 * 4 // base64.StdEncoding.EncodedLen | ||
119 | sha256HexEncLen = sha256.Size * 2 // hex.EncodedLen | ||
120 | ) | ||
121 | |||
122 | func copySeekableBody(dst io.Writer, src io.ReadSeeker) (int64, error) { | ||
123 | curPos, err := src.Seek(0, sdkio.SeekCurrent) | ||
124 | if err != nil { | ||
125 | return 0, err | ||
126 | } | ||
127 | |||
128 | // hash the body. seek back to the first position after reading to reset | ||
129 | // the body for transmission. copy errors may be assumed to be from the | ||
130 | // body. | ||
131 | n, err := io.Copy(dst, src) | ||
132 | if err != nil { | ||
133 | return n, err | ||
134 | } | ||
135 | |||
136 | _, err = src.Seek(curPos, sdkio.SeekStart) | ||
137 | if err != nil { | ||
138 | return n, err | ||
139 | } | ||
140 | |||
141 | return n, nil | ||
142 | } | ||
143 | |||
144 | // Adds the x-amz-te: append_md5 header to the request. This requests the service | ||
145 | // responds with a trailing MD5 checksum. | ||
146 | // | ||
147 | // Will not ask for append MD5 if disabled, the request is presigned or, | ||
148 | // or the API operation does not support content MD5 validation. | ||
149 | func askForTxEncodingAppendMD5(r *request.Request) { | ||
150 | if aws.BoolValue(r.Config.S3DisableContentMD5Validation) { | ||
151 | return | ||
152 | } | ||
153 | if r.IsPresigned() { | ||
154 | return | ||
155 | } | ||
156 | r.HTTPRequest.Header.Set(amzTeHeader, appendMD5TxEncoding) | ||
157 | } | ||
158 | |||
159 | func useMD5ValidationReader(r *request.Request) { | ||
160 | if r.Error != nil { | ||
161 | return | ||
162 | } | ||
163 | |||
164 | if v := r.HTTPResponse.Header.Get(amzTxEncodingHeader); v != appendMD5TxEncoding { | ||
165 | return | ||
166 | } | ||
167 | |||
168 | var bodyReader *io.ReadCloser | ||
169 | var contentLen int64 | ||
170 | switch tv := r.Data.(type) { | ||
171 | case *GetObjectOutput: | ||
172 | bodyReader = &tv.Body | ||
173 | contentLen = aws.Int64Value(tv.ContentLength) | ||
174 | // Update ContentLength hiden the trailing MD5 checksum. | ||
175 | tv.ContentLength = aws.Int64(contentLen - md5.Size) | ||
176 | tv.ContentRange = aws.String(r.HTTPResponse.Header.Get("X-Amz-Content-Range")) | ||
177 | default: | ||
178 | r.Error = awserr.New("ChecksumValidationError", | ||
179 | fmt.Sprintf("%s: %s header received on unsupported API, %s", | ||
180 | amzTxEncodingHeader, appendMD5TxEncoding, r.Operation.Name, | ||
181 | ), nil) | ||
182 | return | ||
183 | } | ||
184 | |||
185 | if contentLen < md5.Size { | ||
186 | r.Error = awserr.New("ChecksumValidationError", | ||
187 | fmt.Sprintf("invalid Content-Length %d for %s %s", | ||
188 | contentLen, appendMD5TxEncoding, amzTxEncodingHeader, | ||
189 | ), nil) | ||
190 | return | ||
191 | } | ||
192 | |||
193 | // Wrap and swap the response body reader with the validation reader. | ||
194 | *bodyReader = newMD5ValidationReader(*bodyReader, contentLen-md5.Size) | ||
195 | } | ||
196 | |||
197 | type md5ValidationReader struct { | ||
198 | rawReader io.ReadCloser | ||
199 | payload io.Reader | ||
200 | hash hash.Hash | ||
201 | |||
202 | payloadLen int64 | ||
203 | read int64 | ||
204 | } | ||
205 | |||
206 | func newMD5ValidationReader(reader io.ReadCloser, payloadLen int64) *md5ValidationReader { | ||
207 | h := md5.New() | ||
208 | return &md5ValidationReader{ | ||
209 | rawReader: reader, | ||
210 | payload: io.TeeReader(&io.LimitedReader{R: reader, N: payloadLen}, h), | ||
211 | hash: h, | ||
212 | payloadLen: payloadLen, | ||
213 | } | ||
214 | } | ||
215 | |||
216 | func (v *md5ValidationReader) Read(p []byte) (n int, err error) { | ||
217 | n, err = v.payload.Read(p) | ||
218 | if err != nil && err != io.EOF { | ||
219 | return n, err | ||
220 | } | ||
221 | |||
222 | v.read += int64(n) | ||
223 | |||
224 | if err == io.EOF { | ||
225 | if v.read != v.payloadLen { | ||
226 | return n, io.ErrUnexpectedEOF | ||
227 | } | ||
228 | expectSum := make([]byte, md5.Size) | ||
229 | actualSum := make([]byte, md5.Size) | ||
230 | if _, sumReadErr := io.ReadFull(v.rawReader, expectSum); sumReadErr != nil { | ||
231 | return n, sumReadErr | ||
232 | } | ||
233 | actualSum = v.hash.Sum(actualSum[0:0]) | ||
234 | if !bytes.Equal(expectSum, actualSum) { | ||
235 | return n, awserr.New("InvalidChecksum", | ||
236 | fmt.Sprintf("expected MD5 checksum %s, got %s", | ||
237 | hex.EncodeToString(expectSum), | ||
238 | hex.EncodeToString(actualSum), | ||
239 | ), | ||
240 | nil) | ||
241 | } | ||
242 | } | ||
243 | |||
244 | return n, err | ||
245 | } | ||
246 | |||
247 | func (v *md5ValidationReader) Close() error { | ||
248 | return v.rawReader.Close() | ||
249 | } | ||