|
1 | 1 | package surl
|
2 | 2 |
|
3 | 3 | import (
|
4 |
| - "bytes" |
5 |
| - "encoding/base64" |
6 |
| - "strconv" |
| 4 | + "net/url" |
7 | 5 | "time"
|
8 | 6 | )
|
9 | 7 |
|
10 | 8 | // Formatter adds/extracts the signature and expiry to/from a URL according to a
|
11 | 9 | // specific format
|
12 | 10 | type Formatter interface {
|
13 |
| - // AddExpiry adds the expiry to the data, creating a payload for signing |
14 |
| - AddExpiry(exp time.Time, data []byte) []byte |
15 |
| - // AddSignature adds the signature to the payload, creating a signed message |
16 |
| - AddSignature(sig, payload []byte) []byte |
17 |
| - // ExtractSignature extracts the signature from the signed message, |
18 |
| - // returning the signature as well as the signed payload. |
19 |
| - ExtractSignature(msg []byte) ([]byte, []byte, error) |
20 |
| - // ExtractExpiry extracts the expiry from the signed payload, returning the |
21 |
| - // expiry as well as the original data. |
22 |
| - ExtractExpiry(payload []byte) (time.Time, []byte, error) |
23 |
| -} |
24 |
| - |
25 |
| -// URLPathFormatter includes the signature and expiry in a |
26 |
| -// message according to the format: <prefix><sig>.<exp>/<data>. Suitable for |
27 |
| -// URL paths as an alternative to using query parameters. |
28 |
| -type URLPathFormatter struct { |
29 |
| - // Prefix message with a string |
30 |
| - Prefix string |
31 |
| -} |
32 |
| - |
33 |
| -// AddExpiry adds expiry as a path component e.g. /foo/bar -> |
34 |
| -// 390830893/foo/bar |
35 |
| -func (u *URLPathFormatter) AddExpiry(exp time.Time, data []byte) []byte { |
36 |
| - // convert expiry to bytes |
37 |
| - expBytes := strconv.FormatInt(exp.Unix(), 10) |
38 |
| - |
39 |
| - payload := make([]byte, 0, len(expBytes)+len(data)) |
40 |
| - // add expiry |
41 |
| - payload = append(payload, expBytes...) |
42 |
| - // add data |
43 |
| - return append(payload, data...) |
44 |
| -} |
45 |
| - |
46 |
| -// AddSignature adds signature as a path component alongside the expiry e.g. |
47 |
| -// abZ3G/foo/bar -> KKLJjd3090fklaJKLJK.abZ3G/foo/bar |
48 |
| -func (u *URLPathFormatter) AddSignature(sig, payload []byte) []byte { |
49 |
| - encSize := base64.RawURLEncoding.EncodedLen(len(sig)) |
50 |
| - // calculate msg capacity |
51 |
| - mcap := encSize + len(payload) + 1 // +1 for '.' |
52 |
| - if u.Prefix != "" { |
53 |
| - mcap += len(u.Prefix) |
54 |
| - } |
55 |
| - msg := make([]byte, 0, mcap) |
56 |
| - // add prefix |
57 |
| - msg = append(msg, u.Prefix...) |
58 |
| - // add encoded sig |
59 |
| - msg = msg[0 : len(u.Prefix)+encSize] |
60 |
| - base64.RawURLEncoding.Encode(msg[len(u.Prefix):], sig) |
61 |
| - // add '.' |
62 |
| - msg = append(msg, '.') |
63 |
| - // add payload |
64 |
| - return append(msg, payload...) |
65 |
| -} |
66 |
| - |
67 |
| -// ExtractSignature decodes and splits the signature and payload from the signed message. |
68 |
| -func (u *URLPathFormatter) ExtractSignature(msg []byte) ([]byte, []byte, error) { |
69 |
| - if !bytes.HasPrefix(msg, []byte(u.Prefix)) { |
70 |
| - return nil, nil, ErrInvalidMessageFormat |
71 |
| - } |
72 |
| - // remove prefix |
73 |
| - msg = msg[len(u.Prefix):] |
74 |
| - |
75 |
| - // prise apart sig and payload |
76 |
| - parts := bytes.SplitN(msg, []byte{'.'}, 2) |
77 |
| - if len(parts) != 2 { |
78 |
| - return nil, nil, ErrInvalidMessageFormat |
79 |
| - } |
80 |
| - sig := parts[0] |
81 |
| - payload := parts[1] |
82 |
| - |
83 |
| - // decode base64-encoded sig into bytes |
84 |
| - decoded := make([]byte, base64.RawURLEncoding.DecodedLen(len(sig))) |
85 |
| - _, err := base64.RawURLEncoding.Decode(decoded, sig) |
86 |
| - if err != nil { |
87 |
| - return nil, nil, err |
88 |
| - } |
89 |
| - |
90 |
| - return decoded, payload, nil |
91 |
| -} |
| 11 | + // AddExpiry adds an expiry to a URL |
| 12 | + AddExpiry(*url.URL, time.Time) |
92 | 13 |
|
93 |
| -// ExtractExpiry decodes and splits the expiry and data from the payload. |
94 |
| -func (u *URLPathFormatter) ExtractExpiry(payload []byte) (time.Time, []byte, error) { |
95 |
| - // prise apart expiry and data |
96 |
| - slash := 0 |
97 |
| - for i, b := range payload { |
98 |
| - if b == '/' { |
99 |
| - slash = i |
100 |
| - break |
101 |
| - } |
102 |
| - } |
103 |
| - if slash == 0 { |
104 |
| - return time.Time{}, nil, ErrInvalidMessageFormat |
105 |
| - } |
106 |
| - expBytes := payload[:slash] |
107 |
| - data := payload[slash:] |
| 14 | + // AddSignature adds a signature to a URL |
| 15 | + AddSignature(*url.URL, []byte) |
108 | 16 |
|
109 |
| - // convert bytes into int |
110 |
| - expInt, err := strconv.ParseInt(string(expBytes), 10, 64) |
111 |
| - if err != nil { |
112 |
| - return time.Time{}, nil, err |
113 |
| - } |
114 |
| - // convert int into time.Time |
115 |
| - t := time.Unix(expInt, 0) |
| 17 | + // ExtractSignature extracts a signature from a URL, returning the modified |
| 18 | + // URL and the signature. |
| 19 | + ExtractSignature(*url.URL) (*url.URL, []byte, error) |
116 | 20 |
|
117 |
| - return t, data, nil |
| 21 | + // ExtractExpiry extracts an expiry from a URL, returning the modified URL |
| 22 | + // and the signature. |
| 23 | + ExtractExpiry(*url.URL) (*url.URL, time.Time, error) |
118 | 24 | }
|
0 commit comments