Files
uberspace-cli/internal/httpapi/client.go

114 lines
2.4 KiB
Go

package httpapi
import (
"bytes"
"errors"
"fmt"
"io"
"net/http"
"net/http/cookiejar"
"net/url"
"strings"
"text/template"
"time"
"uberspace-cli/internal/config"
"uberspace-cli/internal/session"
)
type Client struct {
BaseURL string
HTTP *http.Client
CSRF *session.CSRFToken
}
func New(baseURL string, jar http.CookieJar, csrf *session.CSRFToken) (*Client, error) {
if baseURL == "" {
return nil, errors.New("base_url is required")
}
if jar == nil {
jar, _ = cookiejar.New(nil)
}
return &Client{
BaseURL: strings.TrimRight(baseURL, "/"),
HTTP: &http.Client{
Timeout: 30 * time.Second,
Jar: jar,
},
CSRF: csrf,
}, nil
}
func (c *Client) DoEndpoint(ep config.Endpoint, vars map[string]string) (*http.Response, []byte, error) {
body, err := renderTemplate(ep.Body, vars)
if err != nil {
return nil, nil, err
}
urlStr := c.BaseURL + ep.Path
req, err := http.NewRequest(ep.Method, urlStr, strings.NewReader(body))
if err != nil {
return nil, nil, err
}
for k, v := range ep.Headers {
req.Header.Set(k, v)
}
if c.CSRF != nil && c.CSRF.Header != "" && c.CSRF.Value != "" {
req.Header.Set(c.CSRF.Header, c.CSRF.Value)
}
resp, err := c.HTTP.Do(req)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return resp, nil, err
}
return resp, respBody, nil
}
func (c *Client) CaptureCSRF(ep config.Endpoint, resp *http.Response, body []byte) *session.CSRFToken {
if ep.CSRF == nil {
return nil
}
cfg := ep.CSRF
switch strings.ToLower(cfg.From) {
case "cookie":
u, err := url.Parse(c.BaseURL)
if err != nil {
return nil
}
cookies := c.HTTP.Jar.Cookies(u)
for _, c := range cookies {
if c.Name == cfg.Name {
return &session.CSRFToken{Header: cfg.Header, Value: c.Value}
}
}
case "header":
val := resp.Header.Get(cfg.Name)
if val != "" {
return &session.CSRFToken{Header: cfg.Header, Value: val}
}
}
_ = body
return nil
}
func renderTemplate(tpl string, vars map[string]string) (string, error) {
if tpl == "" {
return "", nil
}
t, err := template.New("body").Option("missingkey=error").Parse(tpl)
if err != nil {
return "", fmt.Errorf("invalid template: %w", err)
}
var buf bytes.Buffer
if err := t.Execute(&buf, vars); err != nil {
return "", fmt.Errorf("template render failed: %w", err)
}
return buf.String(), nil
}