github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite
No package summary is available.
Package
Files: 2. Third party imports: 1. Imports from organisation: 0. Tests: 0. Benchmarks: 0.
Vars
Interface guard
var _ caddyhttp.MiddlewareHandler = (*Rewrite)(nil)
Types
Rewrite
Rewrite is a middleware which can rewrite/mutate HTTP requests.
The Method and URI properties are "setters" (the request URI will be overwritten with the given values). Other properties are "modifiers" (they modify existing values in a differentiable way). It is atypical to combine the use of setters and modifiers in a single rewrite.
To ensure consistent behavior, prefix and suffix stripping is performed in the URL-decoded (unescaped, normalized) space by default except for the specific bytes where an escape sequence is used in the prefix or suffix pattern.
For all modifiers, paths are cleaned before being modified so that multiple, consecutive slashes are collapsed into a single slash, and dot elements are resolved and removed. In the special case of a prefix, suffix, or substring containing "//" (repeated slashes), slashes will not be merged while cleaning the path so that the rewrite can be interpreted literally.
type Rewrite struct {
// Changes the request's HTTP verb.
Method string `json:"method,omitempty"`
// Changes the request's URI, which consists of path and query string.
// Only components of the URI that are specified will be changed.
// For example, a value of "/foo.html" or "foo.html" will only change
// the path and will preserve any existing query string. Similarly, a
// value of "?a=b" will only change the query string and will not affect
// the path. Both can also be changed: "/foo?a=b" - this sets both the
// path and query string at the same time.
//
// You can also use placeholders. For example, to preserve the existing
// query string, you might use: "?{http.request.uri.query}&a=b". Any
// key-value pairs you add to the query string will not overwrite
// existing values (individual pairs are append-only).
//
// To clear the query string, explicitly set an empty one: "?"
URI string `json:"uri,omitempty"`
// Strips the given prefix from the beginning of the URI path.
// The prefix should be written in normalized (unescaped) form,
// but if an escaping (`%xx`) is used, the path will be required
// to have that same escape at that position in order to match.
StripPathPrefix string `json:"strip_path_prefix,omitempty"`
// Strips the given suffix from the end of the URI path.
// The suffix should be written in normalized (unescaped) form,
// but if an escaping (`%xx`) is used, the path will be required
// to have that same escape at that position in order to match.
StripPathSuffix string `json:"strip_path_suffix,omitempty"`
// Performs substring replacements on the URI.
URISubstring []substrReplacer `json:"uri_substring,omitempty"`
// Performs regular expression replacements on the URI path.
PathRegexp []*regexReplacer `json:"path_regexp,omitempty"`
// Mutates the query string of the URI.
Query *queryOps `json:"query,omitempty"`
logger *zap.Logger
}
queryOps
queryOps describes the operations to perform on query keys: add, set, rename and delete.
type queryOps struct {
// Renames a query key from Key to Val, without affecting the value.
Rename []queryOpsArguments `json:"rename,omitempty"`
// Sets query parameters; overwrites a query key with the given value.
Set []queryOpsArguments `json:"set,omitempty"`
// Adds query parameters; does not overwrite an existing query field,
// and only appends an additional value for that key if any already exist.
Add []queryOpsArguments `json:"add,omitempty"`
// Replaces query parameters.
Replace []*queryOpsReplacement `json:"replace,omitempty"`
// Deletes a given query key by name.
Delete []string `json:"delete,omitempty"`
}
queryOpsArguments
This type doesn't have documentation.
type queryOpsArguments struct {
// A key in the query string. Note that query string keys may appear multiple times.
Key string `json:"key,omitempty"`
// The value for the given operation; for add and set, this is
// simply the value of the query, and for rename this is the
// query key to rename to.
Val string `json:"val,omitempty"`
}
queryOpsReplacement
This type doesn't have documentation.
type queryOpsReplacement struct {
// The key to replace in the query string.
Key string `json:"key,omitempty"`
// The substring to search for.
Search string `json:"search,omitempty"`
// The regular expression to search with.
SearchRegexp string `json:"search_regexp,omitempty"`
// The string with which to replace matches.
Replace string `json:"replace,omitempty"`
re *regexp.Regexp
}
regexReplacer
regexReplacer describes a replacement using a regular expression.
type regexReplacer struct {
// The regular expression to find.
Find string `json:"find,omitempty"`
// The substring to replace with. Supports placeholders and
// regular expression capture groups.
Replace string `json:"replace,omitempty"`
re *regexp.Regexp
}
substrReplacer
substrReplacer describes either a simple and fast substring replacement.
type substrReplacer struct {
// A substring to find. Supports placeholders.
Find string `json:"find,omitempty"`
// The substring to replace with. Supports placeholders.
Replace string `json:"replace,omitempty"`
// Maximum number of replacements per string.
// Set to <= 0 for no limit (default).
Limit int `json:"limit,omitempty"`
}
Functions
func (*Rewrite) Provision
Provision sets up rewr.
func (rewr *Rewrite) Provision(ctx caddy.Context) error {
rewr.logger = ctx.Logger()
for i, rep := range rewr.PathRegexp {
if rep.Find == "" {
return fmt.Errorf("path_regexp find cannot be empty")
}
re, err := regexp.Compile(rep.Find)
if err != nil {
return fmt.Errorf("compiling regular expression %d: %v", i, err)
}
rep.re = re
}
if rewr.Query != nil {
for _, replacementOp := range rewr.Query.Replace {
err := replacementOp.Provision(ctx)
if err != nil {
return fmt.Errorf("compiling regular expression %s in query rewrite replace operation: %v", replacementOp.SearchRegexp, err)
}
}
}
return nil
}
Cognitive complexity: 14
, Cyclomatic complexity: 7
func (Rewrite) CaddyModule
CaddyModule returns the Caddy module information.
func (Rewrite) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.handlers.rewrite",
New: func() caddy.Module { return new(Rewrite) },
}
}
Cognitive complexity: 2
, Cyclomatic complexity: 1
func (Rewrite) Rewrite
rewrite performs the rewrites on r using repl, which should have been obtained from r, but is passed in for efficiency. It returns true if any changes were made to r.
func (rewr Rewrite) Rewrite(r *http.Request, repl *caddy.Replacer) bool {
oldMethod := r.Method
oldURI := r.RequestURI
// method
if rewr.Method != "" {
r.Method = strings.ToUpper(repl.ReplaceAll(rewr.Method, ""))
}
// uri (path, query string and... fragment, because why not)
if uri := rewr.URI; uri != "" {
// find the bounds of each part of the URI that exist
pathStart, qsStart, fragStart := -1, -1, -1
pathEnd, qsEnd := -1, -1
loop:
for i, ch := range uri {
switch {
case ch == '?' && qsStart < 0:
pathEnd, qsStart = i, i+1
case ch == '#' && fragStart < 0: // everything after fragment is fragment (very clear in RFC 3986 section 4.2)
if qsStart < 0 {
pathEnd = i
} else {
qsEnd = i
}
fragStart = i + 1
break loop
case pathStart < 0 && qsStart < 0:
pathStart = i
}
}
if pathStart >= 0 && pathEnd < 0 {
pathEnd = len(uri)
}
if qsStart >= 0 && qsEnd < 0 {
qsEnd = len(uri)
}
// isolate the three main components of the URI
var path, query, frag string
if pathStart > -1 {
path = uri[pathStart:pathEnd]
}
if qsStart > -1 {
query = uri[qsStart:qsEnd]
}
if fragStart > -1 {
frag = uri[fragStart:]
}
// build components which are specified, and store them
// in a temporary variable so that they all read the
// same version of the URI
var newPath, newQuery, newFrag string
if path != "" {
// replace the `path` placeholder to escaped path
pathPlaceholder := "{http.request.uri.path}"
if strings.Contains(path, pathPlaceholder) {
path = strings.ReplaceAll(path, pathPlaceholder, r.URL.EscapedPath())
}
newPath = repl.ReplaceAll(path, "")
}
// before continuing, we need to check if a query string
// snuck into the path component during replacements
if before, after, found := strings.Cut(newPath, "?"); found {
// recompute; new path contains a query string
var injectedQuery string
newPath, injectedQuery = before, after
// don't overwrite explicitly-configured query string
if query == "" {
query = injectedQuery
}
}
if query != "" {
newQuery = buildQueryString(query, repl)
}
if frag != "" {
newFrag = repl.ReplaceAll(frag, "")
}
// update the URI with the new components
// only after building them
if pathStart >= 0 {
if path, err := url.PathUnescape(newPath); err != nil {
r.URL.Path = newPath
} else {
r.URL.Path = path
}
}
if qsStart >= 0 {
r.URL.RawQuery = newQuery
}
if fragStart >= 0 {
r.URL.Fragment = newFrag
}
}
// strip path prefix or suffix
if rewr.StripPathPrefix != "" {
prefix := repl.ReplaceAll(rewr.StripPathPrefix, "")
if !strings.HasPrefix(prefix, "/") {
prefix = "/" + prefix
}
mergeSlashes := !strings.Contains(prefix, "//")
changePath(r, func(escapedPath string) string {
escapedPath = caddyhttp.CleanPath(escapedPath, mergeSlashes)
return trimPathPrefix(escapedPath, prefix)
})
}
if rewr.StripPathSuffix != "" {
suffix := repl.ReplaceAll(rewr.StripPathSuffix, "")
mergeSlashes := !strings.Contains(suffix, "//")
changePath(r, func(escapedPath string) string {
escapedPath = caddyhttp.CleanPath(escapedPath, mergeSlashes)
return reverse(trimPathPrefix(reverse(escapedPath), reverse(suffix)))
})
}
// substring replacements in URI
for _, rep := range rewr.URISubstring {
rep.do(r, repl)
}
// regular expression replacements on the path
for _, rep := range rewr.PathRegexp {
rep.do(r, repl)
}
// apply query operations
if rewr.Query != nil {
rewr.Query.do(r, repl)
}
// update the encoded copy of the URI
r.RequestURI = r.URL.RequestURI()
// return true if anything changed
return r.Method != oldMethod || r.RequestURI != oldURI
}
Cognitive complexity: 61
, Cyclomatic complexity: 35
func (Rewrite) ServeHTTP
func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
const message = "rewrote request"
c := rewr.logger.Check(zap.DebugLevel, message)
if c == nil {
rewr.Rewrite(r, repl)
return next.ServeHTTP(w, r)
}
changed := rewr.Rewrite(r, repl)
if changed {
c.Write(
zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: r}),
zap.String("method", r.Method),
zap.String("uri", r.RequestURI),
)
}
return next.ServeHTTP(w, r)
}
Cognitive complexity: 5
, Cyclomatic complexity: 3
Private functions
func applyQueryOps
applyQueryOps (h httpcaddyfile.Helper, qo *queryOps, args []string) error
References: strings.Contains, strings.HasPrefix, strings.Split, strings.TrimLeft.
func buildQueryString
buildQueryString takes an input query string and performs replacements on each component, returning the resulting query string. This function appends duplicate keys rather than replaces.
buildQueryString (qs string, repl *caddy.Replacer) string
References: fmt.Sprintf, fmt.Stringer, strconv.Itoa, strings.Builder, strings.Index, url.QueryEscape.
func changePath
changePath (req *http.Request, newVal func(pathOrRawPath string) string)
References: url.PathUnescape.
func init
init ()
References: httpcaddyfile.RegisterDirective, httpcaddyfile.RegisterHandlerDirective.
func parseCaddyfileHandlePath
parseCaddyfileHandlePath parses the handle_path directive. Syntax:
handle_path [<matcher>] {
<directives...>
}
Only path matchers (with a /
prefix) are supported as this is a shortcut
for the handle directive with a strip_prefix rewrite.
parseCaddyfileHandlePath (h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
References: caddyconfig.JSONModuleObject, caddyhttp.MatchPath, caddyhttp.Route, caddyhttp.Subroute, httpcaddyfile.ParseSegmentAsSubroute, json.RawMessage, strings.HasPrefix, strings.HasSuffix.
func parseCaddyfileMethod
parseCaddyfileMethod sets up a basic method rewrite handler from Caddyfile tokens. Syntax:
method [<matcher>] <method>
parseCaddyfileMethod (h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
func parseCaddyfileRewrite
parseCaddyfileRewrite sets up a basic rewrite handler from Caddyfile tokens. Syntax:
rewrite [<matcher>] <to>
Only URI components which are given in <to> will be set in the resulting URI. See the docs for the rewrite handler for more information.
parseCaddyfileRewrite (h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
func parseCaddyfileURI
parseCaddyfileURI sets up a handler for manipulating (but not "rewriting") the URI from Caddyfile tokens. Syntax:
uri [<matcher>] strip_prefix|strip_suffix|replace|path_regexp <target> [<replacement> [<limit>]]
If strip_prefix or strip_suffix are used, then <target> will be stripped only if it is the beginning or the end, respectively, of the URI path. If replace is used, then <target> will be replaced with <replacement> across the whole URI, up to <limit> times (or unlimited if unspecified). If path_regexp is used, then regular expression replacements will be performed on the path portion of the URI (and a limit cannot be set).
parseCaddyfileURI (h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
References: strconv.Atoi.
func reverse
reverse (s string) string
func trimPathPrefix
trimPathPrefix is like strings.TrimPrefix, but customized for advanced URI path prefix matching. The string prefix will be trimmed from the beginning of escapedPath if escapedPath starts with prefix. Rather than a naive 1:1 comparison of each byte to determine if escapedPath starts with prefix, both strings are iterated in lock-step, and if prefix has a '%' encoding at a particular position, escapedPath must also have the same encoding representation for that character. In other words, if the prefix string uses the escaped form for a character, escapedPath must literally use the same escape at that position. Otherwise, all character comparisons are performed in normalized/unescaped space.
trimPathPrefix (escapedPath,prefix string) string
References: strings.EqualFold, url.PathUnescape.
func do
do performs the substring replacement on r.
do (r *http.Request, repl *caddy.Replacer)
References: caddyhttp.CleanPath, strings.Contains, strings.Replace.
Tests
Files: 1. Third party imports: 0. Imports from organisation: 0. Tests: 1. Benchmarks: 0.