server/pkg/loafer/web/router/path.go

172 lines
3.6 KiB
Go
Raw Normal View History

2019-11-14 15:53:14 +00:00
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
}