From e791901b975a60c6e06b9f1fdefc36ef58c9021a Mon Sep 17 00:00:00 2001 From: Gioele Date: Mon, 23 Jun 2014 08:33:47 +0200 Subject: [PATCH 1/6] Fix Markdown formatting Signed-off-by: Eygene Ryabinkin --- Changelog.rst | 2 ++ README.md | 66 +++++++++++++++++++++++++-------------------------- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 27a31a5..781e252 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -15,6 +15,8 @@ OfflineIMAP v6.5.6.1 (YYYY-MM-DD) * Add missing version bump for 6.5.6 (it was released with 6.5.5 in setup.py and other places). +* Various fixes in documentation. + OfflineIMAP v6.5.6 (2014-05-14) =============================== diff --git a/README.md b/README.md index bc839af..81f78de 100644 --- a/README.md +++ b/README.md @@ -39,18 +39,18 @@ detailed information on how to install and configure OfflineImap. Quick Start =========== -First, install OfflineIMAP. See docs/INSTALL.rst or read -http://docs.offlineimap.org/en/latest/INSTALL.html. -(hint: `sudo python setup.py install`) +First, install OfflineIMAP. See `docs/INSTALL.rst` or read + +(hint: `sudo python setup.py install`). Second, set up your configuration file and run it! The distribution includes offlineimap.conf.minimal (Debian users may find this at -``/usr/share/doc/offlineimap/examples/offlineimap.conf.minimal``) that +`/usr/share/doc/offlineimap/examples/offlineimap.conf.minimal`) that provides you with the bare minimum of setting up OfflineIMAP. You can simply copy this file into your home directory and name it -``.offlineimaprc``. A command such as ``cp offlineimap.conf.minimal -~/.offlineimaprc`` will do it. Or, if you prefer, you can just copy -this text to ``~/.offlineimaprc``: +`.offlineimaprc`. A command such as `cp offlineimap.conf.minimal +~/.offlineimaprc` will do it. Or, if you prefer, you can just copy +this text to `~/.offlineimaprc`: [general] accounts = Test @@ -69,17 +69,17 @@ this text to ``~/.offlineimaprc``: remoteuser = jgoerzen -Now, edit the ``~/.offlineimaprc`` file with your favorite editor. All you have -to do is specify a directory for your folders to be in (on the localfolders -line), the host name of your IMAP server (on the remotehost line), and your -login name on the remote (on the remoteuser line). That's it! +Now, edit the `~/.offlineimaprc` file with your favorite editor. All you have +to do is specify a directory for your folders to be in (on the `localfolders` +line), the host name of your IMAP server (on the `remotehost` line), and your +login name on the remote (on the `remoteuser` line). That's it! -If you prefer to be XDG-compatible, - http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html -then substitute the above ``~/.offlineimaprc'' with -``$XDG\_CONFIG\_HOME/offlineimap/config'' and don't forget to set -XDG\_CONFIG\_HOME properly if you want it to be different from -the default ``$HOME/.config'' for any reason. +If you prefer to be compatible with the [XDG Base Directory +spec](http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html), +then substitute the above `~/.offlineimaprc` with +`$XDG_CONFIG_HOME/offlineimap/config` and don't forget to set +`XDG_CONFIG_HOME` properly if you want it to be different from +the default `$HOME/.config` for any reason. To run OfflineIMAP, you just have to say `offlineimap` ― it will fire up, ask you for a login password if necessary, synchronize your folders, @@ -97,12 +97,12 @@ Mailing list & bug reporting The user discussion, development and all exciting stuff take place in the OfflineImap mailing list at -http://lists.alioth.debian.org/mailman/listinfo/offlineimap-project. You do not +. You do not need to subscribe to send emails. Bugs, issues and contributions should be reported to the mailing list. Bugs can also be reported in the issue tracker at -https://github.com/OfflineIMAP/offlineimap/issues. +. Configuration Examples ====================== @@ -117,22 +117,22 @@ Multiple Accounts with Mutt This example shows you how to set up OfflineIMAP to synchronize multiple accounts with the mutt mail reader. -Start by creating a directory to hold your folders by running ``mkdir ~/Mail``. -Then, in your ``~/.offlineimaprc``, specify: +Start by creating a directory to hold your folders by running `mkdir ~/Mail`. +Then, in your `~/.offlineimaprc`, specify: accounts = Personal, Work -Make sure that you have both an [Account Personal] and an [Account Work] -section. The local repository for each account must have different localfolder -path names. Also, make sure to enable [mbnames]. +Make sure that you have both an `[Account Personal]` and an `[Account Work]` +section. The local repository for each account must have different `localfolder` +path names. Also, make sure to enable `[mbnames]`. In each local repository section, write something like this: localfolders = ~/Mail/Personal -Finally, add these lines to your ``~/.muttrc``: +Finally, add these lines to your `~/.muttrc`: source ~/path-to-mbnames-muttrc-mailboxes folder-hook Personal set from="youremail@personal.com" @@ -149,10 +149,10 @@ UW-IMAPD and References ----------------------- Some users with a UW-IMAPD server need to use OfflineIMAP's "reference" feature -to get at their mailboxes, specifying a reference of ``~/Mail`` or ``#mh/`` +to get at their mailboxes, specifying a reference of `~/Mail` or `#mh/` depending on the configuration. The below configuration from (originally from -docwhat@gerf.org) shows using a reference of Mail, a nametrans that strips the -leading Mail/ off incoming folder names, and a folderfilter that limits the +docwhat@gerf.org) shows using a reference of Mail, a `nametrans` that strips the +leading `Mail/` off incoming folder names, and a `folderfilter` that limits the folders synced to just three: [Account Gerf] @@ -191,16 +191,16 @@ configuration file options that are Python expressions. This example is based on one supplied by Tommi Virtanen for this feature. -In ~/.offlineimaprc, he adds these options: +In `~/.offlineimaprc`, he adds these options: [general] pythonfile=~/.offlineimap.py [Repository foo] foldersort=mycmp -Then, the ~/.offlineimap.py file will contain: +Then, the `~/.offlineimap.py` file will contain: - prioritized = ['INBOX', 'personal', 'announce', 'list'] + prioritized = ['INBOX', 'personal', 'announce', 'list'] def mycmp(x, y): for prefix in prioritized: @@ -221,5 +221,5 @@ Then, the ~/.offlineimap.py file will contain: print folders -This code snippet illustrates how the foldersort option can be customized with a -Python function from the pythonfile to always synchronize certain folders first. +This code snippet illustrates how the `foldersort` option can be customized with a +Python function from the `pythonfile` to always synchronize certain folders first. From afead6c48e33dc8078d768bbf24011da914a7f72 Mon Sep 17 00:00:00 2001 From: Adam Spiers Date: Tue, 24 Jun 2014 18:05:21 +0100 Subject: [PATCH 2/6] Rename addmessageheader()'s crlf parameter to linebreak The parameter's value is a string representing the linebreak, and can sometimes contain just '\n', in which case naming it crlf is slightly misleading. Signed-off-by: Eygene Ryabinkin --- offlineimap/folder/Base.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/offlineimap/folder/Base.py b/offlineimap/folder/Base.py index a80a6e0..63dbada 100644 --- a/offlineimap/folder/Base.py +++ b/offlineimap/folder/Base.py @@ -420,13 +420,13 @@ next line\n """ - def addmessageheader(self, content, crlf, headername, headervalue): + def addmessageheader(self, content, linebreak, headername, headervalue): """ Adds new header to the provided message. Arguments: - content: message content, headers and body as a single string - - crlf: string that carries line ending + - linebreak: string that carries line ending - headername: name of the header to add - headervalue: value of the header to add @@ -434,12 +434,12 @@ next line\n self.ui.debug('', 'addmessageheader: called to add %s: %s' % (headername, headervalue)) - prefix = crlf + prefix = linebreak suffix = '' - insertionpoint = content.find(crlf + crlf) + insertionpoint = content.find(linebreak * 2) if insertionpoint == 0 or insertionpoint == -1: prefix = '' - suffix = crlf + suffix = linebreak if insertionpoint == -1: insertionpoint = 0 # When body starts immediately, without preceding '\n' @@ -447,8 +447,8 @@ next line\n # we seen many broken ones), we should add '\n' to make # new (and the only header, in this case) to be properly # separated from the message body. - if content[0:len(crlf)] != crlf: - suffix = suffix + crlf + if content[0:len(linebreak)] != linebreak: + suffix = suffix + linebreak self.ui.debug('', 'addmessageheader: insertionpoint = %d' % insertionpoint) headers = content[0:insertionpoint] From 37f74d859adee09ebdf245c5eb819bb98160b083 Mon Sep 17 00:00:00 2001 From: Adam Spiers Date: Tue, 24 Jun 2014 18:06:34 +0100 Subject: [PATCH 3/6] addmessageheader: fix case #2 and flesh out docstring The example illustrations were slightly cryptic; modify them to be more obvious. Fix case #2 (message starts with two line breaks) where additional line break was shown and coded. Signed-off-by: Eygene Ryabinkin --- offlineimap/folder/Base.py | 86 ++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/offlineimap/folder/Base.py b/offlineimap/folder/Base.py index 63dbada..ff36d5a 100644 --- a/offlineimap/folder/Base.py +++ b/offlineimap/folder/Base.py @@ -386,40 +386,6 @@ class BaseFolder(object): for uid in uidlist: self.deletemessagelabels(uid, labels) - - """ - Illustration of all cases for addmessageheader(). - '+' means the added contents. - -Case 1: No '\n\n', leading '\n' -+X-Flying-Pig-Header: i am here\n -\n -This is the body\n -next line\n - -Case 2: '\n\n' at position 0 -+X-Flying-Pig-Header: i am here\n -\n -\n -This is the body\n -next line\n - -Case 3: No '\n\n', no leading '\n' -+X-Flying-Pig-Header: i am here\n -+\n -This is the body\n -next line\n - -Case 4: '\n\n' at non-zero position -Subject: Something wrong with OI\n -From: some@person.at+\n -X-Flying-Pig-Header: i am here\n <-- orig '\n' -\n -This is the body\n -next line\n - - """ - def addmessageheader(self, content, linebreak, headername, headervalue): """ Adds new header to the provided message. @@ -430,19 +396,65 @@ next line\n - headername: name of the header to add - headervalue: value of the header to add + This has to deal with strange corner cases where the header is + missing or empty. Here are illustrations for all the cases, + showing where the header gets inserted and what the end result + is. In each illustration, '+' means the added contents. Note + that these examples assume LF for linebreak, not CRLF, so '\n' + denotes a linebreak and '\n\n' corresponds to the transition + between header and body. However if the linebreak parameter + is set to '\r\n' then you would have to substitute '\r\n' for + '\n' in the below examples. + + * Case 1: No '\n\n', leading '\n' + + +X-Flying-Pig-Header: i am here\n + \n + This is the body\n + next line\n + + * Case 2: '\n\n' at position 0 + + +X-Flying-Pig-Header: i am here + \n + \n + This is the body\n + next line\n + + * Case 3: No '\n\n', no leading '\n' + + +X-Flying-Pig-Header: i am here\n + +\n + This is the body\n + next line\n + + * Case 4: '\n\n' at non-zero position + + Subject: Something wrong with OI\n + From: some@person.at + +\nX-Flying-Pig-Header: i am here + \n + \n + This is the body\n + next line\n """ self.ui.debug('', 'addmessageheader: called to add %s: %s' % (headername, headervalue)) + # Hoping for case #4 prefix = linebreak suffix = '' insertionpoint = content.find(linebreak * 2) - if insertionpoint == 0 or insertionpoint == -1: + # Case #2 + if insertionpoint == 0: + prefix = '' + suffix = '' + # Either case #1 or #3 + elif insertionpoint == -1: prefix = '' suffix = linebreak - if insertionpoint == -1: insertionpoint = 0 - # When body starts immediately, without preceding '\n' + # Case #3: when body starts immediately, without preceding '\n' # (this shouldn't happen with proper mail messages, but # we seen many broken ones), we should add '\n' to make # new (and the only header, in this case) to be properly From 807f3da880820f87b8db06e8756deda737ea5347 Mon Sep 17 00:00:00 2001 From: Adam Spiers Date: Tue, 24 Jun 2014 18:07:17 +0100 Subject: [PATCH 4/6] addmessageheader(): add debug for header insertion Signed-off-by: Eygene Ryabinkin --- offlineimap/folder/Base.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/offlineimap/folder/Base.py b/offlineimap/folder/Base.py index ff36d5a..e6c21bd 100644 --- a/offlineimap/folder/Base.py +++ b/offlineimap/folder/Base.py @@ -441,10 +441,22 @@ class BaseFolder(object): self.ui.debug('', 'addmessageheader: called to add %s: %s' % (headername, headervalue)) + + insertionpoint = content.find(linebreak * 2) + if insertionpoint == -1: + self.ui.debug('', 'addmessageheader: headers were missing') + else: + self.ui.debug('', 'addmessageheader: headers end at position %d' % insertionpoint) + mark = '==>EOH<==' + contextstart = max(0, insertionpoint - 100) + contextend = min(len(content), insertionpoint + 100) + self.ui.debug('', 'addmessageheader: header/body transition context (marked by %s): %s' % + (mark, repr(content[contextstart:insertionpoint]) + \ + mark + repr(content[insertionpoint:contextend]))) + # Hoping for case #4 prefix = linebreak suffix = '' - insertionpoint = content.find(linebreak * 2) # Case #2 if insertionpoint == 0: prefix = '' From 863113efa383c23c2b7b02835796ead159697c6b Mon Sep 17 00:00:00 2001 From: Eygene Ryabinkin Date: Mon, 30 Jun 2014 16:41:17 +0400 Subject: [PATCH 5/6] Match header names case-insensitively http://tools.ietf.org/html/rfc5234#section-2.3 says that ABNF strings are case-insensitive. Signed-off-by: Eygene Ryabinkin --- offlineimap/folder/Base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/offlineimap/folder/Base.py b/offlineimap/folder/Base.py index e6c21bd..41fd627 100644 --- a/offlineimap/folder/Base.py +++ b/offlineimap/folder/Base.py @@ -502,6 +502,8 @@ class BaseFolder(object): def getmessageheader(self, content, name): """ Searches for the given header and returns its value. + Header name is case-insensitive. + Arguments: - contents: message itself - name: name of the header to be searched @@ -515,7 +517,7 @@ class BaseFolder(object): headers = content[0:eoh] self.ui.debug('', 'getmessageheader: headers = %s' % repr(headers)) - m = re.search('^%s:(.*)$' % name, headers, flags = re.MULTILINE) + m = re.search('^%s:(.*)$' % name, headers, flags = re.MULTILINE | re.IGNORECASE) if m: return m.group(1).strip() else: From ffd1b1d691d3206758e2549fda4ac0107ceab871 Mon Sep 17 00:00:00 2001 From: Eygene Ryabinkin Date: Tue, 1 Jul 2014 07:44:18 +0400 Subject: [PATCH 6/6] IMAP: provide message-id in error messages This is handy for debug purposes when one tries to locate exact message that was e.g. rejected by server. Feature-request: http://comments.gmane.org/gmane.mail.imap.offlineimap.general/6491 Signed-off-by: Eygene Ryabinkin --- offlineimap/folder/IMAP.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/offlineimap/folder/IMAP.py b/offlineimap/folder/IMAP.py index 3b506e5..1029cf7 100644 --- a/offlineimap/folder/IMAP.py +++ b/offlineimap/folder/IMAP.py @@ -517,6 +517,11 @@ class IMAPFolder(BaseFolder): # get the date of the message, so we can pass it to the server. date = self.__getmessageinternaldate(content, rtime) + # Message-ID is handy for debugging messages + msg_id = self.getmessageheader(content, "message-id") + if not msg_id: + msg_id = '[unknown message-id]' + retry_left = 2 # succeeded in APPENDING? imapobj = self.imapserver.acquireconnection() # NB: in the finally clause for this try we will release @@ -569,9 +574,9 @@ class IMAPFolder(BaseFolder): # In this case, we should immediately abort the repository sync # and continue with the next account. msg = \ - "Saving msg in folder '%s', repository '%s' failed (abort). " \ + "Saving msg (%s) in folder '%s', repository '%s' failed (abort). " \ "Server responded: %s %s\n" % \ - (self, self.getrepository(), typ, dat) + (msg_id, self, self.getrepository(), typ, dat) raise OfflineImapError(msg, OfflineImapError.ERROR.REPO) retry_left = 0 # Mark as success except imapobj.abort as e: @@ -580,10 +585,10 @@ class IMAPFolder(BaseFolder): self.imapserver.releaseconnection(imapobj, True) imapobj = self.imapserver.acquireconnection() if not retry_left: - raise OfflineImapError("Saving msg in folder '%s', " + raise OfflineImapError("Saving msg (%s) in folder '%s', " "repository '%s' failed (abort). Server responded: %s\n" "Message content was: %s" % - (self, self.getrepository(), str(e), dbg_output), + (msg_id, self, self.getrepository(), str(e), dbg_output), OfflineImapError.ERROR.MESSAGE) self.ui.error(e, exc_info()[2]) except imapobj.error as e: # APPEND failed @@ -592,9 +597,9 @@ class IMAPFolder(BaseFolder): # drop conn, it might be bad. self.imapserver.releaseconnection(imapobj, True) imapobj = None - raise OfflineImapError("Saving msg folder '%s', repo '%s'" + raise OfflineImapError("Saving msg (%s) folder '%s', repo '%s'" "failed (error). Server responded: %s\nMessage content was: " - "%s" % (self, self.getrepository(), str(e), dbg_output), + "%s" % (msg_id, self, self.getrepository(), str(e), dbg_output), OfflineImapError.ERROR.MESSAGE) # Checkpoint. Let it write out stuff, etc. Eg searches for # just uploaded messages won't work if we don't do this.