aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/aws/aws-sdk-go/service/s3/body_hash.go
diff options
context:
space:
mode:
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.go249
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 @@
1package s3
2
3import (
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
19const (
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.
30func 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.
55func 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
117const (
118 md5Base64EncLen = (md5.Size + 2) / 3 * 4 // base64.StdEncoding.EncodedLen
119 sha256HexEncLen = sha256.Size * 2 // hex.EncodedLen
120)
121
122func 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.
149func 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
159func 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
197type md5ValidationReader struct {
198 rawReader io.ReadCloser
199 payload io.Reader
200 hash hash.Hash
201
202 payloadLen int64
203 read int64
204}
205
206func 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
216func (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
247func (v *md5ValidationReader) Close() error {
248 return v.rawReader.Close()
249}