From 37e8c8d3244cf170c9f7b4f43582982a2f83157c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 22 Feb 2018 14:48:53 +0000 Subject: [PATCH] BanEntry: work around stupid bug in ext/date https://bugs.php.net/bug.php?id=75992 When plugins do time-limited bans and users enter stupid time values, a shitty bug in ext/date gets triggered, but only when reading the ban entries from disk. DateTime->format() is able to produce formatted strings which have more than 4 digits in the year, which are then considered invalid. This works around it by trying to parse a formatted version on the fly to ensure that it is valid. This also cleans up and improves ban list loading and handling. --- src/pocketmine/permission/BanEntry.php | 76 +++++++++++++++++++------- src/pocketmine/permission/BanList.php | 12 +++- 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/src/pocketmine/permission/BanEntry.php b/src/pocketmine/permission/BanEntry.php index 835a9def7..44d66177c 100644 --- a/src/pocketmine/permission/BanEntry.php +++ b/src/pocketmine/permission/BanEntry.php @@ -56,6 +56,7 @@ class BanEntry{ } public function setCreated(\DateTime $date){ + self::validateDate($date); $this->creationDate = $date; } @@ -78,6 +79,9 @@ class BanEntry{ * @param \DateTime|null $date */ public function setExpires(\DateTime $date = null){ + if($date !== null){ + self::validateDate($date); + } $this->expirationDate = $date; } @@ -110,37 +114,71 @@ class BanEntry{ return $str; } + /** + * Hacky function to validate \DateTime objects due to a bug in PHP. format() with "Y" can emit years with more than + * 4 digits, but createFromFormat() with "Y" doesn't accept them if they have more than 4 digits on the year. + * + * @link https://bugs.php.net/bug.php?id=75992 + * + * @param \DateTime $dateTime + * @throws \RuntimeException if the argument can't be parsed from a formatted date string + */ + private static function validateDate(\DateTime $dateTime) : void{ + self::parseDate($dateTime->format(self::$format)); + } + + /** + * @param string $date + * + * @return \DateTime|null + * @throws \RuntimeException + */ + private static function parseDate(string $date) : ?\DateTime{ + $datetime = \DateTime::createFromFormat(self::$format, $date); + if(!($datetime instanceof \DateTime)){ + throw new \RuntimeException("Error parsing date for BanEntry: " . implode(", ", \DateTime::getLastErrors()["errors"])); + } + + return $datetime; + } + /** * @param string $str * * @return BanEntry|null + * @throws \RuntimeException */ - public static function fromString(string $str){ + public static function fromString(string $str) : ?BanEntry{ if(strlen($str) < 2){ return null; }else{ $str = explode("|", trim($str)); $entry = new BanEntry(trim(array_shift($str))); - if(count($str) > 0){ - $datetime = \DateTime::createFromFormat(self::$format, array_shift($str)); - if(!($datetime instanceof \DateTime)){ - MainLogger::getLogger()->alert("Error parsing date for BanEntry for player \"" . $entry->getName() . "\", the format may be invalid!"); - return $entry; + do{ + if(empty($str)){ + break; } - $entry->setCreated($datetime); - if(count($str) > 0){ - $entry->setSource(trim(array_shift($str))); - if(count($str) > 0){ - $expire = trim(array_shift($str)); - if(strtolower($expire) !== "forever" and strlen($expire) > 0){ - $entry->setExpires(\DateTime::createFromFormat(self::$format, $expire)); - } - if(count($str) > 0){ - $entry->setReason(trim(array_shift($str))); - } - } + + $entry->setCreated(self::parseDate(array_shift($str))); + if(empty($str)){ + break; } - } + + $entry->setSource(trim(array_shift($str))); + if(empty($str)){ + break; + } + + $expire = trim(array_shift($str)); + if(strtolower($expire) !== "forever" and strlen($expire) > 0){ + $entry->setExpires(self::parseDate($expire)); + } + if(empty($str)){ + break; + } + + $entry->setReason(trim(array_shift($str))); + }while(false); return $entry; } diff --git a/src/pocketmine/permission/BanList.php b/src/pocketmine/permission/BanList.php index 0540eca1c..e13be1651 100644 --- a/src/pocketmine/permission/BanList.php +++ b/src/pocketmine/permission/BanList.php @@ -147,9 +147,15 @@ class BanList{ if(is_resource($fp)){ while(($line = fgets($fp)) !== false){ if($line{0} !== "#"){ - $entry = BanEntry::fromString($line); - if($entry instanceof BanEntry){ - $this->list[$entry->getName()] = $entry; + try{ + $entry = BanEntry::fromString($line); + if($entry instanceof BanEntry){ + $this->list[$entry->getName()] = $entry; + } + }catch(\Throwable $e){ + $logger = MainLogger::getLogger(); + $logger->critical("Failed to parse ban entry from string \"$line\": " . $e->getMessage()); + $logger->logException($e); } } }