mirror of
https://github.com/jech/galene.git
synced 2024-12-23 07:45:46 +01:00
111 lines
2.5 KiB
Go
111 lines
2.5 KiB
Go
|
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
|
||
|
}
|