Search code examples
phpregexpreg-replaceipv6ipv4

Anonymize IPv4 and IPv6 addresses with PHP preg_replace?


I needed to anonymize IPv4 and IPv6 addresses so I coded this crude solution:

if (strlen($_SERVER['REMOTE_ADDR']) <= 15) {  // Sorry: 15 NOT 12
    echo htmlentities(substr_replace($_SERVER['REMOTE_ADDR'], 'XXX', -3), ENT_QUOTES);
} else {
    echo htmlentities(substr_replace($_SERVER['REMOTE_ADDR'], 'XXXX:XXXX', -9), ENT_QUOTES);
}

It works fine with full length IPv4 and IPv6 addresses like

207.142.131.005

2001:0db8:0000:08d3:0000:8a2e:0070:7344

but not with abbreviated addresses like

207.142.131.5

2001:0db8::8d3::8a2e:7:7344

I wonder if there is an elegant solution with preg_replace and some regular expression magic?


Solution

  • No conditional is necessary. You can write two patterns and two replacements for a single preg_replace() call to process.

    Target the optional numbers after the last literal dot in the string for replacement. Then target the alphanumeric colon-delimited substrings at the end of the string.

    Code: (Demo)

    $tests = [
        "207.142.131.005",
        "2001:0db8:0000:08d3:0000:8a2e:0070:7344",
        "2001:0db8:0000:08d3:0000:8a2e:0070:734a",
        "207.142.131.5",
        "2001:0db8::8d3::8a2e:7:7344",
        "::1",
        "127.0.0.1"
    ];
    
    $tests = preg_replace(
                 ['/\.\d*$/', '/[\da-f]*:[\da-f]*$/'],
                 ['.XXX', 'XXXX:XXXX'],
                 $tests
             );
    
    var_export($tests);
    

    Output:

    array (
      0 => '207.142.131.XXX',
      1 => '2001:0db8:0000:08d3:0000:8a2e:XXXX:XXXX',
      2 => '2001:0db8:0000:08d3:0000:8a2e:XXXX:XXXX',
      3 => '207.142.131.XXX',
      4 => '2001:0db8::8d3::8a2e:XXXX:XXXX',
      5 => ':XXXX:XXXX',
      6 => '127.0.0.XXX',
    )
    

    Pattern Explanations:

    IPv4:

    /         #Pattern delimiter
    \.        #Match dot literally
    \d*       #Match zero or more digits
    $         #Match the end of the string
    /         #Pattern delimiter
    

    IPv6

    /         #Pattern delimiter
    [\da-f]*  #Match zero or more (digits or a b c d e f)
    :         #Match colon
    [\da-f]*  #Match zero or more (digits or a b c d e f)
    $         #Match the end of the string
    /         #Pattern delimiter