Factor out the date guessing/retrieving

savemessage was too long and complex. Factor out the date guessing part
of the function and put it into a function of its own. The logic of the
date guessing is the same, however, we do not use the
imaplib.Time2InternalDate() function as it is buggy
(http://bugs.python.org/issue11024) and returns localized patches. So we
create INTERNALDATE ourselves and pass it to append() as a string.

This commit fixes a bug that international users used to pass an invalid
date to the IMAP server, which the server will either ignore or complain
about.

Signed-off-by: Sebastian Spaeth <Sebastian@SSpaeth.de>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
This commit is contained in:
Sebastian Spaeth 2011-03-04 17:34:21 +01:00 committed by Nicolas Sebrecht
parent 419f27418e
commit d22c762385
2 changed files with 96 additions and 35 deletions

View File

@ -26,8 +26,9 @@ Bug Fixes
* Allow SSL connections to send keep-alive messages. * Allow SSL connections to send keep-alive messages.
* Fix regression (UIBase is no more). * Fix regression (UIBase is no more).
* Make profiling mode really enforce single-threading * Make profiling mode really enforce single-threading
* Do not send localized date strings to the IMAP server as it will
either ignore or refuse them.
Pending for the next major release Pending for the next major release
================================== ==================================

View File

@ -299,52 +299,112 @@ class IMAPFolder(BaseFolder):
matchinguids.sort() matchinguids.sort()
return long(matchinguids[0]) return long(matchinguids[0])
def getmessageinternaldate(self, content, rtime=None):
"""Parses mail and returns an INTERNALDATE string
It will use information in the following order, falling back as an attempt fails:
- rtime parameter
- Date header of email
We return None, if we couldn't find a valid date. In this case
the IMAP server will use the server local time when appening
(per RFC).
Note, that imaplib's Time2Internaldate is inherently broken as
it returns localized date strings which are invalid for IMAP
servers. However, that function is called for *every* append()
internally. So we need to either pass in `None` or the correct
string (in which case Time2Internaldate() will do nothing) to
append(). The output of this function is designed to work as
input to the imapobj.append() function.
TODO: We should probably be returning a bytearray rather than a
string here, because the IMAP server will expect plain
ASCII. However, imaplib.Time2INternaldate currently returns a
string so we go with the same for now.
:param rtime: epoch timestamp to be used rather than analyzing
the email.
:returns: string in the form of "DD-Mmm-YYYY HH:MM:SS +HHMM"
(including double quotes) or `None` in case of failure
(which is fine as value for append)."""
if rtime is None:
message = rfc822.Message(StringIO(content))
# parsedate returns a 9-tuple that can be passed directly to
# time.mktime(); Will be None if missing or not in a valid
# format. Note that indexes 6, 7, and 8 of the result tuple are
# not usable.
datetuple = rfc822.parsedate(message.getheader('Date'))
if datetuple is None:
#could not determine the date, use the local time.
return None
else:
#rtime is set, use that instead
datetuple = time.localtime(rtime)
try:
# Check for invalid dates
if datetuple[0] < 1981:
raise ValueError
# Check for invalid dates
datetuple_check = time.localtime(time.mktime(datetuple))
if datetuple[:2] != datetuple_check[:2]:
raise ValueError
except (ValueError, OverflowError):
# Argh, sometimes it's a valid format but year is 0102
# or something. Argh. It seems that Time2Internaldate
# will rause a ValueError if the year is 0102 but not 1902,
# but some IMAP servers nonetheless choke on 1902.
self.ui.debug("Message with invalid date %s. Server will use local time." % datetuple)
return None
#produce a string representation of datetuple that works as
#INTERNALDATE
num2mon = {1:'Jan', 2:'Feb', 3:'Mar', 4:'Apr', 5:'May', 6:'Jun',
7:'Jul', 8:'Aug', 9:'Sep', 10:'Oct', 11:'Nov', 12:'Dec'}
if datetuple.tm_isdst == '1':
zone = -time.altzone
else:
zone = -time.timezone
offset_h, offset_m = divmod(zone//60, 60)
internaldate = '"%02d-%s-%04d %02d:%02d:%02d %+03d%02d"' \
% (datetuple.tm_mday, num2mon[datetuple.tm_mon], datetuple.tm_year, \
datetuple.tm_hour, datetuple.tm_min, datetuple.tm_sec, offset_h, offset_m)
return internaldate
def savemessage(self, uid, content, flags, rtime): def savemessage(self, uid, content, flags, rtime):
"""Save the message on the Server
This backend always assigns a new uid, so the uid arg is ignored.
This function will update the self.messagelist dict to contain
the new message after sucessfully saving it.
:param rtime: A timestamp to be
:returns: the UID of the new message as assigned by the
server. If the folder is read-only it will return 0."""
imapobj = self.imapserver.acquireconnection() imapobj = self.imapserver.acquireconnection()
self.ui.debug('imap', 'savemessage: called') self.ui.debug('imap', 'savemessage: called')
try: try:
try: try:
imapobj.select(self.getfullname()) # Needed for search imapobj.select(self.getfullname()) # Needed for search
except imapobj.readonly: except imapobj.readonly:
self.ui.msgtoreadonly(self, uid, content, flags) self.ui.msgtoreadonly(self, uid, content, flags)
# Return indicating message taken, but no UID assigned. # Return indicating message taken, but no UID assigned.
# Fudge it.
return 0 return 0
# This backend always assigns a new uid, so the uid arg is ignored. # get the date of the message file, so we can pass it to the server.
# In order to get the new uid, we need to save off the message ID. date = self.getmessageinternaldate(content, rtime)
message = rfc822.Message(StringIO(content)) self.ui.debug('imap', 'savemessage: using date %s' % date)
datetuple_msg = rfc822.parsedate(message.getheader('Date'))
# Will be None if missing or not in a valid format.
# If time isn't known
if rtime == None and datetuple_msg == None:
datetuple = time.localtime()
elif rtime == None:
datetuple = datetuple_msg
else:
datetuple = time.localtime(rtime)
try:
if datetuple[0] < 1981:
raise ValueError
# Check for invalid date
datetuple_check = time.localtime(time.mktime(datetuple))
if datetuple[:2] != datetuple_check[:2]:
raise ValueError
# This could raise a value error if it's not a valid format.
date = imaplib.Time2Internaldate(datetuple)
except (ValueError, OverflowError):
# Argh, sometimes it's a valid format but year is 0102
# or something. Argh. It seems that Time2Internaldate
# will rause a ValueError if the year is 0102 but not 1902,
# but some IMAP servers nonetheless choke on 1902.
date = imaplib.Time2Internaldate(time.localtime())
self.ui.debug('imap', 'savemessage: using date ' + str(date))
content = re.sub("(?<!\r)\n", "\r\n", content) content = re.sub("(?<!\r)\n", "\r\n", content)
self.ui.debug('imap', 'savemessage: initial content is: ' + repr(content)) self.ui.debug('imap', 'savemessage: initial content is: ' + repr(content))