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.
This commit is contained in:
Dylan K. Taylor 2018-02-22 14:48:53 +00:00
parent aa11dbb928
commit 37e8c8d324
2 changed files with 66 additions and 22 deletions

View File

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

View File

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