Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 48 additions & 3 deletions src/cache/DefaultCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace ipinfo\ipinfo\cache;

use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\CacheItem;
use Symfony\Contracts\Cache\ItemInterface;

/**
Expand Down Expand Up @@ -32,6 +33,8 @@ public function __construct(int $maxsize, int $ttl)
*/
public function has(string $name): bool
{
$name = $this->sanitizeName($name);

return $this->cache->hasItem($name);
}

Expand All @@ -42,11 +45,12 @@ public function has(string $name): bool
*/
public function set(string $name, $value)
{
$name = $this->sanitizeName($name);
if (!$this->cache->hasItem($name)) {
$this->element_queue[] = $name;
}

$this->cache->get($name, function (ItemInterface $item) use ($value) {
$this->cache->get($name, function (ItemInterface $item) use ($value) {
$item->set($value)->expiresAfter($this->ttl);
return $item->get();
});
Expand All @@ -59,9 +63,20 @@ public function set(string $name, $value)
* @param string $ip_address IP address to lookup in cache.
* @return mixed IP address data.
*/
public function get(string $name)
public function get(string $ip_address)
{
return $this->cache->getItem($name)->get();
$sanitizeName = $this->sanitizeName($ip_address);
$result = $this->cache->getItem($sanitizeName)->get();
if (is_array($result) && array_key_exists("ip", $result)) {
/**
* The IPv6 may have different notation and we don't know which one is cached.
* We want to give the user the same notation as the one used in his request which may be different from
* the one used in the cache.
*/
$result["ip"] = $this->getIpAddress($ip_address);
}

return $result;
}

/**
Expand All @@ -79,4 +94,34 @@ private function manageSize()
$this->element_queue = array_slice($this->element_queue, $overflow);
}
}

private function getIpAddress(string $name): string
{
// The $name has the version postfix applied, we need to extract the IP address without it
$parts = explode('_', $name);
return $parts[0];
}

/**
* Remove forbidden characters from cache keys
*/
private function sanitizeName(string $name): string
{
// The $name has the version postfix applied, we need to extract the IP address without it
$parts = explode('_', $name);
$ip = $parts[0];
try {
// Attempt to normalize the IPv6 address
$binary = @inet_pton($ip); // Convert to 16-byte binary
if ($binary !== false && strlen($binary) === 16) { // Valid IPv6
$ip = inet_ntop($binary); // Convert to full notation (e.g., 2001:0db8:...)
}
$name = $ip . '_' . implode('_', array_slice($parts, 1));
} catch (\Exception) {
// If invalid, proceed with original input
}

$forbiddenCharacters = str_split(CacheItem::RESERVED_CHARACTERS);
return str_replace($forbiddenCharacters, '^', $name);
}
}
132 changes: 132 additions & 0 deletions tests/DefaultCacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,136 @@ public function testTimeToLiveExceeded()
sleep(2);
$this->assertEquals(null, $cache->get($key));
}

public function testCacheWithIPv6DifferentNotations()
{
// Create cache instance
$cache = new DefaultCache(10, 600);

// Original IPv6 address
$standard_ip = "2607:f8b0:4005:805::200e";
$standard_value = "standard_value";
$cache->set($standard_ip, $standard_value);

// Variations with zeros
$variations = [
"2607:f8b0:4005:805:0:0:0:200e", // Full form
"2607:f8b0:4005:805:0000:0000:0000:200e", // Full form with leading zeros
"2607:f8b0:4005:805:0000:00:00:200e", // Full form with few leading zeros
"2607:F8B0:4005:805::200E", // Uppercase notation
"2607:f8b0:4005:0805::200e", // Leading zero in a group
"2607:f8b0:4005:805:0::200e", // Partially expanded
"2607:f8b0:4005:805:0000::200e", // Full zeros in a second group
];

// DefaultCache does normalize IPs, so we need to check if the cache has the same value
foreach ($variations as $ip) {
$this->assertTrue($cache->has($ip), "Cache should have variation: $ip");
}
}

public function testDefaultCacheWithIPv4AndPostfixes()
{
// Create cache
$cache = new DefaultCache(5, 600);

// Test IPv4 with various postfixes
$ipv4 = '8.8.8.8';
$postfixes = ['_v1', '_latest', '_beta', '_test_123'];

foreach ($postfixes as $postfix) {
$key = $ipv4 . $postfix;
$value = "value_for_$key";

// Set value with postfix
$cache->set($key, $value);

// Verify it's in cache
$this->assertTrue($cache->has($key), "Cache should have key with postfix: $key");
$this->assertEquals($value, $cache->get($key), "Should get correct value for key with postfix");

// Verify base IP is not affected
if (!$cache->has($ipv4)) {
$cache->set($ipv4, "base_ip_value");
}

$this->assertNotEquals($cache->get($ipv4), $cache->get($key), "Base IP and IP with postfix should have different values");
}

// Check all keys are still available (capacity not exceeded)
foreach ($postfixes as $postfix) {
$this->assertTrue($cache->has($ipv4 . $postfix), "All postfix keys should be available");
}
$this->assertTrue($cache->has($ipv4), "Base IP should be available");
}

public function testCacheWithIPv6AndPostfixes()
{
// Create cache
$cache = new DefaultCache(5, 600);

// Test IPv6 with various postfixes
$ipv6 = '2607:f8b0:4005:805::200e';
$postfixes = ['_v1', '_latest', '_beta', '_test_123'];

foreach ($postfixes as $postfix) {
$key = $ipv6 . $postfix;
$value = "value_for_$key";

// Set value with postfix
$cache->set($key, $value);

// Verify it's in cache
$this->assertTrue($cache->has($key), "Cache should have key with postfix: $key");
$this->assertEquals($value, $cache->get($key), "Should get correct value for key with postfix");
}

// Add the base IP to cache if not present
if (!$cache->has($ipv6)) {
$cache->set($ipv6, "base_ip_value");
}

// Ensure all keys are distinct and have different values
$this->assertEquals("base_ip_value", $cache->get($ipv6), "Base IP should have its own value");
foreach ($postfixes as $postfix) {
$key = $ipv6 . $postfix;
$expected = "value_for_$key";
$this->assertEquals($expected, $cache->get($key), "Each postfix should have its own value");
}
}

public function testCacheWithIPv6NotationsAndPostfixes()
{
// Create cache instance
$cache = new DefaultCache(100, 600);

// Original IPv6 address
$standard_ip = "2607:f8b0:4005:805::200e";
$postfixes = ['_v1', '_latest', '_beta', '_test_123'];

// Variations with zeros
$variations = [
"2607:f8b0:4005:805:0:0:0:200e", // Full form
"2607:f8b0:4005:805:0000:0000:0000:200e", // Full form with leading zeros
"2607:f8b0:4005:805:0000:00:00:200e", // Full form with few leading zeros
"2607:F8B0:4005:805::200E", // Uppercase notation
"2607:f8b0:4005:0805::200e", // Leading zero in a group
"2607:f8b0:4005:805:0::200e", // Partially expanded
"2607:f8b0:4005:805:0000::200e", // Full zeros in a second group
];

foreach ($postfixes as $postfix) {
// Set cache with first postfix
$value = "value_for_$standard_ip";
$cache->set($standard_ip . $postfix, $value);
foreach ($variations as $variation_id => $ip) {
$key = $ip . $postfix;
$this->assertTrue($cache->has($key), "Cache should have variation #$variation_id with postfix: $key");
$this->assertEquals($value, $cache->get($key), "Should get correct value for key with postfix");
}
}

// Check that the base IP not cached
$this->assertFalse($cache->has($standard_ip), "Base IP should not be cached");
}
}
Loading
Loading