1package route
2
3import (
4	"net/http"
5	"path/filepath"
6
7	"github.com/Dreamacro/clash/component/resolver"
8	"github.com/Dreamacro/clash/config"
9	"github.com/Dreamacro/clash/hub/executor"
10	"github.com/Dreamacro/clash/log"
11	P "github.com/Dreamacro/clash/proxy"
12	"github.com/Dreamacro/clash/tunnel"
13
14	"github.com/go-chi/chi/v5"
15	"github.com/go-chi/render"
16)
17
18func configRouter() http.Handler {
19	r := chi.NewRouter()
20	r.Get("/", getConfigs)
21	r.Put("/", updateConfigs)
22	r.Patch("/", patchConfigs)
23	return r
24}
25
26type configSchema struct {
27	Port        *int               `json:"port"`
28	SocksPort   *int               `json:"socks-port"`
29	RedirPort   *int               `json:"redir-port"`
30	TProxyPort  *int               `json:"tproxy-port"`
31	MixedPort   *int               `json:"mixed-port"`
32	AllowLan    *bool              `json:"allow-lan"`
33	BindAddress *string            `json:"bind-address"`
34	Mode        *tunnel.TunnelMode `json:"mode"`
35	LogLevel    *log.LogLevel      `json:"log-level"`
36	IPv6        *bool              `json:"ipv6"`
37}
38
39func getConfigs(w http.ResponseWriter, r *http.Request) {
40	general := executor.GetGeneral()
41	render.JSON(w, r, general)
42}
43
44func pointerOrDefault(p *int, def int) int {
45	if p != nil {
46		return *p
47	}
48
49	return def
50}
51
52func patchConfigs(w http.ResponseWriter, r *http.Request) {
53	general := &configSchema{}
54	if err := render.DecodeJSON(r.Body, general); err != nil {
55		render.Status(r, http.StatusBadRequest)
56		render.JSON(w, r, ErrBadRequest)
57		return
58	}
59
60	if general.AllowLan != nil {
61		P.SetAllowLan(*general.AllowLan)
62	}
63
64	if general.BindAddress != nil {
65		P.SetBindAddress(*general.BindAddress)
66	}
67
68	ports := P.GetPorts()
69	P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
70	P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
71	P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
72	P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort))
73	P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort))
74
75	if general.Mode != nil {
76		tunnel.SetMode(*general.Mode)
77	}
78
79	if general.LogLevel != nil {
80		log.SetLevel(*general.LogLevel)
81	}
82
83	if general.IPv6 != nil {
84		resolver.DisableIPv6 = !*general.IPv6
85	}
86
87	render.NoContent(w, r)
88}
89
90type updateConfigRequest struct {
91	Path    string `json:"path"`
92	Payload string `json:"payload"`
93}
94
95func updateConfigs(w http.ResponseWriter, r *http.Request) {
96	req := updateConfigRequest{}
97	if err := render.DecodeJSON(r.Body, &req); err != nil {
98		render.Status(r, http.StatusBadRequest)
99		render.JSON(w, r, ErrBadRequest)
100		return
101	}
102
103	force := r.URL.Query().Get("force") == "true"
104	var cfg *config.Config
105	var err error
106
107	if req.Payload != "" {
108		cfg, err = executor.ParseWithBytes([]byte(req.Payload))
109		if err != nil {
110			render.Status(r, http.StatusBadRequest)
111			render.JSON(w, r, newError(err.Error()))
112			return
113		}
114	} else {
115		if !filepath.IsAbs(req.Path) {
116			render.Status(r, http.StatusBadRequest)
117			render.JSON(w, r, newError("path is not a absolute path"))
118			return
119		}
120
121		cfg, err = executor.ParseWithPath(req.Path)
122		if err != nil {
123			render.Status(r, http.StatusBadRequest)
124			render.JSON(w, r, newError(err.Error()))
125			return
126		}
127	}
128
129	executor.ApplyConfig(cfg, force)
130	render.NoContent(w, r)
131}
132