7
curl "github.com/andelf/go-curl"
8
. "launchpad.net/gwacl/logging"
13
type X509Request struct {
21
// Print debugging output.
24
func SetVerbose(newVerbose bool) {
28
// newX509RequestGET initializes an X509Request for a GET. You may still need
29
// to set further values.
30
func newX509RequestGET(url, apiVersion string) *X509Request {
34
APIVersion: apiVersion,
38
// newX509RequestPOST initializes an X509Request for a POST. You may still
39
// need to set further values.
40
func newX509RequestPOST(url, apiVersion string, payload []byte, contentType string) *X509Request {
44
APIVersion: apiVersion,
46
ContentType: contentType,
50
// newX509RequestDELETE initializes an X509Request for a DELETE.
51
func newX509RequestDELETE(url, apiVersion string) *X509Request {
55
APIVersion: apiVersion,
59
// newX509RequestPUT initializes an X509Request for a PUT. You may still
60
// need to set further values.
61
func newX509RequestPUT(url, apiVersion string, payload []byte, contentType string) *X509Request {
65
APIVersion: apiVersion,
67
ContentType: contentType,
71
type x509Response struct {
73
// TODO: What exactly do we get back? How will we know its encoding?
79
func newX509Response() *x509Response {
81
Body: make([]byte, 0),
82
RawHeader: make([]byte, 0),
86
// HTTP/MS-DOS-style line break.
89
// stripPreliminaryResponses takes an HTTP response and returns the final
90
// response block. It leaves out any preceding "Continue" responses or
91
// other non-final responses.
92
func stripPreliminaryResponses(httpResponse []byte) []byte {
93
blankLine := []byte(crlf + crlf)
95
// The response ends in a blank line, although we don't want to rely on
96
// that without clear documentation of go-curl's header-writing API.
97
// Ignoring that final optional one, where is the last blank line?
98
withoutLastBlank := httpResponse[:len(httpResponse)-len(blankLine)]
99
lastBlank := bytes.LastIndex(withoutLastBlank, blankLine)
101
// There are no preliminary responses. The response is fine as-is.
104
return httpResponse[lastBlank+len(blankLine):]
107
// stripResponseLine takes an HTTP response and returns the part that makes
108
// up the MIME header.
109
func stripResponseLine(httpHeader []byte) ([]byte, error) {
110
lineBreak := []byte(crlf)
111
newlinePos := bytes.Index(httpHeader, lineBreak)
112
if newlinePos == -1 {
113
msg := fmt.Errorf("HTTP response header lacks leading line: '%s'", httpHeader)
116
return httpHeader[newlinePos+len(lineBreak):], nil
119
// parseHeader parses the raw header as stored in RawHeader, and uses it to
120
// initialize Header.
121
func (response *x509Response) parseHeader() error {
122
rawHeader := stripPreliminaryResponses(response.RawHeader)
123
mimeHeader, err := stripResponseLine(rawHeader)
128
// textproto.NewReader() parses a MIME header. But it requires a
129
// bufio.Reader (struct), not an io.Reader (interface). So wrap the header
130
// contents in an io.Reader using bytes.NewReader(), then wrap that in a
131
// bufio.Reader using bufio.NewReader(), and then pass that to
132
// textproto.NewReader().
133
reader := textproto.NewReader(bufio.NewReader(bytes.NewReader(mimeHeader)))
134
header, err := reader.ReadMIMEHeader()
138
response.Header = http.Header(header)
142
func performX509CurlRequest(session *x509Session, request *X509Request) (*x509Response, error) {
144
Debug("Performing request")
145
Debug("Request url: " + request.URL)
146
Debug("Request method: " + request.Method)
147
Debugf("Request body: %s\n", request.Payload)
149
response := newX509Response()
150
ch := request.makeCurlRequest(session, response)
153
// TODO: Add timeout provision.
154
// TODO: Get rid of the annoying debug message go-curl spits out
155
// for every single request: "debug (Getinfo) 200".
161
status, err := ch.Getinfo(curl.INFO_RESPONSE_CODE)
165
err = response.parseHeader()
169
response.StatusCode = status.(int)
171
Debug("Got response")
172
Debugf("Response status: %d\n", response.StatusCode)
173
Debugf("Response headers: %s\n", response.RawHeader)
174
Debugf("Response body: %s\n", response.Body)
179
// The maximum number of redirection-followings allowed.
180
var _CURL_MAX_REDIRECTS = 10
182
// makeCurlRequest produces a curl.CURL representing the request.
183
// Clean up the request by calling its Cleanup() method after you're done
185
func (request *X509Request) makeCurlRequest(session *x509Session, response *x509Response) *curl.CURL {
186
ch := curl.EasyInit()
188
ch.Setopt(curl.OPT_SSLCERT, session.certFile)
189
ch.Setopt(curl.OPT_KEYPASSWD, "")
190
ch.Setopt(curl.OPT_URL, request.URL)
191
//ch.Setopt(curl.OPT_VERBOSE, true)
192
ch.Setopt(curl.OPT_FOLLOWLOCATION, true)
193
ch.Setopt(curl.OPT_MAXREDIRS, _CURL_MAX_REDIRECTS)
194
ch.Setopt(curl.OPT_SSLVERSION, 3)
195
versionHeader := "x-ms-version: " + request.APIVersion
196
headers := []string{versionHeader}
197
request.setCurlHTTPMethod(ch)
199
if len(request.Payload) != 0 {
200
headers = append(headers, "Content-Type: "+request.ContentType)
201
// Arrange for the request body to be written.
202
headers = append(headers, "Transfer-Encoding: chunked")
203
ch.Setopt(curl.OPT_READFUNCTION, func(buf []byte, _ interface{}) int {
204
// Buffered request write. Copy as much of the payload as will
205
// fit into the buffer curl gave us for it, and return the number
206
// of bytes written. The calls to this callback will stop once
208
// TODO: Test this against various nasty data sizes.
209
bytesCopied := copy(buf, request.Payload)
210
request.Payload = request.Payload[bytesCopied:]
215
// Arrange for the response body to be stored in response.Body.
216
ch.Setopt(curl.OPT_WRITEFUNCTION, func(data []byte, _ interface{}) bool {
217
response.Body = append(response.Body, data...)
218
// Indicate that we're done processing the data.
222
// Arrange for the response header to be stored in response.RawHeader.
223
// After the request we can parse them into response.Header.
224
ch.Setopt(curl.OPT_HEADERFUNCTION, func(data []byte, _ interface{}) bool {
225
response.RawHeader = append(response.RawHeader, data...)
226
// Indicate that we're done processing the data.
230
ch.Setopt(curl.OPT_HTTPHEADER, headers)
234
// setCurlHTTPMethod sets up a CURL object to perform an HTTP request of
235
// the HTTP method (GET, POST, etc.) appropriate for the request.
236
func (request *X509Request) setCurlHTTPMethod(ch *curl.CURL) {
237
switch request.Method {
239
// Nothing to be done. This is the default.
241
ch.Setopt(curl.OPT_POST, true)
243
ch.Setopt(curl.OPT_UPLOAD, true)
245
ch.Setopt(curl.OPT_CUSTOMREQUEST, "DELETE")
247
panic(fmt.Errorf("Unsupported http method: %s", request.Method))