mirror of
https://github.com/caddyserver/caddy.git
synced 2025-07-09 03:04:57 -04:00
caddyhttp: Introduce strict HTTP mode
This commit is contained in:
parent
aaf6794b31
commit
98cd4333a1
@ -111,6 +111,8 @@ type App struct {
|
|||||||
// be forcefully closed.
|
// be forcefully closed.
|
||||||
GracePeriod caddy.Duration `json:"grace_period,omitempty"`
|
GracePeriod caddy.Duration `json:"grace_period,omitempty"`
|
||||||
|
|
||||||
|
Strict *StrictOptions `json:"strict,omitempty"`
|
||||||
|
|
||||||
// Servers is the list of servers, keyed by arbitrary names chosen
|
// Servers is the list of servers, keyed by arbitrary names chosen
|
||||||
// at your discretion for your own convenience; the keys do not
|
// at your discretion for your own convenience; the keys do not
|
||||||
// affect functionality.
|
// affect functionality.
|
||||||
@ -127,6 +129,13 @@ type App struct {
|
|||||||
allCertDomains []string
|
allCertDomains []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StrictOptions struct {
|
||||||
|
Disabled bool `json:"disable,omitempty"`
|
||||||
|
LenientQueryStrings bool `json:"lenient_query_strings,omitempty"`
|
||||||
|
LenientPaths bool `json:"lenient_paths,omitempty"`
|
||||||
|
LenientHeaders bool `json:"lenient_headers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// CaddyModule returns the Caddy module information.
|
// CaddyModule returns the Caddy module information.
|
||||||
func (App) CaddyModule() caddy.ModuleInfo {
|
func (App) CaddyModule() caddy.ModuleInfo {
|
||||||
return caddy.ModuleInfo{
|
return caddy.ModuleInfo{
|
||||||
@ -162,6 +171,7 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||||||
srv.tlsApp = app.tlsApp
|
srv.tlsApp = app.tlsApp
|
||||||
srv.logger = app.logger.Named("log")
|
srv.logger = app.logger.Named("log")
|
||||||
srv.errorLogger = app.logger.Named("log.error")
|
srv.errorLogger = app.logger.Named("log.error")
|
||||||
|
srv.strict = app.Strict
|
||||||
|
|
||||||
// only enable access logs if configured
|
// only enable access logs if configured
|
||||||
if srv.Logs != nil {
|
if srv.Logs != nil {
|
||||||
|
@ -503,15 +503,20 @@ func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||||||
// Match returns true if r matches m. An empty m matches an empty query string.
|
// Match returns true if r matches m. An empty m matches an empty query string.
|
||||||
func (m MatchQuery) Match(r *http.Request) bool {
|
func (m MatchQuery) Match(r *http.Request) bool {
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||||
|
reqQuery, err := url.ParseQuery(r.URL.RawQuery)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
for param, vals := range m {
|
for param, vals := range m {
|
||||||
param = repl.ReplaceAll(param, "")
|
param = repl.ReplaceAll(param, "")
|
||||||
paramVal, found := r.URL.Query()[param]
|
paramVal, found := reqQuery[param]
|
||||||
if found {
|
if !found {
|
||||||
for _, v := range vals {
|
continue
|
||||||
v = repl.ReplaceAll(v, "")
|
}
|
||||||
if paramVal[0] == v || v == "*" {
|
for _, v := range vals {
|
||||||
return true
|
v = repl.ReplaceAll(v, "")
|
||||||
}
|
if paramVal[0] == v || v == "*" {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -624,9 +624,18 @@ func TestQueryMatcher(t *testing.T) {
|
|||||||
input: "/?somekey=1",
|
input: "/?somekey=1",
|
||||||
expect: true,
|
expect: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid query string",
|
||||||
|
match: MatchQuery{"test": []string{"*"}},
|
||||||
|
input: "/?test=1;",
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
|
u, err := url.Parse(tc.input)
|
||||||
u, _ := url.Parse(tc.input)
|
if err != nil {
|
||||||
|
t.Errorf("Test %d: Parsing URL: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
req := &http.Request{URL: u}
|
req := &http.Request{URL: u}
|
||||||
repl := caddy.NewReplacer()
|
repl := caddy.NewReplacer()
|
||||||
|
@ -132,6 +132,7 @@ type Server struct {
|
|||||||
primaryHandlerChain Handler
|
primaryHandlerChain Handler
|
||||||
errorHandlerChain Handler
|
errorHandlerChain Handler
|
||||||
listenerWrappers []caddy.ListenerWrapper
|
listenerWrappers []caddy.ListenerWrapper
|
||||||
|
strict *StrictOptions
|
||||||
|
|
||||||
tlsApp *caddytls.TLS
|
tlsApp *caddytls.TLS
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
@ -315,9 +316,46 @@ func (s *Server) enforcementHandler(w http.ResponseWriter, r *http.Request, next
|
|||||||
return Error(http.StatusMisdirectedRequest, err)
|
return Error(http.StatusMisdirectedRequest, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := s.strict.enforce(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return next.ServeHTTP(w, r)
|
return next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (strict *StrictOptions) enforce(r *http.Request) error {
|
||||||
|
if strict != nil && strict.Disabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject query strings with unencoded ;
|
||||||
|
if strict == nil || !strict.LenientQueryStrings {
|
||||||
|
_, err := url.ParseQuery(r.URL.RawQuery)
|
||||||
|
if err != nil {
|
||||||
|
return Error(http.StatusBadRequest, fmt.Errorf("invalid query string: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject header fields with _ in name (#4830)
|
||||||
|
if strict == nil || !strict.LenientHeaders {
|
||||||
|
for field := range r.Header {
|
||||||
|
if strings.Contains(field, "_") {
|
||||||
|
return Error(http.StatusBadRequest, fmt.Errorf("invalid header field name: %s", field))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject paths with // or ..
|
||||||
|
if strict == nil || !strict.LenientPaths {
|
||||||
|
if strings.Contains(r.URL.Path, "//") || strings.Contains(r.URL.Path, "..") {
|
||||||
|
return Error(http.StatusBadRequest, fmt.Errorf("invalid request path: %s", r.URL.RawPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// listenersUseAnyPortOtherThan returns true if there are any
|
// listenersUseAnyPortOtherThan returns true if there are any
|
||||||
// listeners in s that use a port which is not otherPort.
|
// listeners in s that use a port which is not otherPort.
|
||||||
func (s *Server) listenersUseAnyPortOtherThan(otherPort int) bool {
|
func (s *Server) listenersUseAnyPortOtherThan(otherPort int) bool {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user