Add --port
This commit is contained in:
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user