fup: implement deleting expired files on request
This is a very basic implementation of gating that we only return files which haven't expired. If the file has expired, then we just delete it.
This commit is contained in:
parent
0e69199569
commit
7bac0aee74
2 changed files with 75 additions and 2 deletions
|
@ -11,6 +11,7 @@ import (
|
|||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/google/safehtml"
|
||||
|
@ -34,6 +35,9 @@ type Config struct {
|
|||
|
||||
// If set, redirects to a signed URL if possible instead of serving directly.
|
||||
RedirectToBlobstore bool
|
||||
|
||||
// RedirectExpiry sets the maximum lifetime of a signed URL, if RedirectToBlobstore is in use and the backend supports signed URLs.
|
||||
// Note that if a file has an expiry then the signed URL's validity will be capped at the expiry of the underlying file.
|
||||
RedirectExpiry time.Duration // Defaults to 5 minutes.
|
||||
|
||||
// Set one of these, but not both.
|
||||
|
@ -99,10 +103,45 @@ func (a *Application) rawDownload(rw http.ResponseWriter, r *http.Request) {
|
|||
// OK
|
||||
}
|
||||
|
||||
var exp time.Time
|
||||
if expStr, ok := attrs.Metadata["expires-at"]; ok {
|
||||
// Check for expiry.
|
||||
expSec, err := strconv.ParseInt(expStr, 10, 64)
|
||||
if err != nil {
|
||||
log.Printf("parsing expiration %q for %q: %v", expStr, filename, err)
|
||||
a.internalError(rw, r)
|
||||
return
|
||||
}
|
||||
|
||||
exp = time.Unix(expSec, 0)
|
||||
if exp.Before(time.Now()) {
|
||||
// This file has expired. Delete it from the backing storage.
|
||||
err := a.storageBackend.Delete(ctx, filename)
|
||||
switch gcerrors.Code(err) {
|
||||
case gcerrors.NotFound:
|
||||
// Something probably deleted it before we could get there.
|
||||
case gcerrors.OK:
|
||||
// This was fine.
|
||||
default:
|
||||
log.Printf("deleting expired file %q: %v", filename, err)
|
||||
}
|
||||
a.notFound(rw, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Do things with attributes?
|
||||
if a.redirectToBlobstore {
|
||||
redirectExpiry := a.redirectExpiry
|
||||
if !exp.IsZero() {
|
||||
// Cap the redirect lifetime to the remaining lifetime of the object.
|
||||
remainingLifetime := exp.Sub(time.Now())
|
||||
if remainingLifetime < redirectExpiry {
|
||||
redirectExpiry = remainingLifetime
|
||||
}
|
||||
}
|
||||
u, err := a.storageBackend.SignedURL(ctx, filename, &blob.SignedURLOptions{
|
||||
Expiry: a.redirectExpiry,
|
||||
Expiry: redirectExpiry,
|
||||
})
|
||||
switch gcerrors.Code(err) {
|
||||
case gcerrors.NotFound:
|
||||
|
|
|
@ -97,6 +97,14 @@ func TestRetrieve(t *testing.T) {
|
|||
if err := tc.backend.WriteAll(ctx, "hello.txt", []byte("hello world\n"), nil); err != nil {
|
||||
t.Fatalf("WriteAll: %v", err)
|
||||
}
|
||||
if err := tc.backend.WriteAll(ctx, "expired.txt", []byte("hello world\n"), &blob.WriterOptions{
|
||||
Metadata: map[string]string{
|
||||
// This unix timestamp is very much in the past.
|
||||
"expires-at": "1000",
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("WriteAll: %v", err)
|
||||
}
|
||||
|
||||
a, err := fuphttp.New(ctx, &ccfg)
|
||||
if err != nil {
|
||||
|
@ -177,6 +185,32 @@ func TestRetrieve(t *testing.T) {
|
|||
t.Errorf("read data was %q; want %q", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("expired file", func(t *testing.T) {
|
||||
// Verify the file is there.
|
||||
if ok, err := tc.backend.Exists(ctx, "expired.txt"); err != nil {
|
||||
t.Fatalf("Exists(%q) before test: %v", "expired.txt", err)
|
||||
} else if !ok {
|
||||
t.Fatalf("Exists(%q) before test returned false", "expired.txt")
|
||||
}
|
||||
|
||||
resp, err := s.Client().Get(fmt.Sprintf("%s/raw/expired.txt", s.URL))
|
||||
if err != nil {
|
||||
t.Fatalf("Get: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Errorf("response status was %v; want %v", resp.StatusCode, http.StatusNotFound)
|
||||
}
|
||||
|
||||
// Verify the file is NOT there.
|
||||
if ok, err := tc.backend.Exists(ctx, "expired.txt"); err != nil {
|
||||
t.Fatalf("Exists(%q) after retrieving: %v", "expired.txt", err)
|
||||
} else if ok {
|
||||
t.Fatalf("Exists(%q) after retrieving returned true", "expired.txt")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue