path_replace.go raw
1 package httpbinding
2
3 import (
4 "bytes"
5 "fmt"
6 )
7
8 const (
9 uriTokenStart = '{'
10 uriTokenStop = '}'
11 uriTokenSkip = '+'
12 )
13
14 func bufCap(b []byte, n int) []byte {
15 if cap(b) < n {
16 return make([]byte, 0, n)
17 }
18
19 return b[0:0]
20 }
21
22 // replacePathElement replaces a single element in the path []byte.
23 // Escape is used to control whether the value will be escaped using Amazon path escape style.
24 func replacePathElement(path, fieldBuf []byte, key, val string, escape bool) ([]byte, []byte, error) {
25 // search for "{<key>}". If not found, search for the greedy version "{<key>+}". If none are found, return error
26 fieldBuf = bufCap(fieldBuf, len(key)+2) // { <key> }
27 fieldBuf = append(fieldBuf, uriTokenStart)
28 fieldBuf = append(fieldBuf, key...)
29 fieldBuf = append(fieldBuf, uriTokenStop)
30
31 start := bytes.Index(path, fieldBuf)
32 encodeSep := true
33 if start < 0 {
34 fieldBuf = bufCap(fieldBuf, len(key)+3) // { <key> [+] }
35 fieldBuf = append(fieldBuf, uriTokenStart)
36 fieldBuf = append(fieldBuf, key...)
37 fieldBuf = append(fieldBuf, uriTokenSkip)
38 fieldBuf = append(fieldBuf, uriTokenStop)
39
40 start = bytes.Index(path, fieldBuf)
41 if start < 0 {
42 return path, fieldBuf, fmt.Errorf("invalid path index, start=%d. %s", start, path)
43 }
44 encodeSep = false
45 }
46 end := start + len(fieldBuf)
47
48 if escape {
49 val = EscapePath(val, encodeSep)
50 }
51
52 fieldBuf = bufCap(fieldBuf, len(val))
53 fieldBuf = append(fieldBuf, val...)
54
55 keyLen := end - start
56 valLen := len(fieldBuf)
57
58 if keyLen == valLen {
59 copy(path[start:], fieldBuf)
60 return path, fieldBuf, nil
61 }
62
63 newLen := len(path) + (valLen - keyLen)
64 if len(path) < newLen {
65 path = path[:cap(path)]
66 }
67 if cap(path) < newLen {
68 newURI := make([]byte, newLen)
69 copy(newURI, path)
70 path = newURI
71 }
72
73 // shift
74 copy(path[start+valLen:], path[end:])
75 path = path[:newLen]
76 copy(path[start:], fieldBuf)
77
78 return path, fieldBuf, nil
79 }
80
81 // EscapePath escapes part of a URL path in Amazon style.
82 func EscapePath(path string, encodeSep bool) string {
83 var buf bytes.Buffer
84 for i := 0; i < len(path); i++ {
85 c := path[i]
86 if noEscape[c] || (c == '/' && !encodeSep) {
87 buf.WriteByte(c)
88 } else {
89 fmt.Fprintf(&buf, "%%%02X", c)
90 }
91 }
92 return buf.String()
93 }
94
95 var noEscape [256]bool
96
97 func init() {
98 for i := 0; i < len(noEscape); i++ {
99 // AWS expects every character except these to be escaped
100 noEscape[i] = (i >= 'A' && i <= 'Z') ||
101 (i >= 'a' && i <= 'z') ||
102 (i >= '0' && i <= '9') ||
103 i == '-' ||
104 i == '.' ||
105 i == '_' ||
106 i == '~'
107 }
108 }
109