assertRequiredOptions( self::CONSTRUCTOR_OPTIONS ); $this->options = $options; $this->userIdentityLookup = $userIdentityLookup; $this->userNameUtils = $userNameUtils; $this->wikiId = $wikiId; } /** * From string specification or UserIdentity, get the block target and the * type of target. * * Note that, except for null, it is always safe to treat the target * as a string; for UserIdentityValue objects this will return * UserIdentityValue::__toString() which in turn gives * UserIdentityValue::getName(). * * If the type is not null, it will be an AbstractBlock::TYPE_ constant. * * Since 1.42, it is no longer safe to pass a value from the database field * ipb_address/bt_address to this method, since the username is normalized. * Use parseBlockTargetRow() instead. (T346683) * * @param string|UserIdentity|null $target * @return array [ UserIdentity|String|null, int|null ] */ public function parseBlockTarget( $target ): array { // We may have been through this before if ( $target instanceof UserIdentity ) { if ( IPUtils::isValid( $target->getName() ) ) { return [ $target, AbstractBlock::TYPE_IP ]; } else { return [ $target, AbstractBlock::TYPE_USER ]; } } elseif ( $target === null ) { return [ null, null ]; } $target = trim( $target ); if ( IPUtils::isValid( $target ) ) { return [ UserIdentityValue::newAnonymous( IPUtils::sanitizeIP( $target ), $this->wikiId ), AbstractBlock::TYPE_IP ]; } elseif ( IPUtils::isValidRange( $target ) ) { // Can't create a UserIdentity from an IP range return [ IPUtils::sanitizeRange( $target ), AbstractBlock::TYPE_RANGE ]; } if ( preg_match( '/^#\d+$/', $target ) ) { // Autoblock reference in the form "#12345" return [ substr( $target, 1 ), AbstractBlock::TYPE_AUTO ]; } $userFromDB = $this->userIdentityLookup->getUserIdentityByName( $target ); if ( $userFromDB instanceof UserIdentity ) { // Note that since numbers are valid usernames, a $target of "12345" will be // considered a UserIdentity. If you want to pass a block ID, prepend a hash "#12345", // since hash characters are not valid in usernames or titles generally. return [ $userFromDB, AbstractBlock::TYPE_USER ]; } // Wrap the invalid user in a UserIdentityValue. // This allows validateTarget() to return a "nosuchusershort" message, // which is needed for Special:Block. $canonicalName = $this->userNameUtils->getCanonical( $target ); if ( $canonicalName !== false ) { return [ new UserIdentityValue( 0, $canonicalName ), AbstractBlock::TYPE_USER ]; } return [ null, null ]; } /** * From a row which must contain bt_auto, bt_user, bt_address and bl_id, * and optionally bt_user_text, determine the block target and type. * * @since 1.42 * @param \stdClass $row * @return array [ UserIdentity|String|null, int|null ] */ public function parseBlockTargetRow( $row ) { if ( $row->bt_auto ) { return [ $row->bl_id, AbstractBlock::TYPE_AUTO ]; } elseif ( isset( $row->bt_user ) ) { if ( isset( $row->bt_user_text ) ) { $user = new UserIdentityValue( $row->bt_user, $row->bt_user_text, $this->wikiId ); } else { $user = $this->userIdentityLookup->getUserIdentityByUserId( $row->bt_user ); } return [ $user, AbstractBlock::TYPE_USER ]; } elseif ( $row->bt_address === null ) { return [ null, null ]; } elseif ( IPUtils::isValid( $row->bt_address ) ) { return [ UserIdentityValue::newAnonymous( IPUtils::sanitizeIP( $row->bt_address ), $this->wikiId ), AbstractBlock::TYPE_IP ]; } elseif ( IPUtils::isValidRange( $row->bt_address ) ) { // Can't create a UserIdentity from an IP range return [ IPUtils::sanitizeRange( $row->bt_address ), AbstractBlock::TYPE_RANGE ]; } else { return [ null, null ]; } } /** * Validate block target * * @param string|UserIdentity $value * * @return Status */ public function validateTarget( $value ): Status { [ $target, $type ] = $this->parseBlockTarget( $value ); $status = Status::newGood( $target ); switch ( $type ) { case AbstractBlock::TYPE_USER: if ( !$target->isRegistered() ) { $status->fatal( 'nosuchusershort', wfEscapeWikiText( $target->getName() ) ); } break; case AbstractBlock::TYPE_RANGE: [ $ip, $range ] = explode( '/', $target, 2 ); if ( IPUtils::isIPv4( $ip ) ) { $status->merge( $this->validateIPv4Range( (int)$range ) ); } elseif ( IPUtils::isIPv6( $ip ) ) { $status->merge( $this->validateIPv6Range( (int)$range ) ); } else { // Something is FUBAR $status->fatal( 'badipaddress' ); } break; case AbstractBlock::TYPE_IP: // All is well break; default: $status->fatal( 'badipaddress' ); break; } return $status; } /** * Validate an IPv4 range * * @param int $range * * @return Status */ private function validateIPv4Range( int $range ): Status { $status = Status::newGood(); $blockCIDRLimit = $this->options->get( MainConfigNames::BlockCIDRLimit ); if ( $blockCIDRLimit['IPv4'] == 32 ) { // Range block effectively disabled $status->fatal( 'range_block_disabled' ); } elseif ( $range > 32 ) { // Such a range cannot exist $status->fatal( 'ip_range_invalid' ); } elseif ( $range < $blockCIDRLimit['IPv4'] ) { $status->fatal( 'ip_range_toolarge', $blockCIDRLimit['IPv4'] ); } return $status; } /** * Validate an IPv6 range * * @param int $range * * @return Status */ private function validateIPv6Range( int $range ): Status { $status = Status::newGood(); $blockCIDRLimit = $this->options->get( MainConfigNames::BlockCIDRLimit ); if ( $blockCIDRLimit['IPv6'] == 128 ) { // Range block effectively disabled $status->fatal( 'range_block_disabled' ); } elseif ( $range > 128 ) { // Dodgy range - such a range cannot exist $status->fatal( 'ip_range_invalid' ); } elseif ( $range < $blockCIDRLimit['IPv6'] ) { $status->fatal( 'ip_range_toolarge', $blockCIDRLimit['IPv6'] ); } return $status; } }