Last active
October 24, 2023 07:27
-
-
Save miken32/f402317ce71bf7156e4a4a645e6a500b to your computer and use it in GitHub Desktop.
Laravel CIDR validation rule
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Rules; | |
use Closure; | |
use Illuminate\Contracts\Validation\ValidationRule; | |
use const FILTER_FLAG_IPV4; | |
use const FILTER_FLAG_IPV6; | |
use const FILTER_VALIDATE_INT; | |
use const FILTER_VALIDATE_IP; | |
class Cidr implements ValidationRule | |
{ | |
/** @var bool whether or not the rule has been called with network constraints */ | |
private bool $has_bits; | |
/** | |
* @param int|null $ipv4minbits The minimum number of bits allowed in an IPv4 network | |
* @param int|null $ipv4maxbits The maximum number of bits allowed in an IPv4 network | |
* @param int|null $ipv6minbits The minimum number of bits allowed in an IPv6 network | |
* @param int|null $ipv6maxbits The maximum number of bits allowed in an IPv6 network | |
*/ | |
public function __construct( | |
public ?int $ipv4minbits = null, | |
public ?int $ipv4maxbits = null, | |
public ?int $ipv6minbits = null, | |
public ?int $ipv6maxbits = null, | |
) | |
{ | |
$this->has_bits = func_num_args() > 0; | |
} | |
/** | |
* @param string $attribute The attribute being validated | |
* @param mixed $value The current value of the attribute | |
* @param Closure $fail Closure to be run in case of failure | |
* @return void | |
*/ | |
public function validate(string $attribute, mixed $value, Closure $fail): void | |
{ | |
$mask = null; | |
$valid_mask = true; | |
if (str_contains($value, "/")) { | |
[$value, $mask] = explode("/", $value); | |
} elseif ($this->has_bits) { | |
// if we specify a bit constraint, assume the bits are required | |
$fail($this->message()); | |
} | |
if (str_contains($value, ":")) { | |
// ipv6 | |
if ($mask !== null) { | |
$valid_mask = filter_var( | |
$mask, | |
FILTER_VALIDATE_INT, | |
["options" => ["min_range" => $this->ipv6minbits ?? 0, "max_range" => $this->ipv6maxbits ?? 128]] | |
); | |
} | |
$valid_address = filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); | |
} else { | |
// ipv4 | |
if ($mask !== null) { | |
$valid_mask = filter_var( | |
$mask, | |
FILTER_VALIDATE_INT, | |
["options" => ["min_range" => $this->ipv4minbits ?? 0, "max_range" => $this->ipv4maxbits ?? 32]] | |
); | |
if ($valid_mask) { | |
$long = ip2long($value); | |
$mask = -1 << (32 - $mask); | |
$valid_mask = long2ip($long & $mask) === $value; | |
} | |
} | |
$valid_address = filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); | |
} | |
if (!$valid_address || !$valid_mask) { | |
$fail($this->message()); | |
} | |
} | |
public function message(): string | |
{ | |
return !$this->has_bits | |
? __("The :attribute must be a valid IP address or CIDR subnet.") | |
: sprintf( | |
__("The :attribute must be a valid CIDR subnet with a mask of %d-%d (IPv4) or %d-%d (IPv6) bits."), | |
$this->ipv4minbits ?? 0, | |
$this->ipv4maxbits ?? 32, | |
$this->ipv6minbits ?? 0, | |
$this->ipv6maxbits ?? 128 | |
); | |
} | |
} |
Yeah I can do that @justasSendrauskas give me a couple of days
@justasSendrauskas @jonnott @lrljoe I put up a package miken32/network-rules
if you want to have a look. Consolidated a bunch of one-off code I was using internally and added a bunch of new validations as well. Still marked as pre-release for now.
@miken32 brilliant! thank you very much
Very excited for this!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
wait did we move anywhere with a repo? @miken32 since you are the author that would makes sense for you to do it...