From 2b2cda890b86dad6c78584039d65fcecf9bd69b7 Mon Sep 17 00:00:00 2001 From: "This, that and the other" Date: Sun, 25 Dec 2016 15:55:11 +1100 Subject: [PATCH] Proper handling of invalid/unknown time zones Currently, a user who has an invalid time zone stored in the database is effectively locked out of their account on HHVM sites. This patch addresses this by (1) preventing users from setting invalid time zones, and (2) not throwing an unhandled exception if a user's TZ is unknown. When the user saves their preferences, the code silently rewrites invalid time zones to UTC. I think this is OK, since to cause this to happen you have to manually muck around with the Preferences page DOM or submit the form from a script. Bug: T137182 Change-Id: I28c5e2ac9f2e681718c6080fb49b3b01e4af46dd --- includes/Preferences.php | 38 ++++++++++++++++++++++++++++++-------- languages/Language.php | 18 ++++++++---------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/includes/Preferences.php b/includes/Preferences.php index cf8e7b8088..38dd4bd5d4 100644 --- a/includes/Preferences.php +++ b/includes/Preferences.php @@ -696,19 +696,23 @@ class Preferences { $tzOptions = self::getTimezoneOptions( $context ); $tzSetting = $tzOffset; - if ( count( $tz ) > 1 && $tz[0] == 'Offset' ) { - $minDiff = $tz[1]; - $tzSetting = sprintf( '%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 ); - } elseif ( count( $tz ) > 1 && $tz[0] == 'ZoneInfo' && + if ( count( $tz ) > 1 && $tz[0] == 'ZoneInfo' && !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) ) ) { - # Timezone offset can vary with DST - $userTZ = timezone_open( $tz[2] ); - if ( $userTZ !== false ) { - $minDiff = floor( timezone_offset_get( $userTZ, date_create( 'now' ) ) / 60 ); + // Timezone offset can vary with DST + try { + $userTZ = new DateTimeZone( $tz[2] ); + $minDiff = floor( $userTZ->getOffset( new DateTime( 'now' ) ) / 60 ); $tzSetting = "ZoneInfo|$minDiff|{$tz[2]}"; + } catch ( Exception $e ) { + // User has an invalid time zone set. Fall back to just using the offset + $tz[0] = 'Offset'; } } + if ( count( $tz ) > 1 && $tz[0] == 'Offset' ) { + $minDiff = $tz[1]; + $tzSetting = sprintf( '%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 ); + } $defaultPreferences['timecorrection'] = [ 'class' => 'HTMLSelectOrOtherField', @@ -1391,6 +1395,24 @@ class Preferences { $data = explode( '|', $tz, 3 ); switch ( $data[0] ) { case 'ZoneInfo': + $valid = false; + + if ( count( $data ) === 3 ) { + // Make sure this timezone exists + try { + new DateTimeZone( $data[2] ); + // If the constructor didn't throw, we know it's valid + $valid = true; + } catch ( Exception $e ) { + // Not a valid timezone + } + } + + if ( !$valid ) { + // If the supplied timezone doesn't exist, fall back to the encoded offset + return 'Offset|' . intval( $tz[1] ); + } + return $tz; case 'System': return $tz; default: diff --git a/languages/Language.php b/languages/Language.php index ac8d4cb1d3..5bce76bfcc 100644 --- a/languages/Language.php +++ b/languages/Language.php @@ -2100,17 +2100,15 @@ class Language { $data = explode( '|', $tz, 3 ); if ( $data[0] == 'ZoneInfo' ) { - MediaWiki\suppressWarnings(); - $userTZ = timezone_open( $data[2] ); - MediaWiki\restoreWarnings(); - if ( $userTZ !== false ) { - $date = date_create( $ts, timezone_open( 'UTC' ) ); - date_timezone_set( $date, $userTZ ); - $date = date_format( $date, 'YmdHis' ); - return $date; + try { + $userTZ = new DateTimeZone( $data[2] ); + $date = new DateTime( $ts, new DateTimeZone( 'UTC' ) ); + $date->setTimezone( $userTZ ); + return $date->format( 'YmdHis' ); + } catch ( Exception $e ) { + // Unrecognized timezone, default to 'Offset' with the stored offset. + $data[0] = 'Offset'; } - # Unrecognized timezone, default to 'Offset' with the stored offset. - $data[0] = 'Offset'; } if ( $data[0] == 'System' || $tz == '' ) { -- 2.20.1