172 lines
3.6 KiB
Go
172 lines
3.6 KiB
Go
package router
|
|
|
|
import (
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/savsgio/gotils"
|
|
)
|
|
|
|
type cleanPathBuffer struct {
|
|
n int
|
|
r int
|
|
w int
|
|
trailing bool
|
|
buf []byte
|
|
}
|
|
|
|
var cleanPathBufferPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &cleanPathBuffer{
|
|
n: 0,
|
|
r: 0,
|
|
w: 1,
|
|
trailing: false,
|
|
buf: make([]byte, 140),
|
|
}
|
|
},
|
|
}
|
|
|
|
func (cpb *cleanPathBuffer) reset() {
|
|
cpb.n = 0
|
|
cpb.r = 0
|
|
cpb.w = 1
|
|
cpb.trailing = false
|
|
// cpb.buf = cpb.buf[:0]
|
|
}
|
|
|
|
func acquireCleanPathBuffer() *cleanPathBuffer {
|
|
return cleanPathBufferPool.Get().(*cleanPathBuffer)
|
|
}
|
|
|
|
func releaseCleanPathBuffer(cpb *cleanPathBuffer) {
|
|
cpb.reset()
|
|
cleanPathBufferPool.Put(cpb)
|
|
}
|
|
|
|
// CleanPath is the URL version of path.Clean, it returns a canonical URL path
|
|
// for path, eliminating . and .. elements.
|
|
//
|
|
// The following rules are applied iteratively until no further processing can
|
|
// be done:
|
|
// 1. Replace multiple slashes with a single slash.
|
|
// 2. Eliminate each . path name element (the current directory).
|
|
// 3. Eliminate each inner .. path name element (the parent directory)
|
|
// along with the non-.. element that precedes it.
|
|
// 4. Eliminate .. elements that begin a rooted path:
|
|
// that is, replace "/.." by "/" at the beginning of a path.
|
|
//
|
|
// If the result of this process is an empty string, "/" is returned
|
|
func CleanPath(path string) string {
|
|
cpb := acquireCleanPathBuffer()
|
|
cleanPathWithBuffer(cpb, path)
|
|
|
|
s := string(cpb.buf)
|
|
releaseCleanPathBuffer(cpb)
|
|
|
|
return s
|
|
}
|
|
|
|
func cleanPathWithBuffer(cpb *cleanPathBuffer, path string) {
|
|
// Turn empty string into "/"
|
|
if path == "" {
|
|
cpb.buf = append(cpb.buf[:0], '/')
|
|
return
|
|
}
|
|
|
|
cpb.n = len(path)
|
|
cpb.buf = gotils.ExtendByteSlice(cpb.buf, len(path)+1)
|
|
cpb.buf[0] = '/'
|
|
|
|
cpb.trailing = cpb.n > 2 && path[cpb.n-1] == '/'
|
|
|
|
// A bit more clunky without a 'lazybuf' like the path package, but the loop
|
|
// gets completely inlined (bufApp). So in contrast to the path package this
|
|
// loop has no expensive function calls (except 1x make)
|
|
|
|
for cpb.r < cpb.n {
|
|
// println(path[:cpb.r], " ####### ", string(path[cpb.r]), " ####### ", string(cpb.buf))
|
|
switch {
|
|
case path[cpb.r] == '/':
|
|
// empty path element, trailing slash is added after the end
|
|
cpb.r++
|
|
|
|
case path[cpb.r] == '.' && cpb.r+1 == cpb.n:
|
|
cpb.trailing = true
|
|
cpb.r++
|
|
|
|
case path[cpb.r] == '.' && path[cpb.r+1] == '/':
|
|
// . element
|
|
cpb.r++
|
|
|
|
case path[cpb.r] == '.' && path[cpb.r+1] == '.' && (cpb.r+2 == cpb.n || path[cpb.r+2] == '/'):
|
|
// .. element: remove to last /
|
|
cpb.r += 2
|
|
|
|
if cpb.w > 1 {
|
|
// can backtrack
|
|
cpb.w--
|
|
|
|
for cpb.w > 1 && cpb.buf[cpb.w] != '/' {
|
|
cpb.w--
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
// real path element.
|
|
// add slash if needed
|
|
if cpb.w > 1 {
|
|
cpb.buf[cpb.w] = '/'
|
|
cpb.w++
|
|
}
|
|
|
|
// copy element
|
|
for cpb.r < cpb.n && path[cpb.r] != '/' {
|
|
cpb.buf[cpb.w] = path[cpb.r]
|
|
cpb.w++
|
|
cpb.r++
|
|
}
|
|
}
|
|
}
|
|
|
|
// re-append trailing slash
|
|
if cpb.trailing && cpb.w > 1 {
|
|
cpb.buf[cpb.w] = '/'
|
|
cpb.w++
|
|
}
|
|
|
|
cpb.buf = cpb.buf[:cpb.w]
|
|
}
|
|
|
|
// returns all possible paths when the original path has optional arguments
|
|
func getOptionalPaths(path string) []string {
|
|
paths := make([]string, 0)
|
|
|
|
index := 0
|
|
newParam := false
|
|
for i := 0; i < len(path); i++ {
|
|
c := path[i]
|
|
|
|
if c == ':' {
|
|
index = i
|
|
newParam = true
|
|
} else if i > 0 && newParam && c == '?' {
|
|
p := strings.Replace(path[:index], "?", "", -1)
|
|
p = p[:len(p)-1]
|
|
if !gotils.StringSliceInclude(paths, p) {
|
|
paths = append(paths, p)
|
|
}
|
|
|
|
p = strings.Replace(path[:i], "?", "", -1)
|
|
if !gotils.StringSliceInclude(paths, p) {
|
|
paths = append(paths, p)
|
|
}
|
|
|
|
newParam = false
|
|
}
|
|
}
|
|
|
|
return paths
|
|
}
|