github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/hostmatcher/hostmatcher.go (about) 1 // Copyright 2023 The GitBundle Inc. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // Use of this source code is governed by a MIT-style 4 // license that can be found in the LICENSE file. 5 6 package hostmatcher 7 8 import ( 9 "net" 10 "path/filepath" 11 "strings" 12 ) 13 14 // HostMatchList is used to check if a host or IP is in a list. 15 type HostMatchList struct { 16 SettingKeyHint string 17 SettingValue string 18 19 // builtins networks 20 builtins []string 21 // patterns for host names (with wildcard support) 22 patterns []string 23 // ipNets is the CIDR network list 24 ipNets []*net.IPNet 25 } 26 27 // MatchBuiltinExternal A valid non-private unicast IP, all hosts on public internet are matched 28 const MatchBuiltinExternal = "external" 29 30 // 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. 31 const MatchBuiltinPrivate = "private" 32 33 // MatchBuiltinLoopback 127.0.0.0/8 for IPv4 and ::1/128 for IPv6, localhost is included. 34 const MatchBuiltinLoopback = "loopback" 35 36 func isBuiltin(s string) bool { 37 return s == MatchBuiltinExternal || s == MatchBuiltinPrivate || s == MatchBuiltinLoopback 38 } 39 40 // ParseHostMatchList parses the host list HostMatchList 41 func ParseHostMatchList(settingKeyHint, hostList string) *HostMatchList { 42 hl := &HostMatchList{SettingKeyHint: settingKeyHint, SettingValue: hostList} 43 for _, s := range strings.Split(hostList, ",") { 44 s = strings.ToLower(strings.TrimSpace(s)) 45 if s == "" { 46 continue 47 } 48 _, ipNet, err := net.ParseCIDR(s) 49 if err == nil { 50 hl.ipNets = append(hl.ipNets, ipNet) 51 } else if isBuiltin(s) { 52 hl.builtins = append(hl.builtins, s) 53 } else { 54 hl.patterns = append(hl.patterns, s) 55 } 56 } 57 return hl 58 } 59 60 // ParseSimpleMatchList parse a simple matchlist (no built-in networks, no CIDR support, only wildcard pattern match) 61 func ParseSimpleMatchList(settingKeyHint, matchList string) *HostMatchList { 62 hl := &HostMatchList{ 63 SettingKeyHint: settingKeyHint, 64 SettingValue: matchList, 65 } 66 for _, s := range strings.Split(matchList, ",") { 67 s = strings.ToLower(strings.TrimSpace(s)) 68 if s == "" { 69 continue 70 } 71 // we keep the same result as old `matchlist`, so no builtin/CIDR support here, we only match wildcard patterns 72 hl.patterns = append(hl.patterns, s) 73 } 74 return hl 75 } 76 77 // AppendBuiltin appends more builtins to match 78 func (hl *HostMatchList) AppendBuiltin(builtin string) { 79 hl.builtins = append(hl.builtins, builtin) 80 } 81 82 // AppendPattern appends more pattern to match 83 func (hl *HostMatchList) AppendPattern(pattern string) { 84 hl.patterns = append(hl.patterns, pattern) 85 } 86 87 // IsEmpty checks if the checklist is empty 88 func (hl *HostMatchList) IsEmpty() bool { 89 return hl == nil || (len(hl.builtins) == 0 && len(hl.patterns) == 0 && len(hl.ipNets) == 0) 90 } 91 92 func (hl *HostMatchList) checkPattern(host string) bool { 93 host = strings.ToLower(strings.TrimSpace(host)) 94 for _, pattern := range hl.patterns { 95 if matched, _ := filepath.Match(pattern, host); matched { 96 return true 97 } 98 } 99 return false 100 } 101 102 func (hl *HostMatchList) checkIP(ip net.IP) bool { 103 for _, pattern := range hl.patterns { 104 if pattern == "*" { 105 return true 106 } 107 } 108 for _, builtin := range hl.builtins { 109 switch builtin { 110 case MatchBuiltinExternal: 111 if ip.IsGlobalUnicast() && !ip.IsPrivate() { 112 return true 113 } 114 case MatchBuiltinPrivate: 115 if ip.IsPrivate() { 116 return true 117 } 118 case MatchBuiltinLoopback: 119 if ip.IsLoopback() { 120 return true 121 } 122 } 123 } 124 for _, ipNet := range hl.ipNets { 125 if ipNet.Contains(ip) { 126 return true 127 } 128 } 129 return false 130 } 131 132 // MatchHostName checks if the host matches an allow/deny(block) list 133 func (hl *HostMatchList) MatchHostName(host string) bool { 134 hostname, _, err := net.SplitHostPort(host) 135 if err != nil { 136 hostname = host 137 } 138 139 if hl == nil { 140 return false 141 } 142 if hl.checkPattern(hostname) { 143 return true 144 } 145 if ip := net.ParseIP(hostname); ip != nil { 146 return hl.checkIP(ip) 147 } 148 return false 149 } 150 151 // MatchIPAddr checks if the IP matches an allow/deny(block) list, it's safe to pass `nil` to `ip` 152 func (hl *HostMatchList) MatchIPAddr(ip net.IP) bool { 153 if hl == nil { 154 return false 155 } 156 host := ip.String() // nil-safe, we will get "<nil>" if ip is nil 157 return hl.checkPattern(host) || hl.checkIP(ip) 158 } 159 160 // MatchHostOrIP checks if the host or IP matches an allow/deny(block) list 161 func (hl *HostMatchList) MatchHostOrIP(host string, ip net.IP) bool { 162 return hl.MatchHostName(host) || hl.MatchIPAddr(ip) 163 }