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  }