forked from TrueCloudLab/lego
176 lines
4 KiB
Go
176 lines
4 KiB
Go
|
package internal
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding/xml"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"net/http"
|
||
|
"net/url"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
|
||
|
)
|
||
|
|
||
|
// Default API endpoints.
|
||
|
const (
|
||
|
DefaultBaseURL = "https://api.namecheap.com/xml.response"
|
||
|
SandboxBaseURL = "https://api.sandbox.namecheap.com/xml.response"
|
||
|
)
|
||
|
|
||
|
// Client the API client for Namecheap.
|
||
|
type Client struct {
|
||
|
apiUser string
|
||
|
apiKey string
|
||
|
clientIP string
|
||
|
|
||
|
BaseURL string
|
||
|
HTTPClient *http.Client
|
||
|
}
|
||
|
|
||
|
// NewClient creates a new Client.
|
||
|
func NewClient(apiUser string, apiKey string, clientIP string) *Client {
|
||
|
return &Client{
|
||
|
apiUser: apiUser,
|
||
|
apiKey: apiKey,
|
||
|
clientIP: clientIP,
|
||
|
BaseURL: DefaultBaseURL,
|
||
|
HTTPClient: &http.Client{Timeout: 5 * time.Second},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// GetHosts reads the full list of DNS host records.
|
||
|
// https://www.namecheap.com/support/api/methods/domains-dns/get-hosts.aspx
|
||
|
func (c *Client) GetHosts(ctx context.Context, sld, tld string) ([]Record, error) {
|
||
|
request, err := c.newRequestGet(ctx, "namecheap.domains.dns.getHosts",
|
||
|
addParam("SLD", sld),
|
||
|
addParam("TLD", tld),
|
||
|
)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
var ghr getHostsResponse
|
||
|
err = c.do(request, &ghr)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if len(ghr.Errors) > 0 {
|
||
|
return nil, ghr.Errors[0]
|
||
|
}
|
||
|
|
||
|
return ghr.Hosts, nil
|
||
|
}
|
||
|
|
||
|
// SetHosts writes the full list of DNS host records .
|
||
|
// https://www.namecheap.com/support/api/methods/domains-dns/set-hosts.aspx
|
||
|
func (c *Client) SetHosts(ctx context.Context, sld, tld string, hosts []Record) error {
|
||
|
req, err := c.newRequestPost(ctx, "namecheap.domains.dns.setHosts",
|
||
|
addParam("SLD", sld),
|
||
|
addParam("TLD", tld),
|
||
|
func(values url.Values) {
|
||
|
for i, h := range hosts {
|
||
|
ind := fmt.Sprintf("%d", i+1)
|
||
|
values.Add("HostName"+ind, h.Name)
|
||
|
values.Add("RecordType"+ind, h.Type)
|
||
|
values.Add("Address"+ind, h.Address)
|
||
|
values.Add("MXPref"+ind, h.MXPref)
|
||
|
values.Add("TTL"+ind, h.TTL)
|
||
|
}
|
||
|
},
|
||
|
)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
var shr setHostsResponse
|
||
|
err = c.do(req, &shr)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if len(shr.Errors) > 0 {
|
||
|
return shr.Errors[0]
|
||
|
}
|
||
|
if shr.Result.IsSuccess != "true" {
|
||
|
return errors.New("setHosts failed")
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *Client) do(req *http.Request, result any) error {
|
||
|
resp, err := c.HTTPClient.Do(req)
|
||
|
if err != nil {
|
||
|
return errutils.NewHTTPDoError(req, err)
|
||
|
}
|
||
|
|
||
|
defer func() { _ = resp.Body.Close() }()
|
||
|
|
||
|
if resp.StatusCode >= http.StatusBadRequest {
|
||
|
return errutils.NewUnexpectedResponseStatusCodeError(req, resp)
|
||
|
}
|
||
|
|
||
|
raw, err := io.ReadAll(resp.Body)
|
||
|
if err != nil {
|
||
|
return errutils.NewReadResponseError(req, resp.StatusCode, err)
|
||
|
}
|
||
|
|
||
|
return xml.Unmarshal(raw, result)
|
||
|
}
|
||
|
|
||
|
func (c *Client) newRequestGet(ctx context.Context, cmd string, params ...func(url.Values)) (*http.Request, error) {
|
||
|
query := c.makeQuery(cmd, params...)
|
||
|
|
||
|
endpoint, err := url.Parse(c.BaseURL)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
endpoint.RawQuery = query.Encode()
|
||
|
|
||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("unable to create request: %w", err)
|
||
|
}
|
||
|
|
||
|
return req, nil
|
||
|
}
|
||
|
|
||
|
func (c *Client) newRequestPost(ctx context.Context, cmd string, params ...func(url.Values)) (*http.Request, error) {
|
||
|
query := c.makeQuery(cmd, params...)
|
||
|
|
||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL, strings.NewReader(query.Encode()))
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("unable to create request: %w", err)
|
||
|
}
|
||
|
|
||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||
|
|
||
|
return req, nil
|
||
|
}
|
||
|
|
||
|
func (c *Client) makeQuery(cmd string, params ...func(url.Values)) url.Values {
|
||
|
queryParams := make(url.Values)
|
||
|
queryParams.Set("ApiUser", c.apiUser)
|
||
|
queryParams.Set("ApiKey", c.apiKey)
|
||
|
queryParams.Set("UserName", c.apiUser)
|
||
|
queryParams.Set("Command", cmd)
|
||
|
queryParams.Set("ClientIp", c.clientIP)
|
||
|
|
||
|
for _, param := range params {
|
||
|
param(queryParams)
|
||
|
}
|
||
|
|
||
|
return queryParams
|
||
|
}
|
||
|
|
||
|
func addParam(key, value string) func(url.Values) {
|
||
|
return func(values url.Values) {
|
||
|
values.Set(key, value)
|
||
|
}
|
||
|
}
|