Merge pull request #350 from jrperritt/objectstorage-extract-method

Object Storage extract method; Create TempURL (Closes #349)
diff --git a/acceptance/openstack/objectstorage/v1/accounts_test.go b/acceptance/openstack/objectstorage/v1/accounts_test.go
index f7c01a7..24cc62b 100644
--- a/acceptance/openstack/objectstorage/v1/accounts_test.go
+++ b/acceptance/openstack/objectstorage/v1/accounts_test.go
@@ -17,7 +17,10 @@
 
 	// Update an account's metadata.
 	updateres := accounts.Update(client, accounts.UpdateOpts{Metadata: metadata})
-	th.AssertNoErr(t, updateres.Err)
+	t.Logf("Update Account Response: %+v\n", updateres)
+	updateHeaders, err := updateres.Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Update Account Response Headers: %+v\n", updateHeaders)
 
 	// Defer the deletion of the metadata set above.
 	defer func() {
@@ -29,11 +32,14 @@
 		th.AssertNoErr(t, updateres.Err)
 	}()
 
-	// Retrieve account metadata.
-	getres := accounts.Get(client, nil)
-	th.AssertNoErr(t, getres.Err)
 	// Extract the custom metadata from the 'Get' response.
-	am, err := getres.ExtractMetadata()
+	res := accounts.Get(client, nil)
+
+	h, err := res.Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Get Account Response Headers: %+v\n", h)
+
+	am, err := res.ExtractMetadata()
 	th.AssertNoErr(t, err)
 	for k := range metadata {
 		if am[k] != metadata[strings.Title(k)] {
diff --git a/acceptance/rackspace/objectstorage/v1/accounts_test.go b/acceptance/rackspace/objectstorage/v1/accounts_test.go
index 145e4e0..8b3cde4 100644
--- a/acceptance/rackspace/objectstorage/v1/accounts_test.go
+++ b/acceptance/rackspace/objectstorage/v1/accounts_test.go
@@ -13,11 +13,11 @@
 	c, err := createClient(t, false)
 	th.AssertNoErr(t, err)
 
-	updateres := raxAccounts.Update(c, raxAccounts.UpdateOpts{Metadata: map[string]string{"white": "mountains"}})
-	th.AssertNoErr(t, updateres.Err)
-	t.Logf("Headers from Update Account request: %+v\n", updateres.Header)
+	updateHeaders, err := raxAccounts.Update(c, raxAccounts.UpdateOpts{Metadata: map[string]string{"white": "mountains"}}).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Update Account Response Headers: %+v\n", updateHeaders)
 	defer func() {
-		updateres = raxAccounts.Update(c, raxAccounts.UpdateOpts{Metadata: map[string]string{"white": ""}})
+		updateres := raxAccounts.Update(c, raxAccounts.UpdateOpts{Metadata: map[string]string{"white": ""}})
 		th.AssertNoErr(t, updateres.Err)
 		metadata, err := raxAccounts.Get(c).ExtractMetadata()
 		th.AssertNoErr(t, err)
@@ -25,8 +25,13 @@
 		th.CheckEquals(t, metadata["White"], "")
 	}()
 
-	metadata, err := raxAccounts.Get(c).ExtractMetadata()
-	th.AssertNoErr(t, err)
+	getResp := raxAccounts.Get(c)
+	th.AssertNoErr(t, getResp.Err)
+
+	getHeaders, _ := getResp.Extract()
+	t.Logf("Get Account Response Headers: %+v\n", getHeaders)
+
+	metadata, _ := getResp.ExtractMetadata()
 	t.Logf("Metadata from Get Account request (after update): %+v\n", metadata)
 
 	th.CheckEquals(t, metadata["White"], "mountains")
diff --git a/acceptance/rackspace/objectstorage/v1/cdncontainers_test.go b/acceptance/rackspace/objectstorage/v1/cdncontainers_test.go
index e1bf38b..0f56f49 100644
--- a/acceptance/rackspace/objectstorage/v1/cdncontainers_test.go
+++ b/acceptance/rackspace/objectstorage/v1/cdncontainers_test.go
@@ -26,10 +26,11 @@
 
 	raxCDNClient, err := createClient(t, true)
 	th.AssertNoErr(t, err)
-
-	r := raxCDNContainers.Enable(raxCDNClient, "gophercloud-test", raxCDNContainers.EnableOpts{CDNEnabled: true, TTL: 900})
-	th.AssertNoErr(t, r.Err)
-	t.Logf("Headers from Enable CDN Container request: %+v\n", r.Header)
+	enableRes := raxCDNContainers.Enable(raxCDNClient, "gophercloud-test", raxCDNContainers.EnableOpts{CDNEnabled: true, TTL: 900})
+	t.Logf("Header map from Enable CDN Container request: %+v\n", enableRes.Header)
+	enableHeader, err := enableRes.Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Enable CDN Container request: %+v\n", enableHeader)
 
 	t.Logf("Container Names available to the currently issued token:")
 	count := 0
@@ -51,11 +52,15 @@
 		t.Errorf("No CDN containers listed for your current token.")
 	}
 
-	updateres := raxCDNContainers.Update(raxCDNClient, "gophercloud-test", raxCDNContainers.UpdateOpts{CDNEnabled: false})
-	th.AssertNoErr(t, updateres.Err)
-	t.Logf("Headers from Update CDN Container request: %+v\n", updateres.Header)
-
-	metadata, err := raxCDNContainers.Get(raxCDNClient, "gophercloud-test").ExtractMetadata()
+	updateOpts := raxCDNContainers.UpdateOpts{XCDNEnabled: raxCDNContainers.Disabled, XLogRetention: raxCDNContainers.Enabled}
+	updateHeader, err := raxCDNContainers.Update(raxCDNClient, "gophercloud-test", updateOpts).Extract()
 	th.AssertNoErr(t, err)
-	t.Logf("Headers from Get CDN Container request (after update): %+v\n", metadata)
+	t.Logf("Headers from Update CDN Container request: %+v\n", updateHeader)
+
+	getRes := raxCDNContainers.Get(raxCDNClient, "gophercloud-test")
+	getHeader, err := getRes.Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Get CDN Container request (after update): %+v\n", getHeader)
+	metadata, err := getRes.ExtractMetadata()
+	t.Logf("Metadata from Get CDN Container request (after update): %+v\n", metadata)
 }
diff --git a/acceptance/rackspace/objectstorage/v1/containers_test.go b/acceptance/rackspace/objectstorage/v1/containers_test.go
index a7339cf..c895513 100644
--- a/acceptance/rackspace/objectstorage/v1/containers_test.go
+++ b/acceptance/rackspace/objectstorage/v1/containers_test.go
@@ -57,29 +57,34 @@
 		t.Errorf("No containers listed for your current token.")
 	}
 
-	createres := raxContainers.Create(c, "gophercloud-test", nil)
-	th.AssertNoErr(t, createres.Err)
+	createHeader, err := raxContainers.Create(c, "gophercloud-test", nil).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Create Container request: %+v\n", createHeader)
 	defer func() {
-		res := raxContainers.Delete(c, "gophercloud-test")
-		th.AssertNoErr(t, res.Err)
+		deleteres := raxContainers.Delete(c, "gophercloud-test")
+		deleteHeader, err := deleteres.Extract()
+		th.AssertNoErr(t, err)
+		t.Logf("Headers from Delete Container request: %+v\n", deleteres.Header)
+		t.Logf("Headers from Delete Container request: %+v\n", deleteHeader)
 	}()
 
-	updateres := raxContainers.Update(c, "gophercloud-test", raxContainers.UpdateOpts{Metadata: map[string]string{"white": "mountains"}})
-	th.AssertNoErr(t, updateres.Err)
-	t.Logf("Headers from Update Account request: %+v\n", updateres.Header)
+	updateHeader, err := raxContainers.Update(c, "gophercloud-test", raxContainers.UpdateOpts{Metadata: map[string]string{"white": "mountains"}}).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Update Container request: %+v\n", updateHeader)
 	defer func() {
 		res := raxContainers.Update(c, "gophercloud-test", raxContainers.UpdateOpts{Metadata: map[string]string{"white": ""}})
 		th.AssertNoErr(t, res.Err)
 		metadata, err := raxContainers.Get(c, "gophercloud-test").ExtractMetadata()
 		th.AssertNoErr(t, err)
-		t.Logf("Metadata from Get Account request (after update reverted): %+v\n", metadata)
+		t.Logf("Metadata from Get Container request (after update reverted): %+v\n", metadata)
 		th.CheckEquals(t, metadata["White"], "")
 	}()
 
 	getres := raxContainers.Get(c, "gophercloud-test")
-	t.Logf("Headers from Get Account request (after update): %+v\n", getres.Header)
-	metadata, err := getres.ExtractMetadata()
+	getHeader, err := getres.Extract()
 	th.AssertNoErr(t, err)
-	t.Logf("Metadata from Get Account request (after update): %+v\n", metadata)
+	t.Logf("Headers from Get Container request (after update): %+v\n", getHeader)
+	metadata, err := getres.ExtractMetadata()
+	t.Logf("Metadata from Get Container request (after update): %+v\n", metadata)
 	th.CheckEquals(t, metadata["White"], "mountains")
 }
diff --git a/acceptance/rackspace/objectstorage/v1/objects_test.go b/acceptance/rackspace/objectstorage/v1/objects_test.go
index 462f284..585dea7 100644
--- a/acceptance/rackspace/objectstorage/v1/objects_test.go
+++ b/acceptance/rackspace/objectstorage/v1/objects_test.go
@@ -21,6 +21,7 @@
 	th.AssertNoErr(t, res.Err)
 
 	defer func() {
+		t.Logf("Deleting container...")
 		res := raxContainers.Delete(c, "gophercloud-test")
 		th.AssertNoErr(t, res.Err)
 	}()
@@ -29,7 +30,9 @@
 	options := &osObjects.CreateOpts{ContentType: "text/plain"}
 	createres := raxObjects.Create(c, "gophercloud-test", "o1", content, options)
 	th.AssertNoErr(t, createres.Err)
+
 	defer func() {
+		t.Logf("Deleting object o1...")
 		res := raxObjects.Delete(c, "gophercloud-test", "o1", nil)
 		th.AssertNoErr(t, res.Err)
 	}()
@@ -80,6 +83,7 @@
 	copyres := raxObjects.Copy(c, "gophercloud-test", "o1", &raxObjects.CopyOpts{Destination: "gophercloud-test/o2"})
 	th.AssertNoErr(t, copyres.Err)
 	defer func() {
+		t.Logf("Deleting object o2...")
 		res := raxObjects.Delete(c, "gophercloud-test", "o2", nil)
 		th.AssertNoErr(t, res.Err)
 	}()
@@ -99,7 +103,7 @@
 		metadata, err := raxObjects.Get(c, "gophercloud-test", "o2", nil).ExtractMetadata()
 		th.AssertNoErr(t, err)
 		t.Logf("Metadata from Get Account request (after update reverted): %+v\n", metadata)
-		th.CheckEquals(t, metadata["White"], "")
+		th.CheckEquals(t, "", metadata["White"])
 	}()
 
 	getres := raxObjects.Get(c, "gophercloud-test", "o2", nil)
@@ -108,5 +112,13 @@
 	metadata, err := getres.ExtractMetadata()
 	th.AssertNoErr(t, err)
 	t.Logf("Metadata from Get Account request (after update): %+v\n", metadata)
-	th.CheckEquals(t, metadata["White"], "mountains")
+	th.CheckEquals(t, "mountains", metadata["White"])
+
+	createTempURLOpts := osObjects.CreateTempURLOpts{
+		Method: osObjects.GET,
+		TTL:    600,
+	}
+	tempURL, err := raxObjects.CreateTempURL(c, "gophercloud-test", "o1", createTempURLOpts)
+	th.AssertNoErr(t, err)
+	t.Logf("TempURL for object (%s): %s", "o1", tempURL)
 }
diff --git a/openstack/objectstorage/v1/accounts/fixtures.go b/openstack/objectstorage/v1/accounts/fixtures.go
index 3dad0c5..f22b687 100644
--- a/openstack/objectstorage/v1/accounts/fixtures.go
+++ b/openstack/objectstorage/v1/accounts/fixtures.go
@@ -14,9 +14,8 @@
 // responds with a `Get` response.
 func HandleGetAccountSuccessfully(t *testing.T) {
 	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		th.TestMethod(t, r, "POST")
+		th.TestMethod(t, r, "HEAD")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		th.TestHeader(t, r, "X-Account-Meta-Gophercloud-Test", "accounts")
 
 		w.Header().Set("X-Account-Container-Count", "2")
 		w.Header().Set("X-Account-Bytes-Used", "14")
@@ -30,9 +29,10 @@
 // responds with a `Update` response.
 func HandleUpdateAccountSuccessfully(t *testing.T) {
 	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		th.TestMethod(t, r, "HEAD")
+		th.TestMethod(t, r, "POST")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		w.Header().Set("X-Account-Meta-Foo", "bar")
+		th.TestHeader(t, r, "X-Account-Meta-Gophercloud-Test", "accounts")
+
 		w.WriteHeader(http.StatusNoContent)
 	})
 }
diff --git a/openstack/objectstorage/v1/accounts/requests_test.go b/openstack/objectstorage/v1/accounts/requests_test.go
index d6dc26b..6454c0a 100644
--- a/openstack/objectstorage/v1/accounts/requests_test.go
+++ b/openstack/objectstorage/v1/accounts/requests_test.go
@@ -7,12 +7,10 @@
 	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
-var metadata = map[string]string{"gophercloud-test": "accounts"}
-
 func TestUpdateAccount(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
-	HandleGetAccountSuccessfully(t)
+	HandleUpdateAccountSuccessfully(t)
 
 	options := &UpdateOpts{Metadata: map[string]string{"gophercloud-test": "accounts"}}
 	res := Update(fake.ServiceClient(), options)
@@ -22,12 +20,13 @@
 func TestGetAccount(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
-	HandleUpdateAccountSuccessfully(t)
+	HandleGetAccountSuccessfully(t)
 
-	expected := map[string]string{"Foo": "bar"}
-	actual, err := Get(fake.ServiceClient(), &GetOpts{}).ExtractMetadata()
-	if err != nil {
-		t.Fatalf("Unable to get account metadata: %v", err)
-	}
-	th.CheckDeepEquals(t, expected, actual)
+	expectedMetadata := map[string]string{"Subject": "books"}
+	res := Get(fake.ServiceClient(), &GetOpts{})
+	th.AssertNoErr(t, res.Err)
+	actualMetadata, _ := res.ExtractMetadata()
+	th.CheckDeepEquals(t, expectedMetadata, actualMetadata)
+	//headers, err := res.Extract()
+	//th.AssertNoErr(t, err)
 }
diff --git a/openstack/objectstorage/v1/accounts/results.go b/openstack/objectstorage/v1/accounts/results.go
index abae026..6ab1a23 100644
--- a/openstack/objectstorage/v1/accounts/results.go
+++ b/openstack/objectstorage/v1/accounts/results.go
@@ -2,15 +2,88 @@
 
 import (
 	"strings"
+	"time"
 
 	"github.com/rackspace/gophercloud"
 )
 
+// UpdateResult is returned from a call to the Update function.
+type UpdateResult struct {
+	gophercloud.HeaderResult
+}
+
+// UpdateHeader represents the headers returned in the response from an Update request.
+type UpdateHeader struct {
+	ContentLength string    `mapstructure:"Content-Length"`
+	ContentType   string    `mapstructure:"Content-Type"`
+	Date          time.Time `mapstructure:"-"`
+	TransID       string    `mapstructure:"X-Trans-Id"`
+}
+
+// Extract will return a struct of headers returned from a call to Get. To obtain
+// a map of headers, call the ExtractHeader method on the GetResult.
+func (ur UpdateResult) Extract() (UpdateHeader, error) {
+	var uh UpdateHeader
+	if ur.Err != nil {
+		return uh, ur.Err
+	}
+
+	if err := gophercloud.DecodeHeader(ur.Header, &uh); err != nil {
+		return uh, err
+	}
+
+	if date, ok := ur.Header["Date"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, ur.Header["Date"][0])
+		if err != nil {
+			return uh, err
+		}
+		uh.Date = t
+	}
+
+	return uh, nil
+}
+
+// GetHeader represents the headers returned in the response from a Get request.
+type GetHeader struct {
+	BytesUsed      int64     `mapstructure:"X-Account-Bytes-Used"`
+	ContainerCount int       `mapstructure:"X-Account-Container-Count"`
+	ContentLength  int64     `mapstructure:"Content-Length"`
+	ContentType    string    `mapstructure:"Content-Type"`
+	Date           time.Time `mapstructure:"-"`
+	ObjectCount    int64     `mapstructure:"X-Account-Object-Count"`
+	TransID        string    `mapstructure:"X-Trans-Id"`
+	TempURLKey     string    `mapstructure:"X-Account-Meta-Temp-URL-Key"`
+	TempURLKey2    string    `mapstructure:"X-Account-Meta-Temp-URL-Key-2"`
+}
+
 // GetResult is returned from a call to the Get function.
 type GetResult struct {
 	gophercloud.HeaderResult
 }
 
+// Extract will return a struct of headers returned from a call to Get. To obtain
+// a map of headers, call the ExtractHeader method on the GetResult.
+func (gr GetResult) Extract() (GetHeader, error) {
+	var gh GetHeader
+	if gr.Err != nil {
+		return gh, gr.Err
+	}
+
+	if err := gophercloud.DecodeHeader(gr.Header, &gh); err != nil {
+		return gh, err
+	}
+
+	if date, ok := gr.Header["Date"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, gr.Header["Date"][0])
+		if err != nil {
+			return gh, err
+		}
+		gh.Date = t
+	}
+
+	return gh, nil
+}
+
 // ExtractMetadata is a function that takes a GetResult (of type *http.Response)
 // and returns the custom metatdata associated with the account.
 func (gr GetResult) ExtractMetadata() (map[string]string, error) {
@@ -27,8 +100,3 @@
 	}
 	return metadata, nil
 }
-
-// UpdateResult is returned from a call to the Update function.
-type UpdateResult struct {
-	gophercloud.HeaderResult
-}
diff --git a/openstack/objectstorage/v1/containers/fixtures.go b/openstack/objectstorage/v1/containers/fixtures.go
index 1c0a915..9c84bce 100644
--- a/openstack/objectstorage/v1/containers/fixtures.go
+++ b/openstack/objectstorage/v1/containers/fixtures.go
@@ -94,6 +94,7 @@
 		th.TestHeader(t, r, "Accept", "application/json")
 
 		w.Header().Add("X-Container-Meta-Foo", "bar")
+		w.Header().Add("X-Trans-Id", "1234567")
 		w.WriteHeader(http.StatusNoContent)
 	})
 }
diff --git a/openstack/objectstorage/v1/containers/requests_test.go b/openstack/objectstorage/v1/containers/requests_test.go
index d0ce7f1..f650696 100644
--- a/openstack/objectstorage/v1/containers/requests_test.go
+++ b/openstack/objectstorage/v1/containers/requests_test.go
@@ -58,8 +58,10 @@
 
 	options := CreateOpts{ContentType: "application/json", Metadata: map[string]string{"foo": "bar"}}
 	res := Create(fake.ServiceClient(), "testContainer", options)
-	th.CheckNoErr(t, res.Err)
+	c, err := res.Extract()
+	th.CheckNoErr(t, err)
 	th.CheckEquals(t, "bar", res.Header["X-Container-Meta-Foo"][0])
+	th.CheckEquals(t, "1234567", c.TransID)
 }
 
 func TestDeleteContainer(t *testing.T) {
diff --git a/openstack/objectstorage/v1/containers/results.go b/openstack/objectstorage/v1/containers/results.go
index 74f3286..e682b8d 100644
--- a/openstack/objectstorage/v1/containers/results.go
+++ b/openstack/objectstorage/v1/containers/results.go
@@ -3,6 +3,7 @@
 import (
 	"fmt"
 	"strings"
+	"time"
 
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
@@ -96,11 +97,48 @@
 	}
 }
 
+// GetHeader represents the headers returned in the response from a Get request.
+type GetHeader struct {
+	AcceptRanges     string    `mapstructure:"Accept-Ranges"`
+	BytesUsed        int64     `mapstructure:"X-Account-Bytes-Used"`
+	ContentLength    int64     `mapstructure:"Content-Length"`
+	ContentType      string    `mapstructure:"Content-Type"`
+	Date             time.Time `mapstructure:"-"`
+	ObjectCount      int64     `mapstructure:"X-Container-Object-Count"`
+	Read             string    `mapstructure:"X-Container-Read"`
+	TransID          string    `mapstructure:"X-Trans-Id"`
+	VersionsLocation string    `mapstructure:"X-Versions-Location"`
+	Write            string    `mapstructure:"X-Container-Write"`
+}
+
 // GetResult represents the result of a get operation.
 type GetResult struct {
 	gophercloud.HeaderResult
 }
 
+// Extract will return a struct of headers returned from a call to Get. To obtain
+// a map of headers, call the ExtractHeader method on the GetResult.
+func (gr GetResult) Extract() (GetHeader, error) {
+	var gh GetHeader
+	if gr.Err != nil {
+		return gh, gr.Err
+	}
+
+	if err := gophercloud.DecodeHeader(gr.Header, &gh); err != nil {
+		return gh, err
+	}
+
+	if date, ok := gr.Header["Date"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, gr.Header["Date"][0])
+		if err != nil {
+			return gh, err
+		}
+		gh.Date = t
+	}
+
+	return gh, nil
+}
+
 // ExtractMetadata is a function that takes a GetResult (of type *http.Response)
 // and returns the custom metadata associated with the container.
 func (gr GetResult) ExtractMetadata() (map[string]string, error) {
@@ -117,6 +155,14 @@
 	return metadata, nil
 }
 
+// CreateHeader represents the headers returned in the response from a Create request.
+type CreateHeader struct {
+	ContentLength int64     `mapstructure:"Content-Length"`
+	ContentType   string    `mapstructure:"Content-Type"`
+	Date          time.Time `mapstructure:"-"`
+	TransID       string    `mapstructure:"X-Trans-Id"`
+}
+
 // CreateResult represents the result of a create operation. To extract the
 // the headers from the HTTP response, you can invoke the 'ExtractHeader'
 // method on the result struct.
@@ -124,6 +170,37 @@
 	gophercloud.HeaderResult
 }
 
+// Extract will return a struct of headers returned from a call to Create. To obtain
+// a map of headers, call the ExtractHeader method on the CreateResult.
+func (cr CreateResult) Extract() (CreateHeader, error) {
+	var ch CreateHeader
+	if cr.Err != nil {
+		return ch, cr.Err
+	}
+
+	if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil {
+		return ch, err
+	}
+
+	if date, ok := cr.Header["Date"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, cr.Header["Date"][0])
+		if err != nil {
+			return ch, err
+		}
+		ch.Date = t
+	}
+
+	return ch, nil
+}
+
+// UpdateHeader represents the headers returned in the response from a Update request.
+type UpdateHeader struct {
+	ContentLength int64     `mapstructure:"Content-Length"`
+	ContentType   string    `mapstructure:"Content-Type"`
+	Date          time.Time `mapstructure:"-"`
+	TransID       string    `mapstructure:"X-Trans-Id"`
+}
+
 // UpdateResult represents the result of an update operation. To extract the
 // the headers from the HTTP response, you can invoke the 'ExtractHeader'
 // method on the result struct.
@@ -131,9 +208,63 @@
 	gophercloud.HeaderResult
 }
 
+// Extract will return a struct of headers returned from a call to Update. To obtain
+// a map of headers, call the ExtractHeader method on the UpdateResult.
+func (ur UpdateResult) Extract() (UpdateHeader, error) {
+	var uh UpdateHeader
+	if ur.Err != nil {
+		return uh, ur.Err
+	}
+
+	if err := gophercloud.DecodeHeader(ur.Header, &uh); err != nil {
+		return uh, err
+	}
+
+	if date, ok := ur.Header["Date"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, ur.Header["Date"][0])
+		if err != nil {
+			return uh, err
+		}
+		uh.Date = t
+	}
+
+	return uh, nil
+}
+
+// DeleteHeader represents the headers returned in the response from a Delete request.
+type DeleteHeader struct {
+	ContentLength int64     `mapstructure:"Content-Length"`
+	ContentType   string    `mapstructure:"Content-Type"`
+	Date          time.Time `mapstructure:"-"`
+	TransID       string    `mapstructure:"X-Trans-Id"`
+}
+
 // DeleteResult represents the result of a delete operation. To extract the
 // the headers from the HTTP response, you can invoke the 'ExtractHeader'
 // method on the result struct.
 type DeleteResult struct {
 	gophercloud.HeaderResult
 }
+
+// Extract will return a struct of headers returned from a call to Delete. To obtain
+// a map of headers, call the ExtractHeader method on the DeleteResult.
+func (dr DeleteResult) Extract() (DeleteHeader, error) {
+	var dh DeleteHeader
+	if dr.Err != nil {
+		return dh, dr.Err
+	}
+
+	if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil {
+		return dh, err
+	}
+
+	if date, ok := dr.Header["Date"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, dr.Header["Date"][0])
+		if err != nil {
+			return dh, err
+		}
+		dh.Date = t
+	}
+
+	return dh, nil
+}
diff --git a/openstack/objectstorage/v1/objects/requests.go b/openstack/objectstorage/v1/objects/requests.go
index 0fdb041..def1798 100644
--- a/openstack/objectstorage/v1/objects/requests.go
+++ b/openstack/objectstorage/v1/objects/requests.go
@@ -1,12 +1,16 @@
 package objects
 
 import (
+	"crypto/hmac"
+	"crypto/sha1"
 	"fmt"
 	"io"
+	"strings"
 	"time"
 
 	"github.com/racker/perigee"
 	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts"
 	"github.com/rackspace/gophercloud/pagination"
 )
 
@@ -424,3 +428,51 @@
 	res.Err = err
 	return res
 }
+
+// HTTPMethod represents an HTTP method string (e.g. "GET").
+type HTTPMethod string
+
+var (
+	// GET represents an HTTP "GET" method.
+	GET HTTPMethod = "GET"
+	// POST represents an HTTP "POST" method.
+	POST HTTPMethod = "POST"
+)
+
+// CreateTempURLOpts are options for creating a temporary URL for an object.
+type CreateTempURLOpts struct {
+	// (REQUIRED) Method is the HTTP method to allow for users of the temp URL. Valid values
+	// are "GET" and "POST".
+	Method HTTPMethod
+	// (REQUIRED) TTL is the number of seconds the temp URL should be active.
+	TTL int
+	// (Optional) Split is the string on which to split the object URL. Since only
+	// the object path is used in the hash, the object URL needs to be parsed. If
+	// empty, the default OpenStack URL split point will be used ("/v1/").
+	Split string
+}
+
+// CreateTempURL is a function for creating a temporary URL for an object. It
+// allows users to have "GET" or "POST" access to a particular tenant's object
+// for a limited amount of time.
+func CreateTempURL(c *gophercloud.ServiceClient, containerName, objectName string, opts CreateTempURLOpts) (string, error) {
+	if opts.Split == "" {
+		opts.Split = "/v1/"
+	}
+	duration := time.Duration(opts.TTL) * time.Second
+	expiry := time.Now().Add(duration).Unix()
+	getHeader, err := accounts.Get(c, nil).Extract()
+	if err != nil {
+		return "", err
+	}
+	secretKey := []byte(getHeader.TempURLKey)
+	url := getURL(c, containerName, objectName)
+	splitPath := strings.Split(url, opts.Split)
+	baseURL, objectPath := splitPath[0], splitPath[1]
+	objectPath = opts.Split + objectPath
+	body := fmt.Sprintf("%s\n%d\n%s", opts.Method, expiry, objectPath)
+	hash := hmac.New(sha1.New, secretKey)
+	hash.Write([]byte(body))
+	hexsum := fmt.Sprintf("%x", hash.Sum(nil))
+	return fmt.Sprintf("%s%s?temp_url_sig=%s&temp_url_expires=%d", baseURL, objectPath, hexsum, expiry), nil
+}
diff --git a/openstack/objectstorage/v1/objects/results.go b/openstack/objectstorage/v1/objects/results.go
index b51b840..ecb2c54 100644
--- a/openstack/objectstorage/v1/objects/results.go
+++ b/openstack/objectstorage/v1/objects/results.go
@@ -4,7 +4,9 @@
 	"fmt"
 	"io"
 	"io/ioutil"
+	"strconv"
 	"strings"
+	"time"
 
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
@@ -108,12 +110,67 @@
 	}
 }
 
+// DownloadHeader represents the headers returned in the response from a Download request.
+type DownloadHeader struct {
+	AcceptRanges       string    `mapstructure:"Accept-Ranges"`
+	ContentDisposition string    `mapstructure:"Content-Disposition"`
+	ContentEncoding    string    `mapstructure:"Content-Encoding"`
+	ContentLength      int64     `mapstructure:"Content-Length"`
+	ContentType        string    `mapstructure:"Content-Type"`
+	Date               time.Time `mapstructure:"-"`
+	DeleteAt           time.Time `mapstructure:"-"`
+	ETag               string    `mapstructure:"Etag"`
+	LastModified       time.Time `mapstructure:"-"`
+	ObjectManifest     string    `mapstructure:"X-Object-Manifest"`
+	StaticLargeObject  bool      `mapstructure:"X-Static-Large-Object"`
+	TransID            string    `mapstructure:"X-Trans-Id"`
+}
+
 // DownloadResult is a *http.Response that is returned from a call to the Download function.
 type DownloadResult struct {
 	gophercloud.HeaderResult
 	Body io.ReadCloser
 }
 
+// Extract will return a struct of headers returned from a call to Download. To obtain
+// a map of headers, call the ExtractHeader method on the DownloadResult.
+func (dr DownloadResult) Extract() (DownloadHeader, error) {
+	var dh DownloadHeader
+	if dr.Err != nil {
+		return dh, dr.Err
+	}
+
+	if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil {
+		return dh, err
+	}
+
+	if date, ok := dr.Header["Date"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, date[0])
+		if err != nil {
+			return dh, err
+		}
+		dh.Date = t
+	}
+
+	if date, ok := dr.Header["Last-Modified"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, date[0])
+		if err != nil {
+			return dh, err
+		}
+		dh.LastModified = t
+	}
+
+	if date, ok := dr.Header["X-Delete-At"]; ok && len(date) > 0 {
+		unix, err := strconv.ParseInt(date[0], 10, 64)
+		if err != nil {
+			return dh, err
+		}
+		dh.DeleteAt = time.Unix(unix, 0)
+	}
+
+	return dh, nil
+}
+
 // ExtractContent is a function that takes a DownloadResult's io.Reader body
 // and reads all available data into a slice of bytes. Please be aware that due
 // the nature of io.Reader is forward-only - meaning that it can only be read
@@ -131,11 +188,65 @@
 	return body, nil
 }
 
+// GetHeader represents the headers returned in the response from a Get request.
+type GetHeader struct {
+	ContentDisposition string    `mapstructure:"Content-Disposition"`
+	ContentEncoding    string    `mapstructure:"Content-Encoding"`
+	ContentLength      int64     `mapstructure:"Content-Length"`
+	ContentType        string    `mapstructure:"Content-Type"`
+	Date               time.Time `mapstructure:"-"`
+	DeleteAt           time.Time `mapstructure:"-"`
+	ETag               string    `mapstructure:"Etag"`
+	LastModified       time.Time `mapstructure:"-"`
+	ObjectManifest     string    `mapstructure:"X-Object-Manifest"`
+	StaticLargeObject  bool      `mapstructure:"X-Static-Large-Object"`
+	TransID            string    `mapstructure:"X-Trans-Id"`
+}
+
 // GetResult is a *http.Response that is returned from a call to the Get function.
 type GetResult struct {
 	gophercloud.HeaderResult
 }
 
+// Extract will return a struct of headers returned from a call to Get. To obtain
+// a map of headers, call the ExtractHeader method on the GetResult.
+func (gr GetResult) Extract() (GetHeader, error) {
+	var gh GetHeader
+	if gr.Err != nil {
+		return gh, gr.Err
+	}
+
+	if err := gophercloud.DecodeHeader(gr.Header, &gh); err != nil {
+		return gh, err
+	}
+
+	if date, ok := gr.Header["Date"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, gr.Header["Date"][0])
+		if err != nil {
+			return gh, err
+		}
+		gh.Date = t
+	}
+
+	if date, ok := gr.Header["Last-Modified"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, gr.Header["Last-Modified"][0])
+		if err != nil {
+			return gh, err
+		}
+		gh.LastModified = t
+	}
+
+	if date, ok := gr.Header["X-Delete-At"]; ok && len(date) > 0 {
+		unix, err := strconv.ParseInt(date[0], 10, 64)
+		if err != nil {
+			return gh, err
+		}
+		gh.DeleteAt = time.Unix(unix, 0)
+	}
+
+	return gh, nil
+}
+
 // ExtractMetadata is a function that takes a GetResult (of type *http.Response)
 // and returns the custom metadata associated with the object.
 func (gr GetResult) ExtractMetadata() (map[string]string, error) {
@@ -152,22 +263,176 @@
 	return metadata, nil
 }
 
+// CreateHeader represents the headers returned in the response from a Create request.
+type CreateHeader struct {
+	ContentLength int64     `mapstructure:"Content-Length"`
+	ContentType   string    `mapstructure:"Content-Type"`
+	Date          time.Time `mapstructure:"-"`
+	ETag          string    `mapstructure:"Etag"`
+	LastModified  time.Time `mapstructure:"-"`
+	TransID       string    `mapstructure:"X-Trans-Id"`
+}
+
 // CreateResult represents the result of a create operation.
 type CreateResult struct {
 	gophercloud.HeaderResult
 }
 
+// Extract will return a struct of headers returned from a call to Create. To obtain
+// a map of headers, call the ExtractHeader method on the CreateResult.
+func (cr CreateResult) Extract() (CreateHeader, error) {
+	var ch CreateHeader
+	if cr.Err != nil {
+		return ch, cr.Err
+	}
+
+	if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil {
+		return ch, err
+	}
+
+	if date, ok := cr.Header["Date"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, cr.Header["Date"][0])
+		if err != nil {
+			return ch, err
+		}
+		ch.Date = t
+	}
+
+	if date, ok := cr.Header["Last-Modified"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, cr.Header["Last-Modified"][0])
+		if err != nil {
+			return ch, err
+		}
+		ch.LastModified = t
+	}
+
+	return ch, nil
+}
+
+// UpdateHeader represents the headers returned in the response from a Update request.
+type UpdateHeader struct {
+	ContentLength int64     `mapstructure:"Content-Length"`
+	ContentType   string    `mapstructure:"Content-Type"`
+	Date          time.Time `mapstructure:"-"`
+	TransID       string    `mapstructure:"X-Trans-Id"`
+}
+
 // UpdateResult represents the result of an update operation.
 type UpdateResult struct {
 	gophercloud.HeaderResult
 }
 
+// Extract will return a struct of headers returned from a call to Update. To obtain
+// a map of headers, call the ExtractHeader method on the UpdateResult.
+func (ur UpdateResult) Extract() (UpdateHeader, error) {
+	var uh UpdateHeader
+	if ur.Err != nil {
+		return uh, ur.Err
+	}
+
+	if err := gophercloud.DecodeHeader(ur.Header, &uh); err != nil {
+		return uh, err
+	}
+
+	if date, ok := ur.Header["Date"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, ur.Header["Date"][0])
+		if err != nil {
+			return uh, err
+		}
+		uh.Date = t
+	}
+
+	return uh, nil
+}
+
+// DeleteHeader represents the headers returned in the response from a Delete request.
+type DeleteHeader struct {
+	ContentLength int64     `mapstructure:"Content-Length"`
+	ContentType   string    `mapstructure:"Content-Type"`
+	Date          time.Time `mapstructure:"-"`
+	TransID       string    `mapstructure:"X-Trans-Id"`
+}
+
 // DeleteResult represents the result of a delete operation.
 type DeleteResult struct {
 	gophercloud.HeaderResult
 }
 
+// Extract will return a struct of headers returned from a call to Delete. To obtain
+// a map of headers, call the ExtractHeader method on the DeleteResult.
+func (dr DeleteResult) Extract() (DeleteHeader, error) {
+	var dh DeleteHeader
+	if dr.Err != nil {
+		return dh, dr.Err
+	}
+
+	if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil {
+		return dh, err
+	}
+
+	if date, ok := dr.Header["Date"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, dr.Header["Date"][0])
+		if err != nil {
+			return dh, err
+		}
+		dh.Date = t
+	}
+
+	return dh, nil
+}
+
+// CopyHeader represents the headers returned in the response from a Copy request.
+type CopyHeader struct {
+	ContentLength          int64     `mapstructure:"Content-Length"`
+	ContentType            string    `mapstructure:"Content-Type"`
+	CopiedFrom             string    `mapstructure:"X-Copied-From"`
+	CopiedFromLastModified time.Time `mapstructure:"-"`
+	Date                   time.Time `mapstructure:"-"`
+	ETag                   string    `mapstructure:"Etag"`
+	LastModified           time.Time `mapstructure:"-"`
+	TransID                string    `mapstructure:"X-Trans-Id"`
+}
+
 // CopyResult represents the result of a copy operation.
 type CopyResult struct {
 	gophercloud.HeaderResult
 }
+
+// Extract will return a struct of headers returned from a call to Copy. To obtain
+// a map of headers, call the ExtractHeader method on the CopyResult.
+func (cr CopyResult) Extract() (CopyHeader, error) {
+	var ch CopyHeader
+	if cr.Err != nil {
+		return ch, cr.Err
+	}
+
+	if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil {
+		return ch, err
+	}
+
+	if date, ok := cr.Header["Date"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, cr.Header["Date"][0])
+		if err != nil {
+			return ch, err
+		}
+		ch.Date = t
+	}
+
+	if date, ok := cr.Header["Last-Modified"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, cr.Header["Last-Modified"][0])
+		if err != nil {
+			return ch, err
+		}
+		ch.LastModified = t
+	}
+
+	if date, ok := cr.Header["X-Copied-From-Last-Modified"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, cr.Header["X-Copied-From-Last-Modified"][0])
+		if err != nil {
+			return ch, err
+		}
+		ch.CopiedFromLastModified = t
+	}
+
+	return ch, nil
+}
diff --git a/rackspace/objectstorage/v1/accounts/delegate_test.go b/rackspace/objectstorage/v1/accounts/delegate_test.go
index c568bd6..a1ea98b 100644
--- a/rackspace/objectstorage/v1/accounts/delegate_test.go
+++ b/rackspace/objectstorage/v1/accounts/delegate_test.go
@@ -8,20 +8,22 @@
 	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
-func TestGetAccounts(t *testing.T) {
+func TestUpdateAccounts(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
-	os.HandleGetAccountSuccessfully(t)
+
+	os.HandleUpdateAccountSuccessfully(t)
 
 	options := &UpdateOpts{Metadata: map[string]string{"gophercloud-test": "accounts"}}
 	res := Update(fake.ServiceClient(), options)
 	th.CheckNoErr(t, res.Err)
 }
 
-func TestUpdateAccounts(t *testing.T) {
+func TestGetAccounts(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
-	os.HandleUpdateAccountSuccessfully(t)
+
+	os.HandleGetAccountSuccessfully(t)
 
 	expected := map[string]string{"Foo": "bar"}
 	actual, err := Get(fake.ServiceClient()).ExtractMetadata()
diff --git a/rackspace/objectstorage/v1/cdncontainers/delegate.go b/rackspace/objectstorage/v1/cdncontainers/delegate.go
index d7eef20..89adb83 100644
--- a/rackspace/objectstorage/v1/cdncontainers/delegate.go
+++ b/rackspace/objectstorage/v1/cdncontainers/delegate.go
@@ -1,8 +1,6 @@
 package cdncontainers
 
 import (
-	"strconv"
-
 	"github.com/rackspace/gophercloud"
 	os "github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers"
 	"github.com/rackspace/gophercloud/pagination"
@@ -38,34 +36,3 @@
 func List(c *gophercloud.ServiceClient, opts os.ListOptsBuilder) pagination.Pager {
 	return os.List(c, opts)
 }
-
-// Get is a function that retrieves the metadata of a container. To extract just
-// the custom metadata, pass the GetResult response to the ExtractMetadata
-// function.
-func Get(c *gophercloud.ServiceClient, containerName string) os.GetResult {
-	return os.Get(c, containerName)
-}
-
-// UpdateOpts is a structure that holds parameters for updating, creating, or
-// deleting a container's metadata.
-type UpdateOpts struct {
-	CDNEnabled   bool `h:"X-Cdn-Enabled"`
-	LogRetention bool `h:"X-Log-Retention"`
-	TTL          int  `h:"X-Ttl"`
-}
-
-// ToContainerUpdateMap formats a CreateOpts into a map of headers.
-func (opts UpdateOpts) ToContainerUpdateMap() (map[string]string, error) {
-	h, err := gophercloud.BuildHeaders(opts)
-	if err != nil {
-		return nil, err
-	}
-	h["X-Cdn-Enabled"] = strconv.FormatBool(opts.CDNEnabled)
-	return h, nil
-}
-
-// Update is a function that creates, updates, or deletes a container's
-// metadata.
-func Update(c *gophercloud.ServiceClient, containerName string, opts os.UpdateOptsBuilder) os.UpdateResult {
-	return os.Update(c, containerName, opts)
-}
diff --git a/rackspace/objectstorage/v1/cdncontainers/requests.go b/rackspace/objectstorage/v1/cdncontainers/requests.go
index 0567833..05b1939 100644
--- a/rackspace/objectstorage/v1/cdncontainers/requests.go
+++ b/rackspace/objectstorage/v1/cdncontainers/requests.go
@@ -1,6 +1,8 @@
 package cdncontainers
 
 import (
+	"strconv"
+
 	"github.com/racker/perigee"
 	"github.com/rackspace/gophercloud"
 )
@@ -56,3 +58,100 @@
 	res.Err = err
 	return res
 }
+
+// Get is a function that retrieves the metadata of a container. To extract just
+// the custom metadata, pass the GetResult response to the ExtractMetadata
+// function.
+func Get(c *gophercloud.ServiceClient, containerName string) GetResult {
+	var res GetResult
+	resp, err := perigee.Request("HEAD", getURL(c, containerName), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		OkCodes:     []int{200, 204},
+	})
+	res.Header = resp.HttpResponse.Header
+	res.Err = err
+	return res
+}
+
+// State is the state of an option. It is a pointer to a boolean to enable checking for
+// a zero-value of nil instead of false, which is a valid option.
+type State *bool
+
+var (
+	iTrue  = true
+	iFalse = false
+
+	// Enabled is used for a true value for options in request bodies.
+	Enabled State = &iTrue
+	// Disabled is used for a false value for options in request bodies.
+	Disabled State = &iFalse
+)
+
+// UpdateOptsBuilder allows extensions to add additional parameters to the
+// Update request.
+type UpdateOptsBuilder interface {
+	ToContainerUpdateMap() (map[string]string, error)
+}
+
+// UpdateOpts is a structure that holds parameters for updating, creating, or
+// deleting a container's metadata.
+type UpdateOpts struct {
+	// Whether or not to CDN-enable a container. Prefer using XCDNEnabled, which
+	// is of type *bool underneath.
+	// TODO v2.0: change type to Enabled/Disabled (*bool)
+	CDNEnabled bool `h:"X-Cdn-Enabled"`
+	// Whether or not to enable log retention. Prefer using XLogRetention, which
+	// is of type *bool underneath.
+	// TODO v2.0: change type to Enabled/Disabled (*bool)
+	LogRetention  bool `h:"X-Log-Retention"`
+	XCDNEnabled   *bool
+	XLogRetention *bool
+	TTL           int `h:"X-Ttl"`
+}
+
+// ToContainerUpdateMap formats a CreateOpts into a map of headers.
+func (opts UpdateOpts) ToContainerUpdateMap() (map[string]string, error) {
+	h, err := gophercloud.BuildHeaders(opts)
+	if err != nil {
+		return nil, err
+	}
+	h["X-Cdn-Enabled"] = strconv.FormatBool(opts.CDNEnabled)
+	h["X-Log-Retention"] = strconv.FormatBool(opts.LogRetention)
+
+	if opts.XCDNEnabled != nil {
+		h["X-Cdn-Enabled"] = strconv.FormatBool(*opts.XCDNEnabled)
+	}
+
+	if opts.XLogRetention != nil {
+		h["X-Log-Retention"] = strconv.FormatBool(*opts.XLogRetention)
+	}
+
+	return h, nil
+}
+
+// Update is a function that creates, updates, or deletes a container's
+// metadata.
+func Update(c *gophercloud.ServiceClient, containerName string, opts UpdateOptsBuilder) UpdateResult {
+	var res UpdateResult
+	h := c.AuthenticatedHeaders()
+
+	if opts != nil {
+		headers, err := opts.ToContainerUpdateMap()
+		if err != nil {
+			res.Err = err
+			return res
+		}
+
+		for k, v := range headers {
+			h[k] = v
+		}
+	}
+
+	resp, err := perigee.Request("POST", updateURL(c, containerName), perigee.Options{
+		MoreHeaders: h,
+		OkCodes:     []int{202, 204},
+	})
+	res.Header = resp.HttpResponse.Header
+	res.Err = err
+	return res
+}
diff --git a/rackspace/objectstorage/v1/cdncontainers/results.go b/rackspace/objectstorage/v1/cdncontainers/results.go
index a5097ca..cb0ad30 100644
--- a/rackspace/objectstorage/v1/cdncontainers/results.go
+++ b/rackspace/objectstorage/v1/cdncontainers/results.go
@@ -1,8 +1,149 @@
 package cdncontainers
 
-import "github.com/rackspace/gophercloud"
+import (
+	"strings"
+	"time"
 
-// EnableResult represents the result of a get operation.
+	"github.com/rackspace/gophercloud"
+)
+
+// EnableHeader represents the headers returned in the response from an Enable request.
+type EnableHeader struct {
+	CDNIosURI       string    `mapstructure:"X-Cdn-Ios-Uri"`
+	CDNSslURI       string    `mapstructure:"X-Cdn-Ssl-Uri"`
+	CDNStreamingURI string    `mapstructure:"X-Cdn-Streaming-Uri"`
+	CDNUri          string    `mapstructure:"X-Cdn-Uri"`
+	ContentLength   int       `mapstructure:"Content-Length"`
+	ContentType     string    `mapstructure:"Content-Type"`
+	Date            time.Time `mapstructure:"-"`
+	TransID         string    `mapstructure:"X-Trans-Id"`
+}
+
+// EnableResult represents the result of an Enable operation.
 type EnableResult struct {
 	gophercloud.HeaderResult
 }
+
+// Extract will return extract an EnableHeader from the response to an Enable
+// request. To obtain a map of headers, call the ExtractHeader method on the EnableResult.
+func (er EnableResult) Extract() (EnableHeader, error) {
+	var eh EnableHeader
+	if er.Err != nil {
+		return eh, er.Err
+	}
+
+	if err := gophercloud.DecodeHeader(er.Header, &eh); err != nil {
+		return eh, err
+	}
+
+	if date, ok := er.Header["Date"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, er.Header["Date"][0])
+		if err != nil {
+			return eh, err
+		}
+		eh.Date = t
+	}
+
+	return eh, nil
+}
+
+// GetHeader represents the headers returned in the response from a Get request.
+type GetHeader struct {
+	CDNEnabled      bool      `mapstructure:"X-Cdn-Enabled"`
+	CDNIosURI       string    `mapstructure:"X-Cdn-Ios-Uri"`
+	CDNSslURI       string    `mapstructure:"X-Cdn-Ssl-Uri"`
+	CDNStreamingURI string    `mapstructure:"X-Cdn-Streaming-Uri"`
+	CDNUri          string    `mapstructure:"X-Cdn-Uri"`
+	ContentLength   int       `mapstructure:"Content-Length"`
+	ContentType     string    `mapstructure:"Content-Type"`
+	Date            time.Time `mapstructure:"-"`
+	LogRetention    bool      `mapstructure:"X-Log-Retention"`
+	TransID         string    `mapstructure:"X-Trans-Id"`
+	TTL             int       `mapstructure:"X-Ttl"`
+}
+
+// GetResult represents the result of a Get operation.
+type GetResult struct {
+	gophercloud.HeaderResult
+}
+
+// Extract will return a struct of headers returned from a call to Get. To obtain
+// a map of headers, call the ExtractHeader method on the GetResult.
+func (gr GetResult) Extract() (GetHeader, error) {
+	var gh GetHeader
+	if gr.Err != nil {
+		return gh, gr.Err
+	}
+
+	if err := gophercloud.DecodeHeader(gr.Header, &gh); err != nil {
+		return gh, err
+	}
+
+	if date, ok := gr.Header["Date"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, gr.Header["Date"][0])
+		if err != nil {
+			return gh, err
+		}
+		gh.Date = t
+	}
+
+	return gh, nil
+}
+
+// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
+// and returns the custom metadata associated with the container.
+func (gr GetResult) ExtractMetadata() (map[string]string, error) {
+	if gr.Err != nil {
+		return nil, gr.Err
+	}
+	metadata := make(map[string]string)
+	for k, v := range gr.Header {
+		if strings.HasPrefix(k, "X-Container-Meta-") {
+			key := strings.TrimPrefix(k, "X-Container-Meta-")
+			metadata[key] = v[0]
+		}
+	}
+	return metadata, nil
+}
+
+// UpdateHeader represents the headers returned in the response from a Update request.
+type UpdateHeader struct {
+	CDNIosURI       string    `mapstructure:"X-Cdn-Ios-Uri"`
+	CDNSslURI       string    `mapstructure:"X-Cdn-Ssl-Uri"`
+	CDNStreamingURI string    `mapstructure:"X-Cdn-Streaming-Uri"`
+	CDNUri          string    `mapstructure:"X-Cdn-Uri"`
+	ContentLength   int       `mapstructure:"Content-Length"`
+	ContentType     string    `mapstructure:"Content-Type"`
+	Date            time.Time `mapstructure:"-"`
+	TransID         string    `mapstructure:"X-Trans-Id"`
+}
+
+// UpdateResult represents the result of an update operation. To extract the
+// the headers from the HTTP response, you can invoke the 'ExtractHeader'
+// method on the result struct.
+type UpdateResult struct {
+	gophercloud.HeaderResult
+}
+
+// Extract will return a struct of headers returned from a call to Update. To obtain
+// a map of headers, call the ExtractHeader method on the UpdateResult.
+func (ur UpdateResult) Extract() (UpdateHeader, error) {
+	var uh UpdateHeader
+	if ur.Err != nil {
+		return uh, ur.Err
+	}
+
+	if err := gophercloud.DecodeHeader(ur.Header, &uh); err != nil {
+		return uh, err
+	}
+
+	if date, ok := ur.Header["Date"]; ok && len(date) > 0 {
+		t, err := time.Parse(time.RFC1123, ur.Header["Date"][0])
+		if err != nil {
+			return uh, err
+		}
+		uh.Date = t
+	}
+
+	return uh, nil
+}
diff --git a/rackspace/objectstorage/v1/cdncontainers/urls.go b/rackspace/objectstorage/v1/cdncontainers/urls.go
index 80653f2..541249a 100644
--- a/rackspace/objectstorage/v1/cdncontainers/urls.go
+++ b/rackspace/objectstorage/v1/cdncontainers/urls.go
@@ -5,3 +5,11 @@
 func enableURL(c *gophercloud.ServiceClient, containerName string) string {
 	return c.ServiceURL(containerName)
 }
+
+func getURL(c *gophercloud.ServiceClient, container string) string {
+	return c.ServiceURL(container)
+}
+
+func updateURL(c *gophercloud.ServiceClient, container string) string {
+	return getURL(c, container)
+}
diff --git a/rackspace/objectstorage/v1/objects/delegate.go b/rackspace/objectstorage/v1/objects/delegate.go
index bd4a4f0..028d66a 100644
--- a/rackspace/objectstorage/v1/objects/delegate.go
+++ b/rackspace/objectstorage/v1/objects/delegate.go
@@ -88,3 +88,7 @@
 func Update(c *gophercloud.ServiceClient, containerName, objectName string, opts os.UpdateOptsBuilder) os.UpdateResult {
 	return os.Update(c, containerName, objectName, opts)
 }
+
+func CreateTempURL(c *gophercloud.ServiceClient, containerName, objectName string, opts os.CreateTempURLOpts) (string, error) {
+	return os.CreateTempURL(c, containerName, objectName, opts)
+}
diff --git a/results.go b/results.go
index 3fd5029..7c86ce4 100644
--- a/results.go
+++ b/results.go
@@ -3,6 +3,9 @@
 import (
 	"encoding/json"
 	"net/http"
+	"reflect"
+
+	"github.com/mitchellh/mapstructure"
 )
 
 /*
@@ -82,6 +85,31 @@
 	return hr.Header, hr.Err
 }
 
+// DecodeHeader is a function that decodes a header (usually of type map[string]interface{}) to
+// another type (usually a struct). This function is used by the objectstorage package to give
+// users access to response headers without having to query a map. A DecodeHookFunction is used,
+// because OpenStack-based clients return header values as arrays (Go slices).
+func DecodeHeader(from, to interface{}) error {
+	config := &mapstructure.DecoderConfig{
+		DecodeHook: func(from, to reflect.Kind, data interface{}) (interface{}, error) {
+			if from == reflect.Slice {
+				return data.([]string)[0], nil
+			}
+			return data, nil
+		},
+		Result:           to,
+		WeaklyTypedInput: true,
+	}
+	decoder, err := mapstructure.NewDecoder(config)
+	if err != nil {
+		return err
+	}
+	if err := decoder.Decode(from); err != nil {
+		return err
+	}
+	return nil
+}
+
 // RFC3339Milli describes a common time format used by some API responses.
 const RFC3339Milli = "2006-01-02T15:04:05.999999Z"