Files
net/http/pattern.go
deadprogram 35e809e0e5 Upgrade net package from Go 1.21.4 to Go 1.26.2
Backport upstream Go standard library changes to TinyGo's net package.

Unmodified files (18) replaced directly from Go 1.26.2 source.
Modified files (22) merged via 3-way diff, preserving all TinyGo
netdev adaptations, TINYGO markers, and embedded-device constraints.

Notable upstream changes included:
- net/http: cookie handling improvements, fs enhancements,
  reverse proxy updates, chunked encoding fixes
- net/http: ServeMux routing and pattern matching updates
- net: IP parsing and MAC address handling improvements
- Various doc link syntax modernization (e.g. [Dial], [Buffers])

TinyGo-only files (netdev.go, tlssock.go) unchanged.
All TINYGO comment markers preserved.
2026-04-13 22:55:27 +01:00

209 lines
5.6 KiB
Go

// TINYGO: The following is copied and modified from Go 1.26.2 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
}