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