init
This commit is contained in:
371
lib/validate.nix
Normal file
371
lib/validate.nix
Normal file
@@ -0,0 +1,371 @@
|
||||
{
|
||||
lib,
|
||||
internal,
|
||||
ip,
|
||||
cidr,
|
||||
}:
|
||||
let
|
||||
inherit (builtins)
|
||||
elemAt
|
||||
filter
|
||||
length
|
||||
match
|
||||
split
|
||||
tryEval
|
||||
;
|
||||
inherit (lib) all toInt;
|
||||
in
|
||||
rec {
|
||||
/**
|
||||
Check if a string is a valid IPv4 address.
|
||||
|
||||
# Type
|
||||
|
||||
```
|
||||
isValidIp :: Any -> Bool
|
||||
```
|
||||
|
||||
# Example
|
||||
|
||||
```nix
|
||||
isValidIp "192.168.1.1"
|
||||
=> true
|
||||
```
|
||||
*/
|
||||
isValidIp =
|
||||
str:
|
||||
if !builtins.isString str then
|
||||
false
|
||||
else
|
||||
let
|
||||
# Match basic format: digits.digits.digits.digits
|
||||
m = match "([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)" str;
|
||||
in
|
||||
if m == null then
|
||||
false
|
||||
else
|
||||
let
|
||||
octets = map toInt m;
|
||||
validOctet = o: o >= 0 && o <= 255;
|
||||
in
|
||||
all validOctet octets;
|
||||
|
||||
/**
|
||||
Check if a string is valid CIDR notation.
|
||||
|
||||
# Type
|
||||
|
||||
```
|
||||
isValidCidr :: Any -> Bool
|
||||
```
|
||||
|
||||
# Example
|
||||
|
||||
```nix
|
||||
isValidCidr "192.168.1.0/24"
|
||||
=> true
|
||||
```
|
||||
*/
|
||||
isValidCidr =
|
||||
str:
|
||||
if !builtins.isString str then
|
||||
false
|
||||
else
|
||||
let
|
||||
parts = filter builtins.isString (split "/" str);
|
||||
in
|
||||
if length parts != 2 then
|
||||
false
|
||||
else
|
||||
let
|
||||
ipPart = elemAt parts 0;
|
||||
prefixPart = elemAt parts 1;
|
||||
prefixMatch = match "[0-9]+" prefixPart;
|
||||
in
|
||||
if !isValidIp ipPart then
|
||||
false
|
||||
else if prefixMatch == null then
|
||||
false
|
||||
else
|
||||
let
|
||||
prefix = toInt prefixPart;
|
||||
in
|
||||
prefix >= 0 && prefix <= 32;
|
||||
|
||||
/**
|
||||
Try to parse an IP, return null on failure.
|
||||
|
||||
# Type
|
||||
|
||||
```
|
||||
tryParseIp :: String -> (Int | Null)
|
||||
```
|
||||
|
||||
# Example
|
||||
|
||||
```nix
|
||||
tryParseIp "192.168.1.1"
|
||||
=> 3232235777
|
||||
```
|
||||
*/
|
||||
tryParseIp =
|
||||
str:
|
||||
let
|
||||
result = tryEval (if isValidIp str then ip.parse str else throw "invalid");
|
||||
in
|
||||
if result.success then result.value else null;
|
||||
|
||||
/**
|
||||
Try to parse a CIDR, return null on failure.
|
||||
|
||||
# Type
|
||||
|
||||
```
|
||||
tryParseCidr :: String -> ({ address :: Int; prefixLen :: Int; } | Null)
|
||||
```
|
||||
|
||||
# Example
|
||||
|
||||
```nix
|
||||
tryParseCidr "192.168.1.0/24"
|
||||
=> { address = 3232235776; prefixLen = 24; }
|
||||
```
|
||||
*/
|
||||
tryParseCidr =
|
||||
str:
|
||||
let
|
||||
result = tryEval (if isValidCidr str then cidr.parse str else throw "invalid");
|
||||
in
|
||||
if result.success then result.value else null;
|
||||
|
||||
/**
|
||||
Check if IP is in RFC 1918 private address space.
|
||||
Covers 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16.
|
||||
|
||||
# Type
|
||||
|
||||
```
|
||||
isPrivate :: (String | Int) -> Bool
|
||||
```
|
||||
|
||||
# Example
|
||||
|
||||
```nix
|
||||
isPrivate "192.168.1.1"
|
||||
=> true
|
||||
```
|
||||
*/
|
||||
isPrivate =
|
||||
addr:
|
||||
let
|
||||
ipInt = ip.parse addr;
|
||||
in
|
||||
cidr.contains "10.0.0.0/8" ipInt
|
||||
|| cidr.contains "172.16.0.0/12" ipInt
|
||||
|| cidr.contains "192.168.0.0/16" ipInt;
|
||||
|
||||
/**
|
||||
Check if IP is loopback (127.0.0.0/8).
|
||||
|
||||
# Type
|
||||
|
||||
```
|
||||
isLoopback :: (String | Int) -> Bool
|
||||
```
|
||||
|
||||
# Example
|
||||
|
||||
```nix
|
||||
isLoopback "127.0.0.1"
|
||||
=> true
|
||||
```
|
||||
*/
|
||||
isLoopback = addr: cidr.contains "127.0.0.0/8" (ip.parse addr);
|
||||
|
||||
/**
|
||||
Check if IP is link-local (169.254.0.0/16).
|
||||
|
||||
# Type
|
||||
|
||||
```
|
||||
isLinkLocal :: (String | Int) -> Bool
|
||||
```
|
||||
|
||||
# Example
|
||||
|
||||
```nix
|
||||
isLinkLocal "169.254.1.1"
|
||||
=> true
|
||||
```
|
||||
*/
|
||||
isLinkLocal = addr: cidr.contains "169.254.0.0/16" (ip.parse addr);
|
||||
|
||||
/**
|
||||
Check if IP is multicast (224.0.0.0/4).
|
||||
|
||||
# Type
|
||||
|
||||
```
|
||||
isMulticast :: (String | Int) -> Bool
|
||||
```
|
||||
|
||||
# Example
|
||||
|
||||
```nix
|
||||
isMulticast "224.0.0.1"
|
||||
=> true
|
||||
```
|
||||
*/
|
||||
isMulticast = addr: cidr.contains "224.0.0.0/4" (ip.parse addr);
|
||||
|
||||
/**
|
||||
Check if IP is broadcast (255.255.255.255).
|
||||
|
||||
# Type
|
||||
|
||||
```
|
||||
isBroadcast :: (String | Int) -> Bool
|
||||
```
|
||||
|
||||
# Example
|
||||
|
||||
```nix
|
||||
isBroadcast "255.255.255.255"
|
||||
=> true
|
||||
```
|
||||
*/
|
||||
isBroadcast = addr: (ip.parse addr) == 4294967295;
|
||||
|
||||
/**
|
||||
Check if IP is unspecified (0.0.0.0).
|
||||
|
||||
# Type
|
||||
|
||||
```
|
||||
isUnspecified :: (String | Int) -> Bool
|
||||
```
|
||||
|
||||
# Example
|
||||
|
||||
```nix
|
||||
isUnspecified "0.0.0.0"
|
||||
=> true
|
||||
```
|
||||
*/
|
||||
isUnspecified = addr: (ip.parse addr) == 0;
|
||||
|
||||
/**
|
||||
Check if IP is documentation range.
|
||||
Covers 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24.
|
||||
|
||||
# Type
|
||||
|
||||
```
|
||||
isDocumentation :: (String | Int) -> Bool
|
||||
```
|
||||
|
||||
# Example
|
||||
|
||||
```nix
|
||||
isDocumentation "192.0.2.1"
|
||||
=> true
|
||||
```
|
||||
*/
|
||||
isDocumentation =
|
||||
addr:
|
||||
let
|
||||
ipInt = ip.parse addr;
|
||||
in
|
||||
cidr.contains "192.0.2.0/24" ipInt
|
||||
|| cidr.contains "198.51.100.0/24" ipInt
|
||||
|| cidr.contains "203.0.113.0/24" ipInt;
|
||||
|
||||
/**
|
||||
Check if IP is reserved for future use (240.0.0.0/4, except broadcast).
|
||||
|
||||
# Type
|
||||
|
||||
```
|
||||
isReserved :: (String | Int) -> Bool
|
||||
```
|
||||
|
||||
# Example
|
||||
|
||||
```nix
|
||||
isReserved "240.0.0.1"
|
||||
=> true
|
||||
```
|
||||
*/
|
||||
isReserved =
|
||||
addr:
|
||||
let
|
||||
ipInt = ip.parse addr;
|
||||
in
|
||||
cidr.contains "240.0.0.0/4" ipInt && ipInt != 4294967295;
|
||||
|
||||
/**
|
||||
Check if IP is globally routable (not private, loopback, link-local, etc.).
|
||||
|
||||
# Type
|
||||
|
||||
```
|
||||
isGlobalUnicast :: (String | Int) -> Bool
|
||||
```
|
||||
|
||||
# Example
|
||||
|
||||
```nix
|
||||
isGlobalUnicast "8.8.8.8"
|
||||
=> true
|
||||
```
|
||||
*/
|
||||
isGlobalUnicast =
|
||||
addr:
|
||||
!isPrivate addr
|
||||
&& !isLoopback addr
|
||||
&& !isLinkLocal addr
|
||||
&& !isMulticast addr
|
||||
&& !isBroadcast addr
|
||||
&& !isUnspecified addr
|
||||
&& !isDocumentation addr
|
||||
&& !isReserved addr;
|
||||
|
||||
/**
|
||||
Classify an IP address into its type.
|
||||
Returns one of: "loopback", "private", "link-local", "multicast",
|
||||
"broadcast", "unspecified", "documentation", "reserved", "global-unicast".
|
||||
|
||||
# Type
|
||||
|
||||
```
|
||||
classify :: (String | Int) -> String
|
||||
```
|
||||
|
||||
# Example
|
||||
|
||||
```nix
|
||||
classify "192.168.1.1"
|
||||
=> "private"
|
||||
```
|
||||
*/
|
||||
classify =
|
||||
addr:
|
||||
if isLoopback addr then
|
||||
"loopback"
|
||||
else if isPrivate addr then
|
||||
"private"
|
||||
else if isLinkLocal addr then
|
||||
"link-local"
|
||||
else if isMulticast addr then
|
||||
"multicast"
|
||||
else if isBroadcast addr then
|
||||
"broadcast"
|
||||
else if isUnspecified addr then
|
||||
"unspecified"
|
||||
else if isDocumentation addr then
|
||||
"documentation"
|
||||
else if isReserved addr then
|
||||
"reserved"
|
||||
else
|
||||
"global-unicast";
|
||||
}
|
||||
Reference in New Issue
Block a user