Merge branch 'next-minor' into next-major

This commit is contained in:
Dylan K. Taylor 2023-01-06 01:59:04 +00:00
commit c2918709a3
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
31 changed files with 148 additions and 190 deletions

View File

@ -6,6 +6,12 @@ updates:
interval: daily interval: daily
time: "10:00" time: "10:00"
open-pull-requests-limit: 10 open-pull-requests-limit: 10
ignore:
#only allow patch updates for locale-data - this has to be updated manually due to codegen
- dependency-name: pocketmine/locale-data
update-types:
- "version-update:semver-major"
- "version-update:semver-minor"
- package-ecosystem: gitsubmodule - package-ecosystem: gitsubmodule
directory: "/" directory: "/"

View File

@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Setup PHP and tools - name: Setup PHP and tools
uses: shivammathur/setup-php@2.22.0 uses: shivammathur/setup-php@2.23.0
with: with:
php-version: 8.0 php-version: 8.0

View File

@ -18,7 +18,7 @@ jobs:
submodules: true submodules: true
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@2.22.0 uses: shivammathur/setup-php@2.23.0
with: with:
php-version: 8.0 php-version: 8.0

View File

@ -198,7 +198,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Setup PHP and tools - name: Setup PHP and tools
uses: shivammathur/setup-php@2.22.0 uses: shivammathur/setup-php@2.23.0
with: with:
php-version: 8.0 php-version: 8.0
tools: php-cs-fixer:3.11 tools: php-cs-fixer:3.11

View File

@ -36,8 +36,8 @@ use function ksort;
use function mb_strtoupper; use function mb_strtoupper;
use function preg_match; use function preg_match;
use function sprintf; use function sprintf;
use function str_ends_with;
use function str_replace; use function str_replace;
use function substr;
use const SORT_STRING; use const SORT_STRING;
use const STDERR; use const STDERR;
@ -121,7 +121,7 @@ require dirname(__DIR__) . '/vendor/autoload.php';
if(is_dir($argv[1])){ if(is_dir($argv[1])){
/** @var string $file */ /** @var string $file */
foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1], \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)) as $file){ foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1], \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)) as $file){
if(substr($file, -4) !== ".php"){ if(!str_ends_with($file, ".php")){
continue; continue;
} }

View File

@ -30,3 +30,23 @@ Released 15th December 2022.
## Dependencies ## Dependencies
- Updated BedrockProtocol to [17.1.0](https://github.com/pmmp/BedrockProtocol/releases/tag/17.1.0+bedrock-1.19.50). This adds some missing `LevelSoundEvent` constants and fixes the values for `ContainerUIIds`. - Updated BedrockProtocol to [17.1.0](https://github.com/pmmp/BedrockProtocol/releases/tag/17.1.0+bedrock-1.19.50). This adds some missing `LevelSoundEvent` constants and fixes the values for `ContainerUIIds`.
# 4.12.3
Released 28th December 2022.
## Fixes
- Fixed unauthenticated connections taking up player count slots, preventing players from joining.
- Fixed a possible crash in `World->tickChunk()` when plugins unload chunks during some events.
- `/gamemode` will now report a failure to change game mode if the player is already in the requested game mode.
# 4.12.4
Released 3rd January 2023.
## Fixes
- Added workarounds for an active exploit being used to deny service to servers.
# 4.12.5
Released 6th January 2023.
## Fixes
- Removed a workaround for an old client bug in custom form responses. The code contained a denial-of-service vulnerability.

View File

@ -55,7 +55,7 @@
"symfony/filesystem": "^5.4" "symfony/filesystem": "^5.4"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "1.9.4", "phpstan/phpstan": "1.9.7",
"phpstan/phpstan-phpunit": "^1.1.0", "phpstan/phpstan-phpunit": "^1.1.0",
"phpstan/phpstan-strict-rules": "^1.2.0", "phpstan/phpstan-strict-rules": "^1.2.0",
"phpunit/phpunit": "^9.2" "phpunit/phpunit": "^9.2"

14
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "d833363f328eda3b1e080c3c5a7c76a4", "content-hash": "bf576fbfb1454aecaa4313129d2deadc",
"packages": [ "packages": [
{ {
"name": "adhocore/json-comment", "name": "adhocore/json-comment",
@ -1766,16 +1766,16 @@
}, },
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "1.9.4", "version": "1.9.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "d03bccee595e2146b7c9d174486b84f4dc61b0f2" "reference": "0501435cd342eac7664bd62155b1ef907fc60b6f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/d03bccee595e2146b7c9d174486b84f4dc61b0f2", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0501435cd342eac7664bd62155b1ef907fc60b6f",
"reference": "d03bccee595e2146b7c9d174486b84f4dc61b0f2", "reference": "0501435cd342eac7664bd62155b1ef907fc60b6f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1805,7 +1805,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/phpstan/phpstan/issues", "issues": "https://github.com/phpstan/phpstan/issues",
"source": "https://github.com/phpstan/phpstan/tree/1.9.4" "source": "https://github.com/phpstan/phpstan/tree/1.9.7"
}, },
"funding": [ "funding": [
{ {
@ -1821,7 +1821,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-12-17T13:33:52+00:00" "time": "2023-01-04T21:59:57+00:00"
}, },
{ {
"name": "phpstan/phpstan-phpunit", "name": "phpstan/phpstan-phpunit",

View File

@ -31,7 +31,7 @@ use function array_slice;
use function count; use function count;
use function explode; use function explode;
use function is_int; use function is_int;
use function strpos; use function str_contains;
class SignText{ class SignText{
public const LINE_COUNT = 4; public const LINE_COUNT = 4;
@ -57,7 +57,7 @@ class SignText{
foreach($lines as $k => $line){ foreach($lines as $k => $line){
$this->checkLineIndex($k); $this->checkLineIndex($k);
Utils::checkUTF8($line); Utils::checkUTF8($line);
if(strpos($line, "\n") !== false){ if(str_contains($line, "\n")){
throw new \InvalidArgumentException("Line must not contain newlines"); throw new \InvalidArgumentException("Line must not contain newlines");
} }
//TODO: add length checks //TODO: add length checks

View File

@ -73,8 +73,8 @@ use pocketmine\utils\TextFormat;
use function array_shift; use function array_shift;
use function count; use function count;
use function implode; use function implode;
use function str_contains;
use function strcasecmp; use function strcasecmp;
use function strpos;
use function strtolower; use function strtolower;
use function trim; use function trim;
@ -242,7 +242,7 @@ class SimpleCommandMap implements CommandMap{
$values = $this->server->getCommandAliases(); $values = $this->server->getCommandAliases();
foreach($values as $alias => $commandStrings){ foreach($values as $alias => $commandStrings){
if(strpos($alias, ":") !== false){ if(str_contains($alias, ":")){
$this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_alias_illegal($alias))); $this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_alias_illegal($alias)));
continue; continue;
} }

View File

@ -35,9 +35,15 @@ use function usort;
class CraftingManager{ class CraftingManager{
use DestructorCallbackTrait; use DestructorCallbackTrait;
/** @var ShapedRecipe[][] */ /**
* @var ShapedRecipe[][]
* @phpstan-var array<string, list<ShapedRecipe>>
*/
protected array $shapedRecipes = []; protected array $shapedRecipes = [];
/** @var ShapelessRecipe[][] */ /**
* @var ShapelessRecipe[][]
* @phpstan-var array<string, list<ShapelessRecipe>>
*/
protected array $shapelessRecipes = []; protected array $shapelessRecipes = [];
/** /**
@ -139,6 +145,7 @@ class CraftingManager{
/** /**
* @return ShapelessRecipe[][] * @return ShapelessRecipe[][]
* @phpstan-return array<string, list<ShapelessRecipe>>
*/ */
public function getShapelessRecipes() : array{ public function getShapelessRecipes() : array{
return $this->shapelessRecipes; return $this->shapelessRecipes;
@ -146,6 +153,7 @@ class CraftingManager{
/** /**
* @return ShapedRecipe[][] * @return ShapedRecipe[][]
* @phpstan-return array<string, list<ShapedRecipe>>
*/ */
public function getShapedRecipes() : array{ public function getShapedRecipes() : array{
return $this->shapedRecipes; return $this->shapedRecipes;

View File

@ -28,8 +28,8 @@ use pocketmine\utils\Utils;
use function array_values; use function array_values;
use function count; use function count;
use function implode; use function implode;
use function str_contains;
use function strlen; use function strlen;
use function strpos;
class ShapedRecipe implements CraftingRecipe{ class ShapedRecipe implements CraftingRecipe{
/** @var string[] */ /** @var string[] */
@ -85,7 +85,7 @@ class ShapedRecipe implements CraftingRecipe{
$this->shape = $shape; $this->shape = $shape;
foreach($ingredients as $char => $i){ foreach($ingredients as $char => $i){
if(strpos(implode($this->shape), $char) === false){ if(!str_contains(implode($this->shape), $char)){
throw new \InvalidArgumentException("Symbol '$char' does not appear in the recipe shape"); throw new \InvalidArgumentException("Symbol '$char' does not appear in the recipe shape");
} }

View File

@ -55,6 +55,7 @@ use function phpversion;
use function preg_replace; use function preg_replace;
use function sprintf; use function sprintf;
use function str_split; use function str_split;
use function str_starts_with;
use function strpos; use function strpos;
use function substr; use function substr;
use function zend_version; use function zend_version;
@ -237,7 +238,7 @@ class CrashDump{
private function determinePluginFromFile(string $filePath, bool $crashFrame) : bool{ private function determinePluginFromFile(string $filePath, bool $crashFrame) : bool{
$frameCleanPath = Filesystem::cleanPath($filePath); $frameCleanPath = Filesystem::cleanPath($filePath);
if(strpos($frameCleanPath, Filesystem::CLEAN_PATH_SRC_PREFIX) !== 0){ if(!str_starts_with($frameCleanPath, Filesystem::CLEAN_PATH_SRC_PREFIX)){
if($crashFrame){ if($crashFrame){
$this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_DIRECT; $this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_DIRECT;
}else{ }else{
@ -250,7 +251,7 @@ class CrashDump{
$file->setAccessible(true); $file->setAccessible(true);
foreach($this->server->getPluginManager()->getPlugins() as $plugin){ foreach($this->server->getPluginManager()->getPlugins() as $plugin){
$filePath = Filesystem::cleanPath($file->getValue($plugin)); $filePath = Filesystem::cleanPath($file->getValue($plugin));
if(strpos($frameCleanPath, $filePath) === 0){ if(str_starts_with($frameCleanPath, $filePath)){
$this->data->plugin = $plugin->getName(); $this->data->plugin = $plugin->getName();
break; break;
} }

View File

@ -34,7 +34,9 @@ use function is_dir;
use function ord; use function ord;
use function parse_ini_file; use function parse_ini_file;
use function scandir; use function scandir;
use function str_ends_with;
use function str_replace; use function str_replace;
use function str_starts_with;
use function strlen; use function strlen;
use function strpos; use function strpos;
use function strtolower; use function strtolower;
@ -62,7 +64,7 @@ class Language{
if($allFiles !== false){ if($allFiles !== false){
$files = array_filter($allFiles, function(string $filename) : bool{ $files = array_filter($allFiles, function(string $filename) : bool{
return substr($filename, -4) === ".ini"; return str_ends_with($filename, ".ini");
}); });
$result = []; $result = [];
@ -141,7 +143,7 @@ class Language{
*/ */
public function translateString(string $str, array $params = [], ?string $onlyPrefix = null) : string{ public function translateString(string $str, array $params = [], ?string $onlyPrefix = null) : string{
$baseText = $this->get($str); $baseText = $this->get($str);
$baseText = $this->parseTranslation(($onlyPrefix === null || strpos($str, $onlyPrefix) === 0) ? $baseText : $str, $onlyPrefix); $baseText = $this->parseTranslation(($onlyPrefix === null || str_starts_with($str, $onlyPrefix)) ? $baseText : $str, $onlyPrefix);
foreach($params as $i => $p){ foreach($params as $i => $p){
$replacement = $p instanceof Translatable ? $this->translate($p) : (string) $p; $replacement = $p instanceof Translatable ? $this->translate($p) : (string) $p;

View File

@ -80,6 +80,10 @@ class Network{
return $this->sessionManager->getSessionCount(); return $this->sessionManager->getSessionCount();
} }
public function getValidConnectionCount() : int{
return $this->sessionManager->getValidSessionCount();
}
public function tick() : void{ public function tick() : void{
foreach($this->interfaces as $interface){ foreach($this->interfaces as $interface){
$interface->tick(); $interface->tick();

View File

@ -33,12 +33,25 @@ class NetworkSessionManager{
/** @var NetworkSession[] */ /** @var NetworkSession[] */
private array $sessions = []; private array $sessions = [];
/** @var NetworkSession[] */
private array $pendingLoginSessions = [];
/** /**
* Adds a network session to the manager. This should only be called on session creation. * Adds a network session to the manager. This should only be called on session creation.
*/ */
public function add(NetworkSession $session) : void{ public function add(NetworkSession $session) : void{
$idx = spl_object_id($session); $idx = spl_object_id($session);
$this->sessions[$idx] = $session; $this->sessions[$idx] = $session;
$this->pendingLoginSessions[$idx] = $session;
}
/**
* Marks the session as having sent a login request. After this point, they are counted towards the total player
* count.
*/
public function markLoginReceived(NetworkSession $session) : void{
$idx = spl_object_id($session);
unset($this->pendingLoginSessions[$idx]);
} }
/** /**
@ -48,15 +61,24 @@ class NetworkSessionManager{
public function remove(NetworkSession $session) : void{ public function remove(NetworkSession $session) : void{
$idx = spl_object_id($session); $idx = spl_object_id($session);
unset($this->sessions[$idx]); unset($this->sessions[$idx]);
unset($this->pendingLoginSessions[$idx]);
} }
/** /**
* Returns the number of known connected sessions. * Returns the number of known connected sessions, including sessions which have not yet sent a login request.
*/ */
public function getSessionCount() : int{ public function getSessionCount() : int{
return count($this->sessions); return count($this->sessions);
} }
/**
* Returns the number of connected sessions which have either sent a login request, or have already completed the
* login sequence.
*/
public function getValidSessionCount() : int{
return count($this->sessions) - count($this->pendingLoginSessions);
}
/** @return NetworkSession[] */ /** @return NetworkSession[] */
public function getSessions() : array{ return $this->sessions; } public function getSessions() : array{ return $this->sessions; }

View File

@ -229,6 +229,7 @@ class NetworkSession{
$this->info = $info; $this->info = $info;
$this->logger->info($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_network_session_playerName(TextFormat::AQUA . $info->getUsername() . TextFormat::RESET))); $this->logger->info($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_network_session_playerName(TextFormat::AQUA . $info->getUsername() . TextFormat::RESET)));
$this->logger->setPrefix($this->getLogPrefix()); $this->logger->setPrefix($this->getLogPrefix());
$this->manager->markLoginReceived($this);
}, },
\Closure::fromCallable([$this, "setAuthenticationStatus"]) \Closure::fromCallable([$this, "setAuthenticationStatus"])
)); ));
@ -367,7 +368,7 @@ class NetworkSession{
} }
try{ try{
foreach((new PacketBatch($decompressed))->getPackets($this->packetPool, $this->packetSerializerContext, 500) as [$packet, $buffer]){ foreach((new PacketBatch($decompressed))->getPackets($this->packetPool, $this->packetSerializerContext, 1300) as [$packet, $buffer]){
if($packet === null){ if($packet === null){
$this->logger->debug("Unknown packet: " . base64_encode($buffer)); $this->logger->debug("Unknown packet: " . base64_encode($buffer));
throw new PacketHandlingException("Unknown packet received"); throw new PacketHandlingException("Unknown packet received");

View File

@ -113,7 +113,6 @@ use function array_push;
use function base64_encode; use function base64_encode;
use function count; use function count;
use function fmod; use function fmod;
use function implode;
use function in_array; use function in_array;
use function is_bool; use function is_bool;
use function is_infinite; use function is_infinite;
@ -123,12 +122,9 @@ use function json_encode;
use function max; use function max;
use function mb_strlen; use function mb_strlen;
use function microtime; use function microtime;
use function preg_match;
use function sprintf; use function sprintf;
use function str_starts_with;
use function strlen; use function strlen;
use function strpos;
use function substr;
use function trim;
use const JSON_THROW_ON_ERROR; use const JSON_THROW_ON_ERROR;
/** /**
@ -251,6 +247,10 @@ class InGamePacketHandler extends PacketHandler{
$useItemTransaction = $packet->getItemInteractionData(); $useItemTransaction = $packet->getItemInteractionData();
if($useItemTransaction !== null){ if($useItemTransaction !== null){
if(count($useItemTransaction->getTransactionData()->getActions()) > 100){
throw new PacketHandlingException("Too many actions in item use transaction");
}
$this->inventoryManager->addPredictedSlotChanges($useItemTransaction->getTransactionData()->getActions());
if(!$this->handleUseItemTransaction($useItemTransaction->getTransactionData())){ if(!$this->handleUseItemTransaction($useItemTransaction->getTransactionData())){
$packetHandled = false; $packetHandled = false;
$this->session->getLogger()->debug("Unhandled transaction in PlayerAuthInputPacket (type " . $useItemTransaction->getTransactionData()->getActionType() . ")"); $this->session->getLogger()->debug("Unhandled transaction in PlayerAuthInputPacket (type " . $useItemTransaction->getTransactionData()->getActionType() . ")");
@ -261,6 +261,9 @@ class InGamePacketHandler extends PacketHandler{
$blockActions = $packet->getBlockActions(); $blockActions = $packet->getBlockActions();
if($blockActions !== null){ if($blockActions !== null){
if(count($blockActions) > 100){
throw new PacketHandlingException("Too many block actions in PlayerAuthInputPacket");
}
foreach($blockActions as $k => $blockAction){ foreach($blockActions as $k => $blockAction){
$actionHandled = false; $actionHandled = false;
if($blockAction instanceof PlayerBlockActionStopBreak){ if($blockAction instanceof PlayerBlockActionStopBreak){
@ -307,6 +310,10 @@ class InGamePacketHandler extends PacketHandler{
public function handleInventoryTransaction(InventoryTransactionPacket $packet) : bool{ public function handleInventoryTransaction(InventoryTransactionPacket $packet) : bool{
$result = true; $result = true;
if(count($packet->trData->getActions()) > 100){
throw new PacketHandlingException("Too many actions in inventory transaction");
}
$this->inventoryManager->addPredictedSlotChanges($packet->trData->getActions()); $this->inventoryManager->addPredictedSlotChanges($packet->trData->getActions());
if($packet->trData instanceof NormalTransactionData){ if($packet->trData instanceof NormalTransactionData){
@ -722,7 +729,7 @@ class InGamePacketHandler extends PacketHandler{
} }
public function handleCommandRequest(CommandRequestPacket $packet) : bool{ public function handleCommandRequest(CommandRequestPacket $packet) : bool{
if(strpos($packet->command, '/') === 0){ if(str_starts_with($packet->command, '/')){
$this->player->chat($packet->command); $this->player->chat($packet->command);
return true; return true;
} }
@ -861,60 +868,17 @@ class InGamePacketHandler extends PacketHandler{
//TODO: make APIs for this to allow plugins to use this information //TODO: make APIs for this to allow plugins to use this information
return $this->player->onFormSubmit($packet->formId, null); return $this->player->onFormSubmit($packet->formId, null);
}elseif($packet->formData !== null){ }elseif($packet->formData !== null){
return $this->player->onFormSubmit($packet->formId, self::stupid_json_decode($packet->formData, true)); try{
$responseData = json_decode($packet->formData, true, self::MAX_FORM_RESPONSE_DEPTH, JSON_THROW_ON_ERROR);
}catch(\JsonException $e){
throw PacketHandlingException::wrap($e, "Failed to decode form response data");
}
return $this->player->onFormSubmit($packet->formId, $responseData);
}else{ }else{
throw new PacketHandlingException("Expected either formData or cancelReason to be set in ModalFormResponsePacket"); throw new PacketHandlingException("Expected either formData or cancelReason to be set in ModalFormResponsePacket");
} }
} }
/**
* Hack to work around a stupid bug in Minecraft W10 which causes empty strings to be sent unquoted in form responses.
*
* @return mixed
* @throws PacketHandlingException
*/
private static function stupid_json_decode(string $json, bool $assoc = false){
if(preg_match('/^\[(.+)\]$/s', $json, $matches) > 0){
$raw = $matches[1];
$lastComma = -1;
$newParts = [];
$inQuotes = false;
for($i = 0, $len = strlen($raw); $i <= $len; ++$i){
if($i === $len || ($raw[$i] === "," && !$inQuotes)){
$part = substr($raw, $lastComma + 1, $i - ($lastComma + 1));
if(trim($part) === ""){ //regular parts will have quotes or something else that makes them non-empty
$part = '""';
}
$newParts[] = $part;
$lastComma = $i;
}elseif($raw[$i] === '"'){
if(!$inQuotes){
$inQuotes = true;
}else{
$backslashes = 0;
for(; $backslashes < $i && $raw[$i - $backslashes - 1] === "\\"; ++$backslashes){}
if(($backslashes % 2) === 0){ //unescaped quote
$inQuotes = false;
}
}
}
}
$fixed = "[" . implode(",", $newParts) . "]";
try{
return json_decode($fixed, $assoc, self::MAX_FORM_RESPONSE_DEPTH, JSON_THROW_ON_ERROR);
}catch(\JsonException $e){
throw PacketHandlingException::wrap($e, "Failed to fix JSON (original: $json, modified: $fixed)");
}
}
try{
return json_decode($json, $assoc, self::MAX_FORM_RESPONSE_DEPTH, JSON_THROW_ON_ERROR);
}catch(\JsonException $e){
throw PacketHandlingException::wrap($e);
}
}
public function handleServerSettingsRequest(ServerSettingsRequestPacket $packet) : bool{ public function handleServerSettingsRequest(ServerSettingsRequestPacket $packet) : bool{
return false; //TODO: GUI stuff return false; //TODO: GUI stuff
} }

View File

@ -116,7 +116,7 @@ class LoginPacketHandler extends PacketHandler{
$this->session->getPort(), $this->session->getPort(),
$this->server->requiresAuthentication() $this->server->requiresAuthentication()
); );
if($this->server->getNetwork()->getConnectionCount() > $this->server->getMaxPlayers()){ if($this->server->getNetwork()->getValidConnectionCount() > $this->server->getMaxPlayers()){
$ev->setKickReason(PlayerPreLoginEvent::KICK_REASON_SERVER_FULL, KnownTranslationFactory::disconnectionScreen_serverFull()); $ev->setKickReason(PlayerPreLoginEvent::KICK_REASON_SERVER_FULL, KnownTranslationFactory::disconnectionScreen_serverFull());
} }
if(!$this->server->isWhitelisted($playerInfo->getUsername())){ if(!$this->server->isWhitelisted($playerInfo->getUsername())){

View File

@ -146,8 +146,8 @@ use function morton2d_encode;
use function preg_match; use function preg_match;
use function spl_object_id; use function spl_object_id;
use function sqrt; use function sqrt;
use function str_starts_with;
use function strlen; use function strlen;
use function strpos;
use function strtolower; use function strtolower;
use function substr; use function substr;
use function trim; use function trim;
@ -1436,11 +1436,11 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$message = TextFormat::clean($message, false); $message = TextFormat::clean($message, false);
foreach(explode("\n", $message, $this->messageCounter + 1) as $messagePart){ foreach(explode("\n", $message, $this->messageCounter + 1) as $messagePart){
if(trim($messagePart) !== "" && strlen($messagePart) <= self::MAX_CHAT_BYTE_LENGTH && mb_strlen($messagePart, 'UTF-8') <= self::MAX_CHAT_CHAR_LENGTH && $this->messageCounter-- > 0){ if(trim($messagePart) !== "" && strlen($messagePart) <= self::MAX_CHAT_BYTE_LENGTH && mb_strlen($messagePart, 'UTF-8') <= self::MAX_CHAT_CHAR_LENGTH && $this->messageCounter-- > 0){
if(strpos($messagePart, './') === 0){ if(str_starts_with($messagePart, './')){
$messagePart = substr($messagePart, 1); $messagePart = substr($messagePart, 1);
} }
if(strpos($messagePart, "/") === 0){ if(str_starts_with($messagePart, "/")){
Timings::$playerCommand->startTiming(); Timings::$playerCommand->startTiming();
$this->server->dispatchCommand($this, substr($messagePart, 1)); $this->server->dispatchCommand($this, substr($messagePart, 1));
Timings::$playerCommand->stopTiming(); Timings::$playerCommand->stopTiming();
@ -2463,7 +2463,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->cursorInventory = new PlayerCursorInventory($this); $this->cursorInventory = new PlayerCursorInventory($this);
$this->craftingGrid = new PlayerCraftingInventory($this); $this->craftingGrid = new PlayerCraftingInventory($this);
$this->addPermanentInventories($this->inventory, $this->armorInventory, $this->cursorInventory, $this->offHandInventory); $this->addPermanentInventories($this->inventory, $this->armorInventory, $this->cursorInventory, $this->offHandInventory, $this->craftingGrid);
//TODO: more windows //TODO: more windows
} }

View File

@ -24,8 +24,7 @@ declare(strict_types=1);
namespace pocketmine\plugin; namespace pocketmine\plugin;
use function is_file; use function is_file;
use function strlen; use function str_ends_with;
use function substr;
/** /**
* Handles different types of plugins * Handles different types of plugins
@ -36,8 +35,7 @@ class PharPluginLoader implements PluginLoader{
){} ){}
public function canLoadPlugin(string $path) : bool{ public function canLoadPlugin(string $path) : bool{
$ext = ".phar"; return is_file($path) && str_ends_with($path, ".phar");
return is_file($path) && substr($path, -strlen($ext)) === $ext;
} }
/** /**

View File

@ -41,8 +41,8 @@ use function file_exists;
use function fopen; use function fopen;
use function mkdir; use function mkdir;
use function rtrim; use function rtrim;
use function str_contains;
use function stream_copy_to_stream; use function stream_copy_to_stream;
use function strpos;
use function strtolower; use function strtolower;
use function trim; use function trim;
use const DIRECTORY_SEPARATOR; use const DIRECTORY_SEPARATOR;
@ -145,7 +145,7 @@ abstract class PluginBase implements Plugin, CommandExecutor{
$pluginCmds = []; $pluginCmds = [];
foreach(Utils::stringifyKeys($this->getDescription()->getCommands()) as $key => $data){ foreach(Utils::stringifyKeys($this->getDescription()->getCommands()) as $key => $data){
if(strpos($key, ":") !== false){ if(str_contains($key, ":")){
$this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_commandError($key, $this->getDescription()->getFullName(), ":"))); $this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_commandError($key, $this->getDescription()->getFullName(), ":")));
continue; continue;
} }
@ -161,7 +161,7 @@ abstract class PluginBase implements Plugin, CommandExecutor{
$aliasList = []; $aliasList = [];
foreach($data->getAliases() as $alias){ foreach($data->getAliases() as $alias){
if(strpos($alias, ":") !== false){ if(str_contains($alias, ":")){
$this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_aliasError($alias, $this->getDescription()->getFullName(), ":"))); $this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_aliasError($alias, $this->getDescription()->getFullName(), ":")));
continue; continue;
} }

View File

@ -34,6 +34,7 @@ use function extension_loaded;
use function implode; use function implode;
use function in_array; use function in_array;
use function phpversion; use function phpversion;
use function str_starts_with;
use function stripos; use function stripos;
use function strlen; use function strlen;
use function substr; use function substr;
@ -96,7 +97,7 @@ final class PluginLoadabilityChecker{
} }
foreach(["<=", "le", "<>", "!=", "ne", "<", "lt", "==", "=", "eq", ">=", "ge", ">", "gt"] as $comparator){ foreach(["<=", "le", "<>", "!=", "ne", "<", "lt", "==", "=", "eq", ">=", "ge", ">", "gt"] as $comparator){
// warning: the > character should be quoted in YAML // warning: the > character should be quoted in YAML
if(substr($constr, 0, strlen($comparator)) === $comparator){ if(str_starts_with($constr, $comparator)){
$version = substr($constr, strlen($comparator)); $version = substr($constr, strlen($comparator));
if(!version_compare($gotVersion, $version, $comparator)){ if(!version_compare($gotVersion, $version, $comparator)){
return KnownTranslationFactory::pocketmine_plugin_incompatibleExtensionVersion(extensionName: $extensionName, extensionVersion: $gotVersion, pluginRequirement: $constr); return KnownTranslationFactory::pocketmine_plugin_incompatibleExtensionVersion(extensionName: $extensionName, extensionVersion: $gotVersion, pluginRequirement: $constr);

View File

@ -62,7 +62,7 @@ use function mkdir;
use function realpath; use function realpath;
use function shuffle; use function shuffle;
use function sprintf; use function sprintf;
use function strpos; use function str_contains;
use function strtolower; use function strtolower;
/** /**
@ -296,7 +296,7 @@ class PluginManager{
continue; continue;
} }
if(strpos($name, " ") !== false){ if(str_contains($name, " ")){
$this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_spacesDiscouraged($name))); $this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_spacesDiscouraged($name)));
} }

View File

@ -28,9 +28,8 @@ use function count;
use function file; use function file;
use function implode; use function implode;
use function is_file; use function is_file;
use function strlen; use function str_contains;
use function strpos; use function str_ends_with;
use function substr;
use const FILE_IGNORE_NEW_LINES; use const FILE_IGNORE_NEW_LINES;
use const FILE_SKIP_EMPTY_LINES; use const FILE_SKIP_EMPTY_LINES;
@ -41,8 +40,7 @@ use const FILE_SKIP_EMPTY_LINES;
class ScriptPluginLoader implements PluginLoader{ class ScriptPluginLoader implements PluginLoader{
public function canLoadPlugin(string $path) : bool{ public function canLoadPlugin(string $path) : bool{
$ext = ".php"; return is_file($path) && str_ends_with($path, ".php");
return is_file($path) && substr($path, -strlen($ext)) === $ext;
} }
/** /**
@ -66,7 +64,7 @@ class ScriptPluginLoader implements PluginLoader{
$docCommentLines = []; $docCommentLines = [];
foreach($content as $line){ foreach($content as $line){
if(!$insideHeader){ if(!$insideHeader){
if(strpos($line, "/**") !== false){ if(str_contains($line, "/**")){
$insideHeader = true; $insideHeader = true;
}else{ }else{
continue; continue;
@ -75,7 +73,7 @@ class ScriptPluginLoader implements PluginLoader{
$docCommentLines[] = $line; $docCommentLines[] = $line;
if(strpos($line, "*/") !== false){ if(str_contains($line, "*/")){
break; break;
} }
} }

View File

@ -48,9 +48,9 @@ use function rmdir;
use function rtrim; use function rtrim;
use function scandir; use function scandir;
use function str_replace; use function str_replace;
use function str_starts_with;
use function stream_get_contents; use function stream_get_contents;
use function strlen; use function strlen;
use function strpos;
use function uksort; use function uksort;
use function unlink; use function unlink;
use const DIRECTORY_SEPARATOR; use const DIRECTORY_SEPARATOR;
@ -167,7 +167,7 @@ final class Filesystem{
//this should probably never have integer keys, but it's safer than making PHPStan ignore it //this should probably never have integer keys, but it's safer than making PHPStan ignore it
foreach(Utils::stringifyKeys(self::$cleanedPaths) as $cleanPath => $replacement){ foreach(Utils::stringifyKeys(self::$cleanedPaths) as $cleanPath => $replacement){
$cleanPath = rtrim(str_replace([DIRECTORY_SEPARATOR, "phar://"], ["/", ""], $cleanPath), "/"); $cleanPath = rtrim(str_replace([DIRECTORY_SEPARATOR, "phar://"], ["/", ""], $cleanPath), "/");
if(strpos($result, $cleanPath) === 0){ if(str_starts_with($result, $cleanPath)){
$result = ltrim(str_replace($cleanPath, $replacement, $result), "/"); $result = ltrim(str_replace($cleanPath, $replacement, $result), "/");
} }
} }

View File

@ -38,8 +38,8 @@ use function posix_kill;
use function preg_match; use function preg_match;
use function proc_close; use function proc_close;
use function proc_open; use function proc_open;
use function str_starts_with;
use function stream_get_contents; use function stream_get_contents;
use function strpos;
use function trim; use function trim;
final class Process{ final class Process{
@ -99,9 +99,9 @@ final class Process{
if($mappings === false) throw new AssumptionFailedError("/proc/self/maps should always be accessible"); if($mappings === false) throw new AssumptionFailedError("/proc/self/maps should always be accessible");
foreach($mappings as $line){ foreach($mappings as $line){
if(preg_match("#([a-z0-9]+)\\-([a-z0-9]+) [rwxp\\-]{4} [a-z0-9]+ [^\\[]*\\[([a-zA-z0-9]+)\\]#", trim($line), $matches) > 0){ if(preg_match("#([a-z0-9]+)\\-([a-z0-9]+) [rwxp\\-]{4} [a-z0-9]+ [^\\[]*\\[([a-zA-z0-9]+)\\]#", trim($line), $matches) > 0){
if(strpos($matches[3], "heap") === 0){ if(str_starts_with($matches[3], "heap")){
$heap += (int) hexdec($matches[2]) - (int) hexdec($matches[1]); $heap += (int) hexdec($matches[2]) - (int) hexdec($matches[1]);
}elseif(strpos($matches[3], "stack") === 0){ }elseif(str_starts_with($matches[3], "stack")){
$stack += (int) hexdec($matches[2]) - (int) hexdec($matches[1]); $stack += (int) hexdec($matches[2]) - (int) hexdec($matches[1]);
} }
} }

View File

@ -37,8 +37,9 @@ use function json_decode;
use function parse_ini_file; use function parse_ini_file;
use function preg_match; use function preg_match;
use function readlink; use function readlink;
use function str_contains;
use function str_replace; use function str_replace;
use function strpos; use function str_starts_with;
use function substr; use function substr;
use function timezone_abbreviations_list; use function timezone_abbreviations_list;
use function timezone_name_from_abbr; use function timezone_name_from_abbr;
@ -61,7 +62,7 @@ abstract class Timezone{
* This is here so that people don't come to us complaining and fill up the issue tracker when they put * This is here so that people don't come to us complaining and fill up the issue tracker when they put
* an incorrect timezone abbreviation in php.ini apparently. * an incorrect timezone abbreviation in php.ini apparently.
*/ */
if(strpos($timezone, "/") === false){ if(!str_contains($timezone, "/")){
$default_timezone = timezone_name_from_abbr($timezone); $default_timezone = timezone_name_from_abbr($timezone);
if($default_timezone !== false){ if($default_timezone !== false){
ini_set("date.timezone", $default_timezone); ini_set("date.timezone", $default_timezone);
@ -162,7 +163,7 @@ abstract class Timezone{
return self::parseOffset($offset); return self::parseOffset($offset);
case Utils::OS_MACOS: case Utils::OS_MACOS:
$filename = @readlink('/etc/localtime'); $filename = @readlink('/etc/localtime');
if($filename !== false && strpos($filename, '/usr/share/zoneinfo/') === 0){ if($filename !== false && str_starts_with($filename, '/usr/share/zoneinfo/')){
$timezone = substr($filename, 20); $timezone = substr($filename, 20);
return trim($timezone); return trim($timezone);
} }
@ -178,11 +179,11 @@ abstract class Timezone{
*/ */
private static function parseOffset(string $offset) : string|false{ private static function parseOffset(string $offset) : string|false{
//Make signed offsets unsigned for date_parse //Make signed offsets unsigned for date_parse
if(strpos($offset, '-') !== false){ if(str_starts_with($offset, '-')){
$negative_offset = true; $negative_offset = true;
$offset = str_replace('-', '', $offset); $offset = str_replace('-', '', $offset);
}else{ }else{
if(strpos($offset, '+') !== false){ if(str_starts_with($offset, '+')){
$negative_offset = false; $negative_offset = false;
$offset = str_replace('+', '', $offset); $offset = str_replace('+', '', $offset);
}else{ }else{

View File

@ -79,11 +79,12 @@ use function preg_match_all;
use function preg_replace; use function preg_replace;
use function shell_exec; use function shell_exec;
use function spl_object_id; use function spl_object_id;
use function str_ends_with;
use function str_pad; use function str_pad;
use function str_split; use function str_split;
use function str_starts_with;
use function stripos; use function stripos;
use function strlen; use function strlen;
use function strpos;
use function substr; use function substr;
use function sys_get_temp_dir; use function sys_get_temp_dir;
use function trim; use function trim;
@ -119,7 +120,7 @@ final class Utils{
*/ */
public static function getNiceClosureName(\Closure $closure) : string{ public static function getNiceClosureName(\Closure $closure) : string{
$func = new \ReflectionFunction($closure); $func = new \ReflectionFunction($closure);
if(substr($func->getName(), -strlen('{closure}')) !== '{closure}'){ if(!str_ends_with($func->getName(), '{closure}')){
//closure wraps a named function, can be done with reflection or fromCallable() //closure wraps a named function, can be done with reflection or fromCallable()
//isClosure() is useless here because it just tells us if $func is reflecting a Closure object //isClosure() is useless here because it just tells us if $func is reflecting a Closure object
@ -273,7 +274,7 @@ final class Utils{
if(self::$os === null || $recalculate){ if(self::$os === null || $recalculate){
$uname = php_uname("s"); $uname = php_uname("s");
if(stripos($uname, "Darwin") !== false){ if(stripos($uname, "Darwin") !== false){
if(strpos(php_uname("m"), "iP") === 0){ if(str_starts_with(php_uname("m"), "iP")){
self::$os = self::OS_IOS; self::$os = self::OS_IOS;
}else{ }else{
self::$os = self::OS_MACOS; self::$os = self::OS_MACOS;

View File

@ -1386,7 +1386,9 @@ class World implements ChunkManager{
/** /**
* Notify the blocks at and around the position that the block at the position may have changed. * Notify the blocks at and around the position that the block at the position may have changed.
* This will cause onNeighbourBlockUpdate() to be called for these blocks. * This will cause onNearbyBlockChange() to be called for these blocks.
*
* @see Block::onNearbyBlockChange()
*/ */
public function notifyNeighbourBlockUpdate(Vector3 $pos) : void{ public function notifyNeighbourBlockUpdate(Vector3 $pos) : void{
$this->tryAddToNeighbourUpdateQueue($pos); $this->tryAddToNeighbourUpdateQueue($pos);
@ -1815,10 +1817,7 @@ class World implements ChunkManager{
if($update){ if($update){
$this->updateAllLight($x, $y, $z); $this->updateAllLight($x, $y, $z);
$this->tryAddToNeighbourUpdateQueue($pos); $this->notifyNeighbourBlockUpdate($pos);
foreach($pos->sides() as $side){
$this->tryAddToNeighbourUpdateQueue($side);
}
} }
$this->timings->setBlock->stopTiming(); $this->timings->setBlock->stopTiming();

View File

@ -1,68 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\handler;
use PHPUnit\Framework\TestCase;
class StupidJsonDecodeTest extends TestCase{
/**
* @var \Closure
* @phpstan-var \Closure(string $json, bool $assoc=) : mixed
*/
private $stupidJsonDecodeFunc;
public function setUp() : void{
$this->stupidJsonDecodeFunc = (new \ReflectionMethod(InGamePacketHandler::class, 'stupid_json_decode'))->getClosure();
}
/**
* @return mixed[][]
* @phpstan-return list<array{string,mixed}>
*/
public function stupidJsonDecodeProvider() : array{
return [
["[\n \"a\",\"b,c,d,e\\\" \",,0,1,2, false, 0.001]", ['a', 'b,c,d,e" ', '', 0, 1, 2, false, 0.001]],
["0", 0],
["false", false],
["null", null],
['["\",,\"word","a\",,\"word2",]', ['",,"word', 'a",,"word2', '']],
['["\",,\"word","a\",,\"word2",""]', ['",,"word', 'a",,"word2', '']],
['["Hello,, PocketMine"]', ['Hello,, PocketMine']],
['[,]', ['', '']],
['[]', []]
];
}
/**
* @dataProvider stupidJsonDecodeProvider
*
* @param mixed $expect
*
* @throws \ReflectionException
*/
public function testStupidJsonDecode(string $brokenJson, $expect) : void{
$decoded = ($this->stupidJsonDecodeFunc)($brokenJson, true);
self::assertEquals($expect, $decoded);
}
}