MM-11301: Validates listen address config value. (#9138)
* MM-11301: Validates listen address config value. * MM-11301: Adds some invalid port test cases. * MM-11301: Accept domain names. * MM-11301: Fix for max port.
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
d23ca07133
Коммит
65cd447a61
@@ -6,9 +6,12 @@ package model
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -2352,7 +2355,15 @@ func (ss *ServiceSettings) isValid() *AppError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(*ss.ListenAddress) == 0 {
|
host, port, err := net.SplitHostPort(*ss.ListenAddress)
|
||||||
|
var isValidHost bool
|
||||||
|
if host == "" {
|
||||||
|
isValidHost = true
|
||||||
|
} else {
|
||||||
|
isValidHost = (net.ParseIP(host) != nil) || IsDomainName(host)
|
||||||
|
}
|
||||||
|
portInt, err := strconv.Atoi(port)
|
||||||
|
if err != nil || !isValidHost || portInt < 0 || portInt > math.MaxUint16 {
|
||||||
return NewAppError("Config.IsValid", "model.config.is_valid.listen_address.app_error", nil, "", http.StatusBadRequest)
|
return NewAppError("Config.IsValid", "model.config.is_valid.listen_address.app_error", nil, "", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -519,3 +519,45 @@ func TestDisplaySettingsIsValidCustomUrlSchemes(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListenAddressIsValidated(t *testing.T) {
|
||||||
|
|
||||||
|
testValues := map[string]bool{
|
||||||
|
":8065": true,
|
||||||
|
":9917": true,
|
||||||
|
"0.0.0.0:9917": true,
|
||||||
|
"[2001:db8::68]:9918": true,
|
||||||
|
"[::1]:8065": true,
|
||||||
|
"localhost:8065": true,
|
||||||
|
"test.com:8065": true,
|
||||||
|
":0": true,
|
||||||
|
":33147": true,
|
||||||
|
"123:8065": false,
|
||||||
|
"[::1]:99999": false,
|
||||||
|
"[::1]:-1": false,
|
||||||
|
"[::1]:8065a": false,
|
||||||
|
"0.0.0:9917": false,
|
||||||
|
"0.0.0.0:9917/": false,
|
||||||
|
"0..0.0:9917/": false,
|
||||||
|
"0.0.0222.0:9917/": false,
|
||||||
|
"http://0.0.0.0:9917/": false,
|
||||||
|
"http://0.0.0.0:9917": false,
|
||||||
|
"8065": false,
|
||||||
|
"[2001:db8::68]": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, expected := range testValues {
|
||||||
|
ss := &ServiceSettings{
|
||||||
|
ListenAddress: NewString(key),
|
||||||
|
}
|
||||||
|
ss.SetDefaults()
|
||||||
|
if expected {
|
||||||
|
require.Nil(t, ss.isValid(), fmt.Sprintf("Got an error from '%v'.", key))
|
||||||
|
} else {
|
||||||
|
err := ss.isValid()
|
||||||
|
require.NotNil(t, err, fmt.Sprintf("Expected '%v' to throw an error.", key))
|
||||||
|
require.Equal(t, "model.config.is_valid.listen_address.app_error", err.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -536,3 +536,57 @@ func checkNowhereNil(t *testing.T, name string, value interface{}) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copied from https://golang.org/src/net/dnsclient.go#L119
|
||||||
|
func IsDomainName(s string) bool {
|
||||||
|
// See RFC 1035, RFC 3696.
|
||||||
|
// Presentation format has dots before every label except the first, and the
|
||||||
|
// terminal empty label is optional here because we assume fully-qualified
|
||||||
|
// (absolute) input. We must therefore reserve space for the first and last
|
||||||
|
// labels' length octets in wire format, where they are necessary and the
|
||||||
|
// maximum total length is 255.
|
||||||
|
// So our _effective_ maximum is 253, but 254 is not rejected if the last
|
||||||
|
// character is a dot.
|
||||||
|
l := len(s)
|
||||||
|
if l == 0 || l > 254 || l == 254 && s[l-1] != '.' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
last := byte('.')
|
||||||
|
ok := false // Ok once we've seen a letter.
|
||||||
|
partlen := 0
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_':
|
||||||
|
ok = true
|
||||||
|
partlen++
|
||||||
|
case '0' <= c && c <= '9':
|
||||||
|
// fine
|
||||||
|
partlen++
|
||||||
|
case c == '-':
|
||||||
|
// Byte before dash cannot be dot.
|
||||||
|
if last == '.' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
partlen++
|
||||||
|
case c == '.':
|
||||||
|
// Byte before dot cannot be dot, dash.
|
||||||
|
if last == '.' || last == '-' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if partlen > 63 || partlen == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
partlen = 0
|
||||||
|
}
|
||||||
|
last = c
|
||||||
|
}
|
||||||
|
if last == '-' || partlen > 63 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user