// 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 }