|
25 | 25 | * @author Thomas Müller <thomas.mueller@tmit.eu> |
26 | 26 | * @author Thomas Tanghus <thomas@tanghus.net> |
27 | 27 | * @author Vincent Petry <vincent@nextcloud.com> |
| 28 | + * @author Simon Leiner <simon@leiner.me> |
28 | 29 | * |
29 | 30 | * @license AGPL-3.0 |
30 | 31 | * |
@@ -574,25 +575,87 @@ public function getId(): string { |
574 | 575 |
|
575 | 576 | /** |
576 | 577 | * Checks if given $remoteAddress matches given $trustedProxy. |
577 | | - * If $trustedProxy is an IPv4 IP range given in CIDR notation, true will be returned if |
578 | | - * $remoteAddress is an IPv4 address within that IP range. |
| 578 | + * If $trustedProxy is an IP range given in CIDR notation, true will be returned if |
| 579 | + * $remoteAddress is an IP address within that IP range. |
579 | 580 | * Otherwise $remoteAddress will be compared to $trustedProxy literally and the result |
580 | 581 | * will be returned. |
581 | 582 | * @return boolean true if $remoteAddress matches $trustedProxy, false otherwise |
582 | 583 | */ |
583 | 584 | protected function matchesTrustedProxy($trustedProxy, $remoteAddress) { |
584 | | - $cidrre = '/^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/([0-9]{1,2})$/'; |
| 585 | + [$proxyAddress, $prefixLength] = array_pad(explode('/', $trustedProxy, 2), 2, null); |
| 586 | + return $this->ipAddressInNetwork(inet_pton($remoteAddress), |
| 587 | + inet_pton($proxyAddress), |
| 588 | + intval($prefixLength)); |
| 589 | + } |
| 590 | + |
| 591 | + /** |
| 592 | + * Checks if a given $ipAddr is part of a given subnet, as defined by $netAddr and |
| 593 | + * $netPrefixLength. Both $ipAddr and $netAddr must be given as packed in_addr |
| 594 | + * representation (for more details, see the documentation of inet_pton()). |
| 595 | + * @return boolean true if $ipAddr is part of the given subnet, false otherwise |
| 596 | + */ |
| 597 | + private function ipAddressInNetwork(string $ipAddr, string $netAddr, int $netPrefixLength) : bool { |
| 598 | + $ipLengthBytes = strlen($ipAddr); |
| 599 | + $netLengthBytes = strlen($netAddr); |
| 600 | + |
| 601 | + if ($ipLengthBytes != $netLengthBytes) { |
| 602 | + // $netAddr and $ipAddr have different length. |
| 603 | + // => They cannot match. |
| 604 | + return false; |
| 605 | + } |
| 606 | + |
| 607 | + // A prefix must have positive length. |
| 608 | + // Also, it cannot be larger than the net address. |
| 609 | + $hasValidPrefix = (0 < $netPrefixLength) && ($netPrefixLength <= $ipLengthBytes * 8); |
| 610 | + |
| 611 | + if ($hasValidPrefix) { |
| 612 | + return $this->bitstringsShareCommonPrefix($ipAddr, $netAddr, $netPrefixLength); |
| 613 | + |
| 614 | + } else { |
| 615 | + // No prefix length was given. |
| 616 | + // => The net consists of only a single address. |
| 617 | + return $ipAddr === $netAddr; |
| 618 | + } |
| 619 | + } |
| 620 | + |
| 621 | + /** |
| 622 | + * Equivalent of strncmp with the length parameter referring to bits instead of bytes. |
| 623 | + * The function performs a binary-safe comparison of $str1 and $str2 for at most $numBits bits. |
| 624 | + * @return true if $str1 and $str2 are either equal or, if at least one of them is longer than |
| 625 | + * $numBits,their first $numBits are equal, false otherwise |
| 626 | + */ |
| 627 | + private function bitstringsShareCommonPrefix(string $str1, string $str2, int $numBits) : bool { |
| 628 | + $numBitsInByte = 8; |
585 | 629 |
|
586 | | - if (preg_match($cidrre, $trustedProxy, $match)) { |
587 | | - $net = $match[1]; |
588 | | - $shiftbits = min(32, max(0, 32 - intval($match[2]))); |
589 | | - $netnum = ip2long($net) >> $shiftbits; |
590 | | - $ipnum = ip2long($remoteAddress) >> $shiftbits; |
| 630 | + // $numBits may not be a multiple of 8. Thus, the prefix may span a |
| 631 | + // number of "whole" bytes ($cmpBytes) and possibly one "fractional" byte at |
| 632 | + // the end where only $cmpBits are relevant to the comparison. |
| 633 | + // This implementation first checks the whole bytes, and the fractional one |
| 634 | + // afterwards (if there is one and the whole bytes were all equal). |
| 635 | + $cmpBytes = intdiv($numBits, $numBitsInByte); |
591 | 636 |
|
592 | | - return $ipnum === $netnum; |
| 637 | + // "whole" byte comparison |
| 638 | + if (strncmp($str1, $str2, $cmpBytes) != 0) { |
| 639 | + return false; |
593 | 640 | } |
594 | 641 |
|
595 | | - return $trustedProxy === $remoteAddress; |
| 642 | + // "fractional" byte comparison |
| 643 | + $maxBytes = min(strlen($str1), strlen($str2)); |
| 644 | + if ($cmpBytes >= $maxBytes) { |
| 645 | + // The whole string was already compared. |
| 646 | + // => No need for further comparison. |
| 647 | + return true; |
| 648 | + } |
| 649 | + |
| 650 | + // Because we are only interested in the leftmost $cmpBits bits, we can |
| 651 | + // discard the bits on the right of that. |
| 652 | + $cmpBits = $numBits % $numBitsInByte; |
| 653 | + $irrelevantBits = $numBitsInByte - $cmpBits; |
| 654 | + |
| 655 | + $relevant1 = ord($str1[$cmpBytes]) >> $irrelevantBits; |
| 656 | + $relevant2 = ord($str2[$cmpBytes]) >> $irrelevantBits; |
| 657 | + |
| 658 | + return $relevant1 == $relevant2; |
596 | 659 | } |
597 | 660 |
|
598 | 661 | /** |
|
0 commit comments