/
client.go
153 lines (121 loc) · 4.2 KB
/
client.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
package federation
import (
"encoding/json"
"fmt"
"io"
"net/url"
"strings"
"github.com/stellar/go/address"
proto "github.com/stellar/go/protocols/federation"
"github.com/stellar/go/support/errors"
)
// LookupByAddress performs a federated lookup following to the stellar
// federation protocol using the "name" type request. The provided address is
// used to resolve what server the request should be made against. NOTE: the
// "name" type is a legacy holdover from the legacy stellar network's federation
// protocol. It is unfortunate.
func (c *Client) LookupByAddress(addy string) (*proto.NameResponse, error) {
_, domain, err := address.Split(addy)
if err != nil {
return nil, errors.Wrap(err, "parse address failed")
}
fserv, err := c.getFederationServer(domain)
if err != nil {
return nil, errors.Wrap(err, "lookup federation server failed")
}
qstr := url.Values{}
qstr.Add("type", "name")
qstr.Add("q", addy)
url := c.url(fserv, qstr)
var resp proto.NameResponse
err = c.getJSON(url, &resp)
if err != nil {
return nil, errors.Wrap(err, "get federation failed")
}
if resp.MemoType != "" && resp.Memo.String() == "" {
return nil, errors.New("Invalid federation response (memo)")
}
return &resp, nil
}
// LookupByAccountID performs a federated lookup following to the stellar
// federation protocol using the "id" type request. The provided strkey-encoded
// account id is used to resolve what server the request should be made against.
func (c *Client) LookupByAccountID(aid string) (*proto.IDResponse, error) {
domain, err := c.Horizon.HomeDomainForAccount(aid)
if err != nil {
return nil, errors.Wrap(err, "get homedomain failed")
}
if domain == "" {
return nil, errors.New("homedomain not set")
}
fserv, err := c.getFederationServer(domain)
if err != nil {
return nil, errors.Wrap(err, "lookup federation server failed")
}
qstr := url.Values{}
qstr.Add("type", "id")
qstr.Add("q", aid)
url := c.url(fserv, qstr)
var resp proto.IDResponse
err = c.getJSON(url, &resp)
if err != nil {
return nil, errors.Wrap(err, "get federation failed")
}
return &resp, nil
}
// ForwardRequest performs a federated lookup following to the stellar
// federation protocol using the "forward" type request.
func (c *Client) ForwardRequest(domain string, fields url.Values) (*proto.NameResponse, error) {
fserv, err := c.getFederationServer(domain)
if err != nil {
return nil, errors.Wrap(err, "lookup federation server failed")
}
fields.Add("type", "forward")
url := c.url(fserv, fields)
var resp proto.NameResponse
err = c.getJSON(url, &resp)
if err != nil {
return nil, errors.Wrap(err, "get federation failed")
}
if resp.MemoType != "" && resp.Memo.String() == "" {
return nil, errors.New("Invalid federation response (memo)")
}
return &resp, nil
}
func (c *Client) getFederationServer(domain string) (string, error) {
stoml, err := c.StellarTOML.GetStellarToml(domain)
if err != nil {
return "", errors.Wrap(err, "get stellar.toml failed")
}
if stoml.FederationServer == "" {
return "", errors.New("stellar.toml is missing federation server info")
}
if !c.AllowHTTP && !strings.HasPrefix(stoml.FederationServer, "https://") {
return "", errors.New("non-https federation server disallowed")
}
return stoml.FederationServer, nil
}
// getJSON populates `dest` with the contents at `url`, provided the request
// succeeds and the json can be successfully decoded.
func (c *Client) getJSON(url string, dest interface{}) error {
hresp, err := c.HTTP.Get(url)
if err != nil {
return errors.Wrap(err, "http get errored")
}
defer hresp.Body.Close()
if !(hresp.StatusCode >= 200 && hresp.StatusCode < 300) {
return errors.Errorf("http get failed with (%d) status code", hresp.StatusCode)
}
limitReader := io.LimitReader(hresp.Body, FederationResponseMaxSize)
err = json.NewDecoder(limitReader).Decode(dest)
if err == io.ErrUnexpectedEOF && limitReader.(*io.LimitedReader).N == 0 {
return errors.Errorf("federation response exceeds %d bytes limit", FederationResponseMaxSize)
}
if err != nil {
return errors.Wrap(err, "json decode errored")
}
return nil
}
func (c *Client) url(endpoint string, qstr url.Values) string {
return fmt.Sprintf("%s?%s", endpoint, qstr.Encode())
}