73 lines
2.4 KiB
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
|
|
}
|