aboutsummaryrefslogtreecommitdiff
path: root/object.go
diff options
context:
space:
mode:
authorStefan Majewsky <stefan.majewsky@sap.com>2022-10-28 16:06:40 +0200
committerStefan Majewsky <stefan.majewsky@sap.com>2022-10-28 16:06:40 +0200
commit90dd519a948d06738479c04d331f28dfab99315c (patch)
treed4a9914cb73be3dbe9438b012a08408d79bdb7c9 /object.go
parentfd6e57b6239655722884a49a86be0f051cc32bde (diff)
parent5cf9b60d2ded95d29827389a1a5901f1068d4337 (diff)
downloadgo-schwift-90dd519a948d06738479c04d331f28dfab99315c.tar.gz
Merge remote-tracking branch 'SuperSandro2000:sha2'
Diffstat (limited to 'object.go')
-rw-r--r--object.go278
1 files changed, 157 insertions, 121 deletions
diff --git a/object.go b/object.go
index 266415f..0322906 100644
--- a/object.go
+++ b/object.go
@@ -21,8 +21,9 @@ package schwift
import (
"bytes"
"crypto/hmac"
- "crypto/md5"
- "crypto/sha1"
+ "crypto/md5" //nolint:gosec // Etag uses md5
+ "crypto/sha1" //nolint:gosec // Used by swift
+ "crypto/sha256"
"encoding/hex"
"fmt"
"hash"
@@ -33,9 +34,9 @@ import (
"time"
)
-//Object represents a Swift object. Instances are usually obtained by
-//traversing downwards from a container with Container.Object() or
-//Container.Objects().
+// Object represents a Swift object. Instances are usually obtained by
+// traversing downwards from a container with Container.Object() or
+// Container.Objects().
type Object struct {
c *Container
name string
@@ -44,27 +45,27 @@ type Object struct {
symlinkHeaders *ObjectHeaders //from HEAD/GET with ?symlink=get
}
-//IsEqualTo returns true if both Object instances refer to the same object.
+// IsEqualTo returns true if both Object instances refer to the same object.
func (o *Object) IsEqualTo(other *Object) bool {
return other.name == o.name && other.c.IsEqualTo(o.c)
}
-//Object returns a handle to the object with the given name within this
-//container. This function does not issue any HTTP requests, and therefore cannot
-//ensure that the object exists. Use the Exists() function to check for the
-//object's existence.
+// Object returns a handle to the object with the given name within this
+// container. This function does not issue any HTTP requests, and therefore cannot
+// ensure that the object exists. Use the Exists() function to check for the
+// object's existence.
func (c *Container) Object(name string) *Object {
return &Object{c: c, name: name}
}
-//Container returns a handle to the container this object is stored in.
+// Container returns a handle to the container this object is stored in.
func (o *Object) Container() *Container {
return o.c
}
-//Name returns the object name. This does not parse the name in any way; if you
-//want only the basename portion of the object name, use package path from the
-//standard library in conjunction with this function. For example:
+// Name returns the object name. This does not parse the name in any way; if you
+// want only the basename portion of the object name, use package path from the
+// standard library in conjunction with this function. For example:
//
// obj := account.Container("docs").Object("2018-02-10/invoice.pdf")
// obj.Name() //returns "2018-02-10/invoice.pdf"
@@ -73,10 +74,10 @@ func (o *Object) Name() string {
return o.name
}
-//FullName returns the container name and object name joined together with a
-//slash. This identifier is used by Swift in several places (large object
-//manifests, symlink targets, etc.) to refer to an object within an account.
-//For example:
+// FullName returns the container name and object name joined together with a
+// slash. This identifier is used by Swift in several places (large object
+// manifests, symlink targets, etc.) to refer to an object within an account.
+// For example:
//
// obj := account.Container("docs").Object("2018-02-10/invoice.pdf")
// obj.Name() //returns "2018-02-10/invoice.pdf"
@@ -85,8 +86,8 @@ func (o *Object) FullName() string {
return o.c.name + "/" + o.name
}
-//Exists checks if this object exists, potentially by issuing a HEAD request
-//if no Headers() have been cached yet.
+// Exists checks if this object exists, potentially by issuing a HEAD request
+// if no Headers() have been cached yet.
func (o *Object) Exists() (bool, error) {
_, err := o.Headers()
if Is(err, http.StatusNotFound) {
@@ -97,16 +98,16 @@ func (o *Object) Exists() (bool, error) {
return true, nil
}
-//Headers returns the ObjectHeaders for this object. If the ObjectHeaders
-//has not been cached yet, a HEAD request is issued on the object.
+// Headers returns the ObjectHeaders for this object. If the ObjectHeaders
+// has not been cached yet, a HEAD request is issued on the object.
//
-//For symlinks, this operation returns the metadata for the target object. Use
-//Object.SymlinkHeaders() to obtain the metadata for the symlink instead.
+// For symlinks, this operation returns the metadata for the target object. Use
+// Object.SymlinkHeaders() to obtain the metadata for the symlink instead.
//
-//This operation fails with http.StatusNotFound if the object does not exist.
+// This operation fails with http.StatusNotFound if the object does not exist.
//
-//WARNING: This method is not thread-safe. Calling it concurrently on the same
-//object results in undefined behavior.
+// WARNING: This method is not thread-safe. Calling it concurrently on the same
+// object results in undefined behavior.
func (o *Object) Headers() (ObjectHeaders, error) {
if o.headers != nil {
return *o.headers, nil
@@ -128,7 +129,7 @@ func (o *Object) fetchHeaders(opts *RequestOptions) (*ObjectHeaders, error) {
Options: opts,
//since Openstack LOVES to be inconsistent with everything (incl. itself),
//this returns 200 instead of 204
- ExpectStatusCodes: []int{200},
+ ExpectStatusCodes: []int{http.StatusOK},
DrainResponseBody: true,
}.Do(o.c.a.backend)
if err != nil {
@@ -139,19 +140,19 @@ func (o *Object) fetchHeaders(opts *RequestOptions) (*ObjectHeaders, error) {
return &headers, headers.Validate()
}
-//Update updates the object's headers using a POST request. To add URL
-//parameters, pass a non-nil *RequestOptions.
+// Update updates the object's headers using a POST request. To add URL
+// parameters, pass a non-nil *RequestOptions.
//
-//This operation fails with http.StatusNotFound if the object does not exist.
+// This operation fails with http.StatusNotFound if the object does not exist.
//
-//A successful POST request implies Invalidate() since it may change metadata.
+// A successful POST request implies Invalidate() since it may change metadata.
func (o *Object) Update(headers ObjectHeaders, opts *RequestOptions) error {
_, err := Request{
Method: "POST",
ContainerName: o.c.name,
ObjectName: o.name,
Options: cloneRequestOptions(opts, headers.Headers),
- ExpectStatusCodes: []int{202},
+ ExpectStatusCodes: []int{http.StatusAccepted},
}.Do(o.c.a.backend)
if err == nil {
o.Invalidate()
@@ -159,17 +160,17 @@ func (o *Object) Update(headers ObjectHeaders, opts *RequestOptions) error {
return err
}
-//UploadOptions invokes advanced behavior in the Object.Upload() method.
+// UploadOptions invokes advanced behavior in the Object.Upload() method.
type UploadOptions struct {
//When overwriting a large object, delete its segments. This will cause
//Upload() to call into BulkDelete(), so a BulkError may be returned.
DeleteSegments bool
}
-//Upload creates the object using a PUT request.
+// Upload creates the object using a PUT request.
//
-//If you do not have an io.Reader, but you have a []byte or string instance
-//containing the object, wrap it in a *bytes.Reader instance like so:
+// If you do not have an io.Reader, but you have a []byte or string instance
+// containing the object, wrap it in a *bytes.Reader instance like so:
//
// var buffer []byte
// o.Upload(bytes.NewReader(buffer), opts)
@@ -178,29 +179,29 @@ type UploadOptions struct {
// var buffer string
// o.Upload(bytes.NewReader([]byte(buffer)), opts)
//
-//If you have neither an io.Reader nor a []byte or string, but you have a
-//function that generates the object's content into an io.Writer, use
-//UploadFromWriter instead.
+// If you have neither an io.Reader nor a []byte or string, but you have a
+// function that generates the object's content into an io.Writer, use
+// UploadFromWriter instead.
//
-//If the object is very large and you want to upload it in segments, use
-//LargeObject.Append() instead. See documentation on type LargeObject for
-//details.
+// If the object is very large and you want to upload it in segments, use
+// LargeObject.Append() instead. See documentation on type LargeObject for
+// details.
//
-//If content is a *bytes.Reader or a *bytes.Buffer instance, the Content-Length
-//and Etag request headers will be computed automatically. Otherwise, it is
-//highly recommended that the caller set these headers (if possible) to allow
-//the server to check the integrity of the uploaded file.
+// If content is a *bytes.Reader or a *bytes.Buffer instance, the Content-Length
+// and Etag request headers will be computed automatically. Otherwise, it is
+// highly recommended that the caller set these headers (if possible) to allow
+// the server to check the integrity of the uploaded file.
//
-//If Etag and/or Content-Length is supplied and the content does not match
-//these parameters, http.StatusUnprocessableEntity is returned. If Etag is not
-//supplied and cannot be computed in advance, Upload() will compute the Etag as
-//data is read from the io.Reader, and compare the result to the Etag returned
-//by Swift, returning ErrChecksumMismatch in case of mismatch. The object will
-//have been uploaded at that point, so you will usually want to Delete() it.
+// If Etag and/or Content-Length is supplied and the content does not match
+// these parameters, http.StatusUnprocessableEntity is returned. If Etag is not
+// supplied and cannot be computed in advance, Upload() will compute the Etag as
+// data is read from the io.Reader, and compare the result to the Etag returned
+// by Swift, returning ErrChecksumMismatch in case of mismatch. The object will
+// have been uploaded at that point, so you will usually want to Delete() it.
//
-//This function can be used regardless of whether the object exists or not.
+// This function can be used regardless of whether the object exists or not.
//
-//A successful PUT request implies Invalidate() since it may change metadata.
+// A successful PUT request implies Invalidate() since it may change metadata.
func (o *Object) Upload(content io.Reader, opts *UploadOptions, ropts *RequestOptions) error {
if opts == nil {
opts = &UploadOptions{}
@@ -223,11 +224,14 @@ func (o *Object) Upload(content io.Reader, opts *UploadOptions, ropts *RequestOp
var hasher hash.Hash
if !isManifestUpload {
- tryComputeEtag(content, hdr)
+ err := tryComputeEtag(content, hdr)
+ if err != nil {
+ return err
+ }
//could not compute Etag in advance -> need to check on the fly
if !hdr.Etag().Exists() {
- hasher = md5.New()
+ hasher = md5.New() //nolint:gosec // Etag uses md5
if content != nil {
content = io.TeeReader(content, hasher)
}
@@ -301,10 +305,11 @@ func tryComputeContentLength(content io.Reader) *uint64 {
return nil
}
-func tryComputeEtag(content io.Reader, headers ObjectHeaders) {
+//nolint:gosec // Etag uses md5
+func tryComputeEtag(content io.Reader, headers ObjectHeaders) error {
h := headers.Etag()
if h.Exists() {
- return
+ return nil
}
switch r := content.(type) {
case nil:
@@ -318,16 +323,24 @@ func tryComputeEtag(content io.Reader, headers ObjectHeaders) {
case io.ReadSeeker:
//bytes.Reader does not have such a method, but it is an io.Seeker, so we
//can read the entire thing and then seek back to where we started
- hash := md5.New()
- n, _ := io.Copy(hash, r)
- r.Seek(-n, io.SeekCurrent)
- h.Set(hex.EncodeToString(hash.Sum(nil)))
+ md5Hash := md5.New()
+ n, err := io.Copy(md5Hash, r)
+ if err != nil {
+ return err
+ }
+ _, err = r.Seek(-n, io.SeekCurrent)
+ if err != nil {
+ return err
+ }
+ h.Set(hex.EncodeToString(md5Hash.Sum(nil)))
}
+
+ return nil
}
-//UploadFromWriter is a variant of Upload that can be used when the object's
-//content is generated by some function or package that takes an io.Writer
-//instead of supplying an io.Reader. For example:
+// UploadFromWriter is a variant of Upload that can be used when the object's
+// content is generated by some function or package that takes an io.Writer
+// instead of supplying an io.Reader. For example:
//
// func greeting(target io.Writer, name string) error {
// _, err := fmt.Fprintf(target, "Hello %s!\n", name)
@@ -343,7 +356,7 @@ func tryComputeEtag(content io.Reader, headers ObjectHeaders) {
// return err
// })
//
-//If you do not need an io.Writer, always use Upload instead.
+// If you do not need an io.Writer, always use Upload instead.
func (o *Object) UploadFromWriter(opts *UploadOptions, ropts *RequestOptions, callback func(io.Writer) error) error {
reader, writer := io.Pipe()
errChan := make(chan error)
@@ -356,19 +369,19 @@ func (o *Object) UploadFromWriter(opts *UploadOptions, ropts *RequestOptions, ca
return <-errChan
}
-//DeleteOptions invokes advanced behavior in the Object.Delete() method.
+// DeleteOptions invokes advanced behavior in the Object.Delete() method.
type DeleteOptions struct {
//When deleting a large object, also delete its segments. This will cause
//Delete() to call into BulkDelete(), so a BulkError may be returned.
DeleteSegments bool
}
-//Delete deletes the object using a DELETE request. To add URL parameters,
-//pass a non-nil *RequestOptions.
+// Delete deletes the object using a DELETE request. To add URL parameters,
+// pass a non-nil *RequestOptions.
//
-//This operation fails with http.StatusNotFound if the object does not exist.
+// This operation fails with http.StatusNotFound if the object does not exist.
//
-//A successful DELETE request implies Invalidate().
+// A successful DELETE request implies Invalidate().
func (o *Object) Delete(opts *DeleteOptions, ropts *RequestOptions) error {
if opts == nil {
opts = &DeleteOptions{}
@@ -400,7 +413,7 @@ func (o *Object) Delete(opts *DeleteOptions, ropts *RequestOptions) error {
ContainerName: o.c.name,
ObjectName: o.name,
Options: ropts,
- ExpectStatusCodes: []int{204},
+ ExpectStatusCodes: []int{http.StatusNoContent},
}.Do(o.c.a.backend)
if err == nil {
o.Invalidate()
@@ -408,20 +421,20 @@ func (o *Object) Delete(opts *DeleteOptions, ropts *RequestOptions) error {
return err
}
-//Invalidate clears the internal cache of this Object instance. The next call
-//to Headers() on this instance will issue a HEAD request on the object.
+// Invalidate clears the internal cache of this Object instance. The next call
+// to Headers() on this instance will issue a HEAD request on the object.
//
-//WARNING: This method is not thread-safe. Calling it concurrently on the same
-//object results in undefined behavior.
+// WARNING: This method is not thread-safe. Calling it concurrently on the same
+// object results in undefined behavior.
func (o *Object) Invalidate() {
o.headers = nil
o.symlinkHeaders = nil
}
-//Download retrieves the object's contents using a GET request. This returns a
-//helper object which allows you to select whether you want an io.ReadCloser
-//for reading the object contents progressively, or whether you want the object
-//contents collected into a byte slice or string.
+// Download retrieves the object's contents using a GET request. This returns a
+// helper object which allows you to select whether you want an io.ReadCloser
+// for reading the object contents progressively, or whether you want the object
+// contents collected into a byte slice or string.
//
// reader, err := object.Download(nil).AsReadCloser()
//
@@ -429,17 +442,17 @@ func (o *Object) Invalidate() {
//
// str, err := object.Download(nil).AsString()
//
-//See documentation on type DownloadedObject for details.
+// See documentation on type DownloadedObject for details.
//
-//WARNING: This method is not thread-safe. Calling it concurrently on the same
-//object results in undefined behavior.
+// WARNING: This method is not thread-safe. Calling it concurrently on the same
+// object results in undefined behavior.
func (o *Object) Download(opts *RequestOptions) DownloadedObject {
resp, err := Request{
Method: "GET",
ContainerName: o.c.name,
ObjectName: o.name,
Options: opts,
- ExpectStatusCodes: []int{200},
+ ExpectStatusCodes: []int{http.StatusOK},
}.Do(o.c.a.backend)
var body io.ReadCloser
if err == nil {
@@ -457,7 +470,7 @@ func (o *Object) Download(opts *RequestOptions) DownloadedObject {
return DownloadedObject{body, err}
}
-//CopyOptions invokes advanced behavior in the Object.Copy() method.
+// CopyOptions invokes advanced behavior in the Object.Copy() method.
type CopyOptions struct {
//Copy only the object's content, not its metadata. New metadata can always
//be supplied in the RequestOptions argument of Object.CopyTo().
@@ -466,10 +479,10 @@ type CopyOptions struct {
ShallowCopySymlinks bool
}
-//CopyTo copies the object on the server side using a COPY request.
+// CopyTo copies the object on the server side using a COPY request.
//
-//A successful COPY implies target.Invalidate() since it may change the
-//target's metadata.
+// A successful COPY implies target.Invalidate() since it may change the
+// target's metadata.
func (o *Object) CopyTo(target *Object, opts *CopyOptions, ropts *RequestOptions) error {
ropts = cloneRequestOptions(ropts, nil)
ropts.Headers.Set("Destination", target.FullName())
@@ -490,7 +503,7 @@ func (o *Object) CopyTo(target *Object, opts *CopyOptions, ropts *RequestOptions
ContainerName: o.c.name,
ObjectName: o.name,
Options: ropts,
- ExpectStatusCodes: []int{201},
+ ExpectStatusCodes: []int{http.StatusCreated},
DrainResponseBody: true,
}.Do(o.c.a.backend)
if err == nil {
@@ -499,19 +512,19 @@ func (o *Object) CopyTo(target *Object, opts *CopyOptions, ropts *RequestOptions
return err
}
-//SymlinkOptions invokes advanced behavior in the Object.SymlinkTo() method.
+// SymlinkOptions invokes advanced behavior in the Object.SymlinkTo() method.
type SymlinkOptions struct {
//When overwriting a large object, delete its segments. This will cause
//SymlinkTo() to call into BulkDelete(), so a BulkError may be returned.
DeleteSegments bool
}
-//SymlinkTo creates the object as a symbolic link to another object using a PUT
-//request. Like Object.Upload(), this method works regardless of whether the
-//object already exists or not. Existing object contents will be overwritten by
-//this operation.
+// SymlinkTo creates the object as a symbolic link to another object using a PUT
+// request. Like Object.Upload(), this method works regardless of whether the
+// object already exists or not. Existing object contents will be overwritten by
+// this operation.
//
-//A successful PUT request implies Invalidate() since it may change metadata.
+// A successful PUT request implies Invalidate() since it may change metadata.
func (o *Object) SymlinkTo(target *Object, opts *SymlinkOptions, ropts *RequestOptions) error {
ropts = cloneRequestOptions(ropts, nil)
ropts.Headers.Set("X-Symlink-Target", target.FullName())
@@ -534,24 +547,24 @@ func (o *Object) SymlinkTo(target *Object, opts *SymlinkOptions, ropts *RequestO
return o.Upload(nil, uopts, ropts)
}
-//SymlinkHeaders is similar to Headers, but if the object is a symlink, it
-//returns the metadata of the symlink rather than the metadata of the target.
-//It also returns a reference to the target object.
+// SymlinkHeaders is similar to Headers, but if the object is a symlink, it
+// returns the metadata of the symlink rather than the metadata of the target.
+// It also returns a reference to the target object.
//
-//If this object is not a symlink, Object.SymlinkHeaders() returns the same
-//ObjectHeaders as Object.Headers(), and a nil target object.
+// If this object is not a symlink, Object.SymlinkHeaders() returns the same
+// ObjectHeaders as Object.Headers(), and a nil target object.
//
-//In a nutshell, if Object.Headers() is like os.Stat(), then
-//Object.SymlinkHeaders() is like os.Lstat().
+// In a nutshell, if Object.Headers() is like os.Stat(), then
+// Object.SymlinkHeaders() is like os.Lstat().
//
-//If you do not know whether a given object is a symlink or not, it's a good
-//idea to call Object.SymlinkHeaders() first: If the object turns out not to be
-//a symlink, the cache for Object.Headers() has already been populated.
+// If you do not know whether a given object is a symlink or not, it's a good
+// idea to call Object.SymlinkHeaders() first: If the object turns out not to be
+// a symlink, the cache for Object.Headers() has already been populated.
//
-//This operation fails with http.StatusNotFound if the object does not exist.
+// This operation fails with http.StatusNotFound if the object does not exist.
//
-//WARNING: This method is not thread-safe. Calling it concurrently on the same
-//object results in undefined behavior.
+// WARNING: This method is not thread-safe. Calling it concurrently on the same
+// object results in undefined behavior.
func (o *Object) SymlinkHeaders() (headers ObjectHeaders, target *Object, err error) {
if o.symlinkHeaders == nil {
o.symlinkHeaders, err = o.fetchHeaders(&RequestOptions{
@@ -587,9 +600,9 @@ func (o *Object) SymlinkHeaders() (headers ObjectHeaders, target *Object, err er
return *o.symlinkHeaders, target, nil
}
-//URL returns the canonical URL for the object on the server. This is
-//particularly useful when the ReadACL on the account or container is set to
-//allow anonymous read access.
+// URL returns the canonical URL for the object on the server. This is
+// particularly useful when the ReadACL on the account or container is set to
+// allow anonymous read access.
func (o *Object) URL() (string, error) {
return Request{
ContainerName: o.c.name,
@@ -597,14 +610,24 @@ func (o *Object) URL() (string, error) {
}.URL(o.c.a.backend, nil)
}
-//TempURL is like Object.URL, but includes a token with a limited lifetime (as
-//specified by the `expires` argument) that permits anonymous access to this
-//object using the given HTTP method. This works only when the tempurl
-//middleware is set up on the server, and if the given `key` matches one of the
-//tempurl keys for this object's container or account.
+// Returns true if string is contained in slice
+func contains(s []string, e string) bool {
+ for _, a := range s {
+ if a == e {
+ return true
+ }
+ }
+ return false
+}
+
+// TempURL is like Object.URL, but includes a token with a limited lifetime (as
+// specified by the `expires` argument) that permits anonymous access to this
+// object using the given HTTP method. This works only when the tempurl
+// middleware is set up on the server, and if the given `key` matches one of the
+// tempurl keys for this object's container or account.
//
-//For example, if the ReadACL both on the account and container do not permit
-//anonymous read access (which is the default behavior):
+// For example, if the ReadACL both on the account and container do not permit
+// anonymous read access (which is the default behavior):
//
// var o *schwift.Object
// ...
@@ -623,7 +646,6 @@ func (o *Object) URL() (string, error) {
// url := o.TempURL(key, "GET", time.Now().Add(10 * time.Minute))
// resp, err := http.Get(url)
// //This time, resp.StatusCode == 200 because the URL includes a token.
-//
func (o *Object) TempURL(key, method string, expires time.Time) (string, error) {
urlStr, err := o.URL()
if err != nil {
@@ -634,8 +656,22 @@ func (o *Object) TempURL(key, method string, expires time.Time) (string, error)
return "", err
}
+ capabilities, err := o.c.a.Capabilities()
+ if err != nil {
+ return "", err
+ }
+ allowedDigest := capabilities.TempURL.AllowedDigests
+
+ var mac hash.Hash
+ if contains(allowedDigest, "sha256") {
+ mac = hmac.New(sha256.New, []byte(key))
+ } else if contains(allowedDigest, "sha1") {
+ mac = hmac.New(sha1.New, []byte(key))
+ } else {
+ return "", fmt.Errorf("schwift supports sha1 and sha256 digests but the Swift server only supports: %s", strings.Join(allowedDigest, ", "))
+ }
+
payload := fmt.Sprintf("%s\n%d\n%s", method, expires.Unix(), u.Path)
- mac := hmac.New(sha1.New, []byte(key))
mac.Write([]byte(payload))
signature := hex.EncodeToString(mac.Sum(nil))