// Copyright 2022 The Gogs 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 netutil

import (
	"fmt"
	"net"
)

var localCIDRs []*net.IPNet

func init() {
	// Parsing hardcoded CIDR strings should never fail, if in case it does, let's
	// fail it at start.
	rawCIDRs := []string{
		// https://datatracker.ietf.org/doc/html/rfc5735:
		"127.0.0.0/8",        // Loopback
		"0.0.0.0/8",          // "This" network
		"100.64.0.0/10",      // Shared address space
		"169.254.0.0/16",     // Link local
		"172.16.0.0/12",      // Private-use networks
		"192.0.0.0/24",       // IETF Protocol assignments
		"192.0.2.0/24",       // TEST-NET-1
		"192.88.99.0/24",     // 6to4 Relay anycast
		"192.168.0.0/16",     // Private-use networks
		"198.18.0.0/15",      // Network interconnect
		"198.51.100.0/24",    // TEST-NET-2
		"203.0.113.0/24",     // TEST-NET-3
		"255.255.255.255/32", // Limited broadcast

		// https://datatracker.ietf.org/doc/html/rfc1918:
		"10.0.0.0/8", // Private-use networks

		// https://datatracker.ietf.org/doc/html/rfc6890:
		"::1/128",   // Loopback
		"FC00::/7",  // Unique local address
		"FE80::/10", // Multicast address
	}
	for _, raw := range rawCIDRs {
		_, cidr, err := net.ParseCIDR(raw)
		if err != nil {
			panic(fmt.Sprintf("parse CIDR %q: %v", raw, err))
		}
		localCIDRs = append(localCIDRs, cidr)
	}
}

// IsLocalHostname returns true if given hostname is resolved to local network
// address, except exempted from the allowlist.
func IsLocalHostname(hostname string, allowlist []string) bool {
	for _, allow := range allowlist {
		if hostname == allow {
			return false
		}
	}

	ips, err := net.LookupIP(hostname)
	if err != nil {
		return true
	}
	for _, ip := range ips {
		for _, cidr := range localCIDRs {
			if cidr.Contains(ip) {
				return true
			}
		}
	}
	return false
}
