path.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  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. package myth
  6. // CleanPath is the URL version of path.Clean, it returns a canonical URL path
  7. // for p, eliminating . and .. elements.
  8. //
  9. // The following rules are applied iteratively until no further processing can
  10. // be done:
  11. // 1. Replace multiple slashes with a single slash.
  12. // 2. Eliminate each . path name element (the current directory).
  13. // 3. Eliminate each inner .. path name element (the parent directory)
  14. // along with the non-.. element that precedes it.
  15. // 4. Eliminate .. elements that begin a rooted path:
  16. // that is, replace "/.." by "/" at the beginning of a path.
  17. //
  18. // If the result of this process is an empty string, "/" is returned
  19. func CleanPath(p string) string {
  20. // Turn empty string into "/"
  21. if p == "" {
  22. return "/"
  23. }
  24. n := len(p)
  25. var buf []byte
  26. // Invariants:
  27. // reading from path; r is index of next byte to process.
  28. // writing to buf; w is index of next byte to write.
  29. // path must start with '/'
  30. r := 1
  31. w := 1
  32. if p[0] != '/' {
  33. r = 0
  34. buf = make([]byte, n+1)
  35. buf[0] = '/'
  36. }
  37. trailing := n > 1 && p[n-1] == '/'
  38. // A bit more clunky without a 'lazybuf' like the path package, but the loop
  39. // gets completely inlined (bufApp). So in contrast to the path package this
  40. // loop has no expensive function calls (except 1x make)
  41. for r < n {
  42. switch {
  43. case p[r] == '/':
  44. // empty path element, trailing slash is added after the end
  45. r++
  46. case p[r] == '.' && r+1 == n:
  47. trailing = true
  48. r++
  49. case p[r] == '.' && p[r+1] == '/':
  50. // . element
  51. r += 2
  52. case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
  53. // .. element: remove to last /
  54. r += 3
  55. if w > 1 {
  56. // can backtrack
  57. w--
  58. if buf == nil {
  59. for w > 1 && p[w] != '/' {
  60. w--
  61. }
  62. } else {
  63. for w > 1 && buf[w] != '/' {
  64. w--
  65. }
  66. }
  67. }
  68. default:
  69. // real path element.
  70. // add slash if needed
  71. if w > 1 {
  72. bufApp(&buf, p, w, '/')
  73. w++
  74. }
  75. // copy element
  76. for r < n && p[r] != '/' {
  77. bufApp(&buf, p, w, p[r])
  78. w++
  79. r++
  80. }
  81. }
  82. }
  83. // re-append trailing slash
  84. if trailing && w > 1 {
  85. bufApp(&buf, p, w, '/')
  86. w++
  87. }
  88. if buf == nil {
  89. return p[:w]
  90. }
  91. return string(buf[:w])
  92. }
  93. // internal helper to lazily create a buffer if necessary
  94. func bufApp(buf *[]byte, s string, w int, c byte) {
  95. if *buf == nil {
  96. if s[w] == c {
  97. return
  98. }
  99. *buf = make([]byte, len(s))
  100. copy(*buf, s[:w])
  101. }
  102. (*buf)[w] = c
  103. }