Merge pull request #339 from jrperritt/cdn-openstack-rackspace

OpenStack and Rackspace CDN Service Support
diff --git a/.travis.yml b/.travis.yml
index cf4f8ca..946f98c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,8 @@
 go:
   - 1.1
   - 1.2
+  - 1.3
+  - 1.4
   - tip
 script: script/cibuild
 after_success:
@@ -12,3 +14,4 @@
   - go get github.com/mattn/goveralls
   - export PATH=$PATH:$HOME/gopath/bin/
   - goveralls 2k7PTU3xa474Hymwgdj6XjqenNfGTNkO8
+sudo: false
diff --git a/openstack/objectstorage/v1/objects/fixtures.go b/openstack/objectstorage/v1/objects/fixtures.go
index d951160..ec61637 100644
--- a/openstack/objectstorage/v1/objects/fixtures.go
+++ b/openstack/objectstorage/v1/objects/fixtures.go
@@ -105,13 +105,31 @@
 	})
 }
 
-// HandleCreateObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that
-// responds with a `Create` response.
-func HandleCreateObjectSuccessfully(t *testing.T) {
+// 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) {
+	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")
+		w.WriteHeader(http.StatusCreated)
+	})
+}
+
+// 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) {
 	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, "Accept", "application/json")
+
+		if contentType, present := r.Header["Content-Type"]; present {
+			t.Errorf("Expected Content-Type header to be omitted, but was %#v", contentType)
+		}
+
 		w.WriteHeader(http.StatusCreated)
 	})
 }
diff --git a/openstack/objectstorage/v1/objects/requests.go b/openstack/objectstorage/v1/objects/requests.go
index 7b96fa2..0fdb041 100644
--- a/openstack/objectstorage/v1/objects/requests.go
+++ b/openstack/objectstorage/v1/objects/requests.go
@@ -205,14 +205,20 @@
 		url += query
 	}
 
-	contentType := h["Content-Type"]
-
-	resp, err := perigee.Request("PUT", url, perigee.Options{
-		ContentType: contentType,
+	popts := perigee.Options{
 		ReqBody:     content,
 		MoreHeaders: h,
 		OkCodes:     []int{201, 202},
-	})
+	}
+
+	if contentType, explicit := h["Content-Type"]; explicit {
+		popts.ContentType = contentType
+		delete(h, "Content-Type")
+	} else {
+		popts.OmitContentType = true
+	}
+
+	resp, err := perigee.Request("PUT", url, popts)
 	res.Header = resp.HttpResponse.Header
 	res.Err = err
 	return res
diff --git a/openstack/objectstorage/v1/objects/requests_test.go b/openstack/objectstorage/v1/objects/requests_test.go
index c3c28a7..6be3534 100644
--- a/openstack/objectstorage/v1/objects/requests_test.go
+++ b/openstack/objectstorage/v1/objects/requests_test.go
@@ -83,14 +83,24 @@
 func TestCreateObject(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
-	HandleCreateObjectSuccessfully(t)
+	HandleCreateTextObjectSuccessfully(t)
 
 	content := bytes.NewBufferString("Did gyre and gimble in the wabe")
-	options := &CreateOpts{ContentType: "application/json"}
+	options := &CreateOpts{ContentType: "text/plain"}
 	res := Create(fake.ServiceClient(), "testContainer", "testObject", content, options)
 	th.AssertNoErr(t, res.Err)
 }
 
+func TestCreateObjectWithoutContentType(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleCreateTypelessObjectSuccessfully(t)
+
+	content := bytes.NewBufferString("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)
+}
+
 func TestCopyObject(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
diff --git a/rackspace/objectstorage/v1/objects/delegate_test.go b/rackspace/objectstorage/v1/objects/delegate_test.go
index 08831ec..8ab8029 100644
--- a/rackspace/objectstorage/v1/objects/delegate_test.go
+++ b/rackspace/objectstorage/v1/objects/delegate_test.go
@@ -66,14 +66,24 @@
 func TestCreateObject(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
-	os.HandleCreateObjectSuccessfully(t)
+	os.HandleCreateTextObjectSuccessfully(t)
 
 	content := bytes.NewBufferString("Did gyre and gimble in the wabe")
-	options := &os.CreateOpts{ContentType: "application/json"}
+	options := &os.CreateOpts{ContentType: "text/plain"}
 	res := Create(fake.ServiceClient(), "testContainer", "testObject", content, options)
 	th.AssertNoErr(t, res.Err)
 }
 
+func TestCreateObjectWithoutContentType(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleCreateTypelessObjectSuccessfully(t)
+
+	content := bytes.NewBufferString("The sky was the color of television, tuned to a dead channel.")
+	res := Create(fake.ServiceClient(), "testContainer", "testObject", content, &os.CreateOpts{})
+	th.AssertNoErr(t, res.Err)
+}
+
 func TestCopyObject(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()