summaryrefslogtreecommitdiff
path: root/modules/caddyhttp/rewrite/caddyfile.go
blob: a34c1bb08fdabcedd3ab48281804cd3833956a3f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package rewrite

import (
	"encoding/json"
	"strconv"
	"strings"

	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/caddyconfig"
	"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)

func init() {
	httpcaddyfile.RegisterHandlerDirective("rewrite", parseCaddyfileRewrite)
	httpcaddyfile.RegisterHandlerDirective("method", parseCaddyfileMethod)
	httpcaddyfile.RegisterHandlerDirective("uri", parseCaddyfileURI)
	httpcaddyfile.RegisterDirective("handle_path", parseCaddyfileHandlePath)
}

// 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.
func parseCaddyfileRewrite(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
	var rewr Rewrite
	for h.Next() {
		if !h.NextArg() {
			return nil, h.ArgErr()
		}
		rewr.URI = h.Val()
		if h.NextArg() {
			return nil, h.ArgErr()
		}
	}
	return rewr, nil
}

// parseCaddyfileMethod sets up a basic method rewrite handler from Caddyfile tokens. Syntax:
//
//	method [<matcher>] <method>
func parseCaddyfileMethod(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
	var rewr Rewrite
	for h.Next() {
		if !h.NextArg() {
			return nil, h.ArgErr()
		}
		rewr.Method = h.Val()
		if h.NextArg() {
			return nil, h.ArgErr()
		}
	}
	return rewr, nil
}

// 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).
func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
	var rewr Rewrite
	for h.Next() {
		args := h.RemainingArgs()
		if len(args) < 2 {
			return nil, h.ArgErr()
		}
		switch args[0] {
		case "strip_prefix":
			if len(args) > 2 {
				return nil, h.ArgErr()
			}
			rewr.StripPathPrefix = args[1]
			if !strings.HasPrefix(rewr.StripPathPrefix, "/") {
				rewr.StripPathPrefix = "/" + rewr.StripPathPrefix
			}
		case "strip_suffix":
			if len(args) > 2 {
				return nil, h.ArgErr()
			}
			rewr.StripPathSuffix = args[1]
		case "replace":
			var find, replace, lim string
			switch len(args) {
			case 4:
				lim = args[3]
				fallthrough
			case 3:
				find = args[1]
				replace = args[2]
			default:
				return nil, h.ArgErr()
			}

			var limInt int
			if lim != "" {
				var err error
				limInt, err = strconv.Atoi(lim)
				if err != nil {
					return nil, h.Errf("limit must be an integer; invalid: %v", err)
				}
			}

			rewr.URISubstring = append(rewr.URISubstring, substrReplacer{
				Find:    find,
				Replace: replace,
				Limit:   limInt,
			})
		case "path_regexp":
			if len(args) != 3 {
				return nil, h.ArgErr()
			}
			find, replace := args[1], args[2]
			rewr.PathRegexp = append(rewr.PathRegexp, &regexReplacer{
				Find:    find,
				Replace: replace,
			})
		default:
			return nil, h.Errf("unrecognized URI manipulation '%s'", args[0])
		}
	}
	return rewr, nil
}

// 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.
func parseCaddyfileHandlePath(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
	if !h.Next() {
		return nil, h.ArgErr()
	}
	if !h.NextArg() {
		return nil, h.ArgErr()
	}

	// read the prefix to strip
	path := h.Val()
	if !strings.HasPrefix(path, "/") {
		return nil, h.Errf("path matcher must begin with '/', got %s", path)
	}

	// we only want to strip what comes before the '/' if
	// the user specified it (e.g. /api/* should only strip /api)
	var stripPath string
	if strings.HasSuffix(path, "/*") {
		stripPath = path[:len(path)-2]
	} else if strings.HasSuffix(path, "*") {
		stripPath = path[:len(path)-1]
	} else {
		stripPath = path
	}

	// the ParseSegmentAsSubroute function expects the cursor
	// to be at the token just before the block opening,
	// so we need to rewind because we already read past it
	h.Reset()
	h.Next()

	// parse the block contents as a subroute handler
	handler, err := httpcaddyfile.ParseSegmentAsSubroute(h)
	if err != nil {
		return nil, err
	}
	subroute, ok := handler.(*caddyhttp.Subroute)
	if !ok {
		return nil, h.Errf("segment was not parsed as a subroute")
	}

	// make a matcher on the path and everything below it
	pathMatcher := caddy.ModuleMap{
		"path": h.JSON(caddyhttp.MatchPath{path}),
	}

	// build a route with a rewrite handler to strip the path prefix
	route := caddyhttp.Route{
		HandlersRaw: []json.RawMessage{
			caddyconfig.JSONModuleObject(Rewrite{
				StripPathPrefix: stripPath,
			}, "handler", "rewrite", nil),
		},
	}

	// prepend the route to the subroute
	subroute.Routes = append([]caddyhttp.Route{route}, subroute.Routes...)

	// build and return a route from the subroute
	return h.NewRoute(pathMatcher, subroute), nil
}