From 9bf88c1f65235ea53583b617a865f1d0fb4b7125 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20H=C3=B6rist?= <philipp@hoerist.com>
Date: Fri, 16 Nov 2018 23:11:05 +0100
Subject: [PATCH] Improve Entity Time parsing

- Use parse_datetime()
- Improve tzo node validation
---
 gajim/common/modules/date_and_time.py | 27 ++++++++++++-
 gajim/common/modules/entity_time.py   | 57 +++++++++------------------
 2 files changed, 44 insertions(+), 40 deletions(-)

diff --git a/gajim/common/modules/date_and_time.py b/gajim/common/modules/date_and_time.py
index 1c680c66b0..28030ba15a 100644
--- a/gajim/common/modules/date_and_time.py
+++ b/gajim/common/modules/date_and_time.py
@@ -16,11 +16,13 @@
 
 import re
 import time
+import logging
 from datetime import datetime
 from datetime import timedelta
 from datetime import timezone
 from datetime import tzinfo
 
+log = logging.getLogger('gajim.c.m.date_and_time')
 
 PATTERN_DATETIME = re.compile(
     r'([0-9]{4}-[0-9]{2}-[0-9]{2})'
@@ -94,7 +96,30 @@ class LocalTimezone(tzinfo):
         return tt.tm_isdst > 0
 
 
-def create_tzinfo(hours=0, minutes=0):
+def create_tzinfo(hours=0, minutes=0, tz_string=None):
+    if tz_string is None:
+        return timezone(timedelta(hours=hours, minutes=minutes))
+
+    if tz_string.lower() == 'z':
+        return timezone.utc
+
+    try:
+        hours, minutes = map(int, tz_string.split(':'))
+    except Exception:
+        log.warning('Wrong tz string: %s', tz_string)
+        return
+
+    if hours not in range(-24, 24):
+        log.warning('Wrong tz string: %s', tz_string)
+        return
+
+    if minutes not in range(0, 59):
+        log.warning('Wrong tz string: %s', tz_string)
+        return
+
+    if hours in (24, -24) and minutes != 0:
+        log.warning('Wrong tz string: %s', tz_string)
+        return
     return timezone(timedelta(hours=hours, minutes=minutes))
 
 
diff --git a/gajim/common/modules/entity_time.py b/gajim/common/modules/entity_time.py
index 1088f8dc2b..6676fbaef8 100644
--- a/gajim/common/modules/entity_time.py
+++ b/gajim/common/modules/entity_time.py
@@ -15,13 +15,14 @@
 # XEP-0202: Entity Time
 
 import logging
-import datetime
 import time
 
 import nbxmpp
 
 from gajim.common import app
-from gajim.common.nec import NetworkIncomingEvent
+from gajim.common.nec import NetworkEvent
+from gajim.common.modules.date_and_time import parse_datetime
+from gajim.common.modules.date_and_time import create_tzinfo
 
 log = logging.getLogger('gajim.c.m.entity_time')
 
@@ -61,10 +62,10 @@ class EntityTime:
         log.info('Received: %s %s',
                  stanza.getFrom(), time_info)
 
-        app.nec.push_incoming_event(
-            TimeResultReceivedEvent(None, conn=self._con,
-                                    jid=stanza.getFrom(),
-                                    time_info=time_info))
+        app.nec.push_incoming_event(NetworkEvent('time-result-received',
+                                                 conn=self._con,
+                                                 jid=stanza.getFrom(),
+                                                 time_info=time_info))
 
     @staticmethod
     def _extract_info(stanza):
@@ -74,42 +75,24 @@ class EntityTime:
             return
 
         tzo = time_.getTag('tzo').getData()
-        if tzo.lower() == 'z':
-            tzo = '0:0'
-        try:
-            tzoh, tzom = tzo.split(':')
-        except Exception:
+        if not tzo:
             log.warning('Wrong tzo node: %s', stanza)
             return
-        utc_time = time_.getTag('utc').getData()
 
-        if utc_time[-1:] == 'Z':
-            # Remove the trailing 'Z'
-            utc_time = utc_time[:-1]
-        elif utc_time[-6:] == "+00:00":
-            # Remove the trailing "+00:00"
-            utc_time = utc_time[:-6]
-        else:
+        remote_tz = create_tzinfo(tz_string=tzo)
+        if remote_tz is None:
+            log.warning('Wrong tzo node: %s', stanza)
+            return
+
+        utc_time = time_.getTag('utc').getData()
+        date_time = parse_datetime(utc_time, check_utc=True)
+        if date_time is None:
             log.warning('Wrong timezone defintion: %s %s',
                         utc_time, stanza.getFrom())
             return
 
-        try:
-            dtime = datetime.datetime.strptime(utc_time, '%Y-%m-%dT%H:%M:%S')
-        except ValueError:
-            try:
-                dtime = datetime.datetime.strptime(utc_time,
-                                                   '%Y-%m-%dT%H:%M:%S.%f')
-            except ValueError as error:
-                log.warning('Wrong time format: %s %s',
-                            error, stanza.getFrom())
-                return
-
-        utc = datetime.timezone(datetime.timedelta(0))
-        dtime = dtime.replace(tzinfo=utc)
-        utc_offset = datetime.timedelta(hours=int(tzoh), minutes=int(tzom))
-        contact_tz = datetime.timezone(utc_offset, "remote timezone")
-        return dtime.astimezone(contact_tz).strftime('%c')
+        date_time = date_time.astimezone(remote_tz)
+        return date_time.strftime('%c %Z')
 
     def _answer_request(self, _con, stanza):
         log.info('%s asked for the time', stanza.getFrom())
@@ -132,9 +115,5 @@ class EntityTime:
         raise nbxmpp.NodeProcessed
 
 
-class TimeResultReceivedEvent(NetworkIncomingEvent):
-    name = 'time-result-received'
-
-
 def get_instance(*args, **kwargs):
     return EntityTime(*args, **kwargs), 'EntityTime'
-- 
GitLab