@marcel91200 / caddy Mirror · read-only
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, 01:00 UTC

master
go 294 lines 8.03 KB
Raw
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, &regexReplacer{
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 }