gin-gonic / gin
1
// Copyright 2013 Julien Schmidt. All rights reserved.
2
// Based on the path package, Copyright 2009 The Go Authors.
3
// Use of this source code is governed by a BSD-style license that can be found
4
// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE.
5

6
package gin
7

8
// cleanPath is the URL version of path.Clean, it returns a canonical URL path
9
// for p, eliminating . and .. elements.
10
//
11
// The following rules are applied iteratively until no further processing can
12
// be done:
13
//	1. Replace multiple slashes with a single slash.
14
//	2. Eliminate each . path name element (the current directory).
15
//	3. Eliminate each inner .. path name element (the parent directory)
16
//	   along with the non-.. element that precedes it.
17
//	4. Eliminate .. elements that begin a rooted path:
18
//	   that is, replace "/.." by "/" at the beginning of a path.
19
//
20
// If the result of this process is an empty string, "/" is returned.
21
func cleanPath(p string) string {
22 100
	const stackBufSize = 128
23
	// Turn empty string into "/"
24 100
	if p == "" {
25 100
		return "/"
26
	}
27

28
	// Reasonably sized buffer on stack to avoid allocations in the common case.
29
	// If a larger buffer is required, it gets allocated dynamically.
30 100
	buf := make([]byte, 0, stackBufSize)
31

32 100
	n := len(p)
33

34
	// Invariants:
35
	//      reading from path; r is index of next byte to process.
36
	//      writing to buf; w is index of next byte to write.
37

38
	// path must start with '/'
39 100
	r := 1
40 100
	w := 1
41

42 100
	if p[0] != '/' {
43 100
		r = 0
44

45 100
		if n+1 > stackBufSize {
46 100
			buf = make([]byte, n+1)
47 100
		} else {
48 100
			buf = buf[:n+1]
49
		}
50 100
		buf[0] = '/'
51
	}
52

53 100
	trailing := n > 1 && p[n-1] == '/'
54

55
	// A bit more clunky without a 'lazybuf' like the path package, but the loop
56
	// gets completely inlined (bufApp calls).
57
	// loop has no expensive function calls (except 1x make)		// So in contrast to the path package this loop has no expensive function
58
	// calls (except make, if needed).
59

60
	for r < n {
61 100
		switch {
62 100
		case p[r] == '/':
63
			// empty path element, trailing slash is added after the end
64 100
			r++
65

66 100
		case p[r] == '.' && r+1 == n:
67 100
			trailing = true
68 100
			r++
69

70 100
		case p[r] == '.' && p[r+1] == '/':
71
			// . element
72 100
			r += 2
73

74 100
		case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
75
			// .. element: remove to last /
76 100
			r += 3
77

78 100
			if w > 1 {
79
				// can backtrack
80 100
				w--
81

82 100
				if len(buf) == 0 {
83
					for w > 1 && p[w] != '/' {
84 100
						w--
85
					}
86 100
				} else {
87
					for w > 1 && buf[w] != '/' {
88 100
						w--
89
					}
90
				}
91
			}
92

93 100
		default:
94
			// Real path element.
95
			// Add slash if needed
96 100
			if w > 1 {
97 100
				bufApp(&buf, p, w, '/')
98 100
				w++
99
			}
100

101
			// Copy element
102
			for r < n && p[r] != '/' {
103 100
				bufApp(&buf, p, w, p[r])
104 100
				w++
105 100
				r++
106
			}
107
		}
108
	}
109

110
	// Re-append trailing slash
111 100
	if trailing && w > 1 {
112 100
		bufApp(&buf, p, w, '/')
113 100
		w++
114
	}
115

116
	// If the original string was not modified (or only shortened at the end),
117
	// return the respective substring of the original string.
118
	// Otherwise return a new string from the buffer.
119 100
	if len(buf) == 0 {
120 100
		return p[:w]
121
	}
122 100
	return string(buf[:w])
123
}
124

125
// Internal helper to lazily create a buffer if necessary.
126
// Calls to this function get inlined.
127
func bufApp(buf *[]byte, s string, w int, c byte) {
128 100
	b := *buf
129 100
	if len(b) == 0 {
130
		// No modification of the original string so far.
131
		// If the next character is the same as in the original string, we do
132
		// not yet have to allocate a buffer.
133 100
		if s[w] == c {
134 100
			return
135
		}
136

137
		// Otherwise use either the stack buffer, if it is large enough, or
138
		// allocate a new buffer on the heap, and copy all previous characters.
139 100
		length := len(s)
140 100
		if length > cap(b) {
141 100
			*buf = make([]byte, length)
142 100
		} else {
143 100
			*buf = (*buf)[:length]
144
		}
145 100
		b = *buf
146

147 100
		copy(b, s[:w])
148
	}
149 100
	b[w] = c
150
}

Read our documentation on viewing source code .

Loading