From fec7fa8bfda713e8042b9bbf9a480c7792b78c41 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Mon, 20 May 2019 10:59:20 -0600 Subject: Implement most of static file server; refactor and improve Replacer --- modules/caddyhttp/replacer.go | 164 +++++++++++++++++------------------------- 1 file changed, 64 insertions(+), 100 deletions(-) (limited to 'modules/caddyhttp/replacer.go') diff --git a/modules/caddyhttp/replacer.go b/modules/caddyhttp/replacer.go index e7aa250..6feb143 100644 --- a/modules/caddyhttp/replacer.go +++ b/modules/caddyhttp/replacer.go @@ -1,119 +1,83 @@ package caddyhttp import ( + "fmt" "net" "net/http" - "os" + "path" "strings" "bitbucket.org/lightcodelabs/caddy2" ) -// Replacer can replace values in strings based -// on a request and/or response writer. The zero -// Replacer is not valid; use NewReplacer() to -// initialize one. -type Replacer struct { - req *http.Request - resp http.ResponseWriter - custom map[string]string -} - -// NewReplacer makes a new Replacer, initializing all necessary -// fields. The request and response writer are optional, but -// necessary for most replacements to work. -func NewReplacer(req *http.Request, rw http.ResponseWriter) *Replacer { - return &Replacer{ - req: req, - resp: rw, - custom: make(map[string]string), - } -} - -// Map sets a custom variable mapping to a value. -func (r *Replacer) Map(variable, value string) { - r.custom[variable] = value -} - -// Replace replaces placeholders in input with the value. If -// the value is empty string, the placeholder is substituted -// with the value empty. -func (r *Replacer) Replace(input, empty string) string { - if !strings.Contains(input, phOpen) { - return input - } - - input = r.replaceAll(input, empty, r.defaults()) - input = r.replaceAll(input, empty, r.custom) - - return input -} - -func (r *Replacer) replaceAll(input, empty string, mapping map[string]string) string { - for key, val := range mapping { - if val == "" { - val = empty - } - input = strings.ReplaceAll(input, phOpen+key+phClose, val) - } - return input -} - -func (r *Replacer) defaults() map[string]string { - m := map[string]string{ - "system.hostname": func() string { - // OK if there is an error; just return empty string - name, _ := os.Hostname() - return name - }(), - } - - if r.req != nil { - m["request.host"] = func() string { - host, _, err := net.SplitHostPort(r.req.Host) - if err != nil { - return r.req.Host // OK; there probably was no port +// TODO: A simple way to format or escape or encode each value would be nice +// ... TODO: Should we just use templates? :-/ yeesh... + +func newReplacer(req *http.Request, w http.ResponseWriter) caddy2.Replacer { + repl := caddy2.NewReplacer() + + httpVars := func() map[string]string { + m := make(map[string]string) + if req != nil { + m["http.request.host"] = func() string { + host, _, err := net.SplitHostPort(req.Host) + if err != nil { + return req.Host // OK; there probably was no port + } + return host + }() + m["http.request.hostport"] = req.Host // may include both host and port + m["http.request.method"] = req.Method + m["http.request.port"] = func() string { + // if there is no port, there will be an error; in + // that case, port is the empty string anyway + _, port, _ := net.SplitHostPort(req.Host) + return port + }() + m["http.request.scheme"] = func() string { + if req.TLS != nil { + return "https" + } + return "http" + }() + m["http.request.uri"] = req.URL.RequestURI() + m["http.request.uri.path"] = req.URL.Path + m["http.request.uri.path.file"] = func() string { + _, file := path.Split(req.URL.Path) + return file + }() + m["http.request.uri.path.dir"] = func() string { + dir, _ := path.Split(req.URL.Path) + return dir + }() + + for field, vals := range req.Header { + m["http.request.header."+strings.ToLower(field)] = strings.Join(vals, ",") } - return host - }() - m["request.hostport"] = r.req.Host // may include both host and port - m["request.method"] = r.req.Method - m["request.port"] = func() string { - // if there is no port, there will be an error; in - // that case, port is the empty string anyway - _, port, _ := net.SplitHostPort(r.req.Host) - return port - }() - m["request.scheme"] = func() string { - if r.req.TLS != nil { - return "https" + for _, cookie := range req.Cookies() { + m["http.request.cookie."+cookie.Name] = cookie.Value + } + for param, vals := range req.URL.Query() { + m["http.request.uri.query."+param] = strings.Join(vals, ",") } - return "http" - }() - m["request.uri"] = r.req.URL.RequestURI() - m["request.uri.path"] = r.req.URL.Path - for field, vals := range r.req.Header { - m["request.header."+strings.ToLower(field)] = strings.Join(vals, ",") - } - for _, cookie := range r.req.Cookies() { - m["request.cookie."+cookie.Name] = cookie.Value - } - for param, vals := range r.req.URL.Query() { - m["request.uri.query."+param] = strings.Join(vals, ",") + hostLabels := strings.Split(req.Host, ".") + for i, label := range hostLabels { + key := fmt.Sprintf("http.request.host.labels.%d", len(hostLabels)-i-1) + m[key] = label + } } - } - if r.resp != nil { - for field, vals := range r.resp.Header() { - m["response.header."+strings.ToLower(field)] = strings.Join(vals, ",") + if w != nil { + for field, vals := range w.Header() { + m["http.response.header."+strings.ToLower(field)] = strings.Join(vals, ",") + } } - } - return m -} + return m + } -const phOpen, phClose = "{", "}" + repl.Map(httpVars) -// ReplacerCtxKey is the context key for the request's replacer. -const ReplacerCtxKey caddy2.CtxKey = "replacer" + return repl +} -- cgit v1.2.3