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, 01:00 UTC
| 1 | // Copyright 2015 Matthew Holt and The Caddy Authors |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package rewrite |
| 16 | |
| 17 | import ( |
| 18 | "encoding/json" |
| 19 | "strconv" |
| 20 | "strings" |
| 21 | |
| 22 | "github.com/caddyserver/caddy/v2" |
| 23 | "github.com/caddyserver/caddy/v2/caddyconfig" |
| 24 | "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" |
| 25 | "github.com/caddyserver/caddy/v2/modules/caddyhttp" |
| 26 | ) |
| 27 | |
| 28 | func init() { |
| 29 | httpcaddyfile.RegisterDirective("rewrite", parseCaddyfileRewrite) |
| 30 | httpcaddyfile.RegisterHandlerDirective("method", parseCaddyfileMethod) |
| 31 | httpcaddyfile.RegisterHandlerDirective("uri", parseCaddyfileURI) |
| 32 | httpcaddyfile.RegisterDirective("handle_path", parseCaddyfileHandlePath) |
| 33 | } |
| 34 | |
| 35 | // parseCaddyfileRewrite sets up a basic rewrite handler from Caddyfile tokens. Syntax: |
| 36 | // |
| 37 | // rewrite [<matcher>] <to> |
| 38 | // |
| 39 | // Only URI components which are given in <to> will be set in the resulting URI. |
| 40 | // See the docs for the rewrite handler for more information. |
| 41 | func parseCaddyfileRewrite(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { |
| 42 | h.Next() // consume directive name |
| 43 | |
| 44 | // count the tokens to determine what to do |
| 45 | argsCount := h.CountRemainingArgs() |
| 46 | if argsCount == 0 { |
| 47 | return nil, h.Errf("too few arguments; must have at least a rewrite URI") |
| 48 | } |
| 49 | if argsCount > 2 { |
| 50 | return nil, h.Errf("too many arguments; should only be a matcher and a URI") |
| 51 | } |
| 52 | |
| 53 | // with only one arg, assume it's a rewrite URI with no matcher token |
| 54 | if argsCount == 1 { |
| 55 | if !h.NextArg() { |
| 56 | return nil, h.ArgErr() |
| 57 | } |
| 58 | return h.NewRoute(nil, Rewrite{URI: h.Val()}), nil |
| 59 | } |
| 60 | |
| 61 | // parse the matcher token into a matcher set |
| 62 | userMatcherSet, err := h.ExtractMatcherSet() |
| 63 | if err != nil { |
| 64 | return nil, err |
| 65 | } |
| 66 | h.Next() // consume directive name again, matcher parsing does a reset |
| 67 | h.Next() // advance to the rewrite URI |
| 68 | |
| 69 | return h.NewRoute(userMatcherSet, Rewrite{URI: h.Val()}), nil |
| 70 | } |
| 71 | |
| 72 | // parseCaddyfileMethod sets up a basic method rewrite handler from Caddyfile tokens. Syntax: |
| 73 | // |
| 74 | // method [<matcher>] <method> |
| 75 | func parseCaddyfileMethod(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { |
| 76 | h.Next() // consume directive name |
| 77 | if !h.NextArg() { |
| 78 | return nil, h.ArgErr() |
| 79 | } |
| 80 | if h.NextArg() { |
| 81 | return nil, h.ArgErr() |
| 82 | } |
| 83 | return Rewrite{Method: h.Val()}, nil |
| 84 | } |
| 85 | |
| 86 | // parseCaddyfileURI sets up a handler for manipulating (but not "rewriting") the |
| 87 | // URI from Caddyfile tokens. Syntax: |
| 88 | // |
| 89 | // uri [<matcher>] strip_prefix|strip_suffix|replace|path_regexp <target> [<replacement> [<limit>]] |
| 90 | // |
| 91 | // If strip_prefix or strip_suffix are used, then <target> will be stripped |
| 92 | // only if it is the beginning or the end, respectively, of the URI path. If |
| 93 | // replace is used, then <target> will be replaced with <replacement> across |
| 94 | // the whole URI, up to <limit> times (or unlimited if unspecified). If |
| 95 | // path_regexp is used, then regular expression replacements will be performed |
| 96 | // on the path portion of the URI (and a limit cannot be set). |
| 97 | func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { |
| 98 | h.Next() // consume directive name |
| 99 | |
| 100 | args := h.RemainingArgs() |
| 101 | if len(args) < 1 { |
| 102 | return nil, h.ArgErr() |
| 103 | } |
| 104 | |
| 105 | var rewr Rewrite |
| 106 | |
| 107 | switch args[0] { |
| 108 | case "strip_prefix": |
| 109 | if len(args) != 2 { |
| 110 | return nil, h.ArgErr() |
| 111 | } |
| 112 | rewr.StripPathPrefix = args[1] |
| 113 | |
| 114 | case "strip_suffix": |
| 115 | if len(args) != 2 { |
| 116 | return nil, h.ArgErr() |
| 117 | } |
| 118 | rewr.StripPathSuffix = args[1] |
| 119 | |
| 120 | case "replace": |
| 121 | var find, replace, lim string |
| 122 | switch len(args) { |
| 123 | case 4: |
| 124 | lim = args[3] |
| 125 | fallthrough |
| 126 | case 3: |
| 127 | find = args[1] |
| 128 | replace = args[2] |
| 129 | default: |
| 130 | return nil, h.ArgErr() |
| 131 | } |
| 132 | |
| 133 | var limInt int |
| 134 | if lim != "" { |
| 135 | var err error |
| 136 | limInt, err = strconv.Atoi(lim) |
| 137 | if err != nil { |
| 138 | return nil, h.Errf("limit must be an integer; invalid: %v", err) |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | rewr.URISubstring = append(rewr.URISubstring, substrReplacer{ |
| 143 | Find: find, |
| 144 | Replace: replace, |
| 145 | Limit: limInt, |
| 146 | }) |
| 147 | |
| 148 | case "path_regexp": |
| 149 | if len(args) != 3 { |
| 150 | return nil, h.ArgErr() |
| 151 | } |
| 152 | find, replace := args[1], args[2] |
| 153 | rewr.PathRegexp = append(rewr.PathRegexp, ®exReplacer{ |
| 154 | Find: find, |
| 155 | Replace: replace, |
| 156 | }) |
| 157 | |
| 158 | case "query": |
| 159 | if len(args) > 4 { |
| 160 | return nil, h.ArgErr() |
| 161 | } |
| 162 | rewr.Query = &queryOps{} |
| 163 | var hasArgs bool |
| 164 | if len(args) > 1 { |
| 165 | hasArgs = true |
| 166 | err := applyQueryOps(h, rewr.Query, args[1:]) |
| 167 | if err != nil { |
| 168 | return nil, err |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | for h.NextBlock(0) { |
| 173 | if hasArgs { |
| 174 | return nil, h.Err("Cannot specify uri query rewrites in both argument and block") |
| 175 | } |
| 176 | // nolint:prealloc |
| 177 | queryArgs := []string{h.Val()} |
| 178 | queryArgs = append(queryArgs, h.RemainingArgs()...) |
| 179 | err := applyQueryOps(h, rewr.Query, queryArgs) |
| 180 | if err != nil { |
| 181 | return nil, err |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | default: |
| 186 | return nil, h.Errf("unrecognized URI manipulation '%s'", args[0]) |
| 187 | } |
| 188 | return rewr, nil |
| 189 | } |
| 190 | |
| 191 | func applyQueryOps(h httpcaddyfile.Helper, qo *queryOps, args []string) error { |
| 192 | key := args[0] |
| 193 | switch { |
| 194 | case strings.HasPrefix(key, "-"): |
| 195 | if len(args) != 1 { |
| 196 | return h.ArgErr() |
| 197 | } |
| 198 | qo.Delete = append(qo.Delete, strings.TrimLeft(key, "-")) |
| 199 | |
| 200 | case strings.HasPrefix(key, "+"): |
| 201 | if len(args) != 2 { |
| 202 | return h.ArgErr() |
| 203 | } |
| 204 | param := strings.TrimLeft(key, "+") |
| 205 | qo.Add = append(qo.Add, queryOpsArguments{Key: param, Val: args[1]}) |
| 206 | |
| 207 | case strings.Contains(key, ">"): |
| 208 | if len(args) != 1 { |
| 209 | return h.ArgErr() |
| 210 | } |
| 211 | renameValKey := strings.Split(key, ">") |
| 212 | qo.Rename = append(qo.Rename, queryOpsArguments{Key: renameValKey[0], Val: renameValKey[1]}) |
| 213 | |
| 214 | case len(args) == 3: |
| 215 | qo.Replace = append(qo.Replace, &queryOpsReplacement{Key: key, SearchRegexp: args[1], Replace: args[2]}) |
| 216 | |
| 217 | default: |
| 218 | if len(args) != 2 { |
| 219 | return h.ArgErr() |
| 220 | } |
| 221 | qo.Set = append(qo.Set, queryOpsArguments{Key: key, Val: args[1]}) |
| 222 | } |
| 223 | return nil |
| 224 | } |
| 225 | |
| 226 | // parseCaddyfileHandlePath parses the handle_path directive. Syntax: |
| 227 | // |
| 228 | // handle_path [<matcher>] { |
| 229 | // <directives...> |
| 230 | // } |
| 231 | // |
| 232 | // Only path matchers (with a `/` prefix) are supported as this is a shortcut |
| 233 | // for the handle directive with a strip_prefix rewrite. |
| 234 | func parseCaddyfileHandlePath(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { |
| 235 | h.Next() // consume directive name |
| 236 | |
| 237 | // there must be a path matcher |
| 238 | if !h.NextArg() { |
| 239 | return nil, h.ArgErr() |
| 240 | } |
| 241 | |
| 242 | // read the prefix to strip |
| 243 | path := h.Val() |
| 244 | if !strings.HasPrefix(path, "/") { |
| 245 | return nil, h.Errf("path matcher must begin with '/', got %s", path) |
| 246 | } |
| 247 | |
| 248 | // we only want to strip what comes before the '/' if |
| 249 | // the user specified it (e.g. /api/* should only strip /api) |
| 250 | var stripPath string |
| 251 | if strings.HasSuffix(path, "/*") { |
| 252 | stripPath = path[:len(path)-2] |
| 253 | } else if strings.HasSuffix(path, "*") { |
| 254 | stripPath = path[:len(path)-1] |
| 255 | } else { |
| 256 | stripPath = path |
| 257 | } |
| 258 | |
| 259 | // the ParseSegmentAsSubroute function expects the cursor |
| 260 | // to be at the token just before the block opening, |
| 261 | // so we need to rewind because we already read past it |
| 262 | h.Reset() |
| 263 | h.Next() |
| 264 | |
| 265 | // parse the block contents as a subroute handler |
| 266 | handler, err := httpcaddyfile.ParseSegmentAsSubroute(h) |
| 267 | if err != nil { |
| 268 | return nil, err |
| 269 | } |
| 270 | subroute, ok := handler.(*caddyhttp.Subroute) |
| 271 | if !ok { |
| 272 | return nil, h.Errf("segment was not parsed as a subroute") |
| 273 | } |
| 274 | |
| 275 | // make a matcher on the path and everything below it |
| 276 | pathMatcher := caddy.ModuleMap{ |
| 277 | "path": h.JSON(caddyhttp.MatchPath{path}), |
| 278 | } |
| 279 | |
| 280 | // build a route with a rewrite handler to strip the path prefix |
| 281 | route := caddyhttp.Route{ |
| 282 | HandlersRaw: []json.RawMessage{ |
| 283 | caddyconfig.JSONModuleObject(Rewrite{ |
| 284 | StripPathPrefix: stripPath, |
| 285 | }, "handler", "rewrite", nil), |
| 286 | }, |
| 287 | } |
| 288 | |
| 289 | // prepend the route to the subroute |
| 290 | subroute.Routes = append([]caddyhttp.Route{route}, subroute.Routes...) |
| 291 | |
| 292 | // build and return a route from the subroute |
| 293 | return h.NewRoute(pathMatcher, subroute), nil |
| 294 | } |