@marcel91200 / caddy Mirror · read-only / commits / 8aef859a
Fork 0

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 {