114 lines
2.4 KiB
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
|
|
}
|