Skip to content

Instantly share code, notes, and snippets.

@jonnott
Forked from miken32/Cidr.php
Created June 12, 2023 13:16
Show Gist options
  • Save jonnott/2d9f88a3545463a69ea7c52d260d1fb3 to your computer and use it in GitHub Desktop.
Save jonnott/2d9f88a3545463a69ea7c52d260d1fb3 to your computer and use it in GitHub Desktop.
Laravel CIDR validation rule
<?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
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment