api/ui: Allow per-scratch TTL down to 1m
Format / gofmt (push) Successful in 13s
CI / Build (push) Successful in 22s
CI / Go Tests (push) Successful in 23s
Format / gofmt (pull_request) Successful in 12s
CI / Build (pull_request) Successful in 20s
CI / Go Tests (pull_request) Successful in 20s

#4
This commit is contained in:
s1d3sw1ped_bot
2026-09-01 21:16:36 +00:00
parent 1d301d29b6
commit de465d6818
6 changed files with 439 additions and 12 deletions
+37 -2
View File
@@ -106,6 +106,7 @@ func (s *Server) getUIConfig(w http.ResponseWriter, r *http.Request) {
"max_upload_size_bytes": s.cfg.Limits.MaxUploadSizeBytes,
"upload_allowed": s.isUploadAllowedForRequest(r),
"default_ttl": s.cfg.Limits.DefaultTTL,
"max_ttl": config.MaxScratchTTL.String(),
}
writeJSON(w, http.StatusOK, payload)
}
@@ -184,8 +185,9 @@ func (s *Server) createScratch(w http.ResponseWriter, r *http.Request) {
var reader io.Reader
originalName := ""
contentType := strings.TrimSpace(r.Header.Get("Content-Type"))
multipart := strings.HasPrefix(contentType, "multipart/form-data")
if strings.HasPrefix(contentType, "multipart/form-data") {
if multipart {
if err := r.ParseMultipartForm(multipartMaxMemory); err != nil {
s.writeCreateError(w, err)
return
@@ -228,7 +230,13 @@ func (s *Server) createScratch(w http.ResponseWriter, r *http.Request) {
}
}
meta, err := s.store.CreateWithOriginalName(reader, contentType, originalName, s.cfg.Limits.DefaultTTLDuration)
ttl, err := s.resolveTTL(r, multipart)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
meta, err := s.store.CreateWithOriginalName(reader, contentType, originalName, ttl)
if err != nil {
if strings.Contains(err.Error(), "request body too large") {
http.Error(w, s.maxUploadSizeError(), http.StatusRequestEntityTooLarge)
@@ -364,6 +372,33 @@ func (s *Server) rawScratch(w http.ResponseWriter, r *http.Request) {
http.ServeContent(w, r, meta.ID, meta.CreatedAt, bytes.NewReader(content))
}
// resolveTTL returns the per-scratch TTL for POST /api/scratch.
// Multipart uploads read form field "ttl"; raw-body uploads read query "ttl".
// Omitted or empty values use limits.default_ttl. Invalid or out-of-range
// values (below 1m, above MaxScratchTTL, or non-duration) return an error.
// Infinite TTL is not allowed.
func (s *Server) resolveTTL(r *http.Request, multipart bool) (time.Duration, error) {
raw := ""
if multipart {
raw = strings.TrimSpace(r.FormValue("ttl"))
} else {
raw = strings.TrimSpace(r.URL.Query().Get("ttl"))
}
if raw == "" {
return s.cfg.Limits.DefaultTTLDuration, nil
}
parsed, err := time.ParseDuration(raw)
if err != nil {
return 0, errInvalidScratchTTL
}
if parsed < config.MinScratchTTL || parsed > config.MaxScratchTTL {
return 0, errInvalidScratchTTL
}
return parsed, nil
}
var errInvalidScratchTTL = fmt.Errorf("ttl must be a Go duration between %s and %s", config.MinScratchTTL, config.MaxScratchTTL)
func (s *Server) writeCreateError(w http.ResponseWriter, err error) {
var maxErr *http.MaxBytesError
if errors.As(err, &maxErr) || strings.Contains(err.Error(), "request body too large") {