path.go 2.5 KB

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