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

Commit 0809623

Browse files
author
Jamie Hannaford
committed
Add MD5 checksum check to file uploads
1 parent f956c6c commit 0809623

File tree

3 files changed

+67
-11
lines changed

3 files changed

+67
-11
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: 25 additions & 4 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

@@ -214,11 +216,30 @@ func Create(c *gophercloud.ServiceClient, containerName, objectName string, cont
214216
MoreHeaders: h,
215217
}
216218

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

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) {

0 commit comments

Comments
 (0)