diff options
Diffstat (limited to 'modules/caddyhttp/fileserver/staticfiles.go')
-rw-r--r-- | modules/caddyhttp/fileserver/staticfiles.go | 33 |
1 files changed, 23 insertions, 10 deletions
diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go index 8fba82a8e..433e121da 100644 --- a/modules/caddyhttp/fileserver/staticfiles.go +++ b/modules/caddyhttp/fileserver/staticfiles.go @@ -644,19 +644,32 @@ func (fsrv *FileServer) notFound(w http.ResponseWriter, r *http.Request, next ca return caddyhttp.Error(http.StatusNotFound, nil) } -// calculateEtag produces a strong etag by default, although, for -// efficiency reasons, it does not actually consume the contents -// of the file to make a hash of all the bytes. ¯\_(ツ)_/¯ -// Prefix the etag with "W/" to convert it into a weak etag. -// See: https://tools.ietf.org/html/rfc7232#section-2.3 +// calculateEtag computes an entity tag using a strong validator +// without consuming the contents of the file. It requires the +// file info contain the correct size and modification time. +// It strives to implement the semantics regarding ETags as defined +// by RFC 9110 section 8.8.3 and 8.8.1. See +// https://www.rfc-editor.org/rfc/rfc9110.html#section-8.8.3. +// +// As our implementation uses file modification timestamp and size, +// note the following from RFC 9110 section 8.8.1: "A representation's +// modification time, if defined with only one-second resolution, +// might be a weak validator if it is possible for the representation to +// be modified twice during a single second and retrieved between those +// modifications." The ext4 file system, which underpins the vast majority +// of Caddy deployments, stores mod times with millisecond precision, +// which we consider precise enough to qualify as a strong validator. func calculateEtag(d os.FileInfo) string { - mtime := d.ModTime().Unix() - if mtime == 0 || mtime == 1 { + mtime := d.ModTime() + if mtimeUnix := mtime.Unix(); mtimeUnix == 0 || mtimeUnix == 1 { return "" // not useful anyway; see issue #5548 } - t := strconv.FormatInt(mtime, 36) - s := strconv.FormatInt(d.Size(), 36) - return `"` + t + s + `"` + var sb strings.Builder + sb.WriteRune('"') + sb.WriteString(strconv.FormatInt(mtime.UnixNano(), 36)) + sb.WriteString(strconv.FormatInt(d.Size(), 36)) + sb.WriteRune('"') + return sb.String() } // Finds the first corresponding etag file for a given file in the file system and return its content |