client.go raw
1 package internal
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "net/http"
9 "net/url"
10 "strings"
11 "time"
12
13 "github.com/go-acme/lego/v4/providers/dns/internal/errutils"
14 "golang.org/x/oauth2"
15 )
16
17 const defaultBaseURL = "https://ipv64.net"
18
19 type Client struct {
20 baseURL *url.URL
21 HTTPClient *http.Client
22 }
23
24 func NewClient(hc *http.Client) *Client {
25 baseURL, _ := url.Parse(defaultBaseURL)
26
27 if hc == nil {
28 hc = &http.Client{Timeout: 15 * time.Second}
29 }
30
31 return &Client{
32 baseURL: baseURL,
33 HTTPClient: hc,
34 }
35 }
36
37 func (c *Client) GetDomains(ctx context.Context) (*Domains, error) {
38 endpoint := c.baseURL.JoinPath("api")
39
40 query := endpoint.Query()
41 query.Set("get_domains", "")
42 endpoint.RawQuery = query.Encode()
43
44 req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), http.NoBody)
45 if err != nil {
46 return nil, fmt.Errorf("unable to create request: %w", err)
47 }
48
49 results := &Domains{}
50
51 err = c.do(req, results)
52 if err != nil {
53 return nil, err
54 }
55
56 return results, nil
57 }
58
59 func (c *Client) AddRecord(ctx context.Context, domain, prefix, recordType, content string) error {
60 endpoint := c.baseURL.JoinPath("api")
61
62 data := make(url.Values)
63 data.Set("add_record", domain)
64 data.Set("praefix", prefix)
65 data.Set("type", recordType)
66 data.Set("content", content)
67
68 req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), strings.NewReader(data.Encode()))
69 if err != nil {
70 return fmt.Errorf("unable to create request: %w", err)
71 }
72
73 return c.do(req, nil)
74 }
75
76 func (c *Client) DeleteRecord(ctx context.Context, domain, prefix, recordType, content string) error {
77 endpoint := c.baseURL.JoinPath("api")
78
79 data := make(url.Values)
80 data.Set("del_record", domain)
81 data.Set("praefix", prefix)
82 data.Set("type", recordType)
83 data.Set("content", content)
84
85 req, err := http.NewRequestWithContext(ctx, http.MethodDelete, endpoint.String(), strings.NewReader(data.Encode()))
86 if err != nil {
87 return fmt.Errorf("unable to create request: %w", err)
88 }
89
90 return c.do(req, nil)
91 }
92
93 func (c *Client) do(req *http.Request, result any) error {
94 if req.Method != http.MethodGet {
95 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
96 }
97
98 resp, err := c.HTTPClient.Do(req)
99 if err != nil {
100 return errutils.NewHTTPDoError(req, err)
101 }
102
103 defer func() { _ = resp.Body.Close() }()
104
105 if resp.StatusCode/100 != 2 {
106 return parseError(req, resp)
107 }
108
109 if result == nil {
110 return nil
111 }
112
113 raw, err := io.ReadAll(resp.Body)
114 if err != nil {
115 return errutils.NewReadResponseError(req, resp.StatusCode, err)
116 }
117
118 if string(raw) == "null" {
119 return fmt.Errorf("unexpected response: %s", string(raw))
120 }
121
122 err = json.Unmarshal(raw, result)
123 if err != nil {
124 return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
125 }
126
127 return nil
128 }
129
130 func parseError(req *http.Request, resp *http.Response) error {
131 raw, _ := io.ReadAll(resp.Body)
132
133 errAPI := &APIError{}
134
135 err := json.Unmarshal(raw, errAPI)
136 if err != nil {
137 return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
138 }
139
140 return errAPI
141 }
142
143 func OAuthStaticAccessToken(client *http.Client, accessToken string) *http.Client {
144 if client == nil {
145 client = &http.Client{Timeout: 15 * time.Second}
146 }
147
148 client.Transport = &oauth2.Transport{
149 Source: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: accessToken}),
150 Base: client.Transport,
151 }
152
153 return client
154 }
155