@@ -19,18 +19,29 @@ package gin
1919//
2020// If the result of this process is an empty string, "/" is returned.
2121func cleanPath (p string ) string {
22- const stackBufSize = 128
2322 // Turn empty string into "/"
2423 if p == "" {
2524 return "/"
2625 }
2726
27+ n := len (p )
28+
29+ // If the path length is 1, handle special cases separately:
30+ // - If it is "/" or ".", return "/".
31+ // - Otherwise, prepend "/" to the path and return it.
32+ if n == 1 {
33+ if p [0 ] == '/' || p [0 ] == '.' {
34+ return "/"
35+ }
36+ return "/" + p
37+ }
38+
39+ const stackBufSize = 128
40+
2841 // Reasonably sized buffer on stack to avoid allocations in the common case.
2942 // If a larger buffer is required, it gets allocated dynamically.
3043 buf := make ([]byte , 0 , stackBufSize )
3144
32- n := len (p )
33-
3445 // Invariants:
3546 // reading from path; r is index of next byte to process.
3647 // writing to buf; w is index of next byte to write.
@@ -39,7 +50,7 @@ func cleanPath(p string) string {
3950 r := 1
4051 w := 1
4152
42- if p [0 ] != '/' || ( n > 1 && ( p [ 1 ] == '/' || p [ 1 ] == '\\' )) {
53+ if p [0 ] != '/' {
4354 r = 0
4455
4556 if n + 1 > stackBufSize {
@@ -50,46 +61,52 @@ func cleanPath(p string) string {
5061 buf [0 ] = '/'
5162 }
5263
53- trailing := n > 1 && p [ n - 1 ] == '/'
64+ var trailing bool
5465
5566 // A bit more clunky without a 'lazybuf' like the path package, but the loop
5667 // gets completely inlined (bufApp calls).
5768 // loop has no expensive function calls (except 1x make) // So in contrast to the path package this loop has no expensive function
5869 // calls (except make, if needed).
5970
6071 for r < n {
61- switch {
62- case p [ r ] == '/' :
72+ switch p [ r ] {
73+ case '/' :
6374 // empty path element, trailing slash is added after the end
6475 r ++
6576
66- case p [r ] == '.' && r + 1 == n :
67- trailing = true
68- r ++
69-
70- case p [r ] == '.' && p [r + 1 ] == '/' :
71- // . element
72- r += 2
73-
74- case p [r ] == '.' && p [r + 1 ] == '.' && (r + 2 == n || p [r + 2 ] == '/' ):
75- // .. element: remove to last /
76- r += 3
77-
78- if w > 1 {
79- // can backtrack
80- w --
81-
82- if len (buf ) == 0 {
83- for w > 1 && p [w ] != '/' {
84- w --
85- }
86- } else {
87- for w > 1 && buf [w ] != '/' {
77+ case '.' :
78+ if r + 1 == n {
79+ trailing = true
80+ r ++
81+ // Reduce one comparison between r and n
82+ goto endOfLoop
83+ }
84+ switch p [r + 1 ] {
85+ case '/' :
86+ // . element
87+ r += 2
88+
89+ case '.' :
90+ if r + 2 == n || p [r + 2 ] == '/' {
91+ // .. element: remove to last /
92+ r += 3
93+
94+ if w > 1 {
95+ // can backtrack
8896 w --
97+
98+ if len (buf ) == 0 {
99+ for w > 1 && p [w ] != '/' {
100+ w --
101+ }
102+ } else {
103+ for w > 1 && buf [w ] != '/' {
104+ w --
105+ }
106+ }
89107 }
90108 }
91109 }
92-
93110 default :
94111 // Real path element.
95112 // Add slash if needed
@@ -107,8 +124,9 @@ func cleanPath(p string) string {
107124 }
108125 }
109126
127+ endOfLoop:
110128 // Re-append trailing slash
111- if trailing && w > 1 {
129+ if ( trailing || p [ n - 1 ] == '/' ) && w > 1 {
112130 bufApp (& buf , p , w , '/' )
113131 w ++
114132 }
0 commit comments