-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhelper.go
302 lines (262 loc) · 6.5 KB
/
helper.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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
package sigv4
import (
"bufio"
"crypto/hmac"
"crypto/sha256"
"fmt"
"net/http"
"net/url"
"sort"
"strings"
"time"
)
var (
awsURLNoEscTable [256]bool
awsURLEscTable [256][2]byte
)
func init() {
for i := 0; i < len(awsURLNoEscTable); i++ {
// every char except these must be escaped
awsURLNoEscTable[i] = (i >= 'A' && i <= 'Z') ||
(i >= 'a' && i <= 'z') ||
(i >= '0' && i <= '9') ||
i == '-' ||
i == '.' ||
i == '_' ||
i == '~'
// %<hex><hex>
encoded := fmt.Sprintf("%02X", i)
awsURLEscTable[i] = [2]byte{encoded[0], encoded[1]}
}
}
// hmacsha256 computes a HMAC-SHA256 of data given the provided key.
func hmacsha256(key, data, buf []byte) []byte {
hash := hmac.New(sha256.New, key)
hash.Write(data)
return hash.Sum(buf)
}
// hasPrefixFold tests whether the string s begins with prefix, interpreted as
// UTF-8 strings, under Unicode case-folding.
func hasPrefixFold(s, prefix string) bool {
return len(s) >= len(prefix) &&
strings.EqualFold(s[0:len(prefix)], prefix)
}
// isSameDay returns true if a and b are the same date (dd-mm-yyyy).
func isSameDay(a, b time.Time) bool {
xYear, xMonth, xDay := a.Date()
yYear, yMonth, yDay := b.Date()
if xYear != yYear || xMonth != yMonth {
return false
}
return xDay == yDay
}
// hostOrURLHost returns r.Host, or if empty, r.URL.Host.
func hostOrURLHost(r *http.Request) string {
if r.Host != "" {
return r.Host
}
return r.URL.Host
}
// parsePort returns the port part of u.Host, without the leading colon. Returns
// an empty string if u.Host doesn't contain port.
//
// Adapted from the Go 1.8 standard library (net/url).
func parsePort(hostport string) string {
colon := strings.IndexByte(hostport, ':')
if colon == -1 || colon == len(hostport)-1 {
return ""
}
// take care of ipv6 syntax: [a:b::]:<port>
const ipv6Sep = "]:"
if i := strings.Index(hostport, ipv6Sep); i != -1 {
return hostport[i+len(ipv6Sep):]
}
if strings.Contains(hostport, "]") {
return ""
}
return hostport[colon+1:]
}
// stripPort returns Hostname portion of u.Host, i.e. without any port number.
//
// If hostport is an IPv6 literal with a port number, returns the IPv6 literal
// without the square brackets. IPv6 literals may include a zone identifier.
//
// Adapted from the Go 1.8 standard library (net/url).
func stripPort(hostport string) string {
colon := strings.IndexByte(hostport, ':')
if colon == -1 {
return hostport
}
// ipv6: remove the []
if i := strings.IndexByte(hostport, ']'); i != -1 {
return strings.TrimPrefix(hostport[:i], "[")
}
return hostport[:colon]
}
// isDefaultPort returns true if the specified URI is using the standard port
// (i.e. port 80 for HTTP URIs or 443 for HTTPS URIs).
func isDefaultPort(scheme, port string) bool {
switch strings.ToLower(scheme) {
case "http":
return port == "80"
case "https":
return port == "443"
default:
return false
}
}
func cloneURL(u *url.URL) *url.URL {
if u == nil {
return nil
}
u2 := new(url.URL)
*u2 = *u
if u.User != nil {
u2.User = new(url.Userinfo)
*u2.User = *u.User
}
return u2
}
// writeAWSURIPath writes the escaped URI component from the specified URL (using
// AWS canonical URI specification) into w. URI component is path without query
// string.
func writeAWSURIPath(w *bufio.Writer, u *url.URL, encodeSep bool, isEscaped bool) {
const schemeSep, pathSep, queryStart = "//", "/", "?"
var p string
if u.Opaque == "" {
p = u.EscapedPath()
} else {
opaque := u.Opaque
// discard query string if any
if i := strings.Index(opaque, queryStart); i != -1 {
opaque = opaque[:i]
}
// if has scheme separator as prefix, discard it
if strings.HasPrefix(opaque, schemeSep) {
opaque = opaque[len(schemeSep):]
}
// everything after the first /, including the /
if i := strings.Index(opaque, pathSep); i != -1 {
p = opaque[i:]
}
}
if p == "" {
w.WriteByte('/')
return
}
if isEscaped {
w.WriteString(p)
return
}
// Loop thru first like in https://cs.opensource.google/go/go/+/refs/tags/go1.20.2:/src/net/url/url.go.
// It may add ~800ns but we save on memory alloc and catches cases where there
// is no need to escape.
plen := len(p)
strlen := plen
for i := 0; i < plen; i++ {
c := p[i]
if awsURLNoEscTable[c] || (c == '/' && !encodeSep) {
continue
}
strlen+=2
}
// path already canonical, no need to escape
if plen == strlen {
w.WriteString(p)
return
}
for i := 0; i < plen; i++ {
c := p[i]
if awsURLNoEscTable[c] || (c == '/' && !encodeSep) {
w.WriteByte(c)
continue
}
w.Write([]byte{'%', awsURLEscTable[c][0], awsURLEscTable[c][1]})
}
}
// writeCanonicalQueryParams builds the canonical form of query and writes to w.
//
// Side effect: query values are sorted after this function returns.
func writeCanonicalQueryParams(w *bufio.Writer, query url.Values) {
qlen := len(query)
if qlen == 0 {
return
}
keys := make([]string, 0, qlen)
for k := range query {
keys = append(keys, k)
}
sort.Strings(keys)
for i, k := range keys {
keyEscaped := strings.Replace(url.QueryEscape(k), "+", "%20", -1)
vs := query[k]
if i != 0 {
w.WriteByte('&')
}
if len(vs) == 0 {
w.WriteString(keyEscaped)
w.WriteByte('=')
continue
}
sort.Strings(vs)
for j, v := range vs {
if j != 0 {
w.WriteByte('&')
}
w.WriteString(keyEscaped)
w.WriteByte('=')
if v != "" {
w.WriteString(strings.Replace(url.QueryEscape(v), "+", "%20", -1))
}
}
}
}
// writeCanonicalString removes leading and trailing whitespaces (as defined by Unicode)
// in s, replaces consecutive spaces (' ') in s with a single space, and then
// write the result to w.
func writeCanonicalString(w *bufio.Writer, s string) {
const dblSpace = " "
s = strings.TrimSpace(s)
// bail if str doesn't contain " "
j := strings.Index(s, dblSpace)
if j < 0 {
w.WriteString(s)
return
}
w.WriteString(s[:j])
// replace all " " with " " in a performant way
var lastIsSpace bool
for i, l := j, len(s); i < l; i++ {
if s[i] == ' ' {
if !lastIsSpace {
w.WriteByte(' ')
lastIsSpace = true
}
continue
}
lastIsSpace = false
w.WriteByte(s[i])
}
}
type debugHasher struct {
buf []byte
}
func (dh *debugHasher) Write(b []byte) (int, error) {
dh.buf = append(dh.buf, b...)
return len(b), nil
}
func (dh *debugHasher) Sum(b []byte) []byte {
return nil
}
func (dh *debugHasher) Reset() {
// do nothing
}
func (dh *debugHasher) Size() int {
return 0
}
func (dh *debugHasher) BlockSize() int {
return sha256.BlockSize
}
func (dh *debugHasher) Println() {
fmt.Printf("---%s---\n", dh.buf)
}