1
Fork 0
galene/webserver/precondition.go

111 lines
2.5 KiB
Go
Raw Normal View History

2024-04-09 14:56:10 +02:00
package webserver
import (
"net/http"
"strings"
)
// This is partly based on the Go standard library file net/http/fs.go.
// scanETag determines if a syntactically valid ETag is present at s. If so,
// the ETag and remaining text after consuming ETag is returned. Otherwise,
// it returns "", "".
func scanETag(s string) (etag string, remain string) {
s = strings.TrimLeft(s, " \t\n\r")
start := 0
if strings.HasPrefix(s, "W/") {
start = 2
}
if len(s[start:]) < 2 || s[start] != '"' {
return "", ""
}
// ETag is either W/"text" or "text".
// See RFC 7232 2.3.
for i := start + 1; i < len(s); i++ {
c := s[i]
switch {
// Character values allowed in ETags.
case c == 0x21 || c >= 0x23 && c <= 0x7E || c >= 0x80:
case c == '"':
return s[:i+1], s[i+1:]
default:
return "", ""
}
}
return "", ""
}
// etagMatch returns whether the given eTag matches an IM/INM header value.
// The empty string in etag is interpreted as a non-existent object.
func etagMatch(etag, header string) bool {
if header == "" {
return false
}
if header == etag {
return true
}
for {
header = strings.TrimLeft(header, " \t\n\r")
if len(header) == 0 {
break
}
if header[0] == ',' {
header = header[1:]
continue
}
if header[0] == '*' {
return etag != ""
}
e, remain := scanETag(header)
if e == "" {
break
}
if e == etag {
return true
}
header = remain
}
return false
}
func writeNotModified(w http.ResponseWriter) {
// RFC 7232 section 4.1:
// a sender SHOULD NOT generate representation metadata other than the
// above listed fields unless said metadata exists for the purpose of
// guiding cache updates (e.g., Last-Modified might be useful if the
// response does not have an ETag field).
h := w.Header()
delete(h, "Content-Type")
delete(h, "Content-Length")
delete(h, "Content-Encoding")
if h.Get("Etag") != "" {
delete(h, "Last-Modified")
}
w.WriteHeader(http.StatusNotModified)
}
// checkPreconditions evaluates request preconditions.
// It interprets an empty etag as a non-existent object.
func checkPreconditions(w http.ResponseWriter, r *http.Request, etag string) (done bool) {
// RFC 7232 section 6.
im := r.Header.Get("If-Match")
if im != "" && !etagMatch(etag, im) {
w.WriteHeader(http.StatusPreconditionFailed)
return true
}
inm := r.Header.Get("If-None-Match")
if inm != "" && etagMatch(etag, inm) {
if r.Method == "GET" || r.Method == "HEAD" {
writeNotModified(w)
return true
} else {
w.WriteHeader(http.StatusPreconditionFailed)
return true
}
}
return false
}