1// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package http
6
7// This file implements ServeMux behavior as in Go 1.21.
8// The behavior is controlled by a GODEBUG setting.
9// Most of this code is derived from commit 08e35cc334.
10// Changes are minimal: aside from the different receiver type,
11// they mostly involve renaming functions, usually by unexporting them.
12
13// servemux121.go exists solely to provide a snapshot of
14// the pre-Go 1.22 ServeMux implementation for backwards compatibility.
15// Do not modify this file, it should remain frozen.
16
17import (
18	"internal/godebug"
19	"net/url"
20	"sort"
21	"strings"
22	"sync"
23)
24
25var httpmuxgo121 = godebug.New("httpmuxgo121")
26
27var use121 bool
28
29// Read httpmuxgo121 once at startup, since dealing with changes to it during
30// program execution is too complex and error-prone.
31func init() {
32	if httpmuxgo121.Value() == "1" {
33		use121 = true
34		httpmuxgo121.IncNonDefault()
35	}
36}
37
38// serveMux121 holds the state of a ServeMux needed for Go 1.21 behavior.
39type serveMux121 struct {
40	mu    sync.RWMutex
41	m     map[string]muxEntry
42	es    []muxEntry // slice of entries sorted from longest to shortest.
43	hosts bool       // whether any patterns contain hostnames
44}
45
46type muxEntry struct {
47	h       Handler
48	pattern string
49}
50
51// Formerly ServeMux.Handle.
52func (mux *serveMux121) handle(pattern string, handler Handler) {
53	mux.mu.Lock()
54	defer mux.mu.Unlock()
55
56	if pattern == "" {
57		panic("http: invalid pattern")
58	}
59	if handler == nil {
60		panic("http: nil handler")
61	}
62	if _, exist := mux.m[pattern]; exist {
63		panic("http: multiple registrations for " + pattern)
64	}
65
66	if mux.m == nil {
67		mux.m = make(map[string]muxEntry)
68	}
69	e := muxEntry{h: handler, pattern: pattern}
70	mux.m[pattern] = e
71	if pattern[len(pattern)-1] == '/' {
72		mux.es = appendSorted(mux.es, e)
73	}
74
75	if pattern[0] != '/' {
76		mux.hosts = true
77	}
78}
79
80func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
81	n := len(es)
82	i := sort.Search(n, func(i int) bool {
83		return len(es[i].pattern) < len(e.pattern)
84	})
85	if i == n {
86		return append(es, e)
87	}
88	// we now know that i points at where we want to insert
89	es = append(es, muxEntry{}) // try to grow the slice in place, any entry works.
90	copy(es[i+1:], es[i:])      // Move shorter entries down
91	es[i] = e
92	return es
93}
94
95// Formerly ServeMux.HandleFunc.
96func (mux *serveMux121) handleFunc(pattern string, handler func(ResponseWriter, *Request)) {
97	if handler == nil {
98		panic("http: nil handler")
99	}
100	mux.handle(pattern, HandlerFunc(handler))
101}
102
103// Formerly ServeMux.Handler.
104func (mux *serveMux121) findHandler(r *Request) (h Handler, pattern string) {
105
106	// CONNECT requests are not canonicalized.
107	if r.Method == "CONNECT" {
108		// If r.URL.Path is /tree and its handler is not registered,
109		// the /tree -> /tree/ redirect applies to CONNECT requests
110		// but the path canonicalization does not.
111		if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
112			return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
113		}
114
115		return mux.handler(r.Host, r.URL.Path)
116	}
117
118	// All other requests have any port stripped and path cleaned
119	// before passing to mux.handler.
120	host := stripHostPort(r.Host)
121	path := cleanPath(r.URL.Path)
122
123	// If the given path is /tree and its handler is not registered,
124	// redirect for /tree/.
125	if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
126		return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
127	}
128
129	if path != r.URL.Path {
130		_, pattern = mux.handler(host, path)
131		u := &url.URL{Path: path, RawQuery: r.URL.RawQuery}
132		return RedirectHandler(u.String(), StatusMovedPermanently), pattern
133	}
134
135	return mux.handler(host, r.URL.Path)
136}
137
138// handler is the main implementation of findHandler.
139// The path is known to be in canonical form, except for CONNECT methods.
140func (mux *serveMux121) handler(host, path string) (h Handler, pattern string) {
141	mux.mu.RLock()
142	defer mux.mu.RUnlock()
143
144	// Host-specific pattern takes precedence over generic ones
145	if mux.hosts {
146		h, pattern = mux.match(host + path)
147	}
148	if h == nil {
149		h, pattern = mux.match(path)
150	}
151	if h == nil {
152		h, pattern = NotFoundHandler(), ""
153	}
154	return
155}
156
157// Find a handler on a handler map given a path string.
158// Most-specific (longest) pattern wins.
159func (mux *serveMux121) match(path string) (h Handler, pattern string) {
160	// Check for exact match first.
161	v, ok := mux.m[path]
162	if ok {
163		return v.h, v.pattern
164	}
165
166	// Check for longest valid match.  mux.es contains all patterns
167	// that end in / sorted from longest to shortest.
168	for _, e := range mux.es {
169		if strings.HasPrefix(path, e.pattern) {
170			return e.h, e.pattern
171		}
172	}
173	return nil, ""
174}
175
176// redirectToPathSlash determines if the given path needs appending "/" to it.
177// This occurs when a handler for path + "/" was already registered, but
178// not for path itself. If the path needs appending to, it creates a new
179// URL, setting the path to u.Path + "/" and returning true to indicate so.
180func (mux *serveMux121) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool) {
181	mux.mu.RLock()
182	shouldRedirect := mux.shouldRedirectRLocked(host, path)
183	mux.mu.RUnlock()
184	if !shouldRedirect {
185		return u, false
186	}
187	path = path + "/"
188	u = &url.URL{Path: path, RawQuery: u.RawQuery}
189	return u, true
190}
191
192// shouldRedirectRLocked reports whether the given path and host should be redirected to
193// path+"/". This should happen if a handler is registered for path+"/" but
194// not path -- see comments at ServeMux.
195func (mux *serveMux121) shouldRedirectRLocked(host, path string) bool {
196	p := []string{path, host + path}
197
198	for _, c := range p {
199		if _, exist := mux.m[c]; exist {
200			return false
201		}
202	}
203
204	n := len(path)
205	if n == 0 {
206		return false
207	}
208	for _, c := range p {
209		if _, exist := mux.m[c+"/"]; exist {
210			return path[n-1] != '/'
211		}
212	}
213
214	return false
215}
216