forked from aws/aws-sdk-go-v2
/
path_replace.go
136 lines (114 loc) · 3.01 KB
/
path_replace.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package protocol
import (
"bytes"
"fmt"
)
const (
uriTokenStart = '{'
uriTokenStop = '}'
uriTokenSkip = '+'
)
func bufCap(b []byte, n int) []byte {
if cap(b) < n {
return make([]byte, 0, n)
}
return b[0:0]
}
// PathReplace replaces path elements using field buffers
type PathReplace struct {
// May mutate path slice
path []byte
rawPath []byte
fieldBuf []byte
}
// NewPathReplace creats a built PathReplace value that can be used to replace
// path elements.
func NewPathReplace(path string) PathReplace {
return PathReplace{
path: []byte(path),
rawPath: []byte(path),
}
}
// Encode returns an unescaped path, and escaped path.
func (r *PathReplace) Encode() (path string, rawPath string) {
return string(r.path), string(r.rawPath)
}
// ReplaceElement replaces a single element in the path string.
func (r *PathReplace) ReplaceElement(key, val string) (err error) {
r.path, r.fieldBuf, err = replacePathElement(r.path, r.fieldBuf, key, val, false)
r.rawPath, r.fieldBuf, err = replacePathElement(r.rawPath, r.fieldBuf, key, val, true)
return err
}
func replacePathElement(path, fieldBuf []byte, key, val string, escape bool) ([]byte, []byte, error) {
fieldBuf = bufCap(fieldBuf, len(key)+3) // { <key> [+] }
fieldBuf = append(fieldBuf, uriTokenStart)
fieldBuf = append(fieldBuf, key...)
start := bytes.Index(path, fieldBuf)
end := start + len(fieldBuf)
if start < 0 || len(path[end:]) == 0 {
// TODO what to do about error?
return path, fieldBuf, fmt.Errorf("invalid path index, start=%d,end=%d. %s", start, end, path)
}
encodeSep := true
if path[end] == uriTokenSkip {
// '+' token means do not escape slashes
encodeSep = false
end++
}
if escape {
val = escapePath(val, encodeSep)
}
if path[end] != uriTokenStop {
return path, fieldBuf, fmt.Errorf("invalid path element, does not contain token stop, %s", path)
}
end++
fieldBuf = bufCap(fieldBuf, len(val))
fieldBuf = append(fieldBuf, val...)
keyLen := end - start
valLen := len(fieldBuf)
if keyLen == valLen {
copy(path[start:], fieldBuf)
return path, fieldBuf, nil
}
newLen := len(path) + (valLen - keyLen)
if len(path) < newLen {
path = path[:cap(path)]
}
if cap(path) < newLen {
newURI := make([]byte, newLen)
copy(newURI, path)
path = newURI
}
// shift
copy(path[start+valLen:], path[end:])
path = path[:newLen]
copy(path[start:], fieldBuf)
return path, fieldBuf, nil
}
// copied from rest.EscapePath
// escapes part of a URL path in Amazon style
func escapePath(path string, encodeSep bool) string {
var buf bytes.Buffer
for i := 0; i < len(path); i++ {
c := path[i]
if noEscape[c] || (c == '/' && !encodeSep) {
buf.WriteByte(c)
} else {
fmt.Fprintf(&buf, "%%%02X", c)
}
}
return buf.String()
}
var noEscape [256]bool
func init() {
for i := 0; i < len(noEscape); i++ {
// AWS expects every character except these to be escaped
noEscape[i] = (i >= 'A' && i <= 'Z') ||
(i >= 'a' && i <= 'z') ||
(i >= '0' && i <= '9') ||
i == '-' ||
i == '.' ||
i == '_' ||
i == '~'
}
}