github.com/gramework/gramework@v1.8.1-0.20231027140105-82555c9057f5/fasthttprouter_path.go (about) 1 // Copyright 2013 Julien Schmidt. All rights reserved. 2 // Copyright (c) 2015-2016, 招牌疯子 3 // Copyright (c) 2017, Kirill Danshin 4 // Use of this source code is governed by a BSD-style license that can be found 5 // in the 3rd-Party License/fasthttprouter file. 6 7 package gramework 8 9 // CleanPath is the URL version of path.Clean, it returns a canonical URL path 10 // for p, eliminating . and .. elements. 11 // 12 // The following rules are applied iteratively until no further processing can 13 // be done: 14 // 1. Replace multiple slashes with a single slash. 15 // 2. Eliminate each . path name element (the current directory). 16 // 3. Eliminate each inner .. path name element (the parent directory) 17 // along with the non-.. element that precedes it. 18 // 4. Eliminate .. elements that begin a rooted path: 19 // that is, replace "/.." by "/" at the beginning of a path. 20 // 21 // If the result of this process is an empty string, "/" is returned 22 func CleanPath(p string) string { 23 // Turn empty string into "/" 24 if p == "" { 25 return "/" 26 } 27 28 n := len(p) 29 var buf []byte 30 31 // Invariants: 32 // reading from path; r is index of next byte to process. 33 // writing to buf; w is index of next byte to write. 34 35 // path must start with '/' 36 r := 1 37 w := 1 38 39 if p[0] != '/' { 40 r = 0 41 buf = make([]byte, n+1) 42 buf[0] = '/' 43 } 44 45 trailing := n > 2 && p[n-1] == '/' 46 47 // A bit more clunky without a 'lazybuf' like the path package, but the loop 48 // gets completely inlined (bufApp). So in contrast to the path package this 49 // loop has no expensive function calls (except 1x make) 50 51 for r < n { 52 switch { 53 case p[r] == '/': 54 // empty path element, trailing slash is added after the end 55 r++ 56 57 case p[r] == '.' && r+1 == n: 58 trailing = true 59 r++ 60 61 case p[r] == '.' && p[r+1] == '/': 62 // . element 63 r++ 64 65 case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'): 66 // .. element: remove to last / 67 r += 2 68 69 if w > 1 { 70 // can backtrack 71 w-- 72 73 if buf == nil { 74 for w > 1 && p[w] != '/' { 75 w-- 76 } 77 } else { 78 for w > 1 && buf[w] != '/' { 79 w-- 80 } 81 } 82 } 83 84 default: 85 // real path element. 86 // add slash if needed 87 if w > 1 { 88 bufApp(&buf, p, w, '/') 89 w++ 90 } 91 92 // copy element 93 for r < n && p[r] != '/' { 94 bufApp(&buf, p, w, p[r]) 95 w++ 96 r++ 97 } 98 } 99 } 100 101 // re-append trailing slash 102 if trailing && w > 1 { 103 bufApp(&buf, p, w, '/') 104 w++ 105 } 106 107 if buf == nil { 108 return p[:w] 109 } 110 return string(buf[:w]) 111 } 112 113 // internal helper to lazily create a buffer if necessary 114 func bufApp(buf *[]byte, s string, w int, c byte) { 115 if *buf == nil { 116 if s[w] == c { 117 return 118 } 119 120 *buf = make([]byte, len(s)) 121 copy(*buf, s[:w]) 122 } 123 (*buf)[w] = c 124 }