Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
caddyfile: Less strict URL parsing; allows placeholders
See https://caddy.community/t/caddy-v2-reusable-snippets/6744/11?u=matt
Matthew Holt committed
Jan 9, 2020 at 12:35 UTC
8aef859a5510e883a70fb562d5fb83c7585cc301
2 files changed
+59
-30
caddyconfig/httpcaddyfile/addresses.go
+49
-25
@@ -17,7 +17,6 @@ package httpcaddyfile
17
import (
18
"fmt"
19
"net"
20
- "net/url"
20
"reflect"
21
"strconv"
22
"strings"
@@ -209,44 +208,69 @@ type Address struct {
208
// ParseAddress parses an address string into a structured format with separate
209
// scheme, host, port, and path portions, as well as the original input string.
210
func ParseAddress(str string) (Address, error) {
212
- httpPort, httpsPort := strconv.Itoa(certmagic.HTTPPort), strconv.Itoa(certmagic.HTTPSPort)
213
-
214
- input := str
215
-
216
- // Split input into components (prepend with // to force host portion by default)
217
- if !strings.Contains(str, "//") && !strings.HasPrefix(str, "/") {
218
- str = "//" + str
211
+ const maxLen = 4096
212
+ if len(str) > maxLen {
213
+ str = str[:maxLen]
214
}
220
-
221
- u, err := url.Parse(str)
222
- if err != nil {
223
- return Address{}, err
215
+ remaining := strings.TrimSpace(str)
216
+ a := Address{Original: remaining}
217
+
218
+ // extract scheme
219
+ splitScheme := strings.SplitN(remaining, "://", 2)
220
+ switch len(splitScheme) {
221
+ case 0:
222
+ return a, nil
223
+ case 1:
224
+ remaining = splitScheme[0]
225
+ case 2:
226
+ a.Scheme = splitScheme[0]
227
+ remaining = splitScheme[1]
228
}
229
226
- // separate host and port
227
- host, port, err := net.SplitHostPort(u.Host)
228
- if err != nil {
229
- host, port, err = net.SplitHostPort(u.Host + ":")
230
+ // extract host and port
231
+ hostSplit := strings.SplitN(remaining, "/", 2)
232
+ if len(hostSplit) > 0 {
233
+ host, port, err := net.SplitHostPort(hostSplit[0])
234
if err != nil {
231
- host = u.Host
235
+ host, port, err = net.SplitHostPort(hostSplit[0] + ":")
236
+ if err != nil {
237
+ host = hostSplit[0]
238
+ }
239
}
240
+ a.Host = host
241
+ a.Port = port
242
+ }
243
+ if len(hostSplit) == 2 {
244
+ // all that remains is the path
245
+ a.Path = "/" + hostSplit[1]
246
}
247
248
+ httpPort, httpsPort := strconv.Itoa(certmagic.HTTPPort), strconv.Itoa(certmagic.HTTPSPort)
249
+
250
// see if we can set port based off scheme
236
- if port == "" {
237
- if u.Scheme == "http" {
238
- port = httpPort
239
- } else if u.Scheme == "https" {
240
- port = httpsPort
251
+ if a.Port == "" {
252
+ if a.Scheme == "http" {
253
+ a.Port = httpPort
254
+ } else if a.Scheme == "https" {
255
+ a.Port = httpsPort
256
+ }
257
+ }
258
+
259
+ // make sure port is valid
260
+ if a.Port != "" {
261
+ if portNum, err := strconv.Atoi(a.Port); err != nil {
262
+ return Address{}, fmt.Errorf("invalid port '%s': %v", a.Port, err)
263
+ } else if portNum < 0 || portNum > 65535 {
264
+ return Address{}, fmt.Errorf("port %d is out of range", portNum)
265
}
266
}
267
268
// error if scheme and port combination violate convention
245
- if (u.Scheme == "http" && port == httpsPort) || (u.Scheme == "https" && port == httpPort) {
246
- return Address{}, fmt.Errorf("[%s] scheme and port violate convention", input)
269
+ if (a.Scheme == "http" && a.Port == httpsPort) || (a.Scheme == "https" && a.Port == httpPort) {
270
+ return Address{}, fmt.Errorf("[%s] scheme and port violate convention", str)
271
}
272
249
- return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err
273
+ return a, nil
274
}
275
276
// TODO: which of the methods on Address are even used?
caddyconfig/httpcaddyfile/addresses_test.go
+10
-5
@@ -11,6 +11,7 @@ func TestParseAddress(t *testing.T) {
11
scheme, host, port, path string
12
shouldErr bool
13
}{
14
+ {``, "", "", "", "", false},
15
{`localhost`, "", "localhost", "", "", false},
16
{`localhost:1234`, "", "localhost", "1234", "", false},
17
{`localhost:`, "", "localhost", "", "", false},
@@ -31,6 +32,10 @@ func TestParseAddress(t *testing.T) {
32
{`https://localhost:80`, "", "", "", "", true}, // not conventional
33
{`http://localhost`, "http", "localhost", "80", "", false},
34
{`https://localhost`, "https", "localhost", "443", "", false},
35
+ {`http://{env.APP_DOMAIN}`, "http", "{env.APP_DOMAIN}", "80", "", false},
36
+ {`{env.APP_DOMAIN}:80`, "", "{env.APP_DOMAIN}", "80", "", false},
37
+ {`{env.APP_DOMAIN}/path`, "", "{env.APP_DOMAIN}", "", "/path", false},
38
+ {`example.com/{env.APP_PATH}`, "", "example.com", "", "/{env.APP_PATH}", false},
39
{`http://127.0.0.1`, "http", "127.0.0.1", "80", "", false},
40
{`https://127.0.0.1`, "https", "127.0.0.1", "443", "", false},
41
{`http://[::1]`, "http", "::1", "80", "", false},
@@ -38,12 +43,12 @@ func TestParseAddress(t *testing.T) {
43
{`https://127.0.0.1:1234`, "https", "127.0.0.1", "1234", "", false},
44
{`http://[::1]:1234`, "http", "::1", "1234", "", false},
45
{``, "", "", "", "", false},
41
- {`::1`, "", "::1", "", "", true},
42
- {`localhost::`, "", "localhost::", "", "", true},
43
- {`#$%@`, "", "", "", "", true},
46
+ {`::1`, "", "::1", "", "", false},
47
+ {`localhost::`, "", "localhost::", "", "", false},
48
+ {`#$%@`, "", "#$%@", "", "", false}, // don't want to presume what the hostname could be
49
{`host/path`, "", "host", "", "/path", false},
50
{`http://host/`, "http", "host", "80", "/", false},
46
- {`//asdf`, "", "asdf", "", "", false},
51
+ {`//asdf`, "", "", "", "//asdf", false},
52
{`:1234/asdf`, "", "", "1234", "/asdf", false},
53
{`http://host/path`, "http", "host", "80", "/path", false},
54
{`https://host:443/path/foo`, "https", "host", "443", "/path/foo", false},
@@ -56,7 +61,7 @@ func TestParseAddress(t *testing.T) {
61
t.Errorf("Test %d (%s): Expected no error, but had error: %v", i, test.input, err)
62
}
63
if err == nil && test.shouldErr {
59
- t.Errorf("Test %d (%s): Expected error, but had none", i, test.input)
64
+ t.Errorf("Test %d (%s): Expected error, but had none (%#v)", i, test.input, actual)
65
}
66
67
if !test.shouldErr && actual.Original != test.input {