Skip to content
This repository was archived by the owner on Aug 1, 2023. It is now read-only.

Commit e83aa01

Browse files
committed
Merge pull request #444 from jamiehannaford/checksum
Add MD5 checksum check to file uploads
2 parents f956c6c + 50fc97d commit e83aa01

File tree

4 files changed

+77
-18
lines changed

4 files changed

+77
-18
lines changed

openstack/objectstorage/v1/objects/fixtures.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
package objects
44

55
import (
6+
"crypto/md5"
67
"fmt"
8+
"io"
79
"net/http"
810
"testing"
911

@@ -107,20 +109,26 @@ func HandleListObjectNamesSuccessfully(t *testing.T) {
107109

108110
// HandleCreateTextObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux
109111
// that responds with a `Create` response. A Content-Type of "text/plain" is expected.
110-
func HandleCreateTextObjectSuccessfully(t *testing.T) {
112+
func HandleCreateTextObjectSuccessfully(t *testing.T, content string) {
111113
th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
112114
th.TestMethod(t, r, "PUT")
113115
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
114116
th.TestHeader(t, r, "Content-Type", "text/plain")
115117
th.TestHeader(t, r, "Accept", "application/json")
118+
119+
hash := md5.New()
120+
io.WriteString(hash, content)
121+
localChecksum := hash.Sum(nil)
122+
123+
w.Header().Set("ETag", fmt.Sprintf("%x", localChecksum))
116124
w.WriteHeader(http.StatusCreated)
117125
})
118126
}
119127

120128
// HandleCreateTypelessObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler
121129
// mux that responds with a `Create` response. No Content-Type header may be present in the request, so that server-
122130
// side content-type detection will be triggered properly.
123-
func HandleCreateTypelessObjectSuccessfully(t *testing.T) {
131+
func HandleCreateTypelessObjectSuccessfully(t *testing.T, content string) {
124132
th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
125133
th.TestMethod(t, r, "PUT")
126134
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
@@ -130,6 +138,11 @@ func HandleCreateTypelessObjectSuccessfully(t *testing.T) {
130138
t.Errorf("Expected Content-Type header to be omitted, but was %#v", contentType)
131139
}
132140

141+
hash := md5.New()
142+
io.WriteString(hash, content)
143+
localChecksum := hash.Sum(nil)
144+
145+
w.Header().Set("ETag", fmt.Sprintf("%x", localChecksum))
133146
w.WriteHeader(http.StatusCreated)
134147
})
135148
}

openstack/objectstorage/v1/objects/requests.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package objects
22

33
import (
44
"crypto/hmac"
5+
"crypto/md5"
56
"crypto/sha1"
67
"fmt"
78
"io"
9+
"net/http"
810
"strings"
911
"time"
1012

@@ -188,7 +190,8 @@ func (opts CreateOpts) ToObjectCreateParams() (map[string]string, string, error)
188190
return h, q.String(), nil
189191
}
190192

191-
// Create is a function that creates a new object or replaces an existing object.
193+
// Create is a function that creates a new object or replaces an existing object. If the returned response's ETag
194+
// header fails to match the local checksum, the failed request will automatically be retried up to a maximum of 3 times.
192195
func Create(c *gophercloud.ServiceClient, containerName, objectName string, content io.ReadSeeker, opts CreateOptsBuilder) CreateResult {
193196
var res CreateResult
194197

@@ -214,11 +217,30 @@ func Create(c *gophercloud.ServiceClient, containerName, objectName string, cont
214217
MoreHeaders: h,
215218
}
216219

217-
resp, err := c.Request("PUT", url, ropts)
218-
if resp != nil {
219-
res.Header = resp.Header
220+
doUpload := func() (*http.Response, error) {
221+
resp, err := c.Request("PUT", url, ropts)
222+
if resp != nil {
223+
res.Header = resp.Header
224+
}
225+
return resp, err
220226
}
221-
res.Err = err
227+
228+
hash := md5.New()
229+
io.Copy(hash, content)
230+
localChecksum := hash.Sum(nil)
231+
232+
for i := 1; i <= 3; i++ {
233+
resp, err := doUpload()
234+
if resp.Header.Get("ETag") == fmt.Sprintf("%x", localChecksum) {
235+
res.Err = err
236+
break
237+
}
238+
if i == 3 {
239+
res.Err = fmt.Errorf("Local checksum does not match API ETag header")
240+
return res
241+
}
242+
}
243+
222244
return res
223245
}
224246

openstack/objectstorage/v1/objects/requests_test.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package objects
22

33
import (
44
"bytes"
5+
"fmt"
56
"io"
7+
"net/http"
68
"strings"
79
"testing"
810

@@ -84,22 +86,42 @@ func TestListObjectNames(t *testing.T) {
8486
func TestCreateObject(t *testing.T) {
8587
th.SetupHTTP()
8688
defer th.TeardownHTTP()
87-
HandleCreateTextObjectSuccessfully(t)
8889

89-
content := strings.NewReader("Did gyre and gimble in the wabe")
90+
content := "Did gyre and gimble in the wabe"
91+
92+
HandleCreateTextObjectSuccessfully(t, content)
93+
9094
options := &CreateOpts{ContentType: "text/plain"}
91-
res := Create(fake.ServiceClient(), "testContainer", "testObject", content, options)
95+
res := Create(fake.ServiceClient(), "testContainer", "testObject", strings.NewReader(content), options)
9296
th.AssertNoErr(t, res.Err)
9397
}
9498

9599
func TestCreateObjectWithoutContentType(t *testing.T) {
96100
th.SetupHTTP()
97101
defer th.TeardownHTTP()
98-
HandleCreateTypelessObjectSuccessfully(t)
102+
103+
content := "The sky was the color of television, tuned to a dead channel."
104+
105+
HandleCreateTypelessObjectSuccessfully(t, content)
106+
107+
res := Create(fake.ServiceClient(), "testContainer", "testObject", strings.NewReader(content), &CreateOpts{})
108+
th.AssertNoErr(t, res.Err)
109+
}
110+
111+
func TestErrorIsRaisedForChecksumMismatch(t *testing.T) {
112+
th.SetupHTTP()
113+
defer th.TeardownHTTP()
114+
115+
th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
116+
w.Header().Set("ETag", "acbd18db4cc2f85cedef654fccc4a4d8")
117+
w.WriteHeader(http.StatusCreated)
118+
})
99119

100120
content := strings.NewReader("The sky was the color of television, tuned to a dead channel.")
101121
res := Create(fake.ServiceClient(), "testContainer", "testObject", content, &CreateOpts{})
102-
th.AssertNoErr(t, res.Err)
122+
123+
err := fmt.Errorf("Local checksum does not match API ETag header")
124+
th.AssertDeepEquals(t, err, res.Err)
103125
}
104126

105127
func TestCopyObject(t *testing.T) {

rackspace/objectstorage/v1/objects/delegate_test.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,21 +66,23 @@ func TestListObjectNames(t *testing.T) {
6666
func TestCreateObject(t *testing.T) {
6767
th.SetupHTTP()
6868
defer th.TeardownHTTP()
69-
os.HandleCreateTextObjectSuccessfully(t)
69+
70+
content := "Did gyre and gimble in the wabe"
71+
os.HandleCreateTextObjectSuccessfully(t, content)
7072

71-
content := strings.NewReader("Did gyre and gimble in the wabe")
7273
options := &os.CreateOpts{ContentType: "text/plain"}
73-
res := Create(fake.ServiceClient(), "testContainer", "testObject", content, options)
74+
res := Create(fake.ServiceClient(), "testContainer", "testObject", strings.NewReader(content), options)
7475
th.AssertNoErr(t, res.Err)
7576
}
7677

7778
func TestCreateObjectWithoutContentType(t *testing.T) {
7879
th.SetupHTTP()
7980
defer th.TeardownHTTP()
80-
os.HandleCreateTypelessObjectSuccessfully(t)
8181

82-
content := strings.NewReader("The sky was the color of television, tuned to a dead channel.")
83-
res := Create(fake.ServiceClient(), "testContainer", "testObject", content, &os.CreateOpts{})
82+
content := "The sky was the color of television, tuned to a dead channel."
83+
os.HandleCreateTypelessObjectSuccessfully(t, content)
84+
85+
res := Create(fake.ServiceClient(), "testContainer", "testObject", strings.NewReader(content), &os.CreateOpts{})
8486
th.AssertNoErr(t, res.Err)
8587
}
8688

0 commit comments

Comments
 (0)