-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathhttpparse.go
More file actions
220 lines (190 loc) · 5.22 KB
/
httpparse.go
File metadata and controls
220 lines (190 loc) · 5.22 KB
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
package httpsign
import (
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
)
// some fields (specifically, query params) may appear more than once, and those occurrences are ordered.
type components map[string]string
type parsedMessage struct {
derived components
url *url.URL
headers, trailers http.Header // we abuse this type: names are lowercase instead of canonicalized
qParams url.Values
}
func parseRequest(req *http.Request, withTrailers bool, maxBodySize int64, schemeOverride string) (*parsedMessage, error) {
if req == nil {
return nil, fmt.Errorf("nil request")
}
scheme := "http"
if req.TLS != nil {
scheme = "https"
}
if schemeOverride != "" {
scheme = schemeOverride
}
msg := &Message{
method: req.Method,
url: req.URL,
headers: req.Header,
trailers: req.Trailer,
body: &req.Body,
authority: req.Host,
scheme: scheme,
}
return parseMessage(msg, withTrailers, maxBodySize)
}
//lint:ignore ST1003 QPs is intentional abbreviation for Query Parameters
func reEncodeQPs(values url.Values) url.Values {
escaped := url.Values{}
for key, v := range values { // Re-escape query parameters, both names and values
escapedKey := QueryEscapeForSignature(key)
escaped[escapedKey] = make([]string, len(values[key]))
for key2 := range v {
escaped[escapedKey][key2] = QueryEscapeForSignature(values[key][key2])
}
}
return escaped
}
func normalizeHeaderNames(header http.Header) http.Header {
if header == nil {
return nil
}
var t = http.Header{}
for k, v := range header {
t[strings.ToLower(k)] = v
}
return t
}
func parseResponse(res *http.Response, withTrailers bool, maxBodySize int64) (*parsedMessage, error) {
msg := &Message{
statusCode: &res.StatusCode,
headers: res.Header,
trailers: res.Trailer,
body: &res.Body,
}
return parseMessage(msg, withTrailers, maxBodySize)
}
func validateMessageHeaders(header http.Header) error {
// Go accepts header names that start with "@", which is forbidden by the RFC
for k := range header {
if strings.HasPrefix(k, "@") {
return fmt.Errorf("potentially malicious header detected \"%s\"", k)
}
}
return nil
}
func foldFields(fields []string) string {
if len(fields) == 0 {
return ""
}
ff := strings.TrimSpace(fields[0])
for i := 1; i < len(fields); i++ {
ff += ", " + strings.TrimSpace(fields[i])
}
return ff
}
func derivedComponent(name, v string, components components) {
components[name] = v
}
func generateReqDerivedComponents(method string, u *url.URL, authority string, components components) {
derivedComponent("@method", method, components)
derivedComponent("@target-uri", scTargetURI(u), components)
derivedComponent("@path", scPath(u), components)
derivedComponent("@authority", authority, components)
derivedComponent("@scheme", scScheme(u), components)
derivedComponent("@request-target", scRequestTarget(u), components)
derivedComponent("@query", scQuery(u), components)
}
func scPath(theURL *url.URL) string {
return theURL.EscapedPath()
}
func scQuery(url *url.URL) string {
return "?" + url.RawQuery
}
func scRequestTarget(url *url.URL) string {
path := url.Path
if path == "" {
path = "/" // Normalize path, issue #8, and see https://www.rfc-editor.org/rfc/rfc9110#section-4.2.3
}
if url.RawQuery == "" {
return path
}
return path + "?" + url.RawQuery
}
func scScheme(url *url.URL) string {
if url.Scheme == "" {
return "http"
}
return url.Scheme
}
func scTargetURI(url *url.URL) string {
return url.String()
}
func scStatus(statusCode int) string {
return strconv.Itoa(statusCode)
}
func parseMessage(msg *Message, withTrailers bool, maxBodySize int64) (*parsedMessage, error) {
if msg == nil {
return nil, fmt.Errorf("message must not be nil")
}
err := validateMessageHeaders(msg.headers)
if err != nil {
return nil, err
}
err = validateMessageHeaders(msg.trailers)
if err != nil {
return nil, fmt.Errorf("could not validate trailers: %w", err)
}
if withTrailers {
if msg.body != nil {
_, err = duplicateBody(msg.body, maxBodySize)
if err != nil {
return nil, fmt.Errorf("cannot duplicate message body: %w", err)
}
}
}
derived := components{}
var u *url.URL
var qParams url.Values
if msg.method != "" || msg.url != nil {
if msg.method == "" || msg.url == nil {
return nil, fmt.Errorf("invalid state: method or url without the other")
}
u = msg.url
if u == nil {
u = &url.URL{Path: "/"}
}
if u.Host == "" && msg.authority != "" {
u.Host = msg.authority
}
if u.Scheme == "" {
if msg.scheme != "" {
u.Scheme = msg.scheme
} else {
u.Scheme = "http"
}
}
if u.RawQuery != "" {
values, err := url.ParseQuery(u.RawQuery)
if err != nil {
return nil, fmt.Errorf("cannot parse query: %s", u.RawQuery)
}
qParams = reEncodeQPs(values)
}
generateReqDerivedComponents(msg.method, u, msg.authority, derived)
} else if msg.statusCode != nil {
derivedComponent("@status", scStatus(*msg.statusCode), derived)
} else {
return nil, fmt.Errorf("invalid state: method and url, or status required")
}
return &parsedMessage{
derived: derived,
url: u,
headers: normalizeHeaderNames(msg.headers),
trailers: normalizeHeaderNames(msg.trailers),
qParams: qParams,
}, nil
}