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// in the LICENSE file. 5 6package httprouter 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 21func CleanPath(p string) string { 22 // Turn empty string into "/" 23 if p == "" { 24 return "/" 25 } 26 27 n := len(p) 28 var buf []byte 29 30 // Invariants: 31 // reading from path; r is index of next byte to process. 32 // writing to buf; w is index of next byte to write. 33 34 // path must start with '/' 35 r := 1 36 w := 1 37 38 if p[0] != '/' { 39 r = 0 40 buf = make([]byte, n+1) 41 buf[0] = '/' 42 } 43 44 trailing := n > 1 && p[n-1] == '/' 45 46 // A bit more clunky without a 'lazybuf' like the path package, but the loop 47 // gets completely inlined (bufApp). So in contrast to the path package this 48 // loop has no expensive function calls (except 1x make) 49 50 for r < n { 51 switch { 52 case p[r] == '/': 53 // empty path element, trailing slash is added after the end 54 r++ 55 56 case p[r] == '.' && r+1 == n: 57 trailing = true 58 r++ 59 60 case p[r] == '.' && p[r+1] == '/': 61 // . element 62 r += 2 63 64 case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'): 65 // .. element: remove to last / 66 r += 3 67 68 if w > 1 { 69 // can backtrack 70 w-- 71 72 if buf == nil { 73 for w > 1 && p[w] != '/' { 74 w-- 75 } 76 } else { 77 for w > 1 && buf[w] != '/' { 78 w-- 79 } 80 } 81 } 82 83 default: 84 // real path element. 85 // add slash if needed 86 if w > 1 { 87 bufApp(&buf, p, w, '/') 88 w++ 89 } 90 91 // copy element 92 for r < n && p[r] != '/' { 93 bufApp(&buf, p, w, p[r]) 94 w++ 95 r++ 96 } 97 } 98 } 99 100 // re-append trailing slash 101 if trailing && w > 1 { 102 bufApp(&buf, p, w, '/') 103 w++ 104 } 105 106 if buf == nil { 107 return p[:w] 108 } 109 return string(buf[:w]) 110} 111 112// internal helper to lazily create a buffer if necessary 113func bufApp(buf *[]byte, s string, w int, c byte) { 114 if *buf == nil { 115 if s[w] == c { 116 return 117 } 118 119 *buf = make([]byte, len(s)) 120 copy(*buf, s[:w]) 121 } 122 (*buf)[w] = c 123} 124