package handlers import ( "encoding/json" "fmt" "io" "net/http" "os" "strconv" "strings" "super-frpc/config" "super-frpc/database" "super-frpc/postLog" "super-frpc/utils" "github.com/BurntSushi/toml" ) func CreateProxyHandler(w http.ResponseWriter, r *http.Request) { _, err := utils.Auth(w, r, http.MethodPost, "superuser", "admin") if err != nil { utils.SendErrorResponse(w, http.StatusUnauthorized, err.Error()) postLog.Warning(fmt.Sprintf("[CreateProxyHandler] Auth failed: %v", err)) return } body, err := io.ReadAll(r.Body) if err != nil { postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to read request body: %v", err)) utils.SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body") return } defer r.Body.Close() var reqMap map[string]interface{} if err := json.Unmarshal(body, &reqMap); err != nil { postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to unmarshal request body: %v", err)) utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid request format") return } instanceID := getStringFromMap(reqMap, "instanceID") if instanceID == "" { postLog.Error("[CreateProxyHandler] instanceID is required") utils.SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } proxyInfoMap, ok := reqMap["proxyInfo"].(map[string]interface{}) if !ok { postLog.Error("[CreateProxyHandler] Invalid proxyInfo format") utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid proxyInfo format") return } proxyInfo := config.FrpcProxyInfo{ Name: getStringFromMap(proxyInfoMap, "name"), Type: getStringFromMap(proxyInfoMap, "type"), LocalIP: getStringFromMap(proxyInfoMap, "localIP"), LocalPort: getIntFromMap(proxyInfoMap, "localPort"), RemotePort: getIntFromMap(proxyInfoMap, "remotePort"), } if proxyInfo.Name == "" || proxyInfo.Type == "" || proxyInfo.LocalIP == "" || proxyInfo.LocalPort == 0 || proxyInfo.RemotePort == 0 { postLog.Error("[CreateProxyHandler] Missing required fields in proxyInfo") utils.SendErrorResponse(w, http.StatusBadRequest, "Missing required fields in proxyInfo") return } var instance database.FrpcInstance instanceIDInt, _ := strconv.Atoi(instanceID) instance, err = database.DBQueryFrpcInstanceByID(instanceIDInt) if err != nil { postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to query instance: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } // Check if the instance belongs to the user // if instance.UserID != userID { // postLog.Error(fmt.Sprintf("[CreateProxyHandler] Instance not found for user %d", userID)) // utils.SendErrorResponse(w, http.StatusNotFound, "Instance not found") // return // } configContent, err := os.ReadFile(instance.ConfigPath) if err != nil { postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to read config file %s: %v", instance.ConfigPath, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to read config file") return } updatedContent, err := config.AddFrpcProxy(string(configContent), proxyInfo) if err != nil { postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to add proxy: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to add proxy") return } if err := os.WriteFile(instance.ConfigPath, []byte(updatedContent), 0644); err != nil { postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to write config file %s: %v", instance.ConfigPath, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to write config file") return } utils.SendSuccessResponse(w, "Proxy created successfully", map[string]interface{}{ "instanceID": instance.ID, "configPath": instance.ConfigPath, "proxyName": proxyInfo.Name, }) postLog.Info(fmt.Sprintf("[CreateProxyHandler] Proxy %s created successfully for instance %d", proxyInfo.Name, instance.ID)) } func ModifyProxyHandler(w http.ResponseWriter, r *http.Request) { _, err := utils.Auth(w, r, http.MethodPost, "superuser", "admin") if err != nil { utils.SendErrorResponse(w, http.StatusUnauthorized, err.Error()) postLog.Warning(fmt.Sprintf("[ModifyProxyHandler] Auth failed: %v", err)) return } body, err := io.ReadAll(r.Body) if err != nil { postLog.Error(fmt.Sprintf("[ModifyProxyHandler] Failed to read request body: %v", err)) utils.SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body") return } defer r.Body.Close() var reqMap map[string]interface{} if err := json.Unmarshal(body, &reqMap); err != nil { postLog.Error(fmt.Sprintf("[ModifyProxyHandler] Failed to unmarshal request body: %v", err)) utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid request format") return } instanceID := getStringFromMap(reqMap, "instanceID") if instanceID == "" { postLog.Error("[ModifyProxyHandler] instanceID is required") utils.SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } proxyInfoMap, ok := reqMap["proxyInfo"].(map[string]interface{}) if !ok { postLog.Error("[ModifyProxyHandler] Invalid proxyInfo format") utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid proxyInfo format") return } proxyInfo := config.FrpcProxyInfo{ OldName: getStringFromMap(proxyInfoMap, "oldName"), NewName: getStringFromMap(proxyInfoMap, "newName"), Name: getStringFromMap(proxyInfoMap, "name"), Type: getStringFromMap(proxyInfoMap, "type"), LocalIP: getStringFromMap(proxyInfoMap, "localIP"), LocalPort: getIntFromMap(proxyInfoMap, "localPort"), RemotePort: getIntFromMap(proxyInfoMap, "remotePort"), } if proxyInfo.OldName == "" { postLog.Error("[ModifyProxyHandler] oldName is required") utils.SendErrorResponse(w, http.StatusBadRequest, "oldName is required") return } if proxyInfo.NewName == "" || proxyInfo.Type == "" || proxyInfo.LocalIP == "" || proxyInfo.LocalPort == 0 || proxyInfo.RemotePort == 0 { postLog.Error("[ModifyProxyHandler] Missing required fields in proxyInfo") utils.SendErrorResponse(w, http.StatusBadRequest, "Missing required fields in proxyInfo") return } var instance database.FrpcInstance instanceIDInt, _ := strconv.Atoi(instanceID) instance, err = database.DBQueryFrpcInstanceByID(instanceIDInt) if err != nil { postLog.Error(fmt.Sprintf("[ModifyProxyHandler] Failed to query instance: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } // Check if the instance belongs to the user // if instance.UserID != userID { // postLog.Error(fmt.Sprintf("[ModifyProxyHandler] Instance not found for user %d", userID)) // utils.SendErrorResponse(w, http.StatusNotFound, "Instance not found") // return // } configContent, err := os.ReadFile(instance.ConfigPath) if err != nil { postLog.Error(fmt.Sprintf("[ModifyProxyHandler] Failed to read config file %s: %v", instance.ConfigPath, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to read config file") return } updatedContent, err := config.ModifyFrpcProxy(string(configContent), proxyInfo) if err != nil { postLog.Error(fmt.Sprintf("[ModifyProxyHandler] Failed to modify proxy: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to modify proxy") return } if err := os.WriteFile(instance.ConfigPath, []byte(updatedContent), 0644); err != nil { postLog.Error(fmt.Sprintf("[ModifyProxyHandler] Failed to write config file %s: %v", instance.ConfigPath, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to write config file") return } utils.SendSuccessResponse(w, "Proxy modified successfully", map[string]interface{}{ "instanceID": instance.ID, "configPath": instance.ConfigPath, "proxyName": proxyInfo.NewName, }) postLog.Info(fmt.Sprintf("[ModifyProxyHandler] Proxy %s modified successfully for instance %d", proxyInfo.NewName, instance.ID)) } func DeleteProxyHandler(w http.ResponseWriter, r *http.Request) { _, err := utils.Auth(w, r, http.MethodPost, "superuser", "admin") if err != nil { utils.SendErrorResponse(w, http.StatusUnauthorized, err.Error()) postLog.Warning(fmt.Sprintf("[DeleteProxyHandler] Auth failed: %v", err)) return } body, err := io.ReadAll(r.Body) if err != nil { postLog.Error(fmt.Sprintf("[DeleteProxyHandler] Failed to read request body: %v", err)) utils.SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body") return } defer r.Body.Close() var reqMap map[string]interface{} if err := json.Unmarshal(body, &reqMap); err != nil { postLog.Error(fmt.Sprintf("[DeleteProxyHandler] Failed to unmarshal request body: %v", err)) utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid request format") return } instanceID := getStringFromMap(reqMap, "instanceID") if instanceID == "" { postLog.Error("[DeleteProxyHandler] instanceID is required") utils.SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } proxyName := getStringFromMap(reqMap, "proxyName") if proxyName == "" { postLog.Error("[DeleteProxyHandler] proxyName is required") utils.SendErrorResponse(w, http.StatusBadRequest, "proxyName is required") return } var instance database.FrpcInstance instanceIDInt, _ := strconv.Atoi(instanceID) instance, err = database.DBQueryFrpcInstanceByID(instanceIDInt) if err != nil { postLog.Error(fmt.Sprintf("[DeleteProxyHandler] Failed to query instance: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } // Check if the instance belongs to the user // if instance.UserID != userID { // postLog.Error(fmt.Sprintf("[DeleteProxyHandler] Instance not found for user %d", userID)) // utils.SendErrorResponse(w, http.StatusNotFound, "Instance not found") // return // } configContent, err := os.ReadFile(instance.ConfigPath) if err != nil { postLog.Error(fmt.Sprintf("[DeleteProxyHandler] Failed to read config file %s: %v", instance.ConfigPath, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to read config file") return } updatedContent, err := config.RemoveFrpcProxy(string(configContent), proxyName) if err != nil { postLog.Error(fmt.Sprintf("[DeleteProxyHandler] Failed to remove proxy: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to remove proxy") return } if err := os.WriteFile(instance.ConfigPath, []byte(updatedContent), 0644); err != nil { postLog.Error(fmt.Sprintf("[DeleteProxyHandler] Failed to write config file %s: %v", instance.ConfigPath, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to write config file") return } utils.SendSuccessResponse(w, "Proxy deleted successfully", map[string]interface{}{ "instanceID": instance.ID, "configPath": instance.ConfigPath, "proxyName": proxyName, }) postLog.Info(fmt.Sprintf("[DeleteProxyHandler] Proxy %s deleted successfully from instance %d", proxyName, instance.ID)) } func ListProxiesHandler(w http.ResponseWriter, r *http.Request) { _, err := utils.Auth(w, r, http.MethodGet) if err != nil { utils.SendErrorResponse(w, http.StatusUnauthorized, err.Error()) postLog.Warning(fmt.Sprintf("[ListProxiesHandler] Auth failed: %v", err)) return } queryParams := r.URL.Query() instanceID := queryParams.Get("instanceID") if instanceID == "" { postLog.Error("[ListProxiesHandler] instanceID is required") utils.SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } var instance database.FrpcInstance instanceIDInt, _ := strconv.Atoi(instanceID) instance, err = database.DBQueryFrpcInstanceByID(instanceIDInt) if err != nil { postLog.Error(fmt.Sprintf("[ListProxiesHandler] Failed to query instance: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } configContent, err := os.ReadFile(instance.ConfigPath) if err != nil { postLog.Error(fmt.Sprintf("[ListProxiesHandler] Failed to read config file %s: %v", instance.ConfigPath, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to read config file") return } var cfg config.FrpcConfig if _, err := toml.Decode(string(configContent), &cfg); err != nil { postLog.Error(fmt.Sprintf("[ListProxiesHandler] Failed to parse config: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to parse config file") return } contentStr := string(configContent) proxyList := make([]map[string]interface{}, 0, len(cfg.Proxies)) for _, proxy := range cfg.Proxies { proxyData := map[string]interface{}{ "name": proxy["name"], "type": proxy["type"], "localIP": proxy["localIP"], "localPort": proxy["localPort"], "remotePort": proxy["remotePort"], "enabled": true, } proxyList = append(proxyList, proxyData) } disabledProxies := parseDisabledProxies(strings.Split(contentStr, "\n")) proxyList = append(proxyList, disabledProxies...) utils.SendSuccessResponse(w, "Proxies listed successfully", map[string]interface{}{ "instanceID": instance.ID, "proxyCount": len(proxyList), "proxies": proxyList, }) postLog.Info(fmt.Sprintf("[ListProxiesHandler] Retrieved %d proxies for instance %d", len(proxyList), instance.ID)) } func SwitchProxyHandler(w http.ResponseWriter, r *http.Request) { _, err := utils.Auth(w, r, http.MethodPost, "superuser", "admin") if err != nil { utils.SendErrorResponse(w, http.StatusUnauthorized, err.Error()) postLog.Warning(fmt.Sprintf("[SwitchProxyHandler] Auth failed: %v", err)) return } body, err := io.ReadAll(r.Body) if err != nil { postLog.Error(fmt.Sprintf("[SwitchProxyHandler] Failed to read request body: %v", err)) utils.SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body") return } defer r.Body.Close() var reqMap map[string]interface{} if err := json.Unmarshal(body, &reqMap); err != nil { postLog.Error(fmt.Sprintf("[SwitchProxyHandler] Failed to unmarshal request body: %v", err)) utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid request format") return } instanceID := getIntFromMap(reqMap, "instanceID") if instanceID == 0 { postLog.Error("[SwitchProxyHandler] instanceID is required") utils.SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } proxyName := getStringFromMap(reqMap, "proxyName") if proxyName == "" { postLog.Error("[SwitchProxyHandler] proxyName is required") utils.SendErrorResponse(w, http.StatusBadRequest, "proxyName is required") return } action := getIntFromMap(reqMap, "action") if action != 0 && action != 1 { postLog.Error("[SwitchProxyHandler] action must be 0 or 1") utils.SendErrorResponse(w, http.StatusBadRequest, "action must be 0 (disable) or 1 (enable)") return } var instance database.FrpcInstance instance, err = database.DBQueryFrpcInstanceByID(instanceID) if err != nil { postLog.Error(fmt.Sprintf("[SwitchProxyHandler] Failed to query instance: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } configContent, err := os.ReadFile(instance.ConfigPath) if err != nil { postLog.Error(fmt.Sprintf("[SwitchProxyHandler] Failed to read config file %s: %v", instance.ConfigPath, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to read config file") return } lines := strings.Split(string(configContent), "\n") startLine, endLine := findProxyBlock(lines, proxyName) if startLine < 0 { postLog.Error(fmt.Sprintf("[SwitchProxyHandler] Proxy %s not found in config", proxyName)) utils.SendErrorResponse(w, http.StatusNotFound, "Proxy not found in config") return } if action == 0 { for i := startLine; i <= endLine; i++ { if !strings.HasPrefix(lines[i], "#") { lines[i] = "#" + lines[i] } } } else { for i := startLine; i <= endLine; i++ { lines[i] = strings.TrimPrefix(lines[i], "#") } } updatedContent := strings.Join(lines, "\n") if err := os.WriteFile(instance.ConfigPath, []byte(updatedContent), 0644); err != nil { postLog.Error(fmt.Sprintf("[SwitchProxyHandler] Failed to write config file %s: %v", instance.ConfigPath, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to write config file") return } statusText := "off" if action == 1 { statusText = "on" } utils.SendSuccessResponse(w, "Proxy status has been switched to "+statusText+".", map[string]interface{}{ "instanceID": instance.ID, "proxyName": proxyName, "status": statusText, }) postLog.Info(fmt.Sprintf("[SwitchProxyHandler] Proxy %s switched %s for instance %d", proxyName, statusText, instance.ID)) } func findProxyBlock(lines []string, proxyName string) (int, int) { targetLine := -1 for i, line := range lines { trimmed := strings.TrimSpace(line) uncommented := strings.TrimPrefix(trimmed, "#") uncommented = strings.TrimSpace(uncommented) if strings.HasPrefix(uncommented, "name ") && strings.Contains(uncommented, proxyName) { targetLine = i break } } if targetLine < 0 { return -1, -1 } startLine := targetLine for i := targetLine; i >= 0; i-- { trimmed := strings.TrimSpace(lines[i]) uncommented := strings.TrimPrefix(trimmed, "#") uncommented = strings.TrimSpace(uncommented) if uncommented == "[[proxies]]" { startLine = i break } } endLine := targetLine for i := targetLine + 1; i < len(lines); i++ { trimmed := strings.TrimSpace(lines[i]) uncommented := strings.TrimPrefix(trimmed, "#") uncommented = strings.TrimSpace(uncommented) if uncommented == "[[proxies]]" { endLine = i - 1 break } endLine = i } return startLine, endLine } func parseDisabledProxies(lines []string) []map[string]interface{} { var disabled []map[string]interface{} for i := 0; i < len(lines); i++ { trimmed := strings.TrimSpace(lines[i]) if trimmed != "#[[proxies]]" { continue } proxy := map[string]interface{}{"enabled": false} for j := i + 1; j < len(lines); j++ { line := lines[j] if !strings.HasPrefix(strings.TrimSpace(line), "#") { break } uncommented := strings.TrimPrefix(strings.TrimSpace(line), "#") uncommented = strings.TrimSpace(uncommented) if uncommented == "" || strings.HasPrefix(uncommented, "[[") { break } parts := strings.SplitN(uncommented, "=", 2) if len(parts) != 2 { continue } key := strings.TrimSpace(parts[0]) val := strings.TrimSpace(parts[1]) proxy[key] = parseTOMLValue(val) } if proxy["name"] != nil && proxy["name"] != "" { disabled = append(disabled, proxy) } } return disabled } func parseTOMLValue(val string) interface{} { if len(val) >= 2 && val[0] == '"' && val[len(val)-1] == '"' { return val[1 : len(val)-1] } if n, err := strconv.Atoi(val); err == nil { return n } return val }