mirror of
https://github.com/caddyserver/caddy.git
synced 2025-02-02 06:07:21 +01:00
caddyfile: Less strict URL parsing; allows placeholders
See https://caddy.community/t/caddy-v2-reusable-snippets/6744/11?u=matt
This commit is contained in:
parent
a5ebec0041
commit
8aef859a55
2 changed files with 62 additions and 33 deletions
|
@ -17,7 +17,6 @@ package httpcaddyfile
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -209,44 +208,69 @@ type Address struct {
|
||||||
// ParseAddress parses an address string into a structured format with separate
|
// ParseAddress parses an address string into a structured format with separate
|
||||||
// scheme, host, port, and path portions, as well as the original input string.
|
// scheme, host, port, and path portions, as well as the original input string.
|
||||||
func ParseAddress(str string) (Address, error) {
|
func ParseAddress(str string) (Address, error) {
|
||||||
|
const maxLen = 4096
|
||||||
|
if len(str) > maxLen {
|
||||||
|
str = str[:maxLen]
|
||||||
|
}
|
||||||
|
remaining := strings.TrimSpace(str)
|
||||||
|
a := Address{Original: remaining}
|
||||||
|
|
||||||
|
// extract scheme
|
||||||
|
splitScheme := strings.SplitN(remaining, "://", 2)
|
||||||
|
switch len(splitScheme) {
|
||||||
|
case 0:
|
||||||
|
return a, nil
|
||||||
|
case 1:
|
||||||
|
remaining = splitScheme[0]
|
||||||
|
case 2:
|
||||||
|
a.Scheme = splitScheme[0]
|
||||||
|
remaining = splitScheme[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract host and port
|
||||||
|
hostSplit := strings.SplitN(remaining, "/", 2)
|
||||||
|
if len(hostSplit) > 0 {
|
||||||
|
host, port, err := net.SplitHostPort(hostSplit[0])
|
||||||
|
if err != nil {
|
||||||
|
host, port, err = net.SplitHostPort(hostSplit[0] + ":")
|
||||||
|
if err != nil {
|
||||||
|
host = hostSplit[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.Host = host
|
||||||
|
a.Port = port
|
||||||
|
}
|
||||||
|
if len(hostSplit) == 2 {
|
||||||
|
// all that remains is the path
|
||||||
|
a.Path = "/" + hostSplit[1]
|
||||||
|
}
|
||||||
|
|
||||||
httpPort, httpsPort := strconv.Itoa(certmagic.HTTPPort), strconv.Itoa(certmagic.HTTPSPort)
|
httpPort, httpsPort := strconv.Itoa(certmagic.HTTPPort), strconv.Itoa(certmagic.HTTPSPort)
|
||||||
|
|
||||||
input := str
|
// see if we can set port based off scheme
|
||||||
|
if a.Port == "" {
|
||||||
// Split input into components (prepend with // to force host portion by default)
|
if a.Scheme == "http" {
|
||||||
if !strings.Contains(str, "//") && !strings.HasPrefix(str, "/") {
|
a.Port = httpPort
|
||||||
str = "//" + str
|
} else if a.Scheme == "https" {
|
||||||
}
|
a.Port = httpsPort
|
||||||
|
|
||||||
u, err := url.Parse(str)
|
|
||||||
if err != nil {
|
|
||||||
return Address{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// separate host and port
|
|
||||||
host, port, err := net.SplitHostPort(u.Host)
|
|
||||||
if err != nil {
|
|
||||||
host, port, err = net.SplitHostPort(u.Host + ":")
|
|
||||||
if err != nil {
|
|
||||||
host = u.Host
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// see if we can set port based off scheme
|
// make sure port is valid
|
||||||
if port == "" {
|
if a.Port != "" {
|
||||||
if u.Scheme == "http" {
|
if portNum, err := strconv.Atoi(a.Port); err != nil {
|
||||||
port = httpPort
|
return Address{}, fmt.Errorf("invalid port '%s': %v", a.Port, err)
|
||||||
} else if u.Scheme == "https" {
|
} else if portNum < 0 || portNum > 65535 {
|
||||||
port = httpsPort
|
return Address{}, fmt.Errorf("port %d is out of range", portNum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// error if scheme and port combination violate convention
|
// error if scheme and port combination violate convention
|
||||||
if (u.Scheme == "http" && port == httpsPort) || (u.Scheme == "https" && port == httpPort) {
|
if (a.Scheme == "http" && a.Port == httpsPort) || (a.Scheme == "https" && a.Port == httpPort) {
|
||||||
return Address{}, fmt.Errorf("[%s] scheme and port violate convention", input)
|
return Address{}, fmt.Errorf("[%s] scheme and port violate convention", str)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: which of the methods on Address are even used?
|
// TODO: which of the methods on Address are even used?
|
||||||
|
|
|
@ -11,6 +11,7 @@ func TestParseAddress(t *testing.T) {
|
||||||
scheme, host, port, path string
|
scheme, host, port, path string
|
||||||
shouldErr bool
|
shouldErr bool
|
||||||
}{
|
}{
|
||||||
|
{``, "", "", "", "", false},
|
||||||
{`localhost`, "", "localhost", "", "", false},
|
{`localhost`, "", "localhost", "", "", false},
|
||||||
{`localhost:1234`, "", "localhost", "1234", "", false},
|
{`localhost:1234`, "", "localhost", "1234", "", false},
|
||||||
{`localhost:`, "", "localhost", "", "", false},
|
{`localhost:`, "", "localhost", "", "", false},
|
||||||
|
@ -31,6 +32,10 @@ func TestParseAddress(t *testing.T) {
|
||||||
{`https://localhost:80`, "", "", "", "", true}, // not conventional
|
{`https://localhost:80`, "", "", "", "", true}, // not conventional
|
||||||
{`http://localhost`, "http", "localhost", "80", "", false},
|
{`http://localhost`, "http", "localhost", "80", "", false},
|
||||||
{`https://localhost`, "https", "localhost", "443", "", false},
|
{`https://localhost`, "https", "localhost", "443", "", false},
|
||||||
|
{`http://{env.APP_DOMAIN}`, "http", "{env.APP_DOMAIN}", "80", "", false},
|
||||||
|
{`{env.APP_DOMAIN}:80`, "", "{env.APP_DOMAIN}", "80", "", false},
|
||||||
|
{`{env.APP_DOMAIN}/path`, "", "{env.APP_DOMAIN}", "", "/path", false},
|
||||||
|
{`example.com/{env.APP_PATH}`, "", "example.com", "", "/{env.APP_PATH}", false},
|
||||||
{`http://127.0.0.1`, "http", "127.0.0.1", "80", "", false},
|
{`http://127.0.0.1`, "http", "127.0.0.1", "80", "", false},
|
||||||
{`https://127.0.0.1`, "https", "127.0.0.1", "443", "", false},
|
{`https://127.0.0.1`, "https", "127.0.0.1", "443", "", false},
|
||||||
{`http://[::1]`, "http", "::1", "80", "", false},
|
{`http://[::1]`, "http", "::1", "80", "", false},
|
||||||
|
@ -38,12 +43,12 @@ func TestParseAddress(t *testing.T) {
|
||||||
{`https://127.0.0.1:1234`, "https", "127.0.0.1", "1234", "", false},
|
{`https://127.0.0.1:1234`, "https", "127.0.0.1", "1234", "", false},
|
||||||
{`http://[::1]:1234`, "http", "::1", "1234", "", false},
|
{`http://[::1]:1234`, "http", "::1", "1234", "", false},
|
||||||
{``, "", "", "", "", false},
|
{``, "", "", "", "", false},
|
||||||
{`::1`, "", "::1", "", "", true},
|
{`::1`, "", "::1", "", "", false},
|
||||||
{`localhost::`, "", "localhost::", "", "", true},
|
{`localhost::`, "", "localhost::", "", "", false},
|
||||||
{`#$%@`, "", "", "", "", true},
|
{`#$%@`, "", "#$%@", "", "", false}, // don't want to presume what the hostname could be
|
||||||
{`host/path`, "", "host", "", "/path", false},
|
{`host/path`, "", "host", "", "/path", false},
|
||||||
{`http://host/`, "http", "host", "80", "/", false},
|
{`http://host/`, "http", "host", "80", "/", false},
|
||||||
{`//asdf`, "", "asdf", "", "", false},
|
{`//asdf`, "", "", "", "//asdf", false},
|
||||||
{`:1234/asdf`, "", "", "1234", "/asdf", false},
|
{`:1234/asdf`, "", "", "1234", "/asdf", false},
|
||||||
{`http://host/path`, "http", "host", "80", "/path", false},
|
{`http://host/path`, "http", "host", "80", "/path", false},
|
||||||
{`https://host:443/path/foo`, "https", "host", "443", "/path/foo", false},
|
{`https://host:443/path/foo`, "https", "host", "443", "/path/foo", false},
|
||||||
|
@ -56,7 +61,7 @@ func TestParseAddress(t *testing.T) {
|
||||||
t.Errorf("Test %d (%s): Expected no error, but had error: %v", i, test.input, err)
|
t.Errorf("Test %d (%s): Expected no error, but had error: %v", i, test.input, err)
|
||||||
}
|
}
|
||||||
if err == nil && test.shouldErr {
|
if err == nil && test.shouldErr {
|
||||||
t.Errorf("Test %d (%s): Expected error, but had none", i, test.input)
|
t.Errorf("Test %d (%s): Expected error, but had none (%#v)", i, test.input, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !test.shouldErr && actual.Original != test.input {
|
if !test.shouldErr && actual.Original != test.input {
|
||||||
|
|
Loading…
Reference in a new issue