2021-11-01 11:39:52 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package hostmatcher
import (
"net"
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/util"
)
// HostMatchList is used to check if a host or IP is in a list.
type HostMatchList struct {
2021-11-20 12:34:05 +03:00
SettingKeyHint string
SettingValue string
// builtins networks
builtins [ ] string
// patterns for host names (with wildcard support)
patterns [ ] string
// ipNets is the CIDR network list
2021-11-01 11:39:52 +03:00
ipNets [ ] * net . IPNet
}
// MatchBuiltinExternal A valid non-private unicast IP, all hosts on public internet are matched
const MatchBuiltinExternal = "external"
// MatchBuiltinPrivate RFC 1918 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) and RFC 4193 (FC00::/7). Also called LAN/Intranet.
const MatchBuiltinPrivate = "private"
// MatchBuiltinLoopback 127.0.0.0/8 for IPv4 and ::1/128 for IPv6, localhost is included.
const MatchBuiltinLoopback = "loopback"
2021-11-20 12:34:05 +03:00
func isBuiltin ( s string ) bool {
return s == MatchBuiltinExternal || s == MatchBuiltinPrivate || s == MatchBuiltinLoopback
}
2021-11-01 11:39:52 +03:00
// ParseHostMatchList parses the host list HostMatchList
2021-11-20 12:34:05 +03:00
func ParseHostMatchList ( settingKeyHint string , hostList string ) * HostMatchList {
hl := & HostMatchList { SettingKeyHint : settingKeyHint , SettingValue : hostList }
2021-11-01 11:39:52 +03:00
for _ , s := range strings . Split ( hostList , "," ) {
s = strings . ToLower ( strings . TrimSpace ( s ) )
if s == "" {
continue
}
_ , ipNet , err := net . ParseCIDR ( s )
if err == nil {
hl . ipNets = append ( hl . ipNets , ipNet )
2021-11-20 12:34:05 +03:00
} else if isBuiltin ( s ) {
hl . builtins = append ( hl . builtins , s )
2021-11-01 11:39:52 +03:00
} else {
2021-11-20 12:34:05 +03:00
hl . patterns = append ( hl . patterns , s )
2021-11-01 11:39:52 +03:00
}
}
return hl
}
2021-11-20 12:34:05 +03:00
// ParseSimpleMatchList parse a simple matchlist (no built-in networks, no CIDR support, only wildcard pattern match)
func ParseSimpleMatchList ( settingKeyHint string , matchList string ) * HostMatchList {
hl := & HostMatchList {
SettingKeyHint : settingKeyHint ,
SettingValue : matchList ,
}
for _ , s := range strings . Split ( matchList , "," ) {
s = strings . ToLower ( strings . TrimSpace ( s ) )
if s == "" {
2021-11-01 11:39:52 +03:00
continue
2021-11-20 12:34:05 +03:00
}
// we keep the same result as old `matchlist`, so no builtin/CIDR support here, we only match wildcard patterns
hl . patterns = append ( hl . patterns , s )
}
return hl
}
// AppendBuiltin appends more builtins to match
func ( hl * HostMatchList ) AppendBuiltin ( builtin string ) {
hl . builtins = append ( hl . builtins , builtin )
}
// IsEmpty checks if the checklist is empty
func ( hl * HostMatchList ) IsEmpty ( ) bool {
return hl == nil || ( len ( hl . builtins ) == 0 && len ( hl . patterns ) == 0 && len ( hl . ipNets ) == 0 )
}
func ( hl * HostMatchList ) checkPattern ( host string ) bool {
host = strings . ToLower ( strings . TrimSpace ( host ) )
for _ , pattern := range hl . patterns {
if matched , _ := filepath . Match ( pattern , host ) ; matched {
return true
}
}
return false
}
func ( hl * HostMatchList ) checkIP ( ip net . IP ) bool {
for _ , pattern := range hl . patterns {
if pattern == "*" {
return true
}
}
for _ , builtin := range hl . builtins {
switch builtin {
2021-11-01 11:39:52 +03:00
case MatchBuiltinExternal :
2021-11-20 12:34:05 +03:00
if ip . IsGlobalUnicast ( ) && ! util . IsIPPrivate ( ip ) {
return true
2021-11-01 11:39:52 +03:00
}
case MatchBuiltinPrivate :
2021-11-20 12:34:05 +03:00
if util . IsIPPrivate ( ip ) {
return true
2021-11-01 11:39:52 +03:00
}
case MatchBuiltinLoopback :
2021-11-20 12:34:05 +03:00
if ip . IsLoopback ( ) {
return true
2021-11-01 11:39:52 +03:00
}
}
}
2021-11-20 12:34:05 +03:00
for _ , ipNet := range hl . ipNets {
if ipNet . Contains ( ip ) {
return true
2021-11-01 11:39:52 +03:00
}
}
2021-11-20 12:34:05 +03:00
return false
}
// MatchHostName checks if the host matches an allow/deny(block) list
func ( hl * HostMatchList ) MatchHostName ( host string ) bool {
if hl == nil {
return false
}
if hl . checkPattern ( host ) {
return true
}
if ip := net . ParseIP ( host ) ; ip != nil {
return hl . checkIP ( ip )
}
return false
}
// MatchIPAddr checks if the IP matches an allow/deny(block) list, it's safe to pass `nil` to `ip`
func ( hl * HostMatchList ) MatchIPAddr ( ip net . IP ) bool {
if hl == nil {
return false
}
host := ip . String ( ) // nil-safe, we will get "<nil>" if ip is nil
return hl . checkPattern ( host ) || hl . checkIP ( ip )
}
// MatchHostOrIP checks if the host or IP matches an allow/deny(block) list
func ( hl * HostMatchList ) MatchHostOrIP ( host string , ip net . IP ) bool {
return hl . MatchHostName ( host ) || hl . MatchIPAddr ( ip )
2021-11-01 11:39:52 +03:00
}