From d25008d2c8e2eb5f96b2b37a1cca5b4e140cfe8d Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Mon, 8 Jul 2019 16:46:38 -0600 Subject: Move listen address functions into caddy package; fix unix bug --- listeners.go | 72 ++++++++++++ listeners_test.go | 211 ++++++++++++++++++++++++++++++++++++ modules/caddyhttp/caddyhttp.go | 65 +---------- modules/caddyhttp/caddyhttp_test.go | 202 ---------------------------------- modules/caddyhttp/server.go | 2 +- 5 files changed, 290 insertions(+), 262 deletions(-) create mode 100644 listeners_test.go delete mode 100644 modules/caddyhttp/caddyhttp_test.go diff --git a/listeners.go b/listeners.go index 28adc8b..d97674d 100644 --- a/listeners.go +++ b/listeners.go @@ -17,6 +17,8 @@ package caddy import ( "fmt" "net" + "strconv" + "strings" "sync" "sync/atomic" "time" @@ -160,3 +162,73 @@ var ( listeners = make(map[string]*listenerUsage) listenersMu sync.Mutex ) + +// ParseListenAddr parses addr, a string of the form "network/host:port" +// (with any part optional) into its component parts. Because a port can +// also be a port range, there may be multiple addresses returned. +func ParseListenAddr(addr string) (network string, addrs []string, err error) { + var host, port string + network, host, port, err = SplitListenAddr(addr) + if network == "" { + network = "tcp" + } + if err != nil { + return + } + if network == "unix" { + addrs = []string{host} + return + } + ports := strings.SplitN(port, "-", 2) + if len(ports) == 1 { + ports = append(ports, ports[0]) + } + var start, end int + start, err = strconv.Atoi(ports[0]) + if err != nil { + return + } + end, err = strconv.Atoi(ports[1]) + if err != nil { + return + } + if end < start { + err = fmt.Errorf("end port must be greater than start port") + return + } + for p := start; p <= end; p++ { + addrs = append(addrs, net.JoinHostPort(host, fmt.Sprintf("%d", p))) + } + return +} + +// SplitListenAddr splits a into its network, host, and port components. +// Note that port may be a port range, or omitted for unix sockets. +func SplitListenAddr(a string) (network, host, port string, err error) { + if idx := strings.Index(a, "/"); idx >= 0 { + network = strings.ToLower(strings.TrimSpace(a[:idx])) + a = a[idx+1:] + } + if network == "unix" { + host = a + return + } + host, port, err = net.SplitHostPort(a) + return +} + +// JoinListenAddr combines network, host, and port into a single +// address string of the form "network/host:port". Port may be a +// port range. For unix sockets, the network should be "unix" and +// the path to the socket should be given in the host argument. +func JoinListenAddr(network, host, port string) string { + var a string + if network != "" { + a = network + "/" + } + a += host + if port != "" { + a += ":" + port + } + return a +} diff --git a/listeners_test.go b/listeners_test.go new file mode 100644 index 0000000..7c5a2fc --- /dev/null +++ b/listeners_test.go @@ -0,0 +1,211 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddy + +import ( + "reflect" + "testing" +) + +func TestSplitListenerAddr(t *testing.T) { + for i, tc := range []struct { + input string + expectNetwork string + expectHost string + expectPort string + expectErr bool + }{ + { + input: "", + expectErr: true, + }, + { + input: "foo", + expectErr: true, + }, + { + input: "foo:1234", + expectHost: "foo", + expectPort: "1234", + }, + { + input: "foo:1234-5678", + expectHost: "foo", + expectPort: "1234-5678", + }, + { + input: "udp/foo:1234", + expectNetwork: "udp", + expectHost: "foo", + expectPort: "1234", + }, + { + input: "tcp6/foo:1234-5678", + expectNetwork: "tcp6", + expectHost: "foo", + expectPort: "1234-5678", + }, + { + input: "udp/", + expectNetwork: "udp", + expectErr: true, + }, + { + input: "unix//foo/bar", + expectNetwork: "unix", + expectHost: "/foo/bar", + }, + } { + actualNetwork, actualHost, actualPort, err := SplitListenAddr(tc.input) + if tc.expectErr && err == nil { + t.Errorf("Test %d: Expected error but got: %v", i, err) + } + if !tc.expectErr && err != nil { + t.Errorf("Test %d: Expected no error but got: %v", i, err) + } + if actualNetwork != tc.expectNetwork { + t.Errorf("Test %d: Expected network '%s' but got '%s'", i, tc.expectNetwork, actualNetwork) + } + if actualHost != tc.expectHost { + t.Errorf("Test %d: Expected host '%s' but got '%s'", i, tc.expectHost, actualHost) + } + if actualPort != tc.expectPort { + t.Errorf("Test %d: Expected port '%s' but got '%s'", i, tc.expectPort, actualPort) + } + } +} + +func TestJoinListenerAddr(t *testing.T) { + for i, tc := range []struct { + network, host, port string + expect string + }{ + { + network: "", host: "", port: "", + expect: "", + }, + { + network: "tcp", host: "", port: "", + expect: "tcp/", + }, + { + network: "", host: "foo", port: "", + expect: "foo", + }, + { + network: "", host: "", port: "1234", + expect: ":1234", + }, + { + network: "", host: "", port: "1234-5678", + expect: ":1234-5678", + }, + { + network: "", host: "foo", port: "1234", + expect: "foo:1234", + }, + { + network: "udp", host: "foo", port: "1234", + expect: "udp/foo:1234", + }, + { + network: "udp", host: "", port: "1234", + expect: "udp/:1234", + }, + { + network: "unix", host: "/foo/bar", port: "", + expect: "unix//foo/bar", + }, + } { + actual := JoinListenAddr(tc.network, tc.host, tc.port) + if actual != tc.expect { + t.Errorf("Test %d: Expected '%s' but got '%s'", i, tc.expect, actual) + } + } +} + +func TestParseListenerAddr(t *testing.T) { + for i, tc := range []struct { + input string + expectNetwork string + expectAddrs []string + expectErr bool + }{ + { + input: "", + expectNetwork: "tcp", + expectErr: true, + }, + { + input: ":", + expectNetwork: "tcp", + expectErr: true, + }, + { + input: ":1234", + expectNetwork: "tcp", + expectAddrs: []string{":1234"}, + }, + { + input: "tcp/:1234", + expectNetwork: "tcp", + expectAddrs: []string{":1234"}, + }, + { + input: "tcp6/:1234", + expectNetwork: "tcp6", + expectAddrs: []string{":1234"}, + }, + { + input: "tcp4/localhost:1234", + expectNetwork: "tcp4", + expectAddrs: []string{"localhost:1234"}, + }, + { + input: "unix//foo/bar", + expectNetwork: "unix", + expectAddrs: []string{"/foo/bar"}, + }, + { + input: "localhost:1234-1234", + expectNetwork: "tcp", + expectAddrs: []string{"localhost:1234"}, + }, + { + input: "localhost:2-1", + expectNetwork: "tcp", + expectErr: true, + }, + { + input: "localhost:0", + expectNetwork: "tcp", + expectAddrs: []string{"localhost:0"}, + }, + } { + actualNetwork, actualAddrs, err := ParseListenAddr(tc.input) + if tc.expectErr && err == nil { + t.Errorf("Test %d: Expected error but got: %v", i, err) + } + if !tc.expectErr && err != nil { + t.Errorf("Test %d: Expected no error but got: %v", i, err) + } + if actualNetwork != tc.expectNetwork { + t.Errorf("Test %d: Expected network '%s' but got '%s'", i, tc.expectNetwork, actualNetwork) + } + if !reflect.DeepEqual(tc.expectAddrs, actualAddrs) { + t.Errorf("Test %d: Expected addresses %v but got %v", i, tc.expectAddrs, actualAddrs) + } + } +} diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go index 3277e42..d0c7540 100644 --- a/modules/caddyhttp/caddyhttp.go +++ b/modules/caddyhttp/caddyhttp.go @@ -96,7 +96,7 @@ func (app *App) Validate() error { lnAddrs := make(map[string]string) for srvName, srv := range app.Servers { for _, addr := range srv.Listen { - netw, expanded, err := parseListenAddr(addr) + netw, expanded, err := caddy.ParseListenAddr(addr) if err != nil { return fmt.Errorf("invalid listener address '%s': %v", addr, err) } @@ -137,7 +137,7 @@ func (app *App) Start() error { } for _, lnAddr := range srv.Listen { - network, addrs, err := parseListenAddr(lnAddr) + network, addrs, err := caddy.ParseListenAddr(lnAddr) if err != nil { return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err) } @@ -289,7 +289,7 @@ func (app *App) automaticHTTPS() error { // create HTTP->HTTPS redirects for _, addr := range srv.Listen { - netw, host, port, err := splitListenAddr(addr) + netw, host, port, err := caddy.SplitListenAddr(addr) if err != nil { return fmt.Errorf("%s: invalid listener address: %v", srvName, addr) } @@ -298,7 +298,7 @@ func (app *App) automaticHTTPS() error { if httpPort == 0 { httpPort = DefaultHTTPPort } - httpRedirLnAddr := joinListenAddr(netw, host, strconv.Itoa(httpPort)) + httpRedirLnAddr := caddy.JoinListenAddr(netw, host, strconv.Itoa(httpPort)) lnAddrMap[httpRedirLnAddr] = struct{}{} if parts := strings.SplitN(port, "-", 2); len(parts) == 2 { @@ -339,7 +339,7 @@ func (app *App) automaticHTTPS() error { var lnAddrs []string mapLoop: for addr := range lnAddrMap { - netw, addrs, err := parseListenAddr(addr) + netw, addrs, err := caddy.ParseListenAddr(addr) if err != nil { continue } @@ -364,7 +364,7 @@ func (app *App) automaticHTTPS() error { func (app *App) listenerTaken(network, address string) bool { for _, srv := range app.Servers { for _, addr := range srv.Listen { - netw, addrs, err := parseListenAddr(addr) + netw, addrs, err := caddy.ParseListenAddr(addr) if err != nil || netw != network { continue } @@ -425,59 +425,6 @@ func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) error { // sometimes better than a nil Handler pointer. var emptyHandler HandlerFunc = func(w http.ResponseWriter, r *http.Request) error { return nil } -func parseListenAddr(a string) (network string, addrs []string, err error) { - var host, port string - network, host, port, err = splitListenAddr(a) - if network == "" { - network = "tcp" - } - if err != nil { - return - } - ports := strings.SplitN(port, "-", 2) - if len(ports) == 1 { - ports = append(ports, ports[0]) - } - var start, end int - start, err = strconv.Atoi(ports[0]) - if err != nil { - return - } - end, err = strconv.Atoi(ports[1]) - if err != nil { - return - } - if end < start { - err = fmt.Errorf("end port must be greater than start port") - return - } - for p := start; p <= end; p++ { - addrs = append(addrs, net.JoinHostPort(host, fmt.Sprintf("%d", p))) - } - return -} - -func splitListenAddr(a string) (network, host, port string, err error) { - if idx := strings.Index(a, "/"); idx >= 0 { - network = strings.ToLower(strings.TrimSpace(a[:idx])) - a = a[idx+1:] - } - host, port, err = net.SplitHostPort(a) - return -} - -func joinListenAddr(network, host, port string) string { - var a string - if network != "" { - a = network + "/" - } - a += host - if port != "" { - a += ":" + port - } - return a -} - const ( // DefaultHTTPPort is the default port for HTTP. DefaultHTTPPort = 80 diff --git a/modules/caddyhttp/caddyhttp_test.go b/modules/caddyhttp/caddyhttp_test.go deleted file mode 100644 index 3c0c2f4..0000000 --- a/modules/caddyhttp/caddyhttp_test.go +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2015 Matthew Holt and The Caddy Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package caddyhttp - -import ( - "reflect" - "testing" -) - -func TestSplitListenerAddr(t *testing.T) { - for i, tc := range []struct { - input string - expectNetwork string - expectHost string - expectPort string - expectErr bool - }{ - { - input: "", - expectErr: true, - }, - { - input: "foo", - expectErr: true, - }, - { - input: "foo:1234", - expectHost: "foo", - expectPort: "1234", - }, - { - input: "foo:1234-5678", - expectHost: "foo", - expectPort: "1234-5678", - }, - { - input: "udp/foo:1234", - expectNetwork: "udp", - expectHost: "foo", - expectPort: "1234", - }, - { - input: "tcp6/foo:1234-5678", - expectNetwork: "tcp6", - expectHost: "foo", - expectPort: "1234-5678", - }, - { - input: "udp/", - expectNetwork: "udp", - expectErr: true, - }, - } { - actualNetwork, actualHost, actualPort, err := splitListenAddr(tc.input) - if tc.expectErr && err == nil { - t.Errorf("Test %d: Expected error but got: %v", i, err) - } - if !tc.expectErr && err != nil { - t.Errorf("Test %d: Expected no error but got: %v", i, err) - } - if actualNetwork != tc.expectNetwork { - t.Errorf("Test %d: Expected network '%s' but got '%s'", i, tc.expectNetwork, actualNetwork) - } - if actualHost != tc.expectHost { - t.Errorf("Test %d: Expected host '%s' but got '%s'", i, tc.expectHost, actualHost) - } - if actualPort != tc.expectPort { - t.Errorf("Test %d: Expected port '%s' but got '%s'", i, tc.expectPort, actualPort) - } - } -} - -func TestJoinListenerAddr(t *testing.T) { - for i, tc := range []struct { - network, host, port string - expect string - }{ - { - network: "", host: "", port: "", - expect: "", - }, - { - network: "tcp", host: "", port: "", - expect: "tcp/", - }, - { - network: "", host: "foo", port: "", - expect: "foo", - }, - { - network: "", host: "", port: "1234", - expect: ":1234", - }, - { - network: "", host: "", port: "1234-5678", - expect: ":1234-5678", - }, - { - network: "", host: "foo", port: "1234", - expect: "foo:1234", - }, - { - network: "udp", host: "foo", port: "1234", - expect: "udp/foo:1234", - }, - { - network: "udp", host: "", port: "1234", - expect: "udp/:1234", - }, - } { - actual := joinListenAddr(tc.network, tc.host, tc.port) - if actual != tc.expect { - t.Errorf("Test %d: Expected '%s' but got '%s'", i, tc.expect, actual) - } - } -} - -func TestParseListenerAddr(t *testing.T) { - for i, tc := range []struct { - input string - expectNetwork string - expectAddrs []string - expectErr bool - }{ - { - input: "", - expectNetwork: "tcp", - expectErr: true, - }, - { - input: ":", - expectNetwork: "tcp", - expectErr: true, - }, - { - input: ":1234", - expectNetwork: "tcp", - expectAddrs: []string{":1234"}, - }, - { - input: "tcp/:1234", - expectNetwork: "tcp", - expectAddrs: []string{":1234"}, - }, - { - input: "tcp6/:1234", - expectNetwork: "tcp6", - expectAddrs: []string{":1234"}, - }, - { - input: "tcp4/localhost:1234", - expectNetwork: "tcp4", - expectAddrs: []string{"localhost:1234"}, - }, - { - input: "unix/localhost:1234-1236", - expectNetwork: "unix", - expectAddrs: []string{"localhost:1234", "localhost:1235", "localhost:1236"}, - }, - { - input: "localhost:1234-1234", - expectNetwork: "tcp", - expectAddrs: []string{"localhost:1234"}, - }, - { - input: "localhost:2-1", - expectNetwork: "tcp", - expectErr: true, - }, - { - input: "localhost:0", - expectNetwork: "tcp", - expectAddrs: []string{"localhost:0"}, - }, - } { - actualNetwork, actualAddrs, err := parseListenAddr(tc.input) - if tc.expectErr && err == nil { - t.Errorf("Test %d: Expected error but got: %v", i, err) - } - if !tc.expectErr && err != nil { - t.Errorf("Test %d: Expected no error but got: %v", i, err) - } - if actualNetwork != tc.expectNetwork { - t.Errorf("Test %d: Expected network '%s' but got '%s'", i, tc.expectNetwork, actualNetwork) - } - if !reflect.DeepEqual(tc.expectAddrs, actualAddrs) { - t.Errorf("Test %d: Expected addresses %v but got %v", i, tc.expectAddrs, actualAddrs) - } - } -} diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index 1460152..d40a01d 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -156,7 +156,7 @@ func (s *Server) enforcementHandler(w http.ResponseWriter, r *http.Request, next func (s *Server) listenersUseAnyPortOtherThan(otherPort int) bool { for _, lnAddr := range s.Listen { - _, addrs, err := parseListenAddr(lnAddr) + _, addrs, err := caddy.ParseListenAddr(lnAddr) if err == nil { for _, a := range addrs { _, port, err := net.SplitHostPort(a) -- cgit v1.2.3