2021-11-01 11:39:52 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2021-11-01 11:39:52 +03:00
package hostmatcher
import (
"net"
"path/filepath"
"strings"
)
// 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-12-20 07:41:31 +03:00
func ParseHostMatchList ( settingKeyHint , hostList string ) * HostMatchList {
2021-11-20 12:34:05 +03:00
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)
2021-12-20 07:41:31 +03:00
func ParseSimpleMatchList ( settingKeyHint , matchList string ) * HostMatchList {
2021-11-20 12:34:05 +03:00
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 )
}
2022-08-17 03:15:54 +03:00
// AppendPattern appends more pattern to match
func ( hl * HostMatchList ) AppendPattern ( pattern string ) {
hl . patterns = append ( hl . patterns , pattern )
}
2021-11-20 12:34:05 +03:00
// 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 :
2022-03-18 22:17:57 +03:00
if ip . IsGlobalUnicast ( ) && ! ip . IsPrivate ( ) {
2021-11-20 12:34:05 +03:00
return true
2021-11-01 11:39:52 +03:00
}
case MatchBuiltinPrivate :
2022-03-18 22:17:57 +03:00
if ip . IsPrivate ( ) {
2021-11-20 12:34:05 +03:00
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 {
2022-07-13 04:07:16 +03:00
if hl == nil {
return false
}
2022-04-28 20:39:50 +03:00
hostname , _ , err := net . SplitHostPort ( host )
if err != nil {
hostname = host
}
if hl . checkPattern ( hostname ) {
2021-11-20 12:34:05 +03:00
return true
}
2022-04-28 20:39:50 +03:00
if ip := net . ParseIP ( hostname ) ; ip != nil {
2021-11-20 12:34:05 +03:00
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
}