-
-
Notifications
You must be signed in to change notification settings - Fork 257
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2151 from acelaya-forks/feature/ip-dynamic-redirects
Add logic for IP-based dynamic redirects
- Loading branch information
Showing
23 changed files
with
457 additions
and
79 deletions.
There are no files selected for viewing
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
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
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
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Shlinkio\Shlink\Core\Exception; | ||
|
||
use function sprintf; | ||
|
||
class InvalidIpFormatException extends RuntimeException implements ExceptionInterface | ||
{ | ||
public static function fromInvalidIp(string $ipAddress): self | ||
{ | ||
return new self(sprintf('Provided IP %s does not have the right format. Expected X.X.X.X', $ipAddress)); | ||
} | ||
} |
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
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Shlinkio\Shlink\Core\Util; | ||
|
||
use IPLib\Address\IPv4; | ||
use IPLib\Factory; | ||
use IPLib\Range\RangeInterface; | ||
use Shlinkio\Shlink\Core\Exception\InvalidIpFormatException; | ||
|
||
use function array_keys; | ||
use function array_map; | ||
use function explode; | ||
use function implode; | ||
use function Shlinkio\Shlink\Core\ArrayUtils\some; | ||
use function str_contains; | ||
|
||
final class IpAddressUtils | ||
{ | ||
public static function isStaticIpCidrOrWildcard(string $candidate): bool | ||
{ | ||
return self::candidateToRange($candidate, ['0', '0', '0', '0']) !== null; | ||
} | ||
|
||
/** | ||
* Checks if an IP address matches any of provided groups. | ||
* Every group can be a static IP address (100.200.80.40), a CIDR block (192.168.10.0/24) or a wildcard pattern | ||
* (11.22.*.*). | ||
* | ||
* Matching will happen as follows: | ||
* * Static IP address -> strict equality with provided IP address. | ||
* * CIDR block -> provided IP address is part of that block. | ||
* * Wildcard pattern -> static parts match the corresponding ones in provided IP address. | ||
* | ||
* @param string[] $groups | ||
* @throws InvalidIpFormatException | ||
*/ | ||
public static function ipAddressMatchesGroups(string $ipAddress, array $groups): bool | ||
{ | ||
$ip = IPv4::parseString($ipAddress); | ||
if ($ip === null) { | ||
throw InvalidIpFormatException::fromInvalidIp($ipAddress); | ||
} | ||
|
||
$ipAddressParts = explode('.', $ipAddress); | ||
|
||
return some($groups, function (string $group) use ($ip, $ipAddressParts): bool { | ||
$range = self::candidateToRange($group, $ipAddressParts); | ||
return $range !== null && $range->contains($ip); | ||
}); | ||
} | ||
|
||
/** | ||
* Convert a static IP, CIDR block or wildcard pattern into a Range object | ||
* | ||
* @param string[] $ipAddressParts | ||
*/ | ||
private static function candidateToRange(string $candidate, array $ipAddressParts): ?RangeInterface | ||
{ | ||
return str_contains($candidate, '*') | ||
? self::parseValueWithWildcards($candidate, $ipAddressParts) | ||
: Factory::parseRangeString($candidate); | ||
} | ||
|
||
/** | ||
* Try to generate an IP range from a wildcard pattern. | ||
* Factory::parseRangeString can usually do this automatically, but only if wildcards are at the end. This also | ||
* covers cases where wildcards are in between. | ||
*/ | ||
private static function parseValueWithWildcards(string $value, array $ipAddressParts): ?RangeInterface | ||
{ | ||
$octets = explode('.', $value); | ||
$keys = array_keys($octets); | ||
|
||
// Replace wildcard parts with the corresponding ones from the remote address | ||
return Factory::parseRangeString( | ||
implode('.', array_map( | ||
fn (string $part, int $index) => $part === '*' ? $ipAddressParts[$index] : $part, | ||
$octets, | ||
$keys, | ||
)), | ||
); | ||
} | ||
} |
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
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
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
Oops, something went wrong.