1package main
2
3import (
4	"bytes"
5	"crypto/tls"
6	"crypto/x509"
7	"encoding/json"
8	"fmt"
9	"io/ioutil"
10	"net"
11	"net/http"
12
13	"github.com/btcsuite/btcd/btcjson"
14	"github.com/btcsuite/go-socks/socks"
15)
16
17// newHTTPClient returns a new HTTP client that is configured according to the
18// proxy and TLS settings in the associated connection configuration.
19func newHTTPClient(cfg *config) (*http.Client, error) {
20	// Configure proxy if needed.
21	var dial func(network, addr string) (net.Conn, error)
22	if cfg.Proxy != "" {
23		proxy := &socks.Proxy{
24			Addr:     cfg.Proxy,
25			Username: cfg.ProxyUser,
26			Password: cfg.ProxyPass,
27		}
28		dial = func(network, addr string) (net.Conn, error) {
29			c, err := proxy.Dial(network, addr)
30			if err != nil {
31				return nil, err
32			}
33			return c, nil
34		}
35	}
36
37	// Configure TLS if needed.
38	var tlsConfig *tls.Config
39	if !cfg.NoTLS && cfg.RPCCert != "" {
40		pem, err := ioutil.ReadFile(cfg.RPCCert)
41		if err != nil {
42			return nil, err
43		}
44
45		pool := x509.NewCertPool()
46		pool.AppendCertsFromPEM(pem)
47		tlsConfig = &tls.Config{
48			RootCAs:            pool,
49			InsecureSkipVerify: cfg.TLSSkipVerify,
50		}
51	}
52
53	// Create and return the new HTTP client potentially configured with a
54	// proxy and TLS.
55	client := http.Client{
56		Transport: &http.Transport{
57			Dial:            dial,
58			TLSClientConfig: tlsConfig,
59		},
60	}
61	return &client, nil
62}
63
64// sendPostRequest sends the marshalled JSON-RPC command using HTTP-POST mode
65// to the server described in the passed config struct.  It also attempts to
66// unmarshal the response as a JSON-RPC response and returns either the result
67// field or the error field depending on whether or not there is an error.
68func sendPostRequest(marshalledJSON []byte, cfg *config) ([]byte, error) {
69	// Generate a request to the configured RPC server.
70	protocol := "http"
71	if !cfg.NoTLS {
72		protocol = "https"
73	}
74	url := protocol + "://" + cfg.RPCServer
75	bodyReader := bytes.NewReader(marshalledJSON)
76	httpRequest, err := http.NewRequest("POST", url, bodyReader)
77	if err != nil {
78		return nil, err
79	}
80	httpRequest.Close = true
81	httpRequest.Header.Set("Content-Type", "application/json")
82
83	// Configure basic access authorization.
84	httpRequest.SetBasicAuth(cfg.RPCUser, cfg.RPCPassword)
85
86	// Create the new HTTP client that is configured according to the user-
87	// specified options and submit the request.
88	httpClient, err := newHTTPClient(cfg)
89	if err != nil {
90		return nil, err
91	}
92	httpResponse, err := httpClient.Do(httpRequest)
93	if err != nil {
94		return nil, err
95	}
96
97	// Read the raw bytes and close the response.
98	respBytes, err := ioutil.ReadAll(httpResponse.Body)
99	httpResponse.Body.Close()
100	if err != nil {
101		err = fmt.Errorf("error reading json reply: %v", err)
102		return nil, err
103	}
104
105	// Handle unsuccessful HTTP responses
106	if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 {
107		// Generate a standard error to return if the server body is
108		// empty.  This should not happen very often, but it's better
109		// than showing nothing in case the target server has a poor
110		// implementation.
111		if len(respBytes) == 0 {
112			return nil, fmt.Errorf("%d %s", httpResponse.StatusCode,
113				http.StatusText(httpResponse.StatusCode))
114		}
115		return nil, fmt.Errorf("%s", respBytes)
116	}
117
118	// Unmarshal the response.
119	var resp btcjson.Response
120	if err := json.Unmarshal(respBytes, &resp); err != nil {
121		return nil, err
122	}
123
124	if resp.Error != nil {
125		return nil, resp.Error
126	}
127	return resp.Result, nil
128}
129