From 2cac3c5491e6428441ecf668cc4f5a86e67ed9b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90=E1=BB=97=20Tr=E1=BB=8Dng=20H=E1=BA=A3i?= <41283691+hainenber@users.noreply.github.com> Date: Sat, 9 Sep 2023 01:38:44 +0700 Subject: [PATCH] httpcaddyfile: fix placeholder shorthands in named routes (#5791) Co-authored-by: Francis Lavoie --- .github/workflows/lint.yml | 2 + caddyconfig/httpcaddyfile/httptype.go | 86 ++++------------------- caddyconfig/httpcaddyfile/shorthands.go | 92 +++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 71 deletions(-) create mode 100644 caddyconfig/httpcaddyfile/shorthands.go diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f4a9b44da..2bf2eab3c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -56,3 +56,5 @@ jobs: steps: - name: govulncheck uses: golang/govulncheck-action@v1 + with: + check-latest: true diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index 6fa5a9f7c..78fb7f062 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -18,7 +18,6 @@ import ( "encoding/json" "fmt" "reflect" - "regexp" "sort" "strconv" "strings" @@ -82,46 +81,18 @@ func (st ServerType) Setup( return nil, warnings, err } - originalServerBlocks, err = st.extractNamedRoutes(originalServerBlocks, options, &warnings) + // this will replace both static and user-defined placeholder shorthands + // with actual identifiers used by Caddy + replacer := NewShorthandReplacer() + + originalServerBlocks, err = st.extractNamedRoutes(originalServerBlocks, options, &warnings, replacer) if err != nil { return nil, warnings, err } - // replace shorthand placeholders (which are convenient - // when writing a Caddyfile) with their actual placeholder - // identifiers or variable names - replacer := strings.NewReplacer(placeholderShorthands()...) - - // these are placeholders that allow a user-defined final - // parameters, but we still want to provide a shorthand - // for those, so we use a regexp to replace - regexpReplacements := []struct { - search *regexp.Regexp - replace string - }{ - {regexp.MustCompile(`{header\.([\w-]*)}`), "{http.request.header.$1}"}, - {regexp.MustCompile(`{cookie\.([\w-]*)}`), "{http.request.cookie.$1}"}, - {regexp.MustCompile(`{labels\.([\w-]*)}`), "{http.request.host.labels.$1}"}, - {regexp.MustCompile(`{path\.([\w-]*)}`), "{http.request.uri.path.$1}"}, - {regexp.MustCompile(`{file\.([\w-]*)}`), "{http.request.uri.path.file.$1}"}, - {regexp.MustCompile(`{query\.([\w-]*)}`), "{http.request.uri.query.$1}"}, - {regexp.MustCompile(`{re\.([\w-]*)\.([\w-]*)}`), "{http.regexp.$1.$2}"}, - {regexp.MustCompile(`{vars\.([\w-]*)}`), "{http.vars.$1}"}, - {regexp.MustCompile(`{rp\.([\w-\.]*)}`), "{http.reverse_proxy.$1}"}, - {regexp.MustCompile(`{err\.([\w-\.]*)}`), "{http.error.$1}"}, - {regexp.MustCompile(`{file_match\.([\w-]*)}`), "{http.matchers.file.$1}"}, - } - for _, sb := range originalServerBlocks { - for _, segment := range sb.block.Segments { - for i := 0; i < len(segment); i++ { - // simple string replacements - segment[i].Text = replacer.Replace(segment[i].Text) - // complex regexp replacements - for _, r := range regexpReplacements { - segment[i].Text = r.search.ReplaceAllString(segment[i].Text, r.replace) - } - } + for i := range sb.block.Segments { + replacer.ApplyToSegment(&sb.block.Segments[i]) } if len(sb.block.Keys) == 0 { @@ -452,6 +423,7 @@ func (ServerType) extractNamedRoutes( serverBlocks []serverBlock, options map[string]any, warnings *[]caddyconfig.Warning, + replacer ShorthandReplacer, ) ([]serverBlock, error) { namedRoutes := map[string]*caddyhttp.Route{} @@ -477,11 +449,14 @@ func (ServerType) extractNamedRoutes( continue } - // zip up all the segments since ParseSegmentAsSubroute - // was designed to take a directive+ wholeSegment := caddyfile.Segment{} - for _, segment := range sb.block.Segments { - wholeSegment = append(wholeSegment, segment...) + for i := range sb.block.Segments { + // replace user-defined placeholder shorthands in extracted named routes + replacer.ApplyToSegment(&sb.block.Segments[i]) + + // zip up all the segments since ParseSegmentAsSubroute + // was designed to take a directive+ + wholeSegment = append(wholeSegment, sb.block.Segments[i]...) } h := Helper{ @@ -1449,37 +1424,6 @@ func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (caddy.Modul return msEncoded, nil } -// placeholderShorthands returns a slice of old-new string pairs, -// where the left of the pair is a placeholder shorthand that may -// be used in the Caddyfile, and the right is the replacement. -func placeholderShorthands() []string { - return []string{ - "{dir}", "{http.request.uri.path.dir}", - "{file}", "{http.request.uri.path.file}", - "{host}", "{http.request.host}", - "{hostport}", "{http.request.hostport}", - "{port}", "{http.request.port}", - "{method}", "{http.request.method}", - "{path}", "{http.request.uri.path}", - "{query}", "{http.request.uri.query}", - "{remote}", "{http.request.remote}", - "{remote_host}", "{http.request.remote.host}", - "{remote_port}", "{http.request.remote.port}", - "{scheme}", "{http.request.scheme}", - "{uri}", "{http.request.uri}", - "{tls_cipher}", "{http.request.tls.cipher_suite}", - "{tls_version}", "{http.request.tls.version}", - "{tls_client_fingerprint}", "{http.request.tls.client.fingerprint}", - "{tls_client_issuer}", "{http.request.tls.client.issuer}", - "{tls_client_serial}", "{http.request.tls.client.serial}", - "{tls_client_subject}", "{http.request.tls.client.subject}", - "{tls_client_certificate_pem}", "{http.request.tls.client.certificate_pem}", - "{tls_client_certificate_der_base64}", "{http.request.tls.client.certificate_der_base64}", - "{upstream_hostport}", "{http.reverse_proxy.upstream.hostport}", - "{client_ip}", "{http.vars.client_ip}", - } -} - // WasReplacedPlaceholderShorthand checks if a token string was // likely a replaced shorthand of the known Caddyfile placeholder // replacement outputs. Useful to prevent some user-defined map diff --git a/caddyconfig/httpcaddyfile/shorthands.go b/caddyconfig/httpcaddyfile/shorthands.go new file mode 100644 index 000000000..102bc36d6 --- /dev/null +++ b/caddyconfig/httpcaddyfile/shorthands.go @@ -0,0 +1,92 @@ +package httpcaddyfile + +import ( + "regexp" + "strings" + + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +type ComplexShorthandReplacer struct { + search *regexp.Regexp + replace string +} + +type ShorthandReplacer struct { + complex []ComplexShorthandReplacer + simple *strings.Replacer +} + +func NewShorthandReplacer() ShorthandReplacer { + // replace shorthand placeholders (which are convenient + // when writing a Caddyfile) with their actual placeholder + // identifiers or variable names + replacer := strings.NewReplacer(placeholderShorthands()...) + + // these are placeholders that allow a user-defined final + // parameters, but we still want to provide a shorthand + // for those, so we use a regexp to replace + regexpReplacements := []ComplexShorthandReplacer{ + {regexp.MustCompile(`{header\.([\w-]*)}`), "{http.request.header.$1}"}, + {regexp.MustCompile(`{cookie\.([\w-]*)}`), "{http.request.cookie.$1}"}, + {regexp.MustCompile(`{labels\.([\w-]*)}`), "{http.request.host.labels.$1}"}, + {regexp.MustCompile(`{path\.([\w-]*)}`), "{http.request.uri.path.$1}"}, + {regexp.MustCompile(`{file\.([\w-]*)}`), "{http.request.uri.path.file.$1}"}, + {regexp.MustCompile(`{query\.([\w-]*)}`), "{http.request.uri.query.$1}"}, + {regexp.MustCompile(`{re\.([\w-]*)\.([\w-]*)}`), "{http.regexp.$1.$2}"}, + {regexp.MustCompile(`{vars\.([\w-]*)}`), "{http.vars.$1}"}, + {regexp.MustCompile(`{rp\.([\w-\.]*)}`), "{http.reverse_proxy.$1}"}, + {regexp.MustCompile(`{err\.([\w-\.]*)}`), "{http.error.$1}"}, + {regexp.MustCompile(`{file_match\.([\w-]*)}`), "{http.matchers.file.$1}"}, + } + + return ShorthandReplacer{ + complex: regexpReplacements, + simple: replacer, + } +} + +// placeholderShorthands returns a slice of old-new string pairs, +// where the left of the pair is a placeholder shorthand that may +// be used in the Caddyfile, and the right is the replacement. +func placeholderShorthands() []string { + return []string{ + "{dir}", "{http.request.uri.path.dir}", + "{file}", "{http.request.uri.path.file}", + "{host}", "{http.request.host}", + "{hostport}", "{http.request.hostport}", + "{port}", "{http.request.port}", + "{method}", "{http.request.method}", + "{path}", "{http.request.uri.path}", + "{query}", "{http.request.uri.query}", + "{remote}", "{http.request.remote}", + "{remote_host}", "{http.request.remote.host}", + "{remote_port}", "{http.request.remote.port}", + "{scheme}", "{http.request.scheme}", + "{uri}", "{http.request.uri}", + "{tls_cipher}", "{http.request.tls.cipher_suite}", + "{tls_version}", "{http.request.tls.version}", + "{tls_client_fingerprint}", "{http.request.tls.client.fingerprint}", + "{tls_client_issuer}", "{http.request.tls.client.issuer}", + "{tls_client_serial}", "{http.request.tls.client.serial}", + "{tls_client_subject}", "{http.request.tls.client.subject}", + "{tls_client_certificate_pem}", "{http.request.tls.client.certificate_pem}", + "{tls_client_certificate_der_base64}", "{http.request.tls.client.certificate_der_base64}", + "{upstream_hostport}", "{http.reverse_proxy.upstream.hostport}", + "{client_ip}", "{http.vars.client_ip}", + } +} + +// ApplyToSegment replaces shorthand placeholder to its full placeholder, understandable by Caddy. +func (s ShorthandReplacer) ApplyToSegment(segment *caddyfile.Segment) { + if segment != nil { + for i := 0; i < len(*segment); i++ { + // simple string replacements + (*segment)[i].Text = s.simple.Replace((*segment)[i].Text) + // complex regexp replacements + for _, r := range s.complex { + (*segment)[i].Text = r.search.ReplaceAllString((*segment)[i].Text, r.replace) + } + } + } +}