aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/aws/smithy-go/encoding/httpbinding/path_replace.go
blob: e78926c9a562ddb8acace7d81bc6e6d35101f98a (plain) (blame)
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
package httpbinding

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]
}

// replacePathElement replaces a single element in the path []byte.
// Escape is used to control whether the value will be escaped using Amazon path escape style.
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
}

// 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 == '~'
	}
}