Merge branch 'master' into dependabot/go_modules/all-updates-7546fc046b

This commit is contained in:
Zen Dodd 2026-05-20 16:37:07 +10:00 committed by GitHub
commit 27248315fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 116 additions and 40 deletions

View file

@ -155,7 +155,7 @@ func (l *lexer) next() (bool, error) {
// want to keep.
if ch == '\n' {
if len(val) == 2 {
return false, fmt.Errorf("missing opening heredoc marker on line #%d; must contain only alpha-numeric characters, dashes and underscores; got empty string", l.line)
return false, fmt.Errorf("missing opening heredoc marker on line #%d; must contain only alphanumeric characters, dashes and underscores; got empty string", l.line)
}
// check if there's too many <
@ -165,7 +165,7 @@ func (l *lexer) next() (bool, error) {
heredocMarker = string(val[2:])
if !heredocMarkerRegexp.Match([]byte(heredocMarker)) {
return false, fmt.Errorf("heredoc marker on line #%d must contain only alpha-numeric characters, dashes and underscores; got '%s'", l.line, heredocMarker)
return false, fmt.Errorf("heredoc marker on line #%d must contain only alphanumeric characters, dashes and underscores; got '%s'", l.line, heredocMarker)
}
inHeredoc = true

View file

@ -424,7 +424,7 @@ EOF
{
input: []byte("not-a-heredoc <<\n"),
expectErr: true,
errorMessage: "missing opening heredoc marker on line #1; must contain only alpha-numeric characters, dashes and underscores; got empty string",
errorMessage: "missing opening heredoc marker on line #1; must contain only alphanumeric characters, dashes and underscores; got empty string",
},
{
input: []byte(`heredoc <<<EOF

View file

@ -683,7 +683,7 @@ func (p *parser) directive() error {
// openCurlyBrace expects the current token to be an
// opening curly brace. This acts like an assertion
// because it returns an error if the token is not
// a opening curly brace. It does NOT advance the token.
// an opening curly brace. It does NOT advance the token.
func (p *parser) openCurlyBrace() error {
if p.Val() != "{" {
if p.valLooksLikeGlobalOptionsAfterImportedSnippets() {

View file

@ -518,7 +518,7 @@ func (tc *Tester) AssertResponseCode(req *http.Request, expectedStatusCode int)
return resp
}
// AssertResponse request a URI and assert the status code and the body contains a string
// AssertResponse requests a URI and asserts the status code and body.
func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expectedBody string) (*http.Response, string) {
tc.t.Helper()
@ -541,7 +541,7 @@ func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expe
// Verb specific test functions
// AssertGetResponse GET a URI and expect a statusCode and body text
// AssertGetResponse requests a URI with GET and expects a status code and body text.
func (tc *Tester) AssertGetResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
tc.t.Helper()
@ -553,7 +553,7 @@ func (tc *Tester) AssertGetResponse(requestURI string, expectedStatusCode int, e
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
// AssertDeleteResponse request a URI and expect a statusCode and body text
// AssertDeleteResponse requests a URI with DELETE and expects a status code and body text.
func (tc *Tester) AssertDeleteResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
tc.t.Helper()
@ -565,7 +565,7 @@ func (tc *Tester) AssertDeleteResponse(requestURI string, expectedStatusCode int
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
// AssertPostResponseBody POST to a URI and assert the response code and body
// AssertPostResponseBody requests a URI with POST and asserts the response code and body.
func (tc *Tester) AssertPostResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
tc.t.Helper()
@ -580,7 +580,7 @@ func (tc *Tester) AssertPostResponseBody(requestURI string, requestHeaders []str
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
// AssertPutResponseBody PUT to a URI and assert the response code and body
// AssertPutResponseBody requests a URI with PUT and asserts the response code and body.
func (tc *Tester) AssertPutResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
tc.t.Helper()
@ -595,7 +595,7 @@ func (tc *Tester) AssertPutResponseBody(requestURI string, requestHeaders []stri
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
// AssertPatchResponseBody PATCH to a URI and assert the response code and body
// AssertPatchResponseBody requests a URI with PATCH and asserts the response code and body.
func (tc *Tester) AssertPatchResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
tc.t.Helper()

View file

@ -6,4 +6,4 @@ handle {
END!
}
----------
heredoc marker on line #4 must contain only alpha-numeric characters, dashes and underscores; got 'END!'
heredoc marker on line #4 must contain only alphanumeric characters, dashes and underscores; got 'END!'

View file

@ -159,7 +159,7 @@ func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server {
}
// We only accept HTTP/2!
if r.ProtoMajor != 2 {
t.Error("Not a HTTP/2 request, rejected!")
t.Error("Not an HTTP/2 request, rejected!")
w.WriteHeader(http.StatusInternalServerError)
return
}

View file

@ -566,7 +566,7 @@ argument of --directory. If the directory does not exist, it will be created.
// following format:
//
// - lowercase
// - alphanumeric and hyphen characters only
// - ASCII lowercase letters, digits and hyphens only
// - cannot start or end with a hyphen
// - hyphen cannot be adjacent to another hyphen
//

View file

@ -108,7 +108,7 @@ func (m *MatchExpression) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &m.Expr)
}
// otherwise, it's a full object, so unmarshal it,
// using an temp map to avoid infinite recursion
// using a temp map to avoid infinite recursion
var tmpJson map[string]any
err := json.Unmarshal(data, &tmpJson)
*m = MatchExpression{
@ -118,7 +118,7 @@ func (m *MatchExpression) UnmarshalJSON(data []byte) error {
return err
}
// Provision sets ups m.
// Provision sets up m.
func (m *MatchExpression) Provision(ctx caddy.Context) error {
m.log = ctx.Logger()
@ -319,7 +319,7 @@ func (cr celHTTPRequest) Value() any { return cr }
var pkixNameCELType = cel.ObjectType("pkix.Name", traits.ReceiverType)
// celPkixName wraps an pkix.Name with
// celPkixName wraps a pkix.Name with
// methods to satisfy the ref.Val interface.
type celPkixName struct{ *pkix.Name }

View file

@ -79,7 +79,7 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
wantResult: true,
},
{
name: "header matches an placeholder replaced during the header matcher (MatchHeader)",
name: "header matches a placeholder replaced during the header matcher (MatchHeader)",
expression: &MatchExpression{
Expr: `header({'Field': '\{http.request.uri.path}'})`,
},

View file

@ -162,7 +162,7 @@ func (enc *Encode) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyh
// to comply with RFC 9110 section 8.8.3(.3), we modify the Etag when encoding
// by appending a hyphen and the encoder name; the problem is, the client will
// send back that Etag in a If-None-Match header, but upstream handlers that set
// send back that Etag in an If-None-Match header, but upstream handlers that set
// the Etag in the first place don't know that we appended to their Etag! so here
// we have to strip our addition so the upstream handlers can still honor client
// caches without knowing about our changes...
@ -369,7 +369,7 @@ const sniffLen = 512
// ReadFrom will try to use sendfile to copy from the reader to the response writer.
// It's only used if the response writer implements io.ReaderFrom and the data can't be compressed.
// It's based on stdlin http1.1 response writer implementation.
// It's based on the standard library HTTP/1.1 response writer implementation.
// https://github.com/golang/go/blob/f4e3ec3dbe3b8e04a058d266adf8e048bab563f2/src/net/http/server.go#L586
func (rw *responseWriter) ReadFrom(r io.Reader) (int64, error) {
rf, ok := rw.ResponseWriter.(io.ReaderFrom)

View file

@ -29,6 +29,7 @@ import (
"runtime"
"strconv"
"strings"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
@ -123,7 +124,7 @@ type FileServer struct {
// put "hidden" in the list. To hide only ./hidden, put "./hidden" in the list.
//
// When possible, all paths are resolved to their absolute form before
// comparisons are made. For maximum clarity and explictness, use complete,
// comparisons are made. For maximum clarity and explicitness, use complete,
// absolute paths; or, for greater portability, use relative paths instead.
//
// Note that hide comparisons are case-sensitive. On case-insensitive
@ -579,7 +580,17 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
// that errors generated by ServeContent are written immediately
// to the response, so we cannot handle them (but errors there
// are rare)
http.ServeContent(w, r, info.Name(), info.ModTime(), file.(io.ReadSeeker))
//
// There are a few file modification times that aren't useful
// to send in Last-Modified headers, but the golang http library only
// omits Last-Modified headers for the Unix epoch time. So, force
// the modification time to the epoch time if it's not useful.
zeroTime := time.Time{}
modTime := info.ModTime()
if !usefulModTime(modTime) {
modTime = zeroTime
}
http.ServeContent(w, r, info.Name(), modTime, file.(io.ReadSeeker))
return nil
}
@ -726,6 +737,14 @@ func (fsrv *FileServer) notFound(w http.ResponseWriter, r *http.Request, next ca
return caddyhttp.Error(http.StatusNotFound, nil)
}
// Indicates whether a file's modification time is useful for validator
// generation purposes (i.e. inclusion in ETag and Last-Modified headers).
// See issues #5548 and #7730.
func usefulModTime(modTime time.Time) bool {
mtimeunix := modTime.Unix()
return mtimeunix != 0 && mtimeunix != 1
}
// 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.
@ -743,8 +762,8 @@ func (fsrv *FileServer) notFound(w http.ResponseWriter, r *http.Request, next ca
// which we consider precise enough to qualify as a strong validator.
func calculateEtag(d os.FileInfo) string {
mtime := d.ModTime()
if mtimeUnix := mtime.Unix(); mtimeUnix == 0 || mtimeUnix == 1 {
return "" // not useful anyway; see issue #5548
if !usefulModTime(mtime) {
return ""
}
var sb strings.Builder
sb.WriteRune('"')

View file

@ -15,10 +15,17 @@
package fileserver
import (
"context"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/caddyserver/caddy/v2"
)
func TestFileHidden(t *testing.T) {
@ -128,3 +135,52 @@ func TestFileHidden(t *testing.T) {
}
}
}
// Check to make sure that we don't serve ETag and Last-Modified headers
// for files with invalid modification times
func TestModTimeHeaders(t *testing.T) {
check_validator_headers(time.Now(), true, t)
check_validator_headers(time.Unix(0, 0), false, t)
check_validator_headers(time.Unix(1, 0), false, t)
check_validator_headers(time.Unix(2, 0), true, t)
}
func check_validator_headers(modTime time.Time, expect_headers bool, t *testing.T) {
f := false
fsrv := FileServer{
Root: "./testdata",
CanonicalURIs: &f,
}
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/modtime.txt", nil)
if err != nil {
t.Fatal(err)
}
repl := caddy.NewReplacer()
ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, repl)
r = r.WithContext(ctx)
ctx2, _ := caddy.NewContext(caddy.Context{Context: context.Background()}) // module will be nil by default
fsrv.Provision(ctx2)
path := "testdata/modtime.txt"
os.Chtimes(path, modTime, modTime)
fsrv.ServeHTTP(w, r, nil)
if expect_headers {
if w.Header().Get("ETag") == "" {
t.Errorf("Didn't get ETag header for file with valid mod time %s", modTime)
}
if w.Header().Get("Last-Modified") == "" {
t.Errorf("Didn't get Last-Modified header for file with valid mod time %s", modTime)
}
} else {
if w.Header().Get("ETag") != "" {
t.Errorf("Got ETag header for file with invalid mod time %s", modTime)
}
if w.Header().Get("Last-Modified") != "" {
t.Errorf("Got Last-Modified header for file with invalid mod time %s", modTime)
}
}
}

View file

View file

@ -15,7 +15,7 @@ type connectionStater interface {
// http2Listener wraps the listener to solve the following problems:
// 1. prevent genuine h2c connections from succeeding if h2c is not enabled
// and the connection doesn't implment connectionStater or the resulting NegotiatedProtocol
// and the connection doesn't implement connectionStater or the resulting NegotiatedProtocol
// isn't http2.
// This does allow a connection to pass as tls enabled even if it's not, listener wrappers
// can do this.

View file

@ -101,7 +101,7 @@ type httpRedirectConn struct {
// Read tries to peek at the first few bytes of the request, and if we get
// an error reading the headers, and that error was due to the bytes looking
// like an HTTP request, then we perform a HTTP->HTTPS redirect on the same
// like an HTTP request, then we perform an HTTP->HTTPS redirect on the same
// port as the original connection.
func (c *httpRedirectConn) Read(p []byte) (int, error) {
if c.once {

View file

@ -135,8 +135,8 @@ type client struct {
logger *zap.Logger
}
// Do made the request and returns a io.Reader that translates the data read
// from fcgi responder out of fcgi packet before returning it.
// Do makes the request and returns an io.Reader that translates the data read
// from the FastCGI responder out of FastCGI packets before returning it.
func (c *client) Do(p map[string]string, req io.Reader) (r io.Reader, err error) {
// check for CONTENT_LENGTH, since the lack of it or wrong value will cause the backend to hang
if clStr, ok := p["CONTENT_LENGTH"]; !ok {
@ -179,7 +179,7 @@ func (c *client) Do(p map[string]string, req io.Reader) (r io.Reader, err error)
return r, err
}
// clientCloser is a io.ReadCloser. It wraps a io.Reader with a Closer
// clientCloser is an io.ReadCloser. It wraps an io.Reader with a Closer
// that closes the client connection.
type clientCloser struct {
rwc net.Conn
@ -208,8 +208,8 @@ func (f clientCloser) Close() error {
return f.rwc.Close()
}
// Request returns a HTTP Response with Header and Body
// from fcgi responder
// Request returns an HTTP response with header and body
// from the FastCGI responder.
func (c *client) Request(p map[string]string, req io.Reader) (resp *http.Response, err error) {
r, err := c.Do(p, req)
if err != nil {

View file

@ -522,7 +522,7 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ
body = io.LimitReader(body, h.HealthChecks.Active.MaxSize)
}
defer func() {
// drain any remaining body so connection could be re-used
// drain any remaining body so connection could be reused
_, _ = io.Copy(io.Discard, body)
resp.Body.Close()
}()

View file

@ -568,7 +568,7 @@ func TestQueryHashPolicy(t *testing.T) {
pool[1].setHealthy(false)
h = queryPolicy.Select(pool, request, nil)
if h != nil {
t.Error("Expected query policy policy host to be nil.")
t.Error("Expected query policy host to be nil.")
}
request = httptest.NewRequest(http.MethodGet, "/?foo=aa11&foo=bb22", nil)
@ -630,7 +630,7 @@ func TestURIHashPolicy(t *testing.T) {
pool[1].setHealthy(false)
h = uriPolicy.Select(pool, request, nil)
if h != nil {
t.Error("Expected uri policy policy host to be nil.")
t.Error("Expected uri policy host to be nil.")
}
}

View file

@ -158,7 +158,7 @@ type AutomationPolicy struct {
DisableOCSPStapling bool `json:"disable_ocsp_stapling,omitempty"`
// Overrides the URLs of OCSP responders embedded in certificates.
// Each key is a OCSP server URL to override, and its value is the
// Each key is an OCSP server URL to override, and its value is the
// replacement. An empty value will disable querying of that server.
// EXPERIMENTAL. Subject to change.
OCSPOverrides map[string]string `json:"ocsp_overrides,omitempty"`

View file

@ -137,11 +137,10 @@ func (s *SessionTicketService) stayUpdated() {
case newKeys := <-keysChan:
s.mu.Lock()
s.currentKeys = newKeys
configs := s.configs
s.mu.Unlock()
for cfg := range configs {
for cfg := range s.configs {
cfg.SetSessionTicketKeys(newKeys)
}
s.mu.Unlock()
case <-s.stopChan:
return
}

View file

@ -440,7 +440,7 @@ func (t *TLS) Start() error {
t.EncryptedClientHello.configsMu.Unlock()
if err != nil {
echLogger.Error("rotating ECH configs failed", zap.Error(err))
return
continue
}
err := t.publishECHConfigs(echLogger)
if err != nil {
@ -879,6 +879,8 @@ func (t *TLS) getAutomationPolicyForName(name string) *AutomationPolicy {
// AllMatchingCertificates returns the list of all certificates in
// the cache which could be used to satisfy the given SAN.
func AllMatchingCertificates(san string) []certmagic.Certificate {
certCacheMu.RLock()
defer certCacheMu.RUnlock()
return certCache.AllMatchingCertificates(san)
}

View file

@ -149,10 +149,10 @@ func (f *ReplaceFilter) Filter(in zapcore.Field) zapcore.Field {
// list of IP addresses, where all of the values
// will be masked.
type IPMaskFilter struct {
// The IPv4 mask, as an subnet size CIDR.
// The IPv4 mask, as a subnet size CIDR.
IPv4MaskRaw int `json:"ipv4_cidr,omitempty"`
// The IPv6 mask, as an subnet size CIDR.
// The IPv6 mask, as a subnet size CIDR.
IPv6MaskRaw int `json:"ipv6_cidr,omitempty"`
v4Mask net.IPMask