teg/backend/server_folders_proxy.go
2026-06-09 14:26:35 +02:00

214 lines
5.0 KiB
Go

// backend/server_folders_proxy.go
package main
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)
type serverFoldersAgentConfig struct {
MilestoneHost string
Scheme string
Port string
Token string
}
func (s *Server) handleProxyListServerFolders(w http.ResponseWriter, r *http.Request) {
s.proxyServerFoldersRequest(w, r, http.MethodGet)
}
func (s *Server) handleProxyCreateServerFolder(w http.ResponseWriter, r *http.Request) {
s.proxyServerFoldersRequest(w, r, http.MethodPost)
}
func (s *Server) proxyServerFoldersRequest(w http.ResponseWriter, r *http.Request, method string) {
config, err := s.getServerFoldersAgentConfig(r.Context())
if err != nil {
writeError(w, http.StatusBadGateway, "Server-Folders-Agent-Konfiguration konnte nicht geladen werden")
return
}
targetURL, err := serverFoldersAgentURL(config, forwardedServerFoldersQuery(r.URL.Query()))
if err != nil {
writeError(w, http.StatusBadGateway, err.Error())
return
}
var body []byte
if method == http.MethodPost {
body, err = io.ReadAll(io.LimitReader(r.Body, 1<<20))
if err != nil {
writeError(w, http.StatusBadRequest, "Request konnte nicht gelesen werden")
return
}
}
request, err := http.NewRequestWithContext(
r.Context(),
method,
targetURL,
bytes.NewReader(body),
)
if err != nil {
writeError(w, http.StatusBadGateway, "Proxy-Request konnte nicht erstellt werden")
return
}
request.Header.Set("Accept", "application/json")
if method == http.MethodPost {
request.Header.Set("Content-Type", "application/json")
}
if config.Token != "" {
request.Header.Set("X-Server-Folders-Token", config.Token)
}
client := &http.Client{
Timeout: 20 * time.Second,
}
response, err := client.Do(request)
if err != nil {
writeError(w, http.StatusBadGateway, "Server-Folders-Agent ist nicht erreichbar")
return
}
defer response.Body.Close()
responseBody, err := io.ReadAll(io.LimitReader(response.Body, 4<<20))
if err != nil {
writeError(w, http.StatusBadGateway, "Antwort vom Server-Folders-Agent konnte nicht gelesen werden")
return
}
contentType := response.Header.Get("Content-Type")
if strings.TrimSpace(contentType) == "" {
contentType = "application/json"
}
w.Header().Set("Content-Type", contentType)
w.WriteHeader(response.StatusCode)
_, _ = w.Write(responseBody)
}
func (s *Server) getServerFoldersAgentConfig(ctx context.Context) (serverFoldersAgentConfig, error) {
var config serverFoldersAgentConfig
err := s.db.QueryRow(
ctx,
`
SELECT
host,
COALESCE(server_folders_agent_scheme, 'http'),
COALESCE(server_folders_agent_port, '8099'),
COALESCE(pgp_sym_decrypt(server_folders_agent_token_encrypted, $1), '')
FROM milestone_settings
WHERE id = 1
LIMIT 1
`,
s.milestoneEncryptionKey(),
).Scan(
&config.MilestoneHost,
&config.Scheme,
&config.Port,
&config.Token,
)
if err != nil {
return config, err
}
config.Scheme = normalizeServerFoldersAgentScheme(config.Scheme)
if config.Scheme == "" {
config.Scheme = "http"
}
config.Port = normalizeServerFoldersAgentPort(config.Port)
if config.Port == "" {
config.Port = "8099"
}
config.MilestoneHost = strings.TrimSpace(config.MilestoneHost)
config.Token = strings.TrimSpace(config.Token)
return config, nil
}
func serverFoldersAgentURL(config serverFoldersAgentConfig, rawQuery string) (string, error) {
if strings.TrimSpace(config.MilestoneHost) == "" {
return "", fmt.Errorf("Milestone Host fehlt")
}
normalizedMilestoneHost := strings.TrimSpace(config.MilestoneHost)
if !strings.Contains(normalizedMilestoneHost, "://") {
normalizedMilestoneHost = "http://" + normalizedMilestoneHost
}
parsedMilestoneURL, err := url.Parse(normalizedMilestoneHost)
if err != nil || parsedMilestoneURL.Hostname() == "" {
return "", fmt.Errorf("Milestone Host ist ungültig")
}
agentURL := url.URL{
Scheme: config.Scheme,
Host: parsedMilestoneURL.Hostname() + ":" + config.Port,
Path: "/server-folders",
RawQuery: rawQuery,
}
return agentURL.String(), nil
}
func forwardedServerFoldersQuery(values url.Values) string {
// rootType=storage/archive wird bewusst weitergeleitet.
// Nur Verbindungs-/Auth-Parameter werden entfernt.
nextValues := url.Values{}
for key, value := range values {
switch strings.ToLower(strings.TrimSpace(key)) {
case "schema", "scheme", "agentschema", "agentscheme", "port", "agentport", "token", "agenttoken":
continue
default:
for _, item := range value {
nextValues.Add(key, item)
}
}
}
return nextValues.Encode()
}
func normalizeServerFoldersAgentScheme(value string) string {
value = strings.TrimSpace(strings.ToLower(value))
switch value {
case "", "http", "https":
return value
default:
return ""
}
}
func normalizeServerFoldersAgentPort(value string) string {
value = strings.TrimSpace(value)
value = strings.TrimPrefix(value, ":")
if value == "" {
return ""
}
for _, char := range value {
if char < '0' || char > '9' {
return ""
}
}
return value
}