Merge branch 'stable' into next-minor

This commit is contained in:
Dylan K. Taylor 2023-01-06 01:50:17 +00:00
commit ece49f011c
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
18 changed files with 101 additions and 140 deletions

View File

@ -6,6 +6,12 @@ updates:
interval: daily
time: "10:00"
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
directory: "/"

View File

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

View File

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

View File

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

View File

@ -30,3 +30,23 @@ Released 15th December 2022.
## 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`.
# 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

@ -54,7 +54,7 @@
"webmozart/path-util": "^2.3"
},
"require-dev": {
"phpstan/phpstan": "1.9.4",
"phpstan/phpstan": "1.9.7",
"phpstan/phpstan-phpunit": "^1.1.0",
"phpstan/phpstan-strict-rules": "^1.2.0",
"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",
"This file is @generated automatically"
],
"content-hash": "393c7921d03d080d3ef3b836f90b4415",
"content-hash": "76c6b5521d8f88d9070e8dec1c0ae144",
"packages": [
{
"name": "adhocore/json-comment",
@ -1821,16 +1821,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.9.4",
"version": "1.9.7",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "d03bccee595e2146b7c9d174486b84f4dc61b0f2"
"reference": "0501435cd342eac7664bd62155b1ef907fc60b6f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/d03bccee595e2146b7c9d174486b84f4dc61b0f2",
"reference": "d03bccee595e2146b7c9d174486b84f4dc61b0f2",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/0501435cd342eac7664bd62155b1ef907fc60b6f",
"reference": "0501435cd342eac7664bd62155b1ef907fc60b6f",
"shasum": ""
},
"require": {
@ -1860,7 +1860,7 @@
],
"support": {
"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": [
{
@ -1876,7 +1876,7 @@
"type": "tidelift"
}
],
"time": "2022-12-17T13:33:52+00:00"
"time": "2023-01-04T21:59:57+00:00"
},
{
"name": "phpstan/phpstan-phpunit",

View File

@ -31,7 +31,7 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "4.12.3";
public const BASE_VERSION = "4.12.6";
public const IS_DEVELOPMENT_BUILD = true;
public const BUILD_CHANNEL = "stable";

View File

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

View File

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

View File

@ -32,12 +32,25 @@ class NetworkSessionManager{
/** @var NetworkSession[] */
private array $sessions = [];
/** @var NetworkSession[] */
private array $pendingLoginSessions = [];
/**
* Adds a network session to the manager. This should only be called on session creation.
*/
public function add(NetworkSession $session) : void{
$idx = spl_object_id($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]);
}
/**
@ -47,15 +60,24 @@ class NetworkSessionManager{
public function remove(NetworkSession $session) : void{
$idx = spl_object_id($session);
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{
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[] */
public function getSessions() : array{ return $this->sessions; }

View File

@ -229,6 +229,7 @@ class NetworkSession{
$this->info = $info;
$this->logger->info("Player: " . TextFormat::AQUA . $info->getUsername() . TextFormat::RESET);
$this->logger->setPrefix($this->getLogPrefix());
$this->manager->markLoginReceived($this);
},
function(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPubKey) : void{
$this->setAuthenticationStatus($isAuthenticated, $authRequired, $error, $clientPubKey);
@ -365,7 +366,7 @@ class NetworkSession{
}
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){
$this->logger->debug("Unknown packet: " . base64_encode($buffer));
throw new PacketHandlingException("Unknown packet received");

View File

@ -175,6 +175,7 @@ class TypeConverter{
$nbt = new CompoundTag();
}
$nbt->setInt(self::DAMAGE_TAG, $itemStack->getDamage());
$meta = 0;
}elseif($isBlockItem && $itemStack->getMeta() !== 0){
//TODO HACK: This foul-smelling code ensures that we can correctly deserialize an item when the
//client sends it back to us, because as of 1.16.220, blockitems quietly discard their metadata
@ -183,6 +184,7 @@ class TypeConverter{
$nbt = new CompoundTag();
}
$nbt->setInt(self::PM_META_TAG, $itemStack->getMeta());
$meta = 0;
}
}

View File

@ -113,7 +113,6 @@ use function array_push;
use function base64_encode;
use function count;
use function fmod;
use function implode;
use function in_array;
use function is_bool;
use function is_infinite;
@ -123,12 +122,9 @@ use function json_encode;
use function max;
use function mb_strlen;
use function microtime;
use function preg_match;
use function sprintf;
use function str_starts_with;
use function strlen;
use function substr;
use function trim;
use const JSON_THROW_ON_ERROR;
/**
@ -255,6 +251,10 @@ class InGamePacketHandler extends PacketHandler{
$useItemTransaction = $packet->getItemInteractionData();
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())){
$packetHandled = false;
$this->session->getLogger()->debug("Unhandled transaction in PlayerAuthInputPacket (type " . $useItemTransaction->getTransactionData()->getActionType() . ")");
@ -265,6 +265,9 @@ class InGamePacketHandler extends PacketHandler{
$blockActions = $packet->getBlockActions();
if($blockActions !== null){
if(count($blockActions) > 100){
throw new PacketHandlingException("Too many block actions in PlayerAuthInputPacket");
}
foreach($blockActions as $k => $blockAction){
$actionHandled = false;
if($blockAction instanceof PlayerBlockActionStopBreak){
@ -311,6 +314,10 @@ class InGamePacketHandler extends PacketHandler{
public function handleInventoryTransaction(InventoryTransactionPacket $packet) : bool{
$result = true;
if(count($packet->trData->getActions()) > 100){
throw new PacketHandlingException("Too many actions in inventory transaction");
}
$this->inventoryManager->addPredictedSlotChanges($packet->trData->getActions());
if($packet->trData instanceof NormalTransactionData){
@ -865,60 +872,17 @@ class InGamePacketHandler extends PacketHandler{
//TODO: make APIs for this to allow plugins to use this information
return $this->player->onFormSubmit($packet->formId, 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{
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{
return false; //TODO: GUI stuff
}

View File

@ -123,7 +123,7 @@ class LoginPacketHandler extends PacketHandler{
$this->session->getPort(),
$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, KnownTranslationKeys::DISCONNECTIONSCREEN_SERVERFULL);
}
if(!$this->server->isWhitelisted($playerInfo->getUsername())){

View File

@ -2456,7 +2456,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->cursorInventory = new PlayerCursorInventory($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
}

View File

@ -1363,7 +1363,9 @@ class World implements ChunkManager{
/**
* 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{
$this->tryAddToNeighbourUpdateQueue($pos);

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);
}
}