Toby Chui f8270e46c2 Added UI for plugin system and upnp example
- Added wip UI for plugin tag system
- Added upnp port forwarder plugin
- Added error and fatal printout for plugins
- Optimized UI flow for plugin context window
- Added dev web server for plugin development purpose
2025-03-15 21:02:44 +08:00

328 lines
7.2 KiB
Go

package main
import (
"encoding/json"
"errors"
"net/http"
"strconv"
"strings"
)
/*
API Handlers
*/
func handleUsableState(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
js, _ := json.Marshal(upnpRouterExists)
SendJSONResponse(w, string(js))
} else if r.Method == "POST" {
//Try to probe the UPnP router again
TryStartUPnPClient()
if upnpRouterExists {
SendOK(w)
} else {
SendErrorResponse(w, "UPnP router not found")
}
}
}
// Get or set the enable state of the plugin
func handleEnableState(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
js, _ := json.Marshal(upnpRuntimeConfig.Enabled)
SendJSONResponse(w, string(js))
} else if r.Method == "POST" {
enable, err := PostBool(r, "enable")
if err != nil {
SendErrorResponse(w, err.Error())
return
}
if !enable {
//Close all the port forwards if UPnP client is available
if upnpClient != nil {
for _, record := range upnpRuntimeConfig.ForwardRules {
err = upnpClient.ClosePort(record.PortNumber)
if err != nil {
SendErrorResponse(w, err.Error())
return
}
}
}
} else {
if upnpClient == nil {
SendErrorResponse(w, "No UPnP router in network")
return
}
//Forward all the ports if UPnP client is available
if upnpClient != nil {
for _, record := range upnpRuntimeConfig.ForwardRules {
err = upnpClient.ForwardPort(record.PortNumber, record.RuleName)
if err != nil {
SendErrorResponse(w, err.Error())
return
}
}
}
}
upnpRuntimeConfig.Enabled = enable
SaveRuntimeConfig()
}
}
func handleForwardPortEdit(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
port, err := PostInt(r, "port")
if err != nil {
SendErrorResponse(w, err.Error())
return
}
oldPort, err := PostInt(r, "oldPort")
if err != nil {
SendErrorResponse(w, err.Error())
return
}
name, err := PostPara(r, "name")
if err != nil {
SendErrorResponse(w, err.Error())
return
}
if port < 1 || port > 65535 {
SendErrorResponse(w, "invalid port number")
return
}
//Check if the old port exists
found := false
for _, record := range upnpRuntimeConfig.ForwardRules {
if record.PortNumber == oldPort {
found = true
break
}
}
if !found {
SendErrorResponse(w, "editing forward rule not found")
return
}
//Delete the old port forward
if oldPort != port && upnpClient != nil {
//Remove the port forward if UPnP client is available
err = upnpClient.ClosePort(oldPort)
if err != nil {
SendErrorResponse(w, err.Error())
return
}
}
//Remove from runtime config
for i, record := range upnpRuntimeConfig.ForwardRules {
if record.PortNumber == oldPort {
upnpRuntimeConfig.ForwardRules = append(upnpRuntimeConfig.ForwardRules[:i], upnpRuntimeConfig.ForwardRules[i+1:]...)
break
}
}
//Create the new forward rule
if upnpClient != nil {
//Forward the port if UPnP client is available
err = upnpClient.ForwardPort(port, name)
if err != nil {
SendErrorResponse(w, err.Error())
return
}
}
//Add to runtime config
upnpRuntimeConfig.ForwardRules = append(upnpRuntimeConfig.ForwardRules, &PortForwardRecord{
RuleName: name,
PortNumber: port,
})
//Save the runtime config
SaveRuntimeConfig()
SendOK(w)
}
}
// Remove a port forward
func handleForwardPortRemove(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
port, err := PostInt(r, "port")
if err != nil {
SendErrorResponse(w, err.Error())
return
}
if upnpClient != nil {
//Remove the port forward if UPnP client is available
err = upnpClient.ClosePort(port)
if err != nil {
SendErrorResponse(w, err.Error())
return
}
}
//Remove from runtime config
for i, record := range upnpRuntimeConfig.ForwardRules {
if record.PortNumber == port {
upnpRuntimeConfig.ForwardRules = append(upnpRuntimeConfig.ForwardRules[:i], upnpRuntimeConfig.ForwardRules[i+1:]...)
break
}
}
//Save the runtime config
SaveRuntimeConfig()
SendOK(w)
}
}
// Handle the port forward operations
func handleForwardPort(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
// List all the forwarded ports
js, _ := json.Marshal(upnpRuntimeConfig.ForwardRules)
SendJSONResponse(w, string(js))
} else if r.Method == "POST" {
//Add a new port forward
port, err := PostInt(r, "port")
if err != nil {
SendErrorResponse(w, err.Error())
return
}
name, err := PostPara(r, "name")
if err != nil {
SendErrorResponse(w, err.Error())
return
}
if port < 1 || port > 65535 {
SendErrorResponse(w, "invalid port number")
return
}
if upnpClient != nil {
//Forward the port if UPnP client is available
err = upnpClient.ForwardPort(port, name)
if err != nil {
SendErrorResponse(w, err.Error())
return
}
}
//Add to runtime config
upnpRuntimeConfig.ForwardRules = append(upnpRuntimeConfig.ForwardRules, &PortForwardRecord{
RuleName: name,
PortNumber: port,
})
//Save the runtime config
SaveRuntimeConfig()
SendOK(w)
}
}
/*
Network Utilities
*/
// Send JSON response, with an extra json header
func SendJSONResponse(w http.ResponseWriter, json string) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(json))
}
func SendErrorResponse(w http.ResponseWriter, errMsg string) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte("{\"error\":\"" + errMsg + "\"}"))
}
func SendOK(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte("\"OK\""))
}
// Get GET parameter
func GetPara(r *http.Request, key string) (string, error) {
// Get first value from the URL query
value := r.URL.Query().Get(key)
if len(value) == 0 {
return "", errors.New("invalid " + key + " given")
}
return value, nil
}
// Get GET paramter as boolean, accept 1 or true
func GetBool(r *http.Request, key string) (bool, error) {
x, err := GetPara(r, key)
if err != nil {
return false, err
}
// Convert to lowercase and trim spaces just once to compare
switch strings.ToLower(strings.TrimSpace(x)) {
case "1", "true", "on":
return true, nil
case "0", "false", "off":
return false, nil
}
return false, errors.New("invalid boolean given")
}
// Get POST parameter
func PostPara(r *http.Request, key string) (string, error) {
// Try to parse the form
if err := r.ParseForm(); err != nil {
return "", err
}
// Get first value from the form
x := r.Form.Get(key)
if len(x) == 0 {
return "", errors.New("invalid " + key + " given")
}
return x, nil
}
// Get POST paramter as boolean, accept 1 or true
func PostBool(r *http.Request, key string) (bool, error) {
x, err := PostPara(r, key)
if err != nil {
return false, err
}
// Convert to lowercase and trim spaces just once to compare
switch strings.ToLower(strings.TrimSpace(x)) {
case "1", "true", "on":
return true, nil
case "0", "false", "off":
return false, nil
}
return false, errors.New("invalid boolean given")
}
// Get POST paramter as int
func PostInt(r *http.Request, key string) (int, error) {
x, err := PostPara(r, key)
if err != nil {
return 0, err
}
x = strings.TrimSpace(x)
rx, err := strconv.Atoi(x)
if err != nil {
return 0, err
}
return rx, nil
}