// client.go
// Package gitea provides a client for interacting with the Gitea API
package gitea
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)
// Client handles communication with the Gitea API
type Client struct {
baseURL *url.URL
httpClient *http.Client
token string
}
// VersionResponse represents the Gitea version response
type VersionResponse struct {
Version string `json:"version"`
}
// FetchCSRFToken retrieves a CSRF token from Gitea
func (c *Client) FetchCSRFToken() error {
// Create a request to the Gitea login page to get a CSRF token
u, err := c.baseURL.Parse("/")
if err != nil {
return fmt.Errorf("invalid URL: %w", err)
}
// Use a normal HTTP client without our custom transport for this request
// to avoid circular dependency (we need the token for the transport)
httpClient := &http.Client{}
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
resp, err := httpClient.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
// Extract CSRF token from Set-Cookie header
for _, cookie := range resp.Cookies() {
if cookie.Name == "_csrf" {
// Update the transport with the CSRF token
transport, ok := c.httpClient.Transport.(*CSRFTokenTransport)
if ok {
transport.CSRFToken = cookie.Value
return nil
}
return fmt.Errorf("transport is not a CSRFTokenTransport")
}
}
// If we couldn't find a CSRF token, try to extract it from the response body
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response body: %w", err)
}
// Look for in the HTML
body := string(bodyBytes)
csrfMetaStart := strings.Index(body, `= 300 {
return resp, fmt.Errorf("API returned error: %s - %s", resp.Status, string(bodyBytes))
}
if result != nil && len(bodyBytes) > 0 {
if err := json.NewDecoder(bytes.NewBuffer(bodyBytes)).Decode(result); err != nil {
return resp, fmt.Errorf("failed to decode response: %w", err)
}
}
return resp, nil
}