1package mailgun
2
3import (
4	"bytes"
5	"encoding/json"
6	"fmt"
7	"io"
8	"io/ioutil"
9	"mime/multipart"
10	"net/http"
11	"net/url"
12	"os"
13	"path"
14	"strings"
15)
16
17type httpRequest struct {
18	URL               string
19	Parameters        map[string][]string
20	Headers           map[string]string
21	BasicAuthUser     string
22	BasicAuthPassword string
23	Client            *http.Client
24}
25
26type httpResponse struct {
27	Code int
28	Data []byte
29}
30
31type payload interface {
32	getPayloadBuffer() (*bytes.Buffer, error)
33	getContentType() string
34	getValues() []keyValuePair
35}
36
37type keyValuePair struct {
38	key   string
39	value string
40}
41
42type keyNameRC struct {
43	key   string
44	name  string
45	value io.ReadCloser
46}
47
48type formDataPayload struct {
49	contentType string
50	Values      []keyValuePair
51	Files       []keyValuePair
52	ReadClosers []keyNameRC
53}
54
55type urlEncodedPayload struct {
56	Values []keyValuePair
57}
58
59func newHTTPRequest(url string) *httpRequest {
60	return &httpRequest{URL: url, Client: http.DefaultClient}
61}
62
63func (r *httpRequest) addParameter(name, value string) {
64	if r.Parameters == nil {
65		r.Parameters = make(map[string][]string)
66	}
67	r.Parameters[name] = append(r.Parameters[name], value)
68}
69
70func (r *httpRequest) setClient(c *http.Client) {
71	r.Client = c
72}
73
74func (r *httpRequest) setBasicAuth(user, password string) {
75	r.BasicAuthUser = user
76	r.BasicAuthPassword = password
77}
78
79func newUrlEncodedPayload() *urlEncodedPayload {
80	return &urlEncodedPayload{}
81}
82
83func (f *urlEncodedPayload) addValue(key, value string) {
84	f.Values = append(f.Values, keyValuePair{key: key, value: value})
85}
86
87func (f *urlEncodedPayload) getPayloadBuffer() (*bytes.Buffer, error) {
88	data := url.Values{}
89	for _, keyVal := range f.Values {
90		data.Add(keyVal.key, keyVal.value)
91	}
92	return bytes.NewBufferString(data.Encode()), nil
93}
94
95func (f *urlEncodedPayload) getContentType() string {
96	return "application/x-www-form-urlencoded"
97}
98
99func (f *urlEncodedPayload) getValues() []keyValuePair {
100	return f.Values
101}
102
103func (r *httpResponse) parseFromJSON(v interface{}) error {
104	return json.Unmarshal(r.Data, v)
105}
106
107func newFormDataPayload() *formDataPayload {
108	return &formDataPayload{}
109}
110
111func (f *formDataPayload) getValues() []keyValuePair {
112	return f.Values
113}
114
115func (f *formDataPayload) addValue(key, value string) {
116	f.Values = append(f.Values, keyValuePair{key: key, value: value})
117}
118
119func (f *formDataPayload) addFile(key, file string) {
120	f.Files = append(f.Files, keyValuePair{key: key, value: file})
121}
122
123func (f *formDataPayload) addReadCloser(key, name string, rc io.ReadCloser) {
124	f.ReadClosers = append(f.ReadClosers, keyNameRC{key: key, name: name, value: rc})
125}
126
127func (f *formDataPayload) getPayloadBuffer() (*bytes.Buffer, error) {
128	data := &bytes.Buffer{}
129	writer := multipart.NewWriter(data)
130	defer writer.Close()
131
132	for _, keyVal := range f.Values {
133		if tmp, err := writer.CreateFormField(keyVal.key); err == nil {
134			tmp.Write([]byte(keyVal.value))
135		} else {
136			return nil, err
137		}
138	}
139
140	for _, file := range f.Files {
141		if tmp, err := writer.CreateFormFile(file.key, path.Base(file.value)); err == nil {
142			if fp, err := os.Open(file.value); err == nil {
143				defer fp.Close()
144				io.Copy(tmp, fp)
145			} else {
146				return nil, err
147			}
148		} else {
149			return nil, err
150		}
151	}
152
153	for _, file := range f.ReadClosers {
154		if tmp, err := writer.CreateFormFile(file.key, file.name); err == nil {
155			defer file.value.Close()
156			io.Copy(tmp, file.value)
157		} else {
158			return nil, err
159		}
160	}
161
162	f.contentType = writer.FormDataContentType()
163
164	return data, nil
165}
166
167func (f *formDataPayload) getContentType() string {
168	if f.contentType == "" {
169		f.getPayloadBuffer()
170	}
171	return f.contentType
172}
173
174func (r *httpRequest) addHeader(name, value string) {
175	if r.Headers == nil {
176		r.Headers = make(map[string]string)
177	}
178	r.Headers[name] = value
179}
180
181func (r *httpRequest) makeGetRequest() (*httpResponse, error) {
182	return r.makeRequest("GET", nil)
183}
184
185func (r *httpRequest) makePostRequest(payload payload) (*httpResponse, error) {
186	return r.makeRequest("POST", payload)
187}
188
189func (r *httpRequest) makePutRequest(payload payload) (*httpResponse, error) {
190	return r.makeRequest("PUT", payload)
191}
192
193func (r *httpRequest) makeDeleteRequest() (*httpResponse, error) {
194	return r.makeRequest("DELETE", nil)
195}
196
197func (r *httpRequest) makeRequest(method string, payload payload) (*httpResponse, error) {
198	url, err := r.generateUrlWithParameters()
199	if err != nil {
200		return nil, err
201	}
202
203	var body io.Reader
204	if payload != nil {
205		if body, err = payload.getPayloadBuffer(); err != nil {
206			return nil, err
207		}
208	} else {
209		body = nil
210	}
211
212	req, err := http.NewRequest(method, url, body)
213	if err != nil {
214		return nil, err
215	}
216
217	if payload != nil && payload.getContentType() != "" {
218		req.Header.Add("Content-Type", payload.getContentType())
219	}
220
221	if r.BasicAuthUser != "" && r.BasicAuthPassword != "" {
222		req.SetBasicAuth(r.BasicAuthUser, r.BasicAuthPassword)
223	}
224
225	for header, value := range r.Headers {
226		req.Header.Add(header, value)
227	}
228
229	if Debug {
230		fmt.Println(r.curlString(req, payload))
231	}
232
233	response := httpResponse{}
234
235	resp, err := r.Client.Do(req)
236	if resp != nil {
237		response.Code = resp.StatusCode
238	}
239	if err != nil {
240		return nil, err
241	}
242
243	defer resp.Body.Close()
244	responseBody, err := ioutil.ReadAll(resp.Body)
245	if err != nil {
246		return nil, err
247	}
248
249	response.Data = responseBody
250	return &response, nil
251}
252
253func (r *httpRequest) generateUrlWithParameters() (string, error) {
254	url, err := url.Parse(r.URL)
255	if err != nil {
256		return "", err
257	}
258	q := url.Query()
259	if r.Parameters != nil && len(r.Parameters) > 0 {
260		for name, values := range r.Parameters {
261			for _, value := range values {
262				q.Add(name, value)
263			}
264		}
265	}
266	url.RawQuery = q.Encode()
267
268	return url.String(), nil
269}
270
271func (r *httpRequest) curlString(req *http.Request, p payload) string {
272
273	parts := []string{"curl", "-i", "-X", req.Method, req.URL.String()}
274	for key, value := range req.Header {
275		parts = append(parts, fmt.Sprintf("-H \"%s: %s\"", key, value[0]))
276	}
277
278	//parts = append(parts, fmt.Sprintf(" --user '%s:%s'", r.BasicAuthUser, r.BasicAuthPassword))
279
280	if p != nil {
281		for _, param := range p.getValues() {
282			parts = append(parts, fmt.Sprintf(" -F %s='%s'", param.key, param.value))
283		}
284	}
285	return strings.Join(parts, " ")
286}
287