Add --port

This commit is contained in:
2026-09-12 23:48:03 +02:00
parent a180fe4b52
commit da36645aaf
14 changed files with 677 additions and 40 deletions
+100
View File
@@ -0,0 +1,100 @@
package server
import (
"net"
"net/http"
"strings"
"time"
)
// tokenCookie remembers a caller's token so it does not have to be pasted for
// every upload.
//
// It is HttpOnly, so a script on this origin cannot read it back — which is the
// reason to prefer it over localStorage, where any injected script could
// exfiltrate the credential. The page never needs to see the value: the server
// resolves it and renders who the caller is.
const tokenCookie = "send_token"
// rememberFor is how long a remembered token survives. Tokens are revoked by
// deleting them from the token file, so a long window costs nothing.
const rememberFor = 365 * 24 * time.Hour
// cookieCredential returns the remembered token, if any.
func cookieCredential(r *http.Request) string {
c, err := r.Cookie(tokenCookie)
if err != nil {
return ""
}
return strings.TrimSpace(c.Value)
}
// credential resolves the caller's token from an explicit header first, then
// from the remembered cookie. Upload additionally accepts a form field, which
// takes precedence over both.
func credential(r *http.Request) string {
if t := bearer(r); t != "" {
return t
}
return cookieCredential(r)
}
// remember stores the token in a cookie.
//
// SameSite=Strict is what makes accepting a cookie as a credential safe here:
// without it, any site could make the browser post an upload or a deletion with
// the cookie attached. Scoping the path to the mount point keeps the credential
// out of requests to the rest of the host when running under a subdirectory.
func (s *Server) remember(w http.ResponseWriter, r *http.Request, token string) {
http.SetCookie(w, &http.Cookie{
Name: tokenCookie,
Value: token,
Path: s.cfg.BasePath,
MaxAge: int(rememberFor.Seconds()),
HttpOnly: true,
Secure: s.isHTTPS(r),
SameSite: http.SameSiteStrictMode,
})
}
// forget clears a remembered token.
func (s *Server) forget(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{
Name: tokenCookie,
Value: "",
Path: s.cfg.BasePath,
MaxAge: -1,
HttpOnly: true,
Secure: s.isHTTPS(r),
SameSite: http.SameSiteStrictMode,
})
}
// isHTTPS decides whether the cookie may carry the Secure attribute. Setting it
// on a plain-HTTP development server would stop the browser storing the cookie
// at all, so it is only set when the connection is genuinely secure.
func (s *Server) isHTTPS(r *http.Request) bool {
if strings.HasPrefix(s.cfg.PublicURL, "https://") {
return true // the operator said so, and they are behind the proxy
}
if r.TLS != nil {
return true
}
// Believed only from a proxy that is trusted for forwarded headers at all.
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
if ip := net.ParseIP(host); ip != nil && s.cfg.TrustsProxy(ip) {
return r.Header.Get("X-Forwarded-Proto") == "https"
}
}
return false
}
// handleForget drops the remembered token and returns to the upload page.
func (s *Server) handleForget(w http.ResponseWriter, r *http.Request) {
s.forget(w, r)
if wantsJSON(r) {
writeJSON(w, http.StatusOK, map[string]string{"status": "forgotten"})
return
}
http.Redirect(w, r, s.cfg.BasePath, http.StatusSeeOther)
}