code.gitea.io/gitea@v1.19.3/modules/hostmatcher/hostmatcher.go (about)

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