 9bdcbe0447
			
		
	
	9bdcbe0447
	
	
	
		
			
			Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			389 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			389 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2012 The Gorilla Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package mux
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| type routeRegexpOptions struct {
 | |
| 	strictSlash    bool
 | |
| 	useEncodedPath bool
 | |
| }
 | |
| 
 | |
| type regexpType int
 | |
| 
 | |
| const (
 | |
| 	regexpTypePath regexpType = iota
 | |
| 	regexpTypeHost
 | |
| 	regexpTypePrefix
 | |
| 	regexpTypeQuery
 | |
| )
 | |
| 
 | |
| // newRouteRegexp parses a route template and returns a routeRegexp,
 | |
| // used to match a host, a path or a query string.
 | |
| //
 | |
| // It will extract named variables, assemble a regexp to be matched, create
 | |
| // a "reverse" template to build URLs and compile regexps to validate variable
 | |
| // values used in URL building.
 | |
| //
 | |
| // Previously we accepted only Python-like identifiers for variable
 | |
| // names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
 | |
| // name and pattern can't be empty, and names can't contain a colon.
 | |
| func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*routeRegexp, error) {
 | |
| 	// Check if it is well-formed.
 | |
| 	idxs, errBraces := braceIndices(tpl)
 | |
| 	if errBraces != nil {
 | |
| 		return nil, errBraces
 | |
| 	}
 | |
| 	// Backup the original.
 | |
| 	template := tpl
 | |
| 	// Now let's parse it.
 | |
| 	defaultPattern := "[^/]+"
 | |
| 	if typ == regexpTypeQuery {
 | |
| 		defaultPattern = ".*"
 | |
| 	} else if typ == regexpTypeHost {
 | |
| 		defaultPattern = "[^.]+"
 | |
| 	}
 | |
| 	// Only match strict slash if not matching
 | |
| 	if typ != regexpTypePath {
 | |
| 		options.strictSlash = false
 | |
| 	}
 | |
| 	// Set a flag for strictSlash.
 | |
| 	endSlash := false
 | |
| 	if options.strictSlash && strings.HasSuffix(tpl, "/") {
 | |
| 		tpl = tpl[:len(tpl)-1]
 | |
| 		endSlash = true
 | |
| 	}
 | |
| 	varsN := make([]string, len(idxs)/2)
 | |
| 	varsR := make([]*regexp.Regexp, len(idxs)/2)
 | |
| 	pattern := bytes.NewBufferString("")
 | |
| 	pattern.WriteByte('^')
 | |
| 	reverse := bytes.NewBufferString("")
 | |
| 	var end int
 | |
| 	var err error
 | |
| 	for i := 0; i < len(idxs); i += 2 {
 | |
| 		// Set all values we are interested in.
 | |
| 		raw := tpl[end:idxs[i]]
 | |
| 		end = idxs[i+1]
 | |
| 		parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)
 | |
| 		name := parts[0]
 | |
| 		patt := defaultPattern
 | |
| 		if len(parts) == 2 {
 | |
| 			patt = parts[1]
 | |
| 		}
 | |
| 		// Name or pattern can't be empty.
 | |
| 		if name == "" || patt == "" {
 | |
| 			return nil, fmt.Errorf("mux: missing name or pattern in %q",
 | |
| 				tpl[idxs[i]:end])
 | |
| 		}
 | |
| 		// Build the regexp pattern.
 | |
| 		fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
 | |
| 
 | |
| 		// Build the reverse template.
 | |
| 		fmt.Fprintf(reverse, "%s%%s", raw)
 | |
| 
 | |
| 		// Append variable name and compiled pattern.
 | |
| 		varsN[i/2] = name
 | |
| 		varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 	// Add the remaining.
 | |
| 	raw := tpl[end:]
 | |
| 	pattern.WriteString(regexp.QuoteMeta(raw))
 | |
| 	if options.strictSlash {
 | |
| 		pattern.WriteString("[/]?")
 | |
| 	}
 | |
| 	if typ == regexpTypeQuery {
 | |
| 		// Add the default pattern if the query value is empty
 | |
| 		if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" {
 | |
| 			pattern.WriteString(defaultPattern)
 | |
| 		}
 | |
| 	}
 | |
| 	if typ != regexpTypePrefix {
 | |
| 		pattern.WriteByte('$')
 | |
| 	}
 | |
| 
 | |
| 	var wildcardHostPort bool
 | |
| 	if typ == regexpTypeHost {
 | |
| 		if !strings.Contains(pattern.String(), ":") {
 | |
| 			wildcardHostPort = true
 | |
| 		}
 | |
| 	}
 | |
| 	reverse.WriteString(raw)
 | |
| 	if endSlash {
 | |
| 		reverse.WriteByte('/')
 | |
| 	}
 | |
| 	// Compile full regexp.
 | |
| 	reg, errCompile := regexp.Compile(pattern.String())
 | |
| 	if errCompile != nil {
 | |
| 		return nil, errCompile
 | |
| 	}
 | |
| 
 | |
| 	// Check for capturing groups which used to work in older versions
 | |
| 	if reg.NumSubexp() != len(idxs)/2 {
 | |
| 		panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) +
 | |
| 			"Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")
 | |
| 	}
 | |
| 
 | |
| 	// Done!
 | |
| 	return &routeRegexp{
 | |
| 		template:         template,
 | |
| 		regexpType:       typ,
 | |
| 		options:          options,
 | |
| 		regexp:           reg,
 | |
| 		reverse:          reverse.String(),
 | |
| 		varsN:            varsN,
 | |
| 		varsR:            varsR,
 | |
| 		wildcardHostPort: wildcardHostPort,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // routeRegexp stores a regexp to match a host or path and information to
 | |
| // collect and validate route variables.
 | |
| type routeRegexp struct {
 | |
| 	// The unmodified template.
 | |
| 	template string
 | |
| 	// The type of match
 | |
| 	regexpType regexpType
 | |
| 	// Options for matching
 | |
| 	options routeRegexpOptions
 | |
| 	// Expanded regexp.
 | |
| 	regexp *regexp.Regexp
 | |
| 	// Reverse template.
 | |
| 	reverse string
 | |
| 	// Variable names.
 | |
| 	varsN []string
 | |
| 	// Variable regexps (validators).
 | |
| 	varsR []*regexp.Regexp
 | |
| 	// Wildcard host-port (no strict port match in hostname)
 | |
| 	wildcardHostPort bool
 | |
| }
 | |
| 
 | |
| // Match matches the regexp against the URL host or path.
 | |
| func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
 | |
| 	if r.regexpType == regexpTypeHost {
 | |
| 		host := getHost(req)
 | |
| 		if r.wildcardHostPort {
 | |
| 			// Don't be strict on the port match
 | |
| 			if i := strings.Index(host, ":"); i != -1 {
 | |
| 				host = host[:i]
 | |
| 			}
 | |
| 		}
 | |
| 		return r.regexp.MatchString(host)
 | |
| 	}
 | |
| 
 | |
| 	if r.regexpType == regexpTypeQuery {
 | |
| 		return r.matchQueryString(req)
 | |
| 	}
 | |
| 	path := req.URL.Path
 | |
| 	if r.options.useEncodedPath {
 | |
| 		path = req.URL.EscapedPath()
 | |
| 	}
 | |
| 	return r.regexp.MatchString(path)
 | |
| }
 | |
| 
 | |
| // url builds a URL part using the given values.
 | |
| func (r *routeRegexp) url(values map[string]string) (string, error) {
 | |
| 	urlValues := make([]interface{}, len(r.varsN))
 | |
| 	for k, v := range r.varsN {
 | |
| 		value, ok := values[v]
 | |
| 		if !ok {
 | |
| 			return "", fmt.Errorf("mux: missing route variable %q", v)
 | |
| 		}
 | |
| 		if r.regexpType == regexpTypeQuery {
 | |
| 			value = url.QueryEscape(value)
 | |
| 		}
 | |
| 		urlValues[k] = value
 | |
| 	}
 | |
| 	rv := fmt.Sprintf(r.reverse, urlValues...)
 | |
| 	if !r.regexp.MatchString(rv) {
 | |
| 		// The URL is checked against the full regexp, instead of checking
 | |
| 		// individual variables. This is faster but to provide a good error
 | |
| 		// message, we check individual regexps if the URL doesn't match.
 | |
| 		for k, v := range r.varsN {
 | |
| 			if !r.varsR[k].MatchString(values[v]) {
 | |
| 				return "", fmt.Errorf(
 | |
| 					"mux: variable %q doesn't match, expected %q", values[v],
 | |
| 					r.varsR[k].String())
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return rv, nil
 | |
| }
 | |
| 
 | |
| // getURLQuery returns a single query parameter from a request URL.
 | |
| // For a URL with foo=bar&baz=ding, we return only the relevant key
 | |
| // value pair for the routeRegexp.
 | |
| func (r *routeRegexp) getURLQuery(req *http.Request) string {
 | |
| 	if r.regexpType != regexpTypeQuery {
 | |
| 		return ""
 | |
| 	}
 | |
| 	templateKey := strings.SplitN(r.template, "=", 2)[0]
 | |
| 	val, ok := findFirstQueryKey(req.URL.RawQuery, templateKey)
 | |
| 	if ok {
 | |
| 		return templateKey + "=" + val
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| // findFirstQueryKey returns the same result as (*url.URL).Query()[key][0].
 | |
| // If key was not found, empty string and false is returned.
 | |
| func findFirstQueryKey(rawQuery, key string) (value string, ok bool) {
 | |
| 	query := []byte(rawQuery)
 | |
| 	for len(query) > 0 {
 | |
| 		foundKey := query
 | |
| 		if i := bytes.IndexAny(foundKey, "&;"); i >= 0 {
 | |
| 			foundKey, query = foundKey[:i], foundKey[i+1:]
 | |
| 		} else {
 | |
| 			query = query[:0]
 | |
| 		}
 | |
| 		if len(foundKey) == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		var value []byte
 | |
| 		if i := bytes.IndexByte(foundKey, '='); i >= 0 {
 | |
| 			foundKey, value = foundKey[:i], foundKey[i+1:]
 | |
| 		}
 | |
| 		if len(foundKey) < len(key) {
 | |
| 			// Cannot possibly be key.
 | |
| 			continue
 | |
| 		}
 | |
| 		keyString, err := url.QueryUnescape(string(foundKey))
 | |
| 		if err != nil {
 | |
| 			continue
 | |
| 		}
 | |
| 		if keyString != key {
 | |
| 			continue
 | |
| 		}
 | |
| 		valueString, err := url.QueryUnescape(string(value))
 | |
| 		if err != nil {
 | |
| 			continue
 | |
| 		}
 | |
| 		return valueString, true
 | |
| 	}
 | |
| 	return "", false
 | |
| }
 | |
| 
 | |
| func (r *routeRegexp) matchQueryString(req *http.Request) bool {
 | |
| 	return r.regexp.MatchString(r.getURLQuery(req))
 | |
| }
 | |
| 
 | |
| // braceIndices returns the first level curly brace indices from a string.
 | |
| // It returns an error in case of unbalanced braces.
 | |
| func braceIndices(s string) ([]int, error) {
 | |
| 	var level, idx int
 | |
| 	var idxs []int
 | |
| 	for i := 0; i < len(s); i++ {
 | |
| 		switch s[i] {
 | |
| 		case '{':
 | |
| 			if level++; level == 1 {
 | |
| 				idx = i
 | |
| 			}
 | |
| 		case '}':
 | |
| 			if level--; level == 0 {
 | |
| 				idxs = append(idxs, idx, i+1)
 | |
| 			} else if level < 0 {
 | |
| 				return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if level != 0 {
 | |
| 		return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
 | |
| 	}
 | |
| 	return idxs, nil
 | |
| }
 | |
| 
 | |
| // varGroupName builds a capturing group name for the indexed variable.
 | |
| func varGroupName(idx int) string {
 | |
| 	return "v" + strconv.Itoa(idx)
 | |
| }
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| // routeRegexpGroup
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| // routeRegexpGroup groups the route matchers that carry variables.
 | |
| type routeRegexpGroup struct {
 | |
| 	host    *routeRegexp
 | |
| 	path    *routeRegexp
 | |
| 	queries []*routeRegexp
 | |
| }
 | |
| 
 | |
| // setMatch extracts the variables from the URL once a route matches.
 | |
| func (v routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
 | |
| 	// Store host variables.
 | |
| 	if v.host != nil {
 | |
| 		host := getHost(req)
 | |
| 		if v.host.wildcardHostPort {
 | |
| 			// Don't be strict on the port match
 | |
| 			if i := strings.Index(host, ":"); i != -1 {
 | |
| 				host = host[:i]
 | |
| 			}
 | |
| 		}
 | |
| 		matches := v.host.regexp.FindStringSubmatchIndex(host)
 | |
| 		if len(matches) > 0 {
 | |
| 			extractVars(host, matches, v.host.varsN, m.Vars)
 | |
| 		}
 | |
| 	}
 | |
| 	path := req.URL.Path
 | |
| 	if r.useEncodedPath {
 | |
| 		path = req.URL.EscapedPath()
 | |
| 	}
 | |
| 	// Store path variables.
 | |
| 	if v.path != nil {
 | |
| 		matches := v.path.regexp.FindStringSubmatchIndex(path)
 | |
| 		if len(matches) > 0 {
 | |
| 			extractVars(path, matches, v.path.varsN, m.Vars)
 | |
| 			// Check if we should redirect.
 | |
| 			if v.path.options.strictSlash {
 | |
| 				p1 := strings.HasSuffix(path, "/")
 | |
| 				p2 := strings.HasSuffix(v.path.template, "/")
 | |
| 				if p1 != p2 {
 | |
| 					u, _ := url.Parse(req.URL.String())
 | |
| 					if p1 {
 | |
| 						u.Path = u.Path[:len(u.Path)-1]
 | |
| 					} else {
 | |
| 						u.Path += "/"
 | |
| 					}
 | |
| 					m.Handler = http.RedirectHandler(u.String(), http.StatusMovedPermanently)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	// Store query string variables.
 | |
| 	for _, q := range v.queries {
 | |
| 		queryURL := q.getURLQuery(req)
 | |
| 		matches := q.regexp.FindStringSubmatchIndex(queryURL)
 | |
| 		if len(matches) > 0 {
 | |
| 			extractVars(queryURL, matches, q.varsN, m.Vars)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // getHost tries its best to return the request host.
 | |
| // According to section 14.23 of RFC 2616 the Host header
 | |
| // can include the port number if the default value of 80 is not used.
 | |
| func getHost(r *http.Request) string {
 | |
| 	if r.URL.IsAbs() {
 | |
| 		return r.URL.Host
 | |
| 	}
 | |
| 	return r.Host
 | |
| }
 | |
| 
 | |
| func extractVars(input string, matches []int, names []string, output map[string]string) {
 | |
| 	for i, name := range names {
 | |
| 		output[name] = input[matches[2*i+2]:matches[2*i+3]]
 | |
| 	}
 | |
| }
 |