client.go raw
1 package internal
2
3 import (
4 "bytes"
5 "context"
6 "errors"
7 "fmt"
8 "io"
9 "net/http"
10 "net/url"
11 "strings"
12 "time"
13
14 "github.com/go-acme/lego/v4/providers/dns/internal/errutils"
15 querystring "github.com/google/go-querystring/query"
16 )
17
18 const defaultBaseURL = "https://www.bookmyname.com/dyndns/"
19
20 // Client the BookMyName API client.
21 type Client struct {
22 username string
23 password string
24
25 baseURL string
26 HTTPClient *http.Client
27 }
28
29 // NewClient creates a new Client.
30 func NewClient(username, password string) (*Client, error) {
31 if username == "" || password == "" {
32 return nil, errors.New("credentials missing")
33 }
34
35 return &Client{
36 username: username,
37 password: password,
38 baseURL: defaultBaseURL,
39 HTTPClient: &http.Client{Timeout: 10 * time.Second},
40 }, nil
41 }
42
43 func (c *Client) AddRecord(ctx context.Context, record Record) error {
44 endpoint, err := c.createEndpoint(record, "add")
45 if err != nil {
46 return err
47 }
48
49 err = c.do(ctx, endpoint)
50 if err != nil {
51 return err
52 }
53
54 return nil
55 }
56
57 func (c *Client) RemoveRecord(ctx context.Context, record Record) error {
58 endpoint, err := c.createEndpoint(record, "remove")
59 if err != nil {
60 return err
61 }
62
63 err = c.do(ctx, endpoint)
64 if err != nil {
65 return err
66 }
67
68 return nil
69 }
70
71 func (c *Client) createEndpoint(record Record, action string) (*url.URL, error) {
72 endpoint, err := url.Parse(c.baseURL)
73 if err != nil {
74 return nil, fmt.Errorf("parse URL: %w", err)
75 }
76
77 values, err := querystring.Values(record)
78 if err != nil {
79 return nil, fmt.Errorf("query parameters: %w", err)
80 }
81
82 values.Set("do", action)
83
84 endpoint.RawQuery = values.Encode()
85
86 return endpoint, nil
87 }
88
89 func (c *Client) do(ctx context.Context, endpoint *url.URL) error {
90 endpoint.User = url.UserPassword(c.username, c.password)
91
92 req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), http.NoBody)
93 if err != nil {
94 return fmt.Errorf("unable to create request: %w", err)
95 }
96
97 resp, err := c.HTTPClient.Do(req)
98 if err != nil {
99 return errutils.NewHTTPDoError(req, err)
100 }
101
102 defer func() { _ = resp.Body.Close() }()
103
104 raw, err := io.ReadAll(resp.Body)
105 if err != nil {
106 return errutils.NewReadResponseError(req, resp.StatusCode, err)
107 }
108
109 if resp.StatusCode/100 != 2 {
110 return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
111 }
112
113 if !strings.HasPrefix(string(raw), "good: update done") && !strings.HasPrefix(string(raw), "good: remove done") {
114 return fmt.Errorf("unexpected response: %s", string(bytes.TrimSpace(raw)))
115 }
116
117 return nil
118 }
119