Fix IpAddress::toBulletMasked() for IP addresses with unmasked quadruplets with value...
authorTim Düsterhus <duesterhus@woltlab.com>
Fri, 23 Jul 2021 10:33:23 +0000 (12:33 +0200)
committerTim Düsterhus <duesterhus@woltlab.com>
Fri, 23 Jul 2021 10:38:14 +0000 (12:38 +0200)
Rebuild the IPv6 masking algorithm to a proper parser handling the quadruplets
one-by-one, instead of attempting to process the IP address using regular
expressions.

This patch fixes masking of IP addresses such as:

    2001:db8:1234:0:abcd::1234

Applying a /64 mask will transform the IP address into:

    2001:db8:1234::

Note how the explicit 0 block is elided as well.

Previously the bullet masking algorithm would transform this into:

    2001:db8:1234:••••:••••:••••:••••

Resulting in an IP address with just 7 quadruplets. Now it correctly returns:

    2001:db8:1234:0:••••:••••:••••:••••

wcfsetup/install/files/lib/util/IpAddress.class.php

index d96dc4c557871586ac7fdda3405c45c06a11ce4c..404ec8cf49c2f1380fa6c77dfbe3080ca4cfaab7 100644 (file)
@@ -134,18 +134,43 @@ final class IpAddress
                 (string)$masked
             );
         } else {
-            $maskedDigits = (128 - $mask6) / 4;
-
-            // Partially masked quadruplet.
-            $replacement = \str_repeat("\u{2022}", ($maskedDigits % 4));
-            // Fully masked quadruplets.
-            $replacement .= \str_repeat(":\u{2022}\u{2022}\u{2022}\u{2022}", ($maskedDigits / 4));
+            $quadruplets = [];
+            // We need to check whether we have an all-zero IP address, because $quadruplets
+            // will contain an empty entry otherwise.
+            if ((string)$masked !== '::') {
+                $quadruplets = \explode(
+                    ':',
+                    \preg_replace('/::$/', '', (string)$masked)
+                );
+            }
+            while (\count($quadruplets) < 8) {
+                $quadruplets[] = '0';
+            }
+            
+            $result = [];
+            for ($i = 0; $i < 128; $i += 16) {
+                $quadruplet = \array_shift($quadruplets);
+                if ($mask6 >= ($i + 16)) {
+                    // This quadruplet is completely unmasked. This case is special, because we don't
+                    // apply the padding for formatting reasons.
+                    $result[] = $quadruplet;
+                } else {
+                    // This quadruplet is partially or completely masked.
+                    $visibleDigits = \max(($mask6 - $i) / 4, 0);
+                    $paddedQuadruplet = \str_pad(
+                        $quadruplet,
+                        4,
+                        '0',
+                        \STR_PAD_LEFT
+                    );
+
+                    $maskedQuadruplet = \substr($paddedQuadruplet, 0, $visibleDigits);
+                    $maskedQuadruplet .= \str_repeat("\u{2022}", 4 - $visibleDigits);
+                    $result[] = $maskedQuadruplet;
+                }
+            }
 
-            return \preg_replace(
-                '/.{' . ($maskedDigits % 4) . '}::$/',
-                $replacement,
-                (string)$masked
-            );
+            return \implode(':', $result);
         }
     }