mirror of
https://github.com/tinygo-org/net.git
synced 2026-08-10 14:43:39 +00:00
add support for PathValue and SetPathValue from go v1.23.7 (#38)
* add support for PathValue and SetPathValue from go v1.23.7 * add TINYGO notice to pattern.go, proper copy paste of PathValue func
This commit is contained in:
+208
@@ -0,0 +1,208 @@
|
||||
// TINYGO: The following is copied and modified from Go 1.23.7 official implementation.
|
||||
|
||||
// Copyright 2023 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.
|
||||
|
||||
// Patterns for ServeMux routing.
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// A pattern is something that can be matched against an HTTP request.
|
||||
// It has an optional method, an optional host, and a path.
|
||||
type pattern struct {
|
||||
str string // original string
|
||||
method string
|
||||
host string
|
||||
// The representation of a path differs from the surface syntax, which
|
||||
// simplifies most algorithms.
|
||||
//
|
||||
// Paths ending in '/' are represented with an anonymous "..." wildcard.
|
||||
// For example, the path "a/" is represented as a literal segment "a" followed
|
||||
// by a segment with multi==true.
|
||||
//
|
||||
// Paths ending in "{$}" are represented with the literal segment "/".
|
||||
// For example, the path "a/{$}" is represented as a literal segment "a" followed
|
||||
// by a literal segment "/".
|
||||
segments []segment
|
||||
loc string // source location of registering call, for helpful messages
|
||||
}
|
||||
|
||||
func (p *pattern) String() string { return p.str }
|
||||
|
||||
func (p *pattern) lastSegment() segment {
|
||||
return p.segments[len(p.segments)-1]
|
||||
}
|
||||
|
||||
// A segment is a pattern piece that matches one or more path segments, or
|
||||
// a trailing slash.
|
||||
//
|
||||
// If wild is false, it matches a literal segment, or, if s == "/", a trailing slash.
|
||||
// Examples:
|
||||
//
|
||||
// "a" => segment{s: "a"}
|
||||
// "/{$}" => segment{s: "/"}
|
||||
//
|
||||
// If wild is true and multi is false, it matches a single path segment.
|
||||
// Example:
|
||||
//
|
||||
// "{x}" => segment{s: "x", wild: true}
|
||||
//
|
||||
// If both wild and multi are true, it matches all remaining path segments.
|
||||
// Example:
|
||||
//
|
||||
// "{rest...}" => segment{s: "rest", wild: true, multi: true}
|
||||
type segment struct {
|
||||
s string // literal or wildcard name or "/" for "/{$}".
|
||||
wild bool
|
||||
multi bool // "..." wildcard
|
||||
}
|
||||
|
||||
// parsePattern parses a string into a Pattern.
|
||||
// The string's syntax is
|
||||
//
|
||||
// [METHOD] [HOST]/[PATH]
|
||||
//
|
||||
// where:
|
||||
// - METHOD is an HTTP method
|
||||
// - HOST is a hostname
|
||||
// - PATH consists of slash-separated segments, where each segment is either
|
||||
// a literal or a wildcard of the form "{name}", "{name...}", or "{$}".
|
||||
//
|
||||
// METHOD, HOST and PATH are all optional; that is, the string can be "/".
|
||||
// If METHOD is present, it must be followed by at least one space or tab.
|
||||
// Wildcard names must be valid Go identifiers.
|
||||
// The "{$}" and "{name...}" wildcard must occur at the end of PATH.
|
||||
// PATH may end with a '/'.
|
||||
// Wildcard names in a path must be distinct.
|
||||
func parsePattern(s string) (_ *pattern, err error) {
|
||||
if len(s) == 0 {
|
||||
return nil, errors.New("empty pattern")
|
||||
}
|
||||
off := 0 // offset into string
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("at offset %d: %w", off, err)
|
||||
}
|
||||
}()
|
||||
|
||||
method, rest, found := s, "", false
|
||||
if i := strings.IndexAny(s, " \t"); i >= 0 {
|
||||
method, rest, found = s[:i], strings.TrimLeft(s[i+1:], " \t"), true
|
||||
}
|
||||
if !found {
|
||||
rest = method
|
||||
method = ""
|
||||
}
|
||||
if method != "" && !validMethod(method) {
|
||||
return nil, fmt.Errorf("invalid method %q", method)
|
||||
}
|
||||
p := &pattern{str: s, method: method}
|
||||
|
||||
if found {
|
||||
off = len(method) + 1
|
||||
}
|
||||
i := strings.IndexByte(rest, '/')
|
||||
if i < 0 {
|
||||
return nil, errors.New("host/path missing /")
|
||||
}
|
||||
p.host = rest[:i]
|
||||
rest = rest[i:]
|
||||
if j := strings.IndexByte(p.host, '{'); j >= 0 {
|
||||
off += j
|
||||
return nil, errors.New("host contains '{' (missing initial '/'?)")
|
||||
}
|
||||
// At this point, rest is the path.
|
||||
off += i
|
||||
|
||||
// An unclean path with a method that is not CONNECT can never match,
|
||||
// because paths are cleaned before matching.
|
||||
if method != "" && method != "CONNECT" && rest != cleanPath(rest) {
|
||||
return nil, errors.New("non-CONNECT pattern with unclean path can never match")
|
||||
}
|
||||
|
||||
seenNames := map[string]bool{} // remember wildcard names to catch dups
|
||||
for len(rest) > 0 {
|
||||
// Invariant: rest[0] == '/'.
|
||||
rest = rest[1:]
|
||||
off = len(s) - len(rest)
|
||||
if len(rest) == 0 {
|
||||
// Trailing slash.
|
||||
p.segments = append(p.segments, segment{wild: true, multi: true})
|
||||
break
|
||||
}
|
||||
i := strings.IndexByte(rest, '/')
|
||||
if i < 0 {
|
||||
i = len(rest)
|
||||
}
|
||||
var seg string
|
||||
seg, rest = rest[:i], rest[i:]
|
||||
if i := strings.IndexByte(seg, '{'); i < 0 {
|
||||
// Literal.
|
||||
seg = pathUnescape(seg)
|
||||
p.segments = append(p.segments, segment{s: seg})
|
||||
} else {
|
||||
// Wildcard.
|
||||
if i != 0 {
|
||||
return nil, errors.New("bad wildcard segment (must start with '{')")
|
||||
}
|
||||
if seg[len(seg)-1] != '}' {
|
||||
return nil, errors.New("bad wildcard segment (must end with '}')")
|
||||
}
|
||||
name := seg[1 : len(seg)-1]
|
||||
if name == "$" {
|
||||
if len(rest) != 0 {
|
||||
return nil, errors.New("{$} not at end")
|
||||
}
|
||||
p.segments = append(p.segments, segment{s: "/"})
|
||||
break
|
||||
}
|
||||
name, multi := strings.CutSuffix(name, "...")
|
||||
if multi && len(rest) != 0 {
|
||||
return nil, errors.New("{...} wildcard not at end")
|
||||
}
|
||||
if name == "" {
|
||||
return nil, errors.New("empty wildcard")
|
||||
}
|
||||
if !isValidWildcardName(name) {
|
||||
return nil, fmt.Errorf("bad wildcard name %q", name)
|
||||
}
|
||||
if seenNames[name] {
|
||||
return nil, fmt.Errorf("duplicate wildcard name %q", name)
|
||||
}
|
||||
seenNames[name] = true
|
||||
p.segments = append(p.segments, segment{s: name, wild: true, multi: multi})
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func isValidWildcardName(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
// Valid Go identifier.
|
||||
for i, c := range s {
|
||||
if !unicode.IsLetter(c) && c != '_' && (i == 0 || !unicode.IsDigit(c)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func pathUnescape(path string) string {
|
||||
u, err := url.PathUnescape(path)
|
||||
if err != nil {
|
||||
// Invalidly escaped path; use the original
|
||||
return path
|
||||
}
|
||||
return u
|
||||
}
|
||||
Reference in New Issue
Block a user