-
Notifications
You must be signed in to change notification settings - Fork 29
/
helper.go
161 lines (144 loc) · 3.7 KB
/
helper.go
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 main
import (
"bytes"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"strconv"
"time"
)
// The code in this file has to do communicating with the meek-http-helper
// browser extension.
type JSONRequest struct {
Method string `json:"method,omitempty"`
URL string `json:"url,omitempty"`
Header map[string]string `json:"header,omitempty"`
Body []byte `json:"body,omitempty"`
Proxy *ProxySpec `json:"proxy,omitempty"`
}
type JSONResponse struct {
Error string `json:"error,omitempty"`
Status int `json:"status"`
Body []byte `json:"body"`
}
// ProxySpec encodes information we need to connect through a proxy.
type ProxySpec struct {
// Acceptable values for Type are as in proposal 232: "http", "socks5",
// or "socks4a".
Type string `json:"type"`
Host string `json:"host"`
Port int `json:"port"`
}
// Return a ProxySpec suitable for the proxy URL in u.
func makeProxySpec(u *url.URL) (*ProxySpec, error) {
spec := new(ProxySpec)
var err error
var portStr string
var port uint64
if u == nil {
// No proxy.
return nil, nil
}
// Firefox's nsIProxyInfo doesn't allow credentials.
if u.User != nil {
return nil, errors.New("proxy URLs with a username or password can't be used with the helper")
}
switch u.Scheme {
case "http", "socks5", "socks4a":
spec.Type = u.Scheme
default:
return nil, errors.New("unknown scheme")
}
spec.Host, portStr, err = net.SplitHostPort(u.Host)
if err != nil {
return nil, err
}
if spec.Host == "" {
return nil, errors.New("missing host")
}
port, err = strconv.ParseUint(portStr, 10, 16)
if err != nil {
return nil, err
}
spec.Port = int(port)
return spec, nil
}
// Do an HTTP roundtrip through the configured browser extension, using the
// payload data in buf and the request metadata in info.
func roundTripWithHelper(buf []byte, info *RequestInfo) (*http.Response, error) {
s, err := net.DialTCP("tcp", nil, options.HelperAddr)
if err != nil {
return nil, err
}
defer s.Close()
// Encode our JSON.
req := JSONRequest{
Method: "POST",
URL: info.URL.String(),
Header: make(map[string]string),
Body: buf,
}
req.Header["X-Session-Id"] = info.SessionID
if info.Host != "" {
req.Header["Host"] = info.Host
}
req.Proxy, err = makeProxySpec(options.ProxyURL)
if err != nil {
return nil, err
}
encReq, err := json.Marshal(&req)
if err != nil {
return nil, err
}
// log.Printf("encoded %s", encReq)
// Send the request.
s.SetWriteDeadline(time.Now().Add(helperWriteTimeout))
err = binary.Write(s, binary.BigEndian, uint32(len(encReq)))
if err != nil {
return nil, err
}
_, err = s.Write(encReq)
if err != nil {
return nil, err
}
// Read the response.
var length uint32
s.SetReadDeadline(time.Now().Add(helperReadTimeout))
err = binary.Read(s, binary.BigEndian, &length)
if err != nil {
return nil, err
}
if length > maxHelperResponseLength {
return nil, errors.New(fmt.Sprintf("helper's returned data is too big (%d > %d)",
length, maxHelperResponseLength))
}
encResp := make([]byte, length)
_, err = io.ReadFull(s, encResp)
if err != nil {
return nil, err
}
// log.Printf("received %s", encResp)
// Decode their JSON.
var jsonResp JSONResponse
err = json.Unmarshal(encResp, &jsonResp)
if err != nil {
return nil, err
}
if jsonResp.Error != "" {
return nil, errors.New(fmt.Sprintf("helper returned error: %s", jsonResp.Error))
}
// Mock up an HTTP response.
resp := http.Response{
Status: http.StatusText(jsonResp.Status),
StatusCode: jsonResp.Status,
Body: ioutil.NopCloser(bytes.NewReader(jsonResp.Body)),
ContentLength: int64(len(jsonResp.Body)),
}
return &resp, nil
}