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 )
15
16 const defaultBaseURL = "https://api.reg.ru/api/regru2/"
17
18 // Client the reg.ru client.
19 type Client struct {
20 username string
21 password string
22
23 baseURL *url.URL
24 HTTPClient *http.Client
25 }
26
27 // NewClient Creates a reg.ru client.
28 func NewClient(username, password string) *Client {
29 baseURL, _ := url.Parse(defaultBaseURL)
30
31 return &Client{
32 username: username,
33 password: password,
34 baseURL: baseURL,
35 HTTPClient: &http.Client{Timeout: 5 * time.Second},
36 }
37 }
38
39 // RemoveTxtRecord removes a TXT record.
40 // https://www.reg.ru/support/help/api2#zone_remove_record
41 func (c *Client) RemoveTxtRecord(ctx context.Context, domain, subDomain, content string) error {
42 request := RemoveRecordRequest{
43 Domains: []Domain{{DName: domain}},
44 SubDomain: subDomain,
45 Content: content,
46 RecordType: "TXT",
47 OutputContentType: "plain",
48 }
49
50 resp, err := c.doRequest(ctx, request, "zone", "remove_record")
51 if err != nil {
52 return err
53 }
54
55 return resp.HasError()
56 }
57
58 // AddTXTRecord adds a TXT record.
59 // https://www.reg.ru/support/help/api2#zone_add_txt
60 func (c *Client) AddTXTRecord(ctx context.Context, domain, subDomain, content string) error {
61 request := AddTxtRequest{
62 Domains: []Domain{{DName: domain}},
63 SubDomain: subDomain,
64 Text: content,
65 OutputContentType: "plain",
66 }
67
68 resp, err := c.doRequest(ctx, request, "zone", "add_txt")
69 if err != nil {
70 return err
71 }
72
73 return resp.HasError()
74 }
75
76 func (c *Client) doRequest(ctx context.Context, request any, fragments ...string) (*APIResponse, error) {
77 endpoint := c.baseURL.JoinPath(fragments...)
78
79 inputData, err := json.Marshal(request)
80 if err != nil {
81 return nil, fmt.Errorf("failed to create input data: %w", err)
82 }
83
84 data := url.Values{}
85 data.Set("username", c.username)
86 data.Set("password", c.password)
87 data.Set("input_data", string(inputData))
88 data.Set("input_format", "json")
89
90 req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), strings.NewReader(data.Encode()))
91 if err != nil {
92 return nil, fmt.Errorf("unable to create request: %w", err)
93 }
94
95 req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
96
97 resp, err := c.HTTPClient.Do(req)
98 if err != nil {
99 return nil, errutils.NewHTTPDoError(req, err)
100 }
101
102 defer func() { _ = resp.Body.Close() }()
103
104 if resp.StatusCode/100 != 2 {
105 return nil, parseError(req, resp)
106 }
107
108 raw, err := io.ReadAll(resp.Body)
109 if err != nil {
110 return nil, errutils.NewReadResponseError(req, resp.StatusCode, err)
111 }
112
113 var apiResp APIResponse
114
115 err = json.Unmarshal(raw, &apiResp)
116 if err != nil {
117 return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
118 }
119
120 return &apiResp, nil
121 }
122
123 func parseError(req *http.Request, resp *http.Response) error {
124 raw, _ := io.ReadAll(resp.Body)
125
126 var errAPI APIResponse
127
128 err := json.Unmarshal(raw, &errAPI)
129 if err != nil {
130 return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
131 }
132
133 return fmt.Errorf("status code: %d, %w", resp.StatusCode, errAPI)
134 }
135