Files
go-omni/storage/read.go

73 lines
2.4 KiB
Go

package storage
import (
"context"
"errors"
"mime"
"net/http"
"strings"
"time"
"unicode"
"unicode/utf8"
)
const (
DispositionAttachment = "attachment"
DispositionInline = "inline"
)
// ReadOptions controls the response to one signed GET. Empty Disposition means
// attachment; empty ContentType preserves object metadata. FileName is a display
// name, not an object key or filesystem path. All options are signed.
type ReadOptions struct {
Disposition string
FileName string
ContentType string
}
// ReadLink grants GET access without additional request headers. ExpiresAt is
// the expiry encoded in the signature, at second precision. Provider credentials
// may expire earlier. Do not log the URL. Issuing another link does not revoke it.
type ReadLink struct {
URL string `json:"url"`
ExpiresAt time.Time `json:"expiresAt"`
}
// ReadPresigner is optional; existing Store and Presigner implementations do not
// need to change. Cloud GET signatures do not also authorize HEAD requests.
type ReadPresigner interface {
PresignGetWithOptions(context.Context, string, time.Duration, ReadOptions) (ReadLink, error)
}
// ResponseHeaders validates options and returns a fresh set of response headers
// for provider adapters to include BEFORE signing, requesting private, no-store.
func (o ReadOptions) ResponseHeaders() (http.Header, error) {
disposition := o.Disposition
if disposition == "" {
disposition = DispositionAttachment
}
if disposition != DispositionAttachment && disposition != DispositionInline {
return nil, errors.New("storage: disposition must be inline or attachment")
}
if len(o.FileName) > 1024 || !utf8.ValidString(o.FileName) || strings.IndexFunc(o.FileName, unicode.IsControl) >= 0 || strings.ContainsAny(o.FileName, `/\`) || o.FileName == "." || o.FileName == ".." {
return nil, errors.New("storage: invalid display filename")
}
params := map[string]string{}
if o.FileName != "" {
params["filename"] = o.FileName
}
h := http.Header{"Content-Disposition": {mime.FormatMediaType(disposition, params)}, "Cache-Control": {"private, no-store"}}
if o.ContentType != "" {
if !utf8.ValidString(o.ContentType) || strings.IndexFunc(o.ContentType, unicode.IsControl) >= 0 {
return nil, ErrContentType
}
kind, err := MediaType(o.ContentType)
if err != nil {
return nil, err
}
_, params, _ := mime.ParseMediaType(o.ContentType)
h.Set("Content-Type", mime.FormatMediaType(kind, params))
}
return h, nil
}