@@ -33,11 +33,13 @@ package httpsig
3333
3434import (
3535 "context"
36- "encoding/base64"
3736 "errors"
3837 "fmt"
38+ "net"
3939 "net/http"
4040 "net/url"
41+ "regexp"
42+ "strconv"
4143 "strings"
4244
4345 "github.com/dunglas/httpsfv"
@@ -100,7 +102,7 @@ func parseHeader(values []string) (httpsfv.StructuredFieldValue, error) {
100102 return nil , errors .New ("unable to parse structured header" )
101103}
102104
103- func canonicaliseComponent (component string , params * httpsfv.Params , message * Message ) (httpsfv. StructuredFieldValue , error ) {
105+ func canonicaliseComponent (component string , params * httpsfv.Params , message * Message ) ([] string , error ) {
104106 _ , isReq := params .Get ("req" )
105107 switch component {
106108 case "@method" :
@@ -110,56 +112,71 @@ func canonicaliseComponent(component string, params *httpsfv.Params, message *Me
110112 if ! message .IsRequest && ! isReq {
111113 return nil , errors .New ("method component not valid for responses" )
112114 }
113- return httpsfv . NewItem ( strings .ToUpper (message .Method )) , nil
115+ return [] string { strings .ToUpper (message .Method )} , nil
114116 case "@target-uri" :
115117 // Section 2.2.2 covers canonicalisation of the target-uri.
116118 // https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-19.html#name-target-uri
117119 if ! message .IsRequest && ! isReq {
118120 return nil , errors .New ("target-uri component not valid for responses" )
119121 }
120- return httpsfv . NewItem ( message .URL .String ()) , nil
122+ return [] string { message .URL .String ()} , nil
121123 case "@authority" :
122124 // Section 2.2.3 covers canonicalisation of the target-uri.
123125 // https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-19.html#name-authority
124126 if ! message .IsRequest && ! isReq {
125127 return nil , errors .New ("authority component not valid for responses" )
126128 }
127- return httpsfv .NewItem (message .Authority ), nil
129+ host , port , err := net .SplitHostPort (message .Authority )
130+ if err != nil {
131+ // no port, just use the whole thing
132+ return []string {strings .ToLower (message .Authority )}, nil
133+ }
134+ switch strings .ToLower (message .URL .Scheme ) {
135+ case "http" :
136+ if port == "80" {
137+ return []string {strings .ToLower (host )}, nil
138+ }
139+ case "https" :
140+ if port == "443" {
141+ return []string {strings .ToLower (host )}, nil
142+ }
143+ }
144+ return []string {strings .ToLower (message .Authority )}, nil
128145 case "@scheme" :
129146 // Section 2.2.4 covers canonicalisation of the scheme.
130147 // https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-19.html#name-scheme
131148 // Scheme should always be lowercase.
132149 if ! message .IsRequest && ! isReq {
133150 return nil , errors .New ("scheme component not valid for responses" )
134151 }
135- return httpsfv . NewItem ( strings .ToLower (message .URL .Scheme )) , nil
152+ return [] string { strings .ToLower (message .URL .Scheme )} , nil
136153 case "@request-target" :
137154 // Section 2.2.5 covers canonicalisation of the request-target.
138155 // https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-19.html#name-request-target
139156 if ! message .IsRequest && ! isReq {
140157 return nil , errors .New ("request-target component not valid for responses" )
141158 }
142- return httpsfv . NewItem ( message .URL .RequestURI ()) , nil
159+ return [] string { message .URL .RequestURI ()} , nil
143160 case "@path" :
144161 // Section 2.2.6 covers canonicalisation of the path.
145162 // https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-19.html#name-path
146163 if ! message .IsRequest && ! isReq {
147164 return nil , errors .New ("path component not valid for responses" )
148165 }
149166 // empty path means use `/`
150- path := message .URL .Path
167+ path := message .URL .EscapedPath ()
151168 if path == "" || path [0 ] != '/' {
152169 path = "/" + path
153170 }
154- return httpsfv . NewItem ( path ) , nil
171+ return [] string { path } , nil
155172 case "@query" :
156173 // Section 2.2.7 covers canonicalisation of the query.
157174 // https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-19.html#name-query
158175 if ! message .IsRequest && ! isReq {
159176 return nil , errors .New ("query component not valid for responses" )
160177 }
161178 // absent query params means use `?`
162- return httpsfv . NewItem ( "?" + message .URL .RawQuery ) , nil
179+ return [] string { "?" + message .URL .RawQuery } , nil
163180 case "@query-param" :
164181 // Section 2.2.8 covers canonicalisation of the query-param.
165182 // https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-19.html#name-query-parameters
@@ -173,37 +190,44 @@ func canonicaliseComponent(component string, params *httpsfv.Params, message *Me
173190 if ! ok {
174191 return nil , errors .New ("query-param must have a named parameter" )
175192 }
176- decodedName , err := url .QueryUnescape (name .(string ))
193+ decodedName , err := url .PathUnescape (name .(string ))
177194 if err != nil {
178195 return nil , fmt .Errorf ("unable to decode query parameter name: %w" , err )
179196 }
180197 query := message .URL .Query ()
181198 if ! query .Has (decodedName ) {
182199 return nil , fmt .Errorf ("expected query parameter \" %s\" not found" , name )
183200 }
184- decodedValue , err := url .QueryUnescape (query .Get (decodedName ))
185- if err != nil {
186- return nil , fmt .Errorf ("unable to decode query parameter value: %w" , err )
201+ var values []string
202+ for _ , v := range query [decodedName ] {
203+ decodedValue , err := url .PathUnescape (v )
204+ if err != nil {
205+ return nil , fmt .Errorf ("unable to decode query parameter value: %w" , err )
206+ }
207+ values = append (values , url .PathEscape (decodedValue ))
187208 }
188- return httpsfv . NewItem ( url . QueryEscape ( decodedValue )) , nil
209+ return values , nil
189210 case "@status" :
190211 // Section 2.2.9 covers canonicalisation of the status.
191212 // https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-19.html#name-status-code
192- if message .IsRequest && isReq {
213+ if message .IsRequest || ( ! message . IsRequest && isReq ) {
193214 return nil , errors .New ("status component not valid for requests" )
194215 }
195- return httpsfv . NewItem (message .StatusCode ), nil
216+ return [] string { strconv . Itoa (message .StatusCode )} , nil
196217 default :
197218 return nil , fmt .Errorf ("unknown component: %s" , component )
198219 }
199220}
200221
201- func canonicaliseHeader (header string , params * httpsfv.Params , message * Message ) (httpsfv. StructuredFieldValue , error ) {
222+ func canonicaliseHeader (header string , params * httpsfv.Params , message * Message ) ([] string , error ) {
202223 var v []string
203224 if _ , isReq := params .Get ("req" ); isReq {
204225 if message .IsRequest {
205226 return nil , errors .New ("req parameter not valid for requests" )
206227 }
228+ if message .RequestHeader == nil {
229+ return nil , errors .New ("req parameter requires a request header" )
230+ }
207231 v = message .RequestHeader .Values (header )
208232 } else {
209233 v = message .Header .Values (header )
@@ -242,29 +266,51 @@ func canonicaliseHeader(header string, params *httpsfv.Params, message *Message)
242266 if ! ok {
243267 return nil , fmt .Errorf ("unable to find key \" %s\" in structured field" , key )
244268 }
245- return val , nil
269+
270+ marshalled , err := httpsfv .Marshal (val )
271+ if err != nil {
272+ return nil , err
273+ }
274+
275+ return []string {marshalled }, nil
246276 }
247- return parsed , nil
277+
278+ marshalled , err := httpsfv .Marshal (parsed )
279+ if err != nil {
280+ return nil , err
281+ }
282+
283+ return []string {marshalled }, nil
248284 }
249285
250286 if isBs {
251- encoded := httpsfv.List {}
252- for _ , sv := range v {
253- decoded , err := base64 .StdEncoding .DecodeString (sv )
287+ encoded := make ([]string , len (v ))
288+ for i , sv := range v {
289+ regex := regexp .MustCompile (`\s+` )
290+ values := strings .Split (sv , "," )
291+ for j , v := range values {
292+ values [j ] = regex .ReplaceAllString (strings .TrimSpace (v ), " " )
293+ }
294+ item := httpsfv .NewItem ([]byte (strings .Join (values , ", " )))
295+ marshalled , err := httpsfv .Marshal (item )
254296 if err != nil {
255- return nil , fmt . Errorf ( "unable to decode base64 value %s: %w" , sv , err )
297+ return nil , err
256298 }
257- enc := base64 .StdEncoding .EncodeToString ([]byte (strings .TrimSpace (string (decoded ))))
258- item := httpsfv .NewItem ([]byte (enc ))
259- encoded = append (encoded , item )
299+ encoded [i ] = marshalled
260300 }
301+
261302 return encoded , nil
262303 }
263304
264305 // raw encoding
265- encoded := httpsfv.List {}
266- for _ , sv := range v {
267- encoded = append (encoded , httpsfv .NewItem (strings .TrimSpace (sv )))
306+ encoded := make ([]string , len (v ))
307+ regex := regexp .MustCompile (`\s+` )
308+ for i , sv := range v {
309+ values := strings .Split (sv , "," )
310+ for j , v := range values {
311+ values [j ] = regex .ReplaceAllString (strings .TrimSpace (v ), " " )
312+ }
313+ encoded [i ] = strings .Join (values , ", " )
268314 }
269315 return encoded , nil
270316}
@@ -284,15 +330,6 @@ func quoteString(input string) string {
284330 return input
285331}
286332
287- func unquoteString (input string ) string {
288- // if it's quoted, attempt to unquote
289- bytes := []byte (input )
290- if len (bytes ) > 2 && bytes [0 ] == '"' && bytes [len (bytes )- 1 ] == '"' {
291- bytes = bytes [1 : len (bytes )- 1 ]
292- }
293- return string (bytes )
294- }
295-
296333func formatSignatureBase (items []signatureItem ) (string , error ) {
297334 var b strings.Builder
298335
@@ -302,16 +339,13 @@ func formatSignatureBase(items []signatureItem) (string, error) {
302339 return "" , err
303340 }
304341
305- marshalledValue , err := httpsfv .Marshal (item .value )
306- if err != nil {
307- return "" , err
308- }
342+ value := strings .Join (item .value , ", " )
309343
310- _ , err = b .WriteString (fmt .Sprintf ("%s: %s\n " , marshalledKey , unquoteString ( marshalledValue ) ))
344+ _ , err = b .WriteString (fmt .Sprintf ("%s: %s\n " , marshalledKey , value ))
311345 if err != nil {
312346 return "" , err
313347 }
314348 }
315349
316- return strings .TrimSpace (b .String ()), nil
350+ return strings .TrimRight (b .String (), " \n " ), nil
317351}
0 commit comments