mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 13:32:25 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			381 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			381 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package runtime
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
 | |
| 	"google.golang.org/grpc/grpclog"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	// ErrNotMatch indicates that the given HTTP request path does not match to the pattern.
 | |
| 	ErrNotMatch = errors.New("not match to the path pattern")
 | |
| 	// ErrInvalidPattern indicates that the given definition of Pattern is not valid.
 | |
| 	ErrInvalidPattern = errors.New("invalid pattern")
 | |
| )
 | |
| 
 | |
| type MalformedSequenceError string
 | |
| 
 | |
| func (e MalformedSequenceError) Error() string {
 | |
| 	return "malformed path escape " + strconv.Quote(string(e))
 | |
| }
 | |
| 
 | |
| type op struct {
 | |
| 	code    utilities.OpCode
 | |
| 	operand int
 | |
| }
 | |
| 
 | |
| // Pattern is a template pattern of http request paths defined in
 | |
| // https://github.com/googleapis/googleapis/blob/master/google/api/http.proto
 | |
| type Pattern struct {
 | |
| 	// ops is a list of operations
 | |
| 	ops []op
 | |
| 	// pool is a constant pool indexed by the operands or vars.
 | |
| 	pool []string
 | |
| 	// vars is a list of variables names to be bound by this pattern
 | |
| 	vars []string
 | |
| 	// stacksize is the max depth of the stack
 | |
| 	stacksize int
 | |
| 	// tailLen is the length of the fixed-size segments after a deep wildcard
 | |
| 	tailLen int
 | |
| 	// verb is the VERB part of the path pattern. It is empty if the pattern does not have VERB part.
 | |
| 	verb string
 | |
| }
 | |
| 
 | |
| // NewPattern returns a new Pattern from the given definition values.
 | |
| // "ops" is a sequence of op codes. "pool" is a constant pool.
 | |
| // "verb" is the verb part of the pattern. It is empty if the pattern does not have the part.
 | |
| // "version" must be 1 for now.
 | |
| // It returns an error if the given definition is invalid.
 | |
| func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, error) {
 | |
| 	if version != 1 {
 | |
| 		grpclog.Errorf("unsupported version: %d", version)
 | |
| 		return Pattern{}, ErrInvalidPattern
 | |
| 	}
 | |
| 
 | |
| 	l := len(ops)
 | |
| 	if l%2 != 0 {
 | |
| 		grpclog.Errorf("odd number of ops codes: %d", l)
 | |
| 		return Pattern{}, ErrInvalidPattern
 | |
| 	}
 | |
| 
 | |
| 	var (
 | |
| 		typedOps        []op
 | |
| 		stack, maxstack int
 | |
| 		tailLen         int
 | |
| 		pushMSeen       bool
 | |
| 		vars            []string
 | |
| 	)
 | |
| 	for i := 0; i < l; i += 2 {
 | |
| 		op := op{code: utilities.OpCode(ops[i]), operand: ops[i+1]}
 | |
| 		switch op.code {
 | |
| 		case utilities.OpNop:
 | |
| 			continue
 | |
| 		case utilities.OpPush:
 | |
| 			if pushMSeen {
 | |
| 				tailLen++
 | |
| 			}
 | |
| 			stack++
 | |
| 		case utilities.OpPushM:
 | |
| 			if pushMSeen {
 | |
| 				grpclog.Error("pushM appears twice")
 | |
| 				return Pattern{}, ErrInvalidPattern
 | |
| 			}
 | |
| 			pushMSeen = true
 | |
| 			stack++
 | |
| 		case utilities.OpLitPush:
 | |
| 			if op.operand < 0 || len(pool) <= op.operand {
 | |
| 				grpclog.Errorf("negative literal index: %d", op.operand)
 | |
| 				return Pattern{}, ErrInvalidPattern
 | |
| 			}
 | |
| 			if pushMSeen {
 | |
| 				tailLen++
 | |
| 			}
 | |
| 			stack++
 | |
| 		case utilities.OpConcatN:
 | |
| 			if op.operand <= 0 {
 | |
| 				grpclog.Errorf("negative concat size: %d", op.operand)
 | |
| 				return Pattern{}, ErrInvalidPattern
 | |
| 			}
 | |
| 			stack -= op.operand
 | |
| 			if stack < 0 {
 | |
| 				grpclog.Error("stack underflow")
 | |
| 				return Pattern{}, ErrInvalidPattern
 | |
| 			}
 | |
| 			stack++
 | |
| 		case utilities.OpCapture:
 | |
| 			if op.operand < 0 || len(pool) <= op.operand {
 | |
| 				grpclog.Errorf("variable name index out of bound: %d", op.operand)
 | |
| 				return Pattern{}, ErrInvalidPattern
 | |
| 			}
 | |
| 			v := pool[op.operand]
 | |
| 			op.operand = len(vars)
 | |
| 			vars = append(vars, v)
 | |
| 			stack--
 | |
| 			if stack < 0 {
 | |
| 				grpclog.Error("stack underflow")
 | |
| 				return Pattern{}, ErrInvalidPattern
 | |
| 			}
 | |
| 		default:
 | |
| 			grpclog.Errorf("invalid opcode: %d", op.code)
 | |
| 			return Pattern{}, ErrInvalidPattern
 | |
| 		}
 | |
| 
 | |
| 		if maxstack < stack {
 | |
| 			maxstack = stack
 | |
| 		}
 | |
| 		typedOps = append(typedOps, op)
 | |
| 	}
 | |
| 	return Pattern{
 | |
| 		ops:       typedOps,
 | |
| 		pool:      pool,
 | |
| 		vars:      vars,
 | |
| 		stacksize: maxstack,
 | |
| 		tailLen:   tailLen,
 | |
| 		verb:      verb,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // MustPattern is a helper function which makes it easier to call NewPattern in variable initialization.
 | |
| func MustPattern(p Pattern, err error) Pattern {
 | |
| 	if err != nil {
 | |
| 		grpclog.Fatalf("Pattern initialization failed: %v", err)
 | |
| 	}
 | |
| 	return p
 | |
| }
 | |
| 
 | |
| // MatchAndEscape examines components to determine if they match to a Pattern.
 | |
| // MatchAndEscape will return an error if no Patterns matched or if a pattern
 | |
| // matched but contained malformed escape sequences. If successful, the function
 | |
| // returns a mapping from field paths to their captured values.
 | |
| func (p Pattern) MatchAndEscape(components []string, verb string, unescapingMode UnescapingMode) (map[string]string, error) {
 | |
| 	if p.verb != verb {
 | |
| 		if p.verb != "" {
 | |
| 			return nil, ErrNotMatch
 | |
| 		}
 | |
| 		if len(components) == 0 {
 | |
| 			components = []string{":" + verb}
 | |
| 		} else {
 | |
| 			components = append([]string{}, components...)
 | |
| 			components[len(components)-1] += ":" + verb
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var pos int
 | |
| 	stack := make([]string, 0, p.stacksize)
 | |
| 	captured := make([]string, len(p.vars))
 | |
| 	l := len(components)
 | |
| 	for _, op := range p.ops {
 | |
| 		var err error
 | |
| 
 | |
| 		switch op.code {
 | |
| 		case utilities.OpNop:
 | |
| 			continue
 | |
| 		case utilities.OpPush, utilities.OpLitPush:
 | |
| 			if pos >= l {
 | |
| 				return nil, ErrNotMatch
 | |
| 			}
 | |
| 			c := components[pos]
 | |
| 			if op.code == utilities.OpLitPush {
 | |
| 				if lit := p.pool[op.operand]; c != lit {
 | |
| 					return nil, ErrNotMatch
 | |
| 				}
 | |
| 			} else if op.code == utilities.OpPush {
 | |
| 				if c, err = unescape(c, unescapingMode, false); err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 			}
 | |
| 			stack = append(stack, c)
 | |
| 			pos++
 | |
| 		case utilities.OpPushM:
 | |
| 			end := len(components)
 | |
| 			if end < pos+p.tailLen {
 | |
| 				return nil, ErrNotMatch
 | |
| 			}
 | |
| 			end -= p.tailLen
 | |
| 			c := strings.Join(components[pos:end], "/")
 | |
| 			if c, err = unescape(c, unescapingMode, true); err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			stack = append(stack, c)
 | |
| 			pos = end
 | |
| 		case utilities.OpConcatN:
 | |
| 			n := op.operand
 | |
| 			l := len(stack) - n
 | |
| 			stack = append(stack[:l], strings.Join(stack[l:], "/"))
 | |
| 		case utilities.OpCapture:
 | |
| 			n := len(stack) - 1
 | |
| 			captured[op.operand] = stack[n]
 | |
| 			stack = stack[:n]
 | |
| 		}
 | |
| 	}
 | |
| 	if pos < l {
 | |
| 		return nil, ErrNotMatch
 | |
| 	}
 | |
| 	bindings := make(map[string]string)
 | |
| 	for i, val := range captured {
 | |
| 		bindings[p.vars[i]] = val
 | |
| 	}
 | |
| 	return bindings, nil
 | |
| }
 | |
| 
 | |
| // MatchAndEscape examines components to determine if they match to a Pattern.
 | |
| // It will never perform per-component unescaping (see: UnescapingModeLegacy).
 | |
| // MatchAndEscape will return an error if no Patterns matched. If successful,
 | |
| // the function returns a mapping from field paths to their captured values.
 | |
| //
 | |
| // Deprecated: Use MatchAndEscape.
 | |
| func (p Pattern) Match(components []string, verb string) (map[string]string, error) {
 | |
| 	return p.MatchAndEscape(components, verb, UnescapingModeDefault)
 | |
| }
 | |
| 
 | |
| // Verb returns the verb part of the Pattern.
 | |
| func (p Pattern) Verb() string { return p.verb }
 | |
| 
 | |
| func (p Pattern) String() string {
 | |
| 	var stack []string
 | |
| 	for _, op := range p.ops {
 | |
| 		switch op.code {
 | |
| 		case utilities.OpNop:
 | |
| 			continue
 | |
| 		case utilities.OpPush:
 | |
| 			stack = append(stack, "*")
 | |
| 		case utilities.OpLitPush:
 | |
| 			stack = append(stack, p.pool[op.operand])
 | |
| 		case utilities.OpPushM:
 | |
| 			stack = append(stack, "**")
 | |
| 		case utilities.OpConcatN:
 | |
| 			n := op.operand
 | |
| 			l := len(stack) - n
 | |
| 			stack = append(stack[:l], strings.Join(stack[l:], "/"))
 | |
| 		case utilities.OpCapture:
 | |
| 			n := len(stack) - 1
 | |
| 			stack[n] = fmt.Sprintf("{%s=%s}", p.vars[op.operand], stack[n])
 | |
| 		}
 | |
| 	}
 | |
| 	segs := strings.Join(stack, "/")
 | |
| 	if p.verb != "" {
 | |
| 		return fmt.Sprintf("/%s:%s", segs, p.verb)
 | |
| 	}
 | |
| 	return "/" + segs
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * The following code is adopted and modified from Go's standard library
 | |
|  * and carries the attached license.
 | |
|  *
 | |
|  *     Copyright 2009 The Go Authors. All rights reserved.
 | |
|  *     Use of this source code is governed by a BSD-style
 | |
|  *     license that can be found in the LICENSE file.
 | |
|  */
 | |
| 
 | |
| // ishex returns whether or not the given byte is a valid hex character
 | |
| func ishex(c byte) bool {
 | |
| 	switch {
 | |
| 	case '0' <= c && c <= '9':
 | |
| 		return true
 | |
| 	case 'a' <= c && c <= 'f':
 | |
| 		return true
 | |
| 	case 'A' <= c && c <= 'F':
 | |
| 		return true
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func isRFC6570Reserved(c byte) bool {
 | |
| 	switch c {
 | |
| 	case '!', '#', '$', '&', '\'', '(', ')', '*',
 | |
| 		'+', ',', '/', ':', ';', '=', '?', '@', '[', ']':
 | |
| 		return true
 | |
| 	default:
 | |
| 		return false
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // unhex converts a hex point to the bit representation
 | |
| func unhex(c byte) byte {
 | |
| 	switch {
 | |
| 	case '0' <= c && c <= '9':
 | |
| 		return c - '0'
 | |
| 	case 'a' <= c && c <= 'f':
 | |
| 		return c - 'a' + 10
 | |
| 	case 'A' <= c && c <= 'F':
 | |
| 		return c - 'A' + 10
 | |
| 	}
 | |
| 	return 0
 | |
| }
 | |
| 
 | |
| // shouldUnescapeWithMode returns true if the character is escapable with the
 | |
| // given mode
 | |
| func shouldUnescapeWithMode(c byte, mode UnescapingMode) bool {
 | |
| 	switch mode {
 | |
| 	case UnescapingModeAllExceptReserved:
 | |
| 		if isRFC6570Reserved(c) {
 | |
| 			return false
 | |
| 		}
 | |
| 	case UnescapingModeAllExceptSlash:
 | |
| 		if c == '/' {
 | |
| 			return false
 | |
| 		}
 | |
| 	case UnescapingModeAllCharacters:
 | |
| 		return true
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // unescape unescapes a path string using the provided mode
 | |
| func unescape(s string, mode UnescapingMode, multisegment bool) (string, error) {
 | |
| 	// TODO(v3): remove UnescapingModeLegacy
 | |
| 	if mode == UnescapingModeLegacy {
 | |
| 		return s, nil
 | |
| 	}
 | |
| 
 | |
| 	if !multisegment {
 | |
| 		mode = UnescapingModeAllCharacters
 | |
| 	}
 | |
| 
 | |
| 	// Count %, check that they're well-formed.
 | |
| 	n := 0
 | |
| 	for i := 0; i < len(s); {
 | |
| 		if s[i] == '%' {
 | |
| 			n++
 | |
| 			if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
 | |
| 				s = s[i:]
 | |
| 				if len(s) > 3 {
 | |
| 					s = s[:3]
 | |
| 				}
 | |
| 
 | |
| 				return "", MalformedSequenceError(s)
 | |
| 			}
 | |
| 			i += 3
 | |
| 		} else {
 | |
| 			i++
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if n == 0 {
 | |
| 		return s, nil
 | |
| 	}
 | |
| 
 | |
| 	var t strings.Builder
 | |
| 	t.Grow(len(s))
 | |
| 	for i := 0; i < len(s); i++ {
 | |
| 		switch s[i] {
 | |
| 		case '%':
 | |
| 			c := unhex(s[i+1])<<4 | unhex(s[i+2])
 | |
| 			if shouldUnescapeWithMode(c, mode) {
 | |
| 				t.WriteByte(c)
 | |
| 				i += 2
 | |
| 				continue
 | |
| 			}
 | |
| 			fallthrough
 | |
| 		default:
 | |
| 			t.WriteByte(s[i])
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return t.String(), nil
 | |
| }
 |