1
+package network
2
+
3
+import (
4
+ "errors"
5
+ "net/http"
6
+ "net/url"
7
+ "strings"
8
+
9
+ "go.uber.org/zap"
10
+
11
+ "github.com/caddyserver/caddy/v2"
12
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
13
+)
14
+
15
+func init() {
16
+ caddy.RegisterModule(ProxyFromURL{})
17
+ caddy.RegisterModule(ProxyFromNone{})
18
+}
19
+
20
+// The "url" proxy source uses the defined URL as the proxy
21
+type ProxyFromURL struct {
22
+ URL string `json:"url"`
23
+
24
+ ctx caddy.Context
25
+ logger *zap.Logger
26
+}
27
+
28
+// CaddyModule implements Module.
29
+func (p ProxyFromURL) CaddyModule() caddy.ModuleInfo {
30
+ return caddy.ModuleInfo{
31
+ ID: "caddy.network_proxy.url",
32
+ New: func() caddy.Module {
33
+ return &ProxyFromURL{}
34
+ },
35
+ }
36
+}
37
+
38
+func (p *ProxyFromURL) Provision(ctx caddy.Context) error {
39
+ p.ctx = ctx
40
+ p.logger = ctx.Logger()
41
+ return nil
42
+}
43
+
44
+// Validate implements Validator.
45
+func (p ProxyFromURL) Validate() error {
46
+ if _, err := url.Parse(p.URL); err != nil {
47
+ return err
48
+ }
49
+ return nil
50
+}
51
+
52
+// ProxyFunc implements ProxyFuncProducer.
53
+func (p ProxyFromURL) ProxyFunc() func(*http.Request) (*url.URL, error) {
54
+ if strings.Contains(p.URL, "{") && strings.Contains(p.URL, "}") {
55
+ // courtesy of @ImpostorKeanu: https://github.com/caddyserver/caddy/pull/6397
56
+ return func(r *http.Request) (*url.URL, error) {
57
+ // retrieve the replacer from context.
58
+ repl, ok := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
59
+ if !ok {
60
+ err := errors.New("failed to obtain replacer from request")
61
+ p.logger.Error(err.Error())
62
+ return nil, err
63
+ }
64
+
65
+ // apply placeholders to the value
66
+ // note: h.ForwardProxyURL should never be empty at this point
67
+ s := repl.ReplaceAll(p.URL, "")
68
+ if s == "" {
69
+ p.logger.Error("network_proxy URL was empty after applying placeholders",
70
+ zap.String("initial_value", p.URL),
71
+ zap.String("final_value", s),
72
+ zap.String("hint", "check for invalid placeholders"))
73
+ return nil, errors.New("empty value for network_proxy URL")
74
+ }
75
+
76
+ // parse the url
77
+ pUrl, err := url.Parse(s)
78
+ if err != nil {
79
+ p.logger.Warn("failed to derive transport proxy from network_proxy URL")
80
+ pUrl = nil
81
+ } else if pUrl.Host == "" || strings.Split("", pUrl.Host)[0] == ":" {
82
+ // url.Parse does not return an error on these values:
83
+ //
84
+ // - http://:80
85
+ // - pUrl.Host == ":80"
86
+ // - /some/path
87
+ // - pUrl.Host == ""
88
+ //
89
+ // Super edge cases, but humans are human.
90
+ err = errors.New("supplied network_proxy URL is missing a host value")
91
+ pUrl = nil
92
+ } else {
93
+ p.logger.Debug("setting transport proxy url", zap.String("url", s))
94
+ }
95
+
96
+ return pUrl, err
97
+ }
98
+ }
99
+ return func(r *http.Request) (*url.URL, error) {
100
+ return url.Parse(p.URL)
101
+ }
102
+}
103
+
104
+// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
105
+func (p *ProxyFromURL) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
106
+ d.Next()
107
+ d.Next()
108
+ p.URL = d.Val()
109
+ return nil
110
+}
111
+
112
+// The "none" proxy source module disables the use of network proxy.
113
+type ProxyFromNone struct{}
114
+
115
+func (p ProxyFromNone) CaddyModule() caddy.ModuleInfo {
116
+ return caddy.ModuleInfo{
117
+ ID: "caddy.network_proxy.none",
118
+ New: func() caddy.Module {
119
+ return &ProxyFromNone{}
120
+ },
121
+ }
122
+}
123
+
124
+// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
125
+func (p ProxyFromNone) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
126
+ return nil
127
+}
128
+
129
+// ProxyFunc implements ProxyFuncProducer.
130
+func (p ProxyFromNone) ProxyFunc() func(*http.Request) (*url.URL, error) {
131
+ return nil
132
+}
133
+
134
+var (
135
+ _ caddy.Module = ProxyFromURL{}
136
+ _ caddy.Provisioner = (*ProxyFromURL)(nil)
137
+ _ caddy.Validator = ProxyFromURL{}
138
+ _ caddy.ProxyFuncProducer = ProxyFromURL{}
139
+ _ caddyfile.Unmarshaler = (*ProxyFromURL)(nil)
140
+
141
+ _ caddy.Module = ProxyFromNone{}
142
+ _ caddy.ProxyFuncProducer = ProxyFromNone{}
143
+ _ caddyfile.Unmarshaler = ProxyFromNone{}
144
+)