def savemessageflags(self, uid, flags): oldfilename = self.messagelist[uid]['filename'] dir_prefix, newname = os.path.split(oldfilename) tmpdir = os.path.join(self.getfullname(), 'tmp') if 'S' in flags: # If a message has been seen, it goes into the cur # directory. CR debian#152482 dir_prefix = 'cur' else: dir_prefix = 'new' infostr = self.infosep infomatch = re.search('(' + self.infosep + '.*)$', newname) if infomatch: # If the info string is present.. infostr = infomatch.group(1) newname = newname.split( self.infosep)[0] # Strip off the info string. infostr = re.sub('2,[A-Z]*', '', infostr) infostr += '2,' + ''.join(sorted(flags)) newname += infostr newfilename = os.path.join(dir_prefix, newname) if (newfilename != oldfilename): try: os.rename(os.path.join(self.getfullname(), oldfilename), os.path.join(self.getfullname(), newfilename)) except OSError, e: raise OfflineImapError( "Can't rename file '%s' to '%s': %s" % (oldfilename, newfilename, e[1]), OfflineImapError.ERROR.FOLDER) self.messagelist[uid]['flags'] = flags self.messagelist[uid]['filename'] = newfilename
def change_message_uid(self, uid, new_uid): """Change the message from existing uid to new_uid This will not update the statusfolder UID, you need to do that yourself. :param new_uid: (optional) If given, the old UID will be changed to a new UID. The Maildir backend can implement this as an efficient rename. """ if not uid in self.messagelist: raise OfflineImapError( "Cannot change unknown Maildir UID %s" % uid, OfflineImapError.ERROR.MESSAGE) if uid == new_uid: return oldfilename = self.messagelist[uid]['filename'] dir_prefix, filename = os.path.split(oldfilename) flags = self.getmessageflags(uid) # TODO: we aren't keeping the prefix timestamp so we don't honor the # filename_use_mail_timestamp configuration option. newfilename = os.path.join(dir_prefix, self.new_message_filename(new_uid, flags)) os.rename(os.path.join(self.getfullname(), oldfilename), os.path.join(self.getfullname(), newfilename)) self.messagelist[new_uid] = self.messagelist[uid] self.messagelist[new_uid]['filename'] = newfilename del self.messagelist[uid]
def makefolder(self, foldername): """Create a folder on the IMAP server This will not update the list cached in :meth:`getfolders`. You will need to invoke :meth:`forgetfolders` to force new caching when you are done creating folders yourself. :param foldername: Full path of the folder to be created.""" if foldername is '': return if self.getreference(): foldername = self.getreference() + self.getsep() + foldername if not foldername: # Create top level folder as folder separator. foldername = self.getsep() self.ui.makefolder(self, foldername) if self.account.dryrun: return imapobj = self.imapserver.acquireconnection() try: result = imapobj.create(foldername) if result[0] != 'OK': raise OfflineImapError( "Folder '%s'[%s] could not be created. " "Server responded: %s" % (foldername, self, str(result)), OfflineImapError.ERROR.FOLDER) finally: self.imapserver.releaseconnection(imapobj)
def get_auth_mechanisms(self): """ Get the AUTH mechanisms. We have (ranged from the strongest to weakest) these methods: "GSSAPI", "XOAUTH2", "CRAM-MD5", "PLAIN", "LOGIN" Returns: The supported AUTH Methods """ supported = ["GSSAPI", "XOAUTH2", "CRAM-MD5", "PLAIN", "LOGIN"] # Mechanisms are ranged from the strongest to the # weakest ones. # TODO: we need DIGEST-MD5, it must come before CRAM-MD5 # due to the chosen-plaintext resistance. default = ["GSSAPI", "XOAUTH2", "CRAM-MD5", "PLAIN", "LOGIN"] mechs = self.getconflist('auth_mechanisms', r',\s*', default) for mech in mechs: if mech not in supported: raise OfflineImapError( "Repository %s: " % self + "unknown authentication mechanism '%s'" % mech, OfflineImapError.ERROR.REPO) self.ui.debug('imap', "Using authentication mechanisms %s" % mechs) return mechs
def makefolder_single(self, foldername): """ Create a IMAP folder. Args: foldername: Folder's name to create Returns: None """ self.ui.makefolder(self, foldername) if self.account.dryrun: return imapobj = self.imapserver.acquireconnection() try: if self.account.utf_8_support: foldername = imaputil.utf8_IMAP(foldername) result = imapobj.create(foldername) if result[0] != 'OK': msg = "Folder '%s'[%s] could not be created. "\ "Server responded: %s" % (foldername, self, str(result)) raise OfflineImapError(msg, OfflineImapError.ERROR.FOLDER) finally: self.imapserver.releaseconnection(imapobj)
def _fetch_from_imap(self, imapobj, uids, query, retry_num=1): fails_left = retry_num # retry on dropped connection while fails_left: try: imapobj.select(self.getfullname(), readonly = True) res_type, data = imapobj.uid('fetch', uids, query) fails_left = 0 except imapobj.abort as e: # Release dropped connection, and get a new one self.imapserver.releaseconnection(imapobj, True) imapobj = self.imapserver.acquireconnection() self.ui.error(e, exc_info()[2]) fails_left -= 1 if not fails_left: raise e if data == [None] or res_type != 'OK': #IMAP server says bad request or UID does not exist severity = OfflineImapError.ERROR.MESSAGE reason = "IMAP server '%s' failed to fetch messages UID '%s'."\ "Server responded: %s %s" % (self.getrepository(), uids, res_type, data) if data == [None]: #IMAP server did not find a message with this UID reason = "IMAP server '%s' does not have a message "\ "with UID '%s'" % (self.getrepository(), uids) raise OfflineImapError(reason, severity) return data
def change_message_uid(self, uid, new_uid): """Change the message from existing uid to new_uid This will not update the statusfolder UID, you need to do that yourself. :param new_uid: (optional) If given, the old UID will be changed to a new UID. The Maildir backend can implement this as an efficient rename. """ if not uid in self.messagelist: raise OfflineImapError("Cannot change unknown Maildir UID %s" % uid) if uid == new_uid: return oldfilename = self.messagelist[uid]['filename'] dir_prefix, filename = os.path.split(oldfilename) flags = self.getmessageflags(uid) content = self.getmessage(uid) rtime = emailutil.get_message_date(content) newfilename = os.path.join( dir_prefix, self.new_message_filename(new_uid, flags, rtime=rtime)) os.rename(os.path.join(self.getfullname(), oldfilename), os.path.join(self.getfullname(), newfilename)) self.messagelist[new_uid] = self.messagelist[uid] self.messagelist[new_uid]['filename'] = newfilename del self.messagelist[uid]
def makefolder(self, foldername): """Create a folder on the IMAP server This will not update the list cached in :meth:`getfolders`. You will need to invoke :meth:`forgetfolders` to force new caching when you are done creating folders yourself. :param foldername: Full path of the folder to be created.""" #TODO: IMHO this existing commented out code is correct and #should be enabled, but this would change the behavior for #existing configurations who have a 'reference' set on a Mapped #IMAP server....: #if self.getreference() != '""': # newname = self.getreference() + self.getsep() + foldername #else: # newname = foldername imapobj = self.imapserver.acquireconnection() try: self.ui._msg("Creating new IMAP folder '%s' on server %s" %\ (foldername, self)) result = imapobj.create(foldername) if result[0] != 'OK': raise OfflineImapError("Folder '%s'[%s] could not be created. " "Server responded: %s" % \ (foldername, self, str(result)), OfflineImapError.ERROR.FOLDER) finally: self.imapserver.releaseconnection(imapobj)
def change_message_uid(self, ruid, new_ruid): """Change the message from existing ruid to new_ruid :param new_uid: The old remote UID will be changed to a new UID. The UIDMaps case handles this efficiently by simply changing the mappings file.""" if ruid not in self.r2l: raise OfflineImapError("Cannot change unknown Maildir UID %s" % ruid, OfflineImapError.ERROR.MESSAGE) if ruid == new_ruid: return # sanity check shortcut with self.maplock: luid = self.r2l[ruid] self.l2r[luid] = new_ruid del self.r2l[ruid] self.r2l[new_ruid] = luid # TODO: diskl2r|r2l are a pain to sync and should be done away with # diskl2r only contains positive UIDs, so wrap in ifs. if luid > 0: self.diskl2r[luid] = new_ruid if ruid > 0: del self.diskr2l[ruid] if new_ruid > 0: self.diskr2l[new_ruid] = luid self._savemaps()
def migratefmd5(self, dryrun=False): """Migrate FMD5 hashes from versions prior to 6.3.5 :param dryrun: Run in dry run mode :type fix: Boolean :return: None """ oldfmd5 = md5(self.name).hexdigest() msglist = self._scanfolder() for mkey, mvalue in msglist.iteritems(): filename = os.path.join(self.getfullname(), mvalue['filename']) match = re.search("FMD5=([a-fA-F0-9]+)", filename) if match is None: self.ui.debug( "maildir", "File `%s' doesn't have an FMD5 assigned" % filename) elif match.group(1) == oldfmd5: self.ui.info("Migrating file `%s' to FMD5 `%s'" % (filename, self._foldermd5)) if not dryrun: newfilename = filename.replace("FMD5=" + match.group(1), "FMD5=" + self._foldermd5) try: os.rename(filename, newfilename) except OSError as e: raise OfflineImapError( "Can't rename file '%s' to '%s': %s" % (filename, newfilename, e[1]), OfflineImapError.ERROR.FOLDER), None, exc_info()[2] elif match.group(1) != self._foldermd5: self.ui.warn(("Inconsistent FMD5 for file `%s':" " Neither `%s' nor `%s' found") % (filename, oldfmd5, self._foldermd5))
def savemessagelabels(self, uid, labels, ignorelabels=set()): """Change a message's labels to `labels`. Note that this function does not check against dryrun settings, so you need to ensure that it is never called in a dryrun mode.""" filename = self.messagelist[uid]['filename'] filepath = os.path.join(self.getfullname(), filename) file = open(filepath, 'rt') content = file.read() file.close() oldlabels = set() for hstr in self.getmessageheaderlist(content, self.labelsheader): oldlabels.update( imaputil.labels_from_header(self.labelsheader, hstr)) labels = labels - ignorelabels ignoredlabels = oldlabels & ignorelabels oldlabels = oldlabels - ignorelabels # Nothing to change. if labels == oldlabels: return # Change labels into content. labels_str = imaputil.format_labels_string( self.labelsheader, sorted(labels | ignoredlabels)) # First remove old labels header, and then add the new one. content = self.deletemessageheaders(content, self.labelsheader) content = self.addmessageheader(content, '\n', self.labelsheader, labels_str) mtime = int(os.stat(filepath).st_mtime) # Write file with new labels to a unique file in tmp. messagename = self.new_message_filename(uid, set()) tmpname = self.save_to_tmp_file(messagename, content) tmppath = os.path.join(self.getfullname(), tmpname) # Move to actual location. try: os.rename(tmppath, filepath) except OSError as e: six.reraise( OfflineImapError, OfflineImapError( "Can't rename file '%s' to '%s': %s" % (tmppath, filepath, e[1]), OfflineImapError.ERROR.FOLDER), exc_info()[2]) # If utime_from_header=true, we don't want to change the mtime. if self._utime_from_header and mtime: os.utime(filepath, (mtime, mtime)) # save the new mtime and labels self.messagelist[uid]['mtime'] = int(os.stat(filepath).st_mtime) self.messagelist[uid]['labels'] = labels
def open(self, host=None, port=None): if not self.ca_certs and not self._fingerprint: raise OfflineImapError("No CA certificates " + \ "and no server fingerprints configured. " + \ "You must configure at least something, otherwise " + \ "having SSL helps nothing.", OfflineImapError.ERROR.REPO) super(WrappedIMAP4_SSL, self).open(host, port) if self._fingerprint: # compare fingerprints fingerprint = sha1(self.sock.getpeercert(True)).hexdigest() if fingerprint not in self._fingerprint: raise OfflineImapError("Server SSL fingerprint '%s' " % fingerprint + \ "for hostname '%s' " % host + \ "does not match configured fingerprint(s) %s. " % self._fingerprint + \ "Please verify and set 'cert_fingerprint' accordingly " + \ "if not set yet.", OfflineImapError.ERROR.REPO)
class UsefulIMAPMixIn(object): def getselectedfolder(self): if self.state == 'SELECTED': return self.mailbox return None def select(self, mailbox='INBOX', readonly=False, force = 0): """Selects a mailbox on the IMAP server :returns: 'OK' on success, nothing if the folder was already selected or raises an :exc:`OfflineImapError`""" if self.getselectedfolder() == mailbox and self.is_readonly == readonly \ and not force: # No change; return. return # Wipe out all old responses, to maintain semantics with old imaplib2 del self.untagged_responses[:] try: result = super(UsefulIMAPMixIn, self).select(mailbox, readonly) except self.abort, e: # self.abort is raised when we are supposed to retry errstr = "Server '%s' closed connection, error on SELECT '%s'. Ser"\ "ver said: %s" % (self.host, mailbox, e.args[0]) severity = OfflineImapError.ERROR.FOLDER_RETRY raise OfflineImapError(errstr, severity) if result[0] != 'OK': #in case of error, bail out with OfflineImapError errstr = "Error SELECTing mailbox '%s', server reply:\n%s" %\ (mailbox, result) severity = OfflineImapError.ERROR.FOLDER raise OfflineImapError(errstr, severity) return result
def change_message_uid(self, uid, new_uid): """Change the message from existing uid to new_uid If the backend supports it. IMAP does not and will throw errors.""" raise OfflineImapError( 'IMAP backend cannot change a messages UID from ' '%d to %d' % (uid, new_uid), OfflineImapError.ERROR.MESSAGE)
def __savemessage_searchforheader(self, imapobj, headername, headervalue): self.ui.debug('imap', '__savemessage_searchforheader called for %s: %s'% (headername, headervalue)) # Now find the UID it got. headervalue = imapobj._quote(headervalue) try: matchinguids = imapobj.uid('search', 'HEADER', headername, headervalue)[1][0] except imapobj.error as err: # IMAP server doesn't implement search or had a problem. self.ui.debug('imap', "__savemessage_searchforheader: got IMAP " "error '%s' while attempting to UID SEARCH for message with " "header %s"% (err, headername)) return 0 self.ui.debug('imap', "__savemessage_searchforheader got initial " "matchinguids: " + repr(matchinguids)) if matchinguids == '': self.ui.debug('imap', "__savemessage_searchforheader: UID SEARCH " "for message with header %s yielded no results"% headername) return 0 matchinguids = matchinguids.split(' ') self.ui.debug('imap', '__savemessage_searchforheader: matchinguids now ' + repr(matchinguids)) if len(matchinguids) != 1 or matchinguids[0] is None: raise OfflineImapError( "While attempting to find UID for message with " "header %s, got wrong-sized matchinguids of %s"% (headername, str(matchinguids)), OfflineImapError.ERROR.MESSAGE ) return int(matchinguids[0])
def cachemessagelist(self): if not self.synclabels: return super(GmailFolder, self).cachemessagelist() self.messagelist = {} self.ui.collectingdata(None, self) imapobj = self.imapserver.acquireconnection() try: msgsToFetch = self._msgs_to_fetch(imapobj) if not msgsToFetch: return # No messages to sync # Get the flags and UIDs for these. single-quotes prevent # imaplib2 from quoting the sequence. # # NB: msgsToFetch are sequential numbers, not UID's res_type, response = imapobj.fetch("'%s'" % msgsToFetch, '(FLAGS X-GM-LABELS UID)') if res_type != 'OK': raise OfflineImapError("FETCHING UIDs in folder [%s]%s failed. " % \ (self.getrepository(), self) + \ "Server responded '[%s] %s'" % \ (res_type, response), OfflineImapError.ERROR.FOLDER), \ None, exc_info()[2] finally: self.imapserver.releaseconnection(imapobj) for messagestr in response: # looks like: '1 (FLAGS (\\Seen Old) X-GM-LABELS (\\Inbox \\Favorites) UID 4807)' or None if no msg # Discard initial message number. if messagestr == None: continue messagestr = messagestr.split(' ', 1)[1] options = imaputil.flags2hash(messagestr) if not 'UID' in options: self.ui.warn('No UID in message with options %s' %\ str(options), minor = 1) else: uid = long(options['UID']) self.messagelist[uid] = self.msglist_item_initializer(uid) flags = imaputil.flagsimap2maildir(options['FLAGS']) m = re.search('\(([^\)]*)\)', options['X-GM-LABELS']) if m: labels = set([ imaputil.dequote(lb) for lb in imaputil.imapsplit(m.group(1)) ]) else: labels = set() labels = labels - self.ignorelabels rtime = imaplibutil.Internaldate2epoch(messagestr) self.messagelist[uid] = { 'uid': uid, 'flags': flags, 'labels': labels, 'time': rtime }
def getmaxage(self): if self.config.getdefault("Account %s" % self.accountname, "maxage", None): six.reraise( OfflineImapError, OfflineImapError("maxage is not supported on IMAP-IMAP sync", OfflineImapError.ERROR.REPO), exc_info()[2])
def _uidlist(self, mapping, items): try: return [mapping[x] for x in items] except KeyError as e: raise OfflineImapError("Could not find UID for msg '{0}' (f:'{1}'." " This is usually a bad thing and should be reported on the ma" "iling list.".format(e.args[0], self), OfflineImapError.ERROR.MESSAGE), None, exc_info()[2]
def __xoauth2handler(self, response): if self.oauth2_access_token is None: if self.oauth2_request_url is None: raise OfflineImapError("No remote oauth2_request_url for " "repository '%s' specified."% self, OfflineImapError.ERROR.REPO) # Generate new access token. params = {} params['client_id'] = self.oauth2_client_id params['client_secret'] = self.oauth2_client_secret params['refresh_token'] = self.oauth2_refresh_token params['grant_type'] = 'refresh_token' self.ui.debug('imap', 'xoauth2handler: url "%s"'% self.oauth2_request_url) self.ui.debug('imap', 'xoauth2handler: params "%s"'% params) original_socket = socket.socket socket.socket = self.authproxied_socket try: response = urllib.urlopen( self.oauth2_request_url, urllib.urlencode(params)).read() except Exception as e: try: msg = "%s (configuration is: %s)"% (e, str(params)) except Exception as eparams: msg = "%s [cannot display configuration: %s]"% (e, eparams) six.reraise(type(e), type(e)(msg), exc_info()[2]) finally: socket.socket = original_socket resp = json.loads(response) self.ui.debug('imap', 'xoauth2handler: response "%s"'% resp) if u'error' in resp: raise OfflineImapError("xoauth2handler got: %s"% resp, OfflineImapError.ERROR.REPO) self.oauth2_access_token = resp['access_token'] self.ui.debug('imap', 'xoauth2handler: access_token "%s"'% self.oauth2_access_token) auth_string = 'user=%s\1auth=Bearer %s\1\1'% ( self.username, self.oauth2_access_token) #auth_string = base64.b64encode(auth_string) self.ui.debug('imap', 'xoauth2handler: returning "%s"'% auth_string) return auth_string
def getsslcacertfile(self): """Determines CA bundle. Returns path to the CA bundle. It is explicitely specified or requested via "OS-DEFAULT" value (and we will search known locations for the current OS and distribution). If it is not specified, we will search it in the known locations. If search route, via "OS-DEFAULT" or because is not specified, yields nothing, we will throw an exception to make our callers distinguish between not specified value and non-existent default CA bundle. It is also an error to specify non-existent file via configuration: it will error out later, but, perhaps, with less verbose explanation, so we will also throw an exception. It is consistent with the above behaviour, so any explicitely-requested configuration that doesn't result in an existing file will give an exception. """ xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath] cacertfile = self.getconf_xform('sslcacertfile', xforms, None) # Can't use above cacertfile because of abspath. conf_sslacertfile = self.getconf('sslcacertfile', None) if conf_sslacertfile == "OS-DEFAULT" or \ conf_sslacertfile is None or \ conf_sslacertfile == '': cacertfile = get_os_sslcertfile() if cacertfile is None: searchpath = get_os_sslcertfile_searchpath() if searchpath: reason = "Default CA bundle was requested, " \ "but no existing locations available. " \ "Tried %s." % (", ".join(searchpath)) else: reason = "Default CA bundle was requested, " \ "but OfflineIMAP doesn't know any for your " \ "current operating system." raise OfflineImapError(reason, OfflineImapError.ERROR.REPO) if cacertfile is None: return None if not os.path.isfile(cacertfile): reason = "CA certfile for repository '%s' couldn't be found. " \ "No such file: '%s'" % (self.name, cacertfile) raise OfflineImapError(reason, OfflineImapError.ERROR.REPO) return cacertfile
def __start_tls(self, imapobj): if 'STARTTLS' in imapobj.capabilities and not self.usessl: self.ui.debug('imap', 'Using STARTTLS connection') try: imapobj.starttls() except imapobj.error as e: raise OfflineImapError("Failed to start " "TLS connection: %s"% str(e), OfflineImapError.ERROR.REPO, None, exc_info()[2])
def search(search_conditions): """Actually request the server with the specified conditions. Returns: range(s) for messages or None if no messages are to be fetched.""" try: res_type, res_data = imapobj.search(None, search_conditions) if res_type != 'OK': msg = "SEARCH in folder [%s]%s failed. " \ "Search string was '%s'. " \ "Server responded '[%s] %s'" % \ (self.getrepository(), self, search_cond, res_type, res_data) raise OfflineImapError(msg, OfflineImapError.ERROR.FOLDER) except Exception as e: msg = "SEARCH in folder [%s]%s failed. "\ "Search string was '%s'. Error: %s" % \ (self.getrepository(), self, search_cond, str(e)) raise OfflineImapError(msg, OfflineImapError.ERROR.FOLDER) """ In Py2, with IMAP, imaplib2 returned a list of one element string. ['1, 2, 3, ...'] -> in Py3 is [b'1 2 3,...'] In Py2, with Davmail, imaplib2 returned a list of strings. ['1', '2', '3', ...] -> in Py3 should be [b'1', b'2', b'3',...] In my tests with Py3, I get a list with one element: [b'1 2 3 ...'] Then I convert the values to string and I get ['1 2 3 ...'] With Davmail, it should be [b'1', b'2', b'3',...] When I convert the values to string, I get ['1', '2', '3',...] """ res_data = [x.decode('utf-8') for x in res_data] # Then, I can do the check in the same way than Python 2 # with string comparison: if len(res_data) > 0 and (' ' in res_data[0] or res_data[0] == ''): res_data = res_data[0].split() # Some servers are broken. if 0 in res_data: self.ui.warn("server returned UID with 0; ignoring.") res_data.remove(0) return res_data
def __authn_login(self, imapobj): # Use LOGIN command, unless LOGINDISABLED is advertized # (per RFC 2595) if 'LOGINDISABLED' in imapobj.capabilities: raise OfflineImapError("IMAP LOGIN is " "disabled by server. Need to use SSL?", OfflineImapError.ERROR.REPO) else: self.__loginauth(imapobj) return True
def getmessage(self, uid): """Returns an email message object.""" filename = self.messagelist[uid]['filename'] filepath = os.path.join(self.getfullname(), filename) fd = open(filepath, 'rb') _fd_bytes = fd.read() fd.close() try: retval = self.parser['8bit'].parsebytes(_fd_bytes) except: err = exc_info() msg_id = self._extract_message_id(_fd_bytes)[0].decode( 'ascii', errors='surrogateescape') raise OfflineImapError( "Exception parsing message with ID ({}) from file ({}).\n {}: {}" .format(msg_id, filename, err[0].__name__, err[1]), OfflineImapError.ERROR.MESSAGE) if len(retval.defects) > 0: # We don't automatically apply fixes as to attempt to preserve the original message self.ui.warn("UID {} has defects: {}".format(uid, retval.defects)) if any( isinstance(defect, NoBoundaryInMultipartDefect) for defect in retval.defects): # (Hopefully) Rare defect from a broken client where multipart boundary is # not properly quoted. Attempt to solve by fixing the boundary and parsing self.ui.warn(" ... applying multipart boundary fix.") retval = self.parser['8bit'].parsebytes( self._quote_boundary_fix(_fd_bytes)) try: # See if the defects after fixes are preventing us from obtaining bytes _ = retval.as_bytes(policy=self.policy['8bit']) except UnicodeEncodeError as err: # Unknown issue which is causing failure of as_bytes() msg_id = self.getmessageheader(retval, "message-id") if msg_id is None: msg_id = '<unknown-message-id>' raise OfflineImapError( "UID {} ({}) has defects preventing it from being processed!\n {}: {}" .format(uid, msg_id, type(err).__name__, err), OfflineImapError.ERROR.MESSAGE) return retval
def _store_to_imap(self, imapobj, uid, field, data): imapobj.select(self.getfullname()) res_type, retdata = imapobj.uid('store', uid, field, data) if res_type != 'OK': severity = OfflineImapError.ERROR.MESSAGE reason = "IMAP server '%s' failed to store %s for message UID '%d'."\ "Server responded: %s %s" % (self.getrepository(), field, uid, res_type, retdata) raise OfflineImapError(reason, severity) return retdata[0]
def cachemessagelist(self, min_date=None, min_uid=None): self.ui.loadmessagelist(self.repository, self) self.dropmessagelistcache() imapobj = self.imapserver.acquireconnection() try: msgsToFetch = self._msgs_to_fetch(imapobj, min_date=min_date, min_uid=min_uid) if not msgsToFetch: return # No messages to sync. # Get the flags and UIDs for these. single-quotes prevent # imaplib2 from quoting the sequence. fetch_msg = "%s" % msgsToFetch self.ui.debug( 'imap', "calling imaplib2 fetch command: %s %s" % (fetch_msg, '(FLAGS UID INTERNALDATE)')) res_type, response = imapobj.fetch(fetch_msg, '(FLAGS UID INTERNALDATE)') if res_type != 'OK': raise OfflineImapError( "FETCHING UIDs in folder [%s]%s failed. " "Server responded '[%s] %s'" % (self.getrepository(), self, res_type, response), OfflineImapError.ERROR.FOLDER) finally: self.imapserver.releaseconnection(imapobj) for messagestr in response: # Looks like: '1 (FLAGS (\\Seen Old) UID 4807)' or None if no msg. # Discard initial message number. if messagestr is None: continue messagestr = messagestr.decode('utf-8').split(' ', 1)[1] options = imaputil.flags2hash(messagestr) if 'UID' not in options: self.ui.warn('No UID in message with options %s' % str(options), minor=1) else: uid = int(options['UID']) self.messagelist[uid] = self.msglist_item_initializer(uid) flags = imaputil.flagsimap2maildir(options['FLAGS']) keywords = imaputil.flagsimap2keywords(options['FLAGS']) rtime = imaplibutil.Internaldate2epoch( messagestr.encode('utf-8')) self.messagelist[uid] = { 'uid': uid, 'flags': flags, 'time': rtime, 'keywords': keywords } self.ui.messagelistloaded(self.repository, self, self.getmessagecount())
def open(self, host=None, port=None): super(WrappedIMAP4_SSL, self).open(host, port) if (self._fingerprint or not self.ca_certs): # compare fingerprints fingerprint = sha1(self.sock.getpeercert(True)).hexdigest() if fingerprint != self._fingerprint: raise OfflineImapError( "Server SSL fingerprint '%s' for hostnam" "e '%s' does not match configured fingerprint. Please ver" "ify and set 'cert_fingerprint' accordingly if not set ye" "t." % (fingerprint, host), OfflineImapError.ERROR.REPO)
def savemessage(self, uid, content, flags, rtime): """Writes a new message, with the specified uid. See folder/Base for detail. Note that savemessage() does not check against dryrun settings, so you need to ensure that savemessage is never called in a dryrun mode.""" # This function only ever saves to tmp/, # but it calls savemessageflags() to actually save to cur/ or new/. self.ui.savemessage('maildir', uid, flags, self) if uid < 0: # We cannot assign a new uid. return uid if uid in self.messagelist: # We already have it, just update flags. self.savemessageflags(uid, flags) return uid # Otherwise, save the message in tmp/ and then call savemessageflags() # to give it a permanent home. tmpdir = os.path.join(self.getfullname(), 'tmp') messagename = self.new_message_filename(uid, flags) # open file and write it out try: fd = os.open(os.path.join(tmpdir, messagename), os.O_EXCL | os.O_CREAT | os.O_WRONLY, 0o666) except OSError as e: if e.errno == 17: #FILE EXISTS ALREADY severity = OfflineImapError.ERROR.MESSAGE raise OfflineImapError("Unique filename %s already existing." %\ messagename, severity) else: raise file = os.fdopen(fd, 'wt') file.write(content) # Make sure the data hits the disk file.flush() if self.dofsync: os.fsync(fd) file.close() if rtime != None: os.utime(os.path.join(tmpdir, messagename), (rtime, rtime)) self.messagelist[uid] = { 'flags': flags, 'filename': os.path.join('tmp', messagename) } # savemessageflags moves msg to 'cur' or 'new' as appropriate self.savemessageflags(uid, flags) self.ui.debug('maildir', 'savemessage: returning uid %d' % uid) return uid
def deletefolder(self, foldername): """Delete a folder on the IMAP server.""" imapobj = self.imapserver.acquireconnection() try: result = imapobj.delete(foldername) if result[0] != 'OK': raise OfflineImapError("Folder '%s'[%s] could not be deleted. " "Server responded: %s"% (foldername, self, str(result)), OfflineImapError.ERROR.FOLDER) finally: self.imapserver.releaseconnection(imapobj)
def __init__(self, reposname, account): """Initialize a GmailRepository object.""" # Enforce SSL usage account.getconfig().set('Repository ' + reposname, 'ssl', 'yes') IMAPRepository.__init__(self, reposname, account) if self.account.getconfboolean('synclabels', 0) and \ self.account.getconf('status_backend', 'plain') != 'sqlite': raise OfflineImapError( "The Gmail repository needs the sqlite backend to sync labels.\n" "To enable it add 'status_backend = sqlite' in the account section", OfflineImapError.ERROR.REPO)