Skip to content

Instantly share code, notes, and snippets.

@miken32
Last active October 24, 2023 07:27
Show Gist options
  • Save miken32/f402317ce71bf7156e4a4a645e6a500b to your computer and use it in GitHub Desktop.
Save miken32/f402317ce71bf7156e4a4a645e6a500b 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
);
}
}
@devmaufh
Copy link

Thanks! you saved me a lot of time. 🚀

@jonnott
Copy link

jonnott commented May 26, 2023

@miken32 I've updated this class for Laravel 10.x (new ValidationRule contract) and PHP 8.1 (str_contains()) .. are you interested in having this to update?

@miken32
Copy link
Author

miken32 commented May 26, 2023

@jonnott nah, I've updated months ago in our project, just didn't remember I'd posted this here.

@jonnott
Copy link

jonnott commented May 26, 2023

@jonnott nah, I've updated months ago in our project, just didn't remember I'd posted this here.

Sweet, I've grabbed your new version. Thnx!

@lrljoe
Copy link

lrljoe commented Jun 12, 2023

Appreciate this, I've used the basis but have a version/rule to check if an IP is in one or more CIDR subnets, not sure if it'd be useful to chuck a link on here or if you'd find that useful.

@jonnott
Copy link

jonnott commented Jun 12, 2023

I think this could be cool as a little package like https://github.com/Propaganistas/Laravel-Phone

@miken32 would you be up for collab on this? i.e. if I turned it into a similar package + co-maintain .. ?

@justasSendrauskas
Copy link

how is it going to put it into a repo guys?

@jonnott
Copy link

jonnott commented Sep 21, 2023

how is it going to put it into a repo guys?

Are you keen for a package then?

@justasSendrauskas
Copy link

how is it going to put it into a repo guys?

Are you keen for a package then?

sure

@justasSendrauskas
Copy link

wait did we move anywhere with a repo? @miken32 since you are the author that would makes sense for you to do it...

@miken32
Copy link
Author

miken32 commented Oct 4, 2023

Yeah I can do that @justasSendrauskas give me a couple of days

@miken32
Copy link
Author

miken32 commented Oct 18, 2023

@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.

@justasSendrauskas
Copy link

@miken32 brilliant! thank you very much

@jonnott
Copy link

jonnott commented Oct 20, 2023

Very excited for this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment