@marcel91200 / caddy Mirror · read-only / commits / 7d241244
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 17:15 UTC

caddyhttp: Reject invalid Host header (fix #7449)

Matthew Holt committed Jan 30, 2026 at 12:24 UTC 7d24124430ed8a2bfc3aa0625a742082361e3ff8
1 file changed +60 -46
modules/caddyhttp/server.go
+60 -46
@@ -18,6 +18,7 @@ import (
18 "context"
19 "crypto/tls"
20 "encoding/json"
21 + "errors"
22 "fmt"
23 "io"
24 "net"
@@ -297,6 +298,8 @@ var (
298
299 // ServeHTTP is the entry point for all HTTP requests.
300 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
301 + start := time.Now()
302 +
303 // If there are listener wrappers that process tls connections but don't return a *tls.Conn, this field will be nil.
304 if r.TLS == nil {
305 if tlsConnStateFunc, ok := r.Context().Value(tlsConnectionStateFuncCtxKey).(func() *tls.ConnectionState); ok {
@@ -304,6 +307,17 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
307 }
308 }
309
310 + // enable full-duplex for HTTP/1, ensuring the entire
311 + // request body gets consumed before writing the response
312 + if s.EnableFullDuplex && r.ProtoMajor == 1 {
313 + if err := http.NewResponseController(w).EnableFullDuplex(); err != nil { //nolint:bodyclose
314 + if c := s.logger.Check(zapcore.WarnLevel, "failed to enable full duplex"); c != nil {
315 + c.Write(zap.Error(err))
316 + }
317 + }
318 + }
319 +
320 + // set the Server header
321 h := w.Header()
322 h["Server"] = serverHeader
323
@@ -316,39 +330,14 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
330 }
331 }
332
319 - // reject very long methods; probably a mistake or an attack
320 - if len(r.Method) > 32 {
321 - if s.shouldLogRequest(r) {
322 - if c := s.accessLogger.Check(zapcore.DebugLevel, "rejecting request with long method"); c != nil {
323 - c.Write(
324 - zap.String("method_trunc", r.Method[:32]),
325 - zap.String("remote_addr", r.RemoteAddr),
326 - )
327 - }
328 - }
329 - w.WriteHeader(http.StatusMethodNotAllowed)
330 - return
331 - }
332 -
333 + // prepare internals of the request for the handler pipeline
334 repl := caddy.NewReplacer()
335 r = PrepareRequest(r, repl, w, s)
336
336 - // enable full-duplex for HTTP/1, ensuring the entire
337 - // request body gets consumed before writing the response
338 - if s.EnableFullDuplex && r.ProtoMajor == 1 {
339 - if err := http.NewResponseController(w).EnableFullDuplex(); err != nil { //nolint:bodyclose
340 - if c := s.logger.Check(zapcore.WarnLevel, "failed to enable full duplex"); c != nil {
341 - c.Write(zap.Error(err))
342 - }
343 - }
344 - }
345 -
346 - // clone the request for logging purposes before
347 - // it enters any handler chain; this is necessary
348 - // to capture the original request in case it gets
349 - // modified during handling
350 - // cloning the request and using .WithLazy is considerably faster
351 - // than using .With, which will JSON encode the request immediately
337 + // clone the request for logging purposes before it enters any handler chain;
338 + // this is necessary to capture the original request in case it gets modified
339 + // during handling (cloning the request and using .WithLazy is considerably
340 + // faster than using .With, which will JSON-encode the request immediately)
341 shouldLogCredentials := s.Logs != nil && s.Logs.ShouldLogCredentials
342 loggableReq := zap.Object("request", LoggableHTTPRequest{
343 Request: r.Clone(r.Context()),
@@ -376,36 +365,33 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
365 }
366
367 // capture the original version of the request
379 - accLog := s.accessLogger.With(loggableReq)
368 + accLog := s.accessLogger.WithLazy(loggableReq)
369
370 defer s.logRequest(accLog, r, wrec, &duration, repl, bodyReader, shouldLogCredentials)
371 }
372
384 - start := time.Now()
385 -
386 - // guarantee ACME HTTP challenges; handle them
387 - // separately from any user-defined handlers
373 + // guarantee ACME HTTP challenges; handle them separately from any user-defined handlers
374 if s.tlsApp.HandleHTTPChallenge(w, r) {
375 duration = time.Since(start)
376 return
377 }
378
393 - // execute the primary handler chain
394 - err := s.primaryHandlerChain.ServeHTTP(w, r)
379 + err := s.serveHTTP(w, r)
380 duration = time.Since(start)
381
397 - // if no errors, we're done!
382 if err == nil {
383 return
384 }
385
386 // restore original request before invoking error handler chain (issue #3717)
403 - // TODO: this does not restore original headers, if modified (for efficiency)
404 - origReq := r.Context().Value(OriginalRequestCtxKey).(http.Request)
405 - r.Method = origReq.Method
406 - r.RemoteAddr = origReq.RemoteAddr
407 - r.RequestURI = origReq.RequestURI
408 - cloneURL(origReq.URL, r.URL)
387 + // NOTE: this does not restore original headers if modified (for efficiency)
388 + origReq, ok := r.Context().Value(OriginalRequestCtxKey).(http.Request)
389 + if ok {
390 + r.Method = origReq.Method
391 + r.RemoteAddr = origReq.RemoteAddr
392 + r.RequestURI = origReq.RequestURI
393 + cloneURL(origReq.URL, r.URL)
394 + }
395
396 // prepare the error log
397 errLog = errLog.With(zap.Duration("duration", duration))
@@ -424,8 +410,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
410 if s.Errors != nil && len(s.Errors.Routes) > 0 {
411 // execute user-defined error handling route
412 if err2 := s.errorHandlerChain.ServeHTTP(w, r); err2 == nil {
427 - // user's error route handled the error response
428 - // successfully, so now just log the error
413 + // user's error route handled the error response successfully, so now just log the error
414 for _, logger := range errLoggers {
415 if c := logger.Check(zapcore.DebugLevel, errMsg); c != nil {
416 if fields == nil {
@@ -473,6 +458,35 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
458 }
459 }
460
461 +func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) error {
462 + // reject very long methods; probably a mistake or an attack
463 + if len(r.Method) > 32 {
464 + if s.shouldLogRequest(r) {
465 + if c := s.accessLogger.Check(zapcore.DebugLevel, "rejecting request with long method"); c != nil {
466 + c.Write(
467 + zap.String("method_trunc", r.Method[:32]),
468 + zap.String("remote_addr", r.RemoteAddr),
469 + )
470 + }
471 + }
472 + return HandlerError{StatusCode: http.StatusMethodNotAllowed}
473 + }
474 +
475 + // RFC 9112 section 3.2: "A server MUST respond with a 400 (Bad Request) status
476 + // code to any HTTP/1.1 request message that lacks a Host header field and to any
477 + // request message that contains more than one Host header field line or a Host
478 + // header field with an invalid field value."
479 + if r.Host == "" {
480 + return HandlerError{
481 + Err: errors.New("rfc9112 forbids empty Host"),
482 + StatusCode: http.StatusBadRequest,
483 + }
484 + }
485 +
486 + // execute the primary handler chain
487 + return s.primaryHandlerChain.ServeHTTP(w, r)
488 +}
489 +
490 // wrapPrimaryRoute wraps stack (a compiled middleware handler chain)
491 // in s.enforcementHandler which performs crucial security checks, etc.
492 func (s *Server) wrapPrimaryRoute(stack Handler) Handler {