-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathrequest.go
More file actions
161 lines (134 loc) · 4.42 KB
/
request.go
File metadata and controls
161 lines (134 loc) · 4.42 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
package bricklinkapi
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"sort"
"strconv"
"strings"
"time"
)
// RequestHandler defines the request interface
type RequestHandler interface {
Request(method, uri string) (body []byte, err error)
}
type request struct {
consumerKey string
consumerSecret string
token string
tokenSecret string
}
// request() handles the request process. It builds of the oauth header,
// sets the request parameters and issues the request.
// The response body is returned as a []byte slice.
func (r request) Request(method, uri string) (body []byte, err error) {
// new client
client := http.Client{
Timeout: time.Second * 30, // Maximum of 5 secs
}
// build new request
req, err := http.NewRequest(method, brickLinkAPIBaseURL+uri, nil)
if err != nil {
return body, fmt.Errorf("could not build new request: %v", err)
}
// construct timestamp and nonce used in the oauth
timeUnix := time.Now().Unix()
timestamp := strconv.FormatInt(timeUnix, 10)
nonce := strconv.FormatInt(rand.New(rand.NewSource(timeUnix)).Int63(), 10)
// construct values for oauth params
var oauthParams []string
oauthParams = append(oauthParams, "oauth_consumer_key="+r.consumerKey)
oauthParams = append(oauthParams, "oauth_token="+r.token)
oauthParams = append(oauthParams, "oauth_signature_method="+oauthSignatureMethod)
oauthParams = append(oauthParams, "oauth_timestamp="+timestamp)
oauthParams = append(oauthParams, "oauth_nonce="+nonce)
oauthParams = append(oauthParams, "oauth_version="+oauthVersion)
// extract uri params from URI and add to oauth params map
uriSplit := strings.Split(req.URL.String(), "?")
if len(uriSplit) > 1 {
uriParamString := strings.Split(uriSplit[1], "&")
for _, s := range uriParamString {
oauthParams = append(oauthParams, s)
}
}
// generate signature
baseURL := generateBaseURL(req, oauthParams)
signature := generateSignature(baseURL, r.consumerSecret, r.tokenSecret)
// set header
req.Header.Set("User-Agent", "bricklinkapi-test")
// build authorization string for the header
authorization := "OAuth "
authorization += "oauth_consumer_key=\"" + r.consumerKey + "\","
authorization += "oauth_token=\"" + r.token + "\","
authorization += "oauth_signature_method=\"" + oauthSignatureMethod + "\","
authorization += "oauth_signature=\"" + signature + "\","
authorization += "oauth_timestamp=\"" + timestamp + "\","
authorization += "oauth_nonce=\"" + nonce + "\","
authorization += "oauth_version=\"" + oauthVersion + "\""
req.Header.Set("Authorization", authorization)
// start request
resp, err := client.Do(req)
if err != nil {
return body, err
}
// read response body
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return body, err
}
return body, nil
}
// generateBaseURL generates the base URL for the signature
func generateBaseURL(req *http.Request, params []string) string {
base := req.Method + "&"
base += encode(strings.Split(req.URL.String(), "?")[0])
// sort params
sort.Strings(params)
paramString := strings.Join(params, "&")
encodedParamString := encode(paramString)
if len(encodedParamString) != 0 {
base += "&" + encodedParamString
}
return base
}
// generateSignature generates the OAuth signature for the request.
// It receives the base string, the consumer secret and the token secret
func generateSignature(base, cs, ts string) string {
// construct the key
key := encode(cs) + "&" + encode(ts)
// encrypt with HMAC-SHA1
h := hmac.New(sha1.New, []byte(key))
h.Write([]byte(base))
rawSignature := h.Sum(nil)
// base64 encode
base64Signature := make([]byte, base64.StdEncoding.EncodedLen(len(rawSignature)))
base64.StdEncoding.Encode(base64Signature, rawSignature)
// percent encode and return
signature := encode(string(base64Signature))
return signature
}
// Implements percent encoding. The Golang std library implementation of
// url.QueryEscape is not valid for the oauth spec. Mainly spaces getting
// encoded as "+" instead of "%20"
func encode(s string) string {
e := []byte(nil)
for i := 0; i < len(s); i++ {
b := s[i]
if encodable(b) {
e = append(e, '%')
e = append(e, "0123456789ABCDEF"[b>>4])
e = append(e, "0123456789ABCDEF"[b&15])
} else {
e = append(e, b)
}
}
return string(e)
}
func encodable(b byte) bool {
return !('A' <= b && b <= 'Z' || 'a' <= b && b <= 'z' ||
'0' <= b && b <= '9' || b == '-' || b == '.' || b == '_' || b == '~')
}