Skip to content

Commit

Permalink
service/s3: Fixes parsing S3 error messages in HTTP 200 responses
Browse files Browse the repository at this point in the history
If a S3 Copy operation fails a HTTP 200 response could still be returned with an error message body. This change adds special handling to S3 unmarshalling to look for error responses for these operations.

http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectCOPY.html
  • Loading branch information
jasdel committed Sep 3, 2015
1 parent f096b7d commit 34eb9e6
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 1 deletion.
2 changes: 1 addition & 1 deletion internal/protocol/json/jsonutil/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,4 @@ func elemOf(value reflect.Value) reflect.Value {
value = value.Elem()
}
return value
}
}
2 changes: 2 additions & 0 deletions service/s3/customizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ func init() {
case opCreateBucket:
// Auto-populate LocationConstraint with current region
r.Handlers.Validate.PushFront(populateLocationConstraint)
case opCopyObject, opUploadPartCopy, opCompleteMultipartUpload:
r.Handlers.Unmarshal.PushFront(copyMultipartStatusOKUnmarhsalError)
}
}
}
36 changes: 36 additions & 0 deletions service/s3/statusok_error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package s3

import (
"bytes"
"io/ioutil"
"net/http"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
)

func copyMultipartStatusOKUnmarhsalError(r *request.Request) {
b, err := ioutil.ReadAll(r.HTTPResponse.Body)
if err != nil {
r.Error = awserr.New("SerializationError", "unable to read response body", err)
return
}
body := bytes.NewReader(b)
r.HTTPResponse.Body = aws.ReadSeekCloser(body)
defer r.HTTPResponse.Body.(aws.ReaderSeekerCloser).Seek(0, 0)

if body.Len() == 0 {
// If there is no body don't attempt to parse the body.
return
}

unmarshalError(r)
if err, ok := r.Error.(awserr.Error); ok && err != nil {
if err.Code() == "SerializationError" {
r.Error = nil
return
}
r.HTTPResponse.StatusCode = http.StatusServiceUnavailable
}
}
129 changes: 129 additions & 0 deletions service/s3/statusok_error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package s3_test

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
"time"
)

const errMsg = `<Error><Code>ErrorCode</Code><Message>message body</Message><RequestId>requestID</RequestId><HostId>hostID=</HostId></Error>`

var lastModifiedTime = time.Date(2009, 11, 23, 0, 0, 0, 0, time.UTC)

func TestCopyObjectNoError(t *testing.T) {
const successMsg = `
<?xml version="1.0" encoding="UTF-8"?>
<CopyObjectResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><LastModified>2009-11-23T0:00:00Z</LastModified><ETag>&quot;1da64c7f13d1e8dbeaea40b905fd586c&quot;</ETag></CopyObjectResult>`

res, err := newCopyTestSvc(successMsg).CopyObject(&s3.CopyObjectInput{
Bucket: aws.String("bucketname"),
CopySource: aws.String("bucketname/exists.txt"),
Key: aws.String("destination.txt"),
})

require.NoError(t, err)

assert.Equal(t, fmt.Sprintf(`%q`, "1da64c7f13d1e8dbeaea40b905fd586c"), *res.CopyObjectResult.ETag)
assert.Equal(t, lastModifiedTime, *res.CopyObjectResult.LastModified)
}

func TestCopyObjectError(t *testing.T) {
_, err := newCopyTestSvc(errMsg).CopyObject(&s3.CopyObjectInput{
Bucket: aws.String("bucketname"),
CopySource: aws.String("bucketname/doesnotexist.txt"),
Key: aws.String("destination.txt"),
})

require.Error(t, err)
e := err.(awserr.Error)

assert.Equal(t, "ErrorCode", e.Code())
assert.Equal(t, "message body", e.Message())
}

func TestUploadPartCopySuccess(t *testing.T) {
const successMsg = `
<?xml version="1.0" encoding="UTF-8"?>
<UploadPartCopyResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><LastModified>2009-11-23T0:00:00Z</LastModified><ETag>&quot;1da64c7f13d1e8dbeaea40b905fd586c&quot;</ETag></CopyObjectResult>`

res, err := newCopyTestSvc(successMsg).UploadPartCopy(&s3.UploadPartCopyInput{
Bucket: aws.String("bucketname"),
CopySource: aws.String("bucketname/doesnotexist.txt"),
Key: aws.String("destination.txt"),
PartNumber: aws.Int64(0),
UploadId: aws.String("uploadID"),
})

require.NoError(t, err)

assert.Equal(t, fmt.Sprintf(`%q`, "1da64c7f13d1e8dbeaea40b905fd586c"), *res.CopyPartResult.ETag)
assert.Equal(t, lastModifiedTime, *res.CopyPartResult.LastModified)
}

func TestUploadPartCopyError(t *testing.T) {
_, err := newCopyTestSvc(errMsg).UploadPartCopy(&s3.UploadPartCopyInput{
Bucket: aws.String("bucketname"),
CopySource: aws.String("bucketname/doesnotexist.txt"),
Key: aws.String("destination.txt"),
PartNumber: aws.Int64(0),
UploadId: aws.String("uploadID"),
})

require.Error(t, err)
e := err.(awserr.Error)

assert.Equal(t, "ErrorCode", e.Code())
assert.Equal(t, "message body", e.Message())
}

func TestCompleteMultipartUploadSuccess(t *testing.T) {
const successMsg = `
<?xml version="1.0" encoding="UTF-8"?>
<CompleteMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Location>locationName</Location><Bucket>bucketName</Bucket><Key>keyName</Key><ETag>"etagVal"</ETag></CompleteMultipartUploadResult>`
res, err := newCopyTestSvc(successMsg).CompleteMultipartUpload(&s3.CompleteMultipartUploadInput{
Bucket: aws.String("bucketname"),
Key: aws.String("key"),
UploadId: aws.String("uploadID"),
})

require.NoError(t, err)

assert.Equal(t, `"etagVal"`, *res.ETag)
assert.Equal(t, "bucketName", *res.Bucket)
assert.Equal(t, "keyName", *res.Key)
assert.Equal(t, "locationName", *res.Location)
}

func TestCompleteMultipartUploadError(t *testing.T) {
_, err := newCopyTestSvc(errMsg).CompleteMultipartUpload(&s3.CompleteMultipartUploadInput{
Bucket: aws.String("bucketname"),
Key: aws.String("key"),
UploadId: aws.String("uploadID"),
})

require.Error(t, err)
e := err.(awserr.Error)

assert.Equal(t, "ErrorCode", e.Code())
assert.Equal(t, "message body", e.Message())
}

func newCopyTestSvc(errMsg string) *s3.S3 {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, errMsg, http.StatusOK)
}))
return s3.New(aws.NewConfig().
WithEndpoint(server.URL).
WithDisableSSL(true).
WithMaxRetries(0).
WithS3ForcePathStyle(true))
}

0 comments on commit 34eb9e6

Please sign in to comment.