Add MD5 checksum check to file uploads
diff --git a/openstack/objectstorage/v1/objects/fixtures.go b/openstack/objectstorage/v1/objects/fixtures.go
index ec61637..7a6e6e1 100644
--- a/openstack/objectstorage/v1/objects/fixtures.go
+++ b/openstack/objectstorage/v1/objects/fixtures.go
@@ -3,7 +3,9 @@
package objects
import (
+ "crypto/md5"
"fmt"
+ "io"
"net/http"
"testing"
@@ -107,12 +109,18 @@
// HandleCreateTextObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux
// that responds with a `Create` response. A Content-Type of "text/plain" is expected.
-func HandleCreateTextObjectSuccessfully(t *testing.T) {
+func HandleCreateTextObjectSuccessfully(t *testing.T, content string) {
th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Content-Type", "text/plain")
th.TestHeader(t, r, "Accept", "application/json")
+
+ hash := md5.New()
+ io.WriteString(hash, content)
+ localChecksum := hash.Sum(nil)
+
+ w.Header().Set("ETag", fmt.Sprintf("%x", localChecksum))
w.WriteHeader(http.StatusCreated)
})
}
@@ -120,7 +128,7 @@
// HandleCreateTypelessObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler
// mux that responds with a `Create` response. No Content-Type header may be present in the request, so that server-
// side content-type detection will be triggered properly.
-func HandleCreateTypelessObjectSuccessfully(t *testing.T) {
+func HandleCreateTypelessObjectSuccessfully(t *testing.T, content string) {
th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
@@ -130,6 +138,11 @@
t.Errorf("Expected Content-Type header to be omitted, but was %#v", contentType)
}
+ hash := md5.New()
+ io.WriteString(hash, content)
+ localChecksum := hash.Sum(nil)
+
+ w.Header().Set("ETag", fmt.Sprintf("%x", localChecksum))
w.WriteHeader(http.StatusCreated)
})
}
diff --git a/openstack/objectstorage/v1/objects/requests.go b/openstack/objectstorage/v1/objects/requests.go
index b9a0e7e..013d50e 100644
--- a/openstack/objectstorage/v1/objects/requests.go
+++ b/openstack/objectstorage/v1/objects/requests.go
@@ -2,9 +2,11 @@
import (
"crypto/hmac"
+ "crypto/md5"
"crypto/sha1"
"fmt"
"io"
+ "net/http"
"strings"
"time"
@@ -214,11 +216,30 @@
MoreHeaders: h,
}
- resp, err := c.Request("PUT", url, ropts)
- if resp != nil {
- res.Header = resp.Header
+ doUpload := func() (*http.Response, error) {
+ resp, err := c.Request("PUT", url, ropts)
+ if resp != nil {
+ res.Header = resp.Header
+ }
+ return resp, err
}
- res.Err = err
+
+ hash := md5.New()
+ io.Copy(hash, content)
+ localChecksum := hash.Sum(nil)
+
+ for i := 1; i <= 3; i++ {
+ resp, err := doUpload()
+ if resp.Header.Get("ETag") == fmt.Sprintf("%x", localChecksum) {
+ res.Err = err
+ break
+ }
+ if i == 3 {
+ res.Err = fmt.Errorf("Local checksum does not match API ETag header")
+ return res
+ }
+ }
+
return res
}
diff --git a/openstack/objectstorage/v1/objects/requests_test.go b/openstack/objectstorage/v1/objects/requests_test.go
index 941e59e..f7d6822 100644
--- a/openstack/objectstorage/v1/objects/requests_test.go
+++ b/openstack/objectstorage/v1/objects/requests_test.go
@@ -2,7 +2,9 @@
import (
"bytes"
+ "fmt"
"io"
+ "net/http"
"strings"
"testing"
@@ -84,22 +86,42 @@
func TestCreateObject(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
- HandleCreateTextObjectSuccessfully(t)
- content := strings.NewReader("Did gyre and gimble in the wabe")
+ content := "Did gyre and gimble in the wabe"
+
+ HandleCreateTextObjectSuccessfully(t, content)
+
options := &CreateOpts{ContentType: "text/plain"}
- res := Create(fake.ServiceClient(), "testContainer", "testObject", content, options)
+ res := Create(fake.ServiceClient(), "testContainer", "testObject", strings.NewReader(content), options)
th.AssertNoErr(t, res.Err)
}
func TestCreateObjectWithoutContentType(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
- HandleCreateTypelessObjectSuccessfully(t)
+
+ content := "The sky was the color of television, tuned to a dead channel."
+
+ HandleCreateTypelessObjectSuccessfully(t, content)
+
+ res := Create(fake.ServiceClient(), "testContainer", "testObject", strings.NewReader(content), &CreateOpts{})
+ th.AssertNoErr(t, res.Err)
+}
+
+func TestErrorIsRaisedForChecksumMismatch(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("ETag", "acbd18db4cc2f85cedef654fccc4a4d8")
+ w.WriteHeader(http.StatusCreated)
+ })
content := strings.NewReader("The sky was the color of television, tuned to a dead channel.")
res := Create(fake.ServiceClient(), "testContainer", "testObject", content, &CreateOpts{})
- th.AssertNoErr(t, res.Err)
+
+ err := fmt.Errorf("Local checksum does not match API ETag header")
+ th.AssertDeepEquals(t, err, res.Err)
}
func TestCopyObject(t *testing.T) {