| 
									
										
										
										
											2020-06-27 09:12:37 +12:00
										 |  |  | // 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 maphandler | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2020-06-27 09:12:37 +12:00
										 |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"regexp" | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2020-06-27 09:12:37 +12:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/caddyserver/caddy/v2" | 
					
						
							|  |  |  | 	"github.com/caddyserver/caddy/v2/modules/caddyhttp" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func init() { | 
					
						
							|  |  |  | 	caddy.RegisterModule(Handler{}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | // Handler implements a middleware that maps inputs to outputs. Specifically, it | 
					
						
							|  |  |  | // compares a source value against the map inputs, and for one that matches, it | 
					
						
							|  |  |  | // applies the output values to each destination. Destinations become placeholder | 
					
						
							|  |  |  | // names. | 
					
						
							| 
									
										
										
										
											2020-06-27 09:12:37 +12:00
										 |  |  | // | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | // Mapped placeholders are not evaluated until they are used, so even for very | 
					
						
							|  |  |  | // large mappings, this handler is quite efficient. | 
					
						
							| 
									
										
										
										
											2020-06-27 09:12:37 +12:00
										 |  |  | type Handler struct { | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 	// Source is the placeholder from which to get the input value. | 
					
						
							| 
									
										
										
										
											2020-06-27 09:12:37 +12:00
										 |  |  | 	Source string `json:"source,omitempty"` | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 15:23:52 -06:00
										 |  |  | 	// Destinations are the names of placeholders in which to store the outputs. | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 	Destinations []string `json:"destinations,omitempty"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Mappings from source values (inputs) to destination values (outputs). | 
					
						
							| 
									
										
										
										
											2020-10-02 16:08:28 -06:00
										 |  |  | 	// The first matching, non-nil mapping will be applied. | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 	Mappings []Mapping `json:"mappings,omitempty"` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 15:23:52 -06:00
										 |  |  | 	// If no mappings match or if the mapped output is null/nil, the associated | 
					
						
							|  |  |  | 	// default output will be applied (optional). | 
					
						
							| 
									
										
										
										
											2021-01-16 09:56:06 -07:00
										 |  |  | 	Defaults []string `json:"defaults,omitempty"` | 
					
						
							| 
									
										
										
										
											2020-06-27 09:12:37 +12:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // CaddyModule returns the Caddy module information. | 
					
						
							|  |  |  | func (Handler) CaddyModule() caddy.ModuleInfo { | 
					
						
							|  |  |  | 	return caddy.ModuleInfo{ | 
					
						
							|  |  |  | 		ID:  "http.handlers.map", | 
					
						
							|  |  |  | 		New: func() caddy.Module { return new(Handler) }, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | // Provision sets up h. | 
					
						
							| 
									
										
										
										
											2020-06-27 09:12:37 +12:00
										 |  |  | func (h *Handler) Provision(_ caddy.Context) error { | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 	for j, dest := range h.Destinations { | 
					
						
							| 
									
										
										
										
											2022-09-01 21:15:20 -06:00
										 |  |  | 		if strings.Count(dest, "{") != 1 || !strings.HasPrefix(dest, "{") { | 
					
						
							|  |  |  | 			return fmt.Errorf("destination must be a placeholder and only a placeholder") | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 		h.Destinations[j] = strings.Trim(dest, "{}") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i, m := range h.Mappings { | 
					
						
							|  |  |  | 		if m.InputRegexp == "" { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		var err error | 
					
						
							|  |  |  | 		h.Mappings[i].re, err = regexp.Compile(m.InputRegexp) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return fmt.Errorf("compiling regexp for mapping %d: %v", i, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// TODO: improve efficiency even further by using an actual map type | 
					
						
							|  |  |  | 	// for the non-regexp mappings, OR sort them and do a binary search | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Validate ensures that h is configured properly. | 
					
						
							|  |  |  | func (h *Handler) Validate() error { | 
					
						
							|  |  |  | 	nDest, nDef := len(h.Destinations), len(h.Defaults) | 
					
						
							|  |  |  | 	if nDef > 0 && nDef != nDest { | 
					
						
							|  |  |  | 		return fmt.Errorf("%d destinations != %d defaults", nDest, nDef) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	seen := make(map[string]int) | 
					
						
							|  |  |  | 	for i, m := range h.Mappings { | 
					
						
							| 
									
										
										
										
											2020-10-02 16:08:28 -06:00
										 |  |  | 		// prevent confusing/ambiguous mappings | 
					
						
							|  |  |  | 		if m.Input != "" && m.InputRegexp != "" { | 
					
						
							|  |  |  | 			return fmt.Errorf("mapping %d has both input and input_regexp fields specified, which is confusing", i) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 		// prevent duplicate mappings | 
					
						
							| 
									
										
										
										
											2020-10-02 16:08:28 -06:00
										 |  |  | 		input := m.Input | 
					
						
							|  |  |  | 		if m.InputRegexp != "" { | 
					
						
							|  |  |  | 			input = m.InputRegexp | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if prev, ok := seen[input]; ok { | 
					
						
							|  |  |  | 			return fmt.Errorf("mapping %d has a duplicate input '%s' previously used with mapping %d", i, input, prev) | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-10-02 16:08:28 -06:00
										 |  |  | 		seen[input] = i | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-01 21:15:20 -06:00
										 |  |  | 		// prevent infinite recursion | 
					
						
							|  |  |  | 		for _, out := range m.Outputs { | 
					
						
							|  |  |  | 			for _, dest := range h.Destinations { | 
					
						
							|  |  |  | 				if strings.Contains(caddy.ToString(out), dest) || | 
					
						
							|  |  |  | 					strings.Contains(m.Input, dest) { | 
					
						
							|  |  |  | 					return fmt.Errorf("mapping %d requires value of {%s} to define value of {%s}: infinite recursion", i, dest, dest) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 		// ensure mappings have 1:1 output-to-destination correspondence | 
					
						
							|  |  |  | 		nOut := len(m.Outputs) | 
					
						
							|  |  |  | 		if nOut != nDest { | 
					
						
							|  |  |  | 			return fmt.Errorf("mapping %d has %d outputs but there are %d destinations defined", i, nOut, nDest) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-06-27 09:12:37 +12:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { | 
					
						
							|  |  |  | 	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 	// defer work until a variable is actually evaluated by using replacer's Map callback | 
					
						
							| 
									
										
										
										
											2022-08-02 16:39:09 -04:00
										 |  |  | 	repl.Map(func(key string) (any, bool) { | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 		// return early if the variable is not even a configured destination | 
					
						
							|  |  |  | 		destIdx := h.destinationIndex(key) | 
					
						
							|  |  |  | 		if destIdx < 0 { | 
					
						
							|  |  |  | 			return nil, false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		input := repl.ReplaceAll(h.Source, "") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// find the first mapping matching the input and return | 
					
						
							|  |  |  | 		// the requested destination/output value | 
					
						
							|  |  |  | 		for _, m := range h.Mappings { | 
					
						
							| 
									
										
										
										
											2021-10-19 12:25:36 -06:00
										 |  |  | 			output := m.Outputs[destIdx] | 
					
						
							|  |  |  | 			if output == nil { | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-09-01 21:15:20 -06:00
										 |  |  | 			outputStr := caddy.ToString(output) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// evaluate regular expression if configured | 
					
						
							| 
									
										
										
										
											2021-10-19 12:25:36 -06:00
										 |  |  | 			if m.re != nil { | 
					
						
							|  |  |  | 				var result []byte | 
					
						
							|  |  |  | 				matches := m.re.FindStringSubmatchIndex(input) | 
					
						
							|  |  |  | 				if matches == nil { | 
					
						
							| 
									
										
										
										
											2020-10-02 16:08:28 -06:00
										 |  |  | 					continue | 
					
						
							| 
									
										
										
										
											2020-10-02 15:23:52 -06:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-09-01 21:15:20 -06:00
										 |  |  | 				result = m.re.ExpandString(result, outputStr, input, matches) | 
					
						
							| 
									
										
										
										
											2021-10-19 12:25:36 -06:00
										 |  |  | 				return string(result), true | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-09-01 21:15:20 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// otherwise simple string comparison | 
					
						
							| 
									
										
										
										
											2021-10-19 12:25:36 -06:00
										 |  |  | 			if input == m.Input { | 
					
						
							| 
									
										
										
										
											2022-09-01 21:15:20 -06:00
										 |  |  | 				return repl.ReplaceAll(outputStr, ""), true | 
					
						
							| 
									
										
										
										
											2020-06-27 09:12:37 +12:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 15:23:52 -06:00
										 |  |  | 		// fall back to default if no match or if matched nil value | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 		if len(h.Defaults) > destIdx { | 
					
						
							|  |  |  | 			return h.Defaults[destIdx], true | 
					
						
							| 
									
										
										
										
											2020-06-27 09:12:37 +12:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		return nil, true | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-27 09:12:37 +12:00
										 |  |  | 	return next.ServeHTTP(w, r) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | // destinationIndex returns the positional index of the destination | 
					
						
							|  |  |  | // is name is a known destination; otherwise it returns -1. | 
					
						
							|  |  |  | func (h Handler) destinationIndex(name string) int { | 
					
						
							|  |  |  | 	for i, dest := range h.Destinations { | 
					
						
							|  |  |  | 		if dest == name { | 
					
						
							|  |  |  | 			return i | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return -1 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Mapping describes a mapping from input to outputs. | 
					
						
							|  |  |  | type Mapping struct { | 
					
						
							|  |  |  | 	// The input value to match. Must be distinct from other mappings. | 
					
						
							|  |  |  | 	// Mutually exclusive to input_regexp. | 
					
						
							|  |  |  | 	Input string `json:"input,omitempty"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// The input regular expression to match. Mutually exclusive to input. | 
					
						
							|  |  |  | 	InputRegexp string `json:"input_regexp,omitempty"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Upon a match with the input, each output is positionally correlated | 
					
						
							| 
									
										
										
										
											2020-10-02 16:08:28 -06:00
										 |  |  | 	// with each destination of the parent handler. An output that is null | 
					
						
							|  |  |  | 	// (nil) will be treated as if it was not mapped at all. | 
					
						
							| 
									
										
										
										
											2022-08-02 16:39:09 -04:00
										 |  |  | 	Outputs []any `json:"outputs,omitempty"` | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	re *regexp.Regexp | 
					
						
							| 
									
										
										
										
											2020-06-27 09:12:37 +12:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Interface guards | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	_ caddy.Provisioner           = (*Handler)(nil) | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 	_ caddy.Validator             = (*Handler)(nil) | 
					
						
							| 
									
										
										
										
											2020-06-27 09:12:37 +12:00
										 |  |  | 	_ caddyhttp.MiddlewareHandler = (*Handler)(nil) | 
					
						
							|  |  |  | ) |