package s3 import ( "context" "errors" "net/url" "strconv" "time" "git.yuntuo.hk/k2safe/go-omni/storage" ) func (s *Store) PresignGetWithOptions(ctx context.Context, key string, ttl time.Duration, options storage.ReadOptions) (storage.ReadLink, error) { if err := storage.Check(ctx, key); err != nil { return storage.ReadLink{}, err } if ttl < time.Second || ttl > time.Hour { return storage.ReadLink{}, errors.New("storage/s3: TTL must be 1s..1h") } headers, err := options.ResponseHeaders() if err != nil { return storage.ReadLink{}, err } query := url.Values{"response-content-disposition": {headers.Get("Content-Disposition")}, "response-cache-control": {headers.Get("Cache-Control")}} if contentType := headers.Get("Content-Type"); contentType != "" { query.Set("response-content-type", contentType) } u, err := s.client.PresignedGetObject(ctx, s.bucket, key, ttl, query) if err != nil { return storage.ReadLink{}, err } // Derive the exact expiry from the SDK's signed URL, not a second clock read // around credential discovery or bucket-region lookup. q := u.Query() var expires time.Time if q.Get("X-Amz-Signature") != "" { start, e := time.Parse("20060102T150405Z", q.Get("X-Amz-Date")) seconds, e2 := strconv.ParseInt(q.Get("X-Amz-Expires"), 10, 64) if e != nil || e2 != nil || seconds < 1 || seconds > 3600 { return storage.ReadLink{}, errors.New("storage/s3: invalid signed expiry") } expires = start.Add(time.Duration(seconds) * time.Second) } else { seconds, e := strconv.ParseInt(q.Get("Expires"), 10, 64) if e != nil || q.Get("Signature") == "" { return storage.ReadLink{}, errors.New("storage/s3: missing signed expiry") } expires = time.Unix(seconds, 0).UTC() } return storage.ReadLink{URL: u.String(), ExpiresAt: expires}, nil } var _ storage.ReadPresigner = (*Store)(nil)