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