Proper handling of invalid/unknown time zones
authorThis, that and the other <at.light@live.com.au>
Sun, 25 Dec 2016 04:55:11 +0000 (15:55 +1100)
committerTTO <at.light@live.com.au>
Thu, 5 Jan 2017 05:24:45 +0000 (05:24 +0000)
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
languages/Language.php

index cf8e7b8..38dd4bd 100644 (file)
@@ -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:
index ac8d4cb..5bce76b 100644 (file)
@@ -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 == '' ) {