[-]
[+]
|
Changed |
getmail.changes
|
|
[-]
[+]
|
Changed |
getmail.spec
^
|
|
[-]
[+]
|
Changed |
getmail-4.30.1.tar.bz2/PKG-INFO
^
|
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: getmail
-Version: 4.26.0
+Version: 4.30.1
Summary: a mail retrieval, sorting, and delivering system
Home-page: http://pyropus.ca/software/getmail/
Author: Charles Cazabon
|
[-]
[+]
|
Changed |
getmail-4.30.1.tar.bz2/docs/CHANGELOG
^
|
@@ -1,9 +1,53 @@
+Version 4.30.1
+21 June 2012
+ -silence a nuisance deprecation warning about the sets module when running
+ with Python >= 2.5 which was reintroduced in 4.29.0. Thanks: Stephan
+ Schulz.
+
+Version 4.30.0
+21 June 2012
+ -fix breakage introduced in 4.29.0 where BrokenUIDLPOP3Retriever would fail
+ with a TypeError at logout time. Thanks: Scott Robbins, Stephan Schulz.
+ -fix breakage introduced in 4.29.0 where deleted mail was not being expunged
+ from the last (or only) folder retrieved from in an IMAP session. Thanks:
+ Paul Howarth.
+
+Version 4.29.0
+19 June 2012
+ -update old contact information for Free Software Foundation. Thanks: Ricky
+ Zhou.
+ -fix incorrect character encoding in plaintext documentation. Thanks: Ricky
+ Zhou.
+ -ensure getmail exits nonzero if a server refuses login due to a credential
+ problem. Thanks: Stephan Schulz.
+
+Version 4.28.0
+26 May 2012
+ -ensure getmail exits nonzero if various error conditions (like POP/IMAP
+ authentication failure) occur. Thanks: Ryan J., Stephan Schulz.
+ -python versions prior to 2.5.0 contain a bug when dealing with read-only
+ IMAP mailboxes. Monkey-patch imaplib when running with Python<2.5.0.
+ Thanks: Les Barstow.
+ -do IMAP modified-utf7 conversion of mailbox names containing non-ASCII
+ characters. Thanks: A. Lapraitis, Randall Mason.
+ -add special ALL value for retrieving mail from all selectable IMAP
+ mailboxes in the account.
+ -change IMAP retrieval strategy to retrieve all messages from a mailbox,
+ then move on to the next mailbox, etc. Should result in increased speed,
+ but if you set `max_messages_per_session` too low, this could result in
+ later mailboxes not being retrieved from.
+
+Version 4.27.0
+20 May 2012
+ -make use of IMAP BODY.PEEK configurable; set the IMAP retriever parameter
+ `use_peek` to False to disable use of PEEK to get getmail's historical IMAP
+ behaviour.
+
Version 4.26.0
14 April 2012
-switch to using BODY.PEEK in IMAP retrieval; I no longer see problems with
this feature in my testing. If users experience incompatibility with any
IMAP servers where 4.25.0 worked, please let me know.
-
Version 4.25.0
1 February 2012
|
[-]
[+]
|
Changed |
getmail-4.30.1.tar.bz2/docs/COPYING
^
|
@@ -1,12 +1,12 @@
- GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
- Preamble
+ Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
@@ -15,7 +15,7 @@
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
-the GNU Library General Public License instead.) You can apply it to
+the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
@@ -55,8 +55,8 @@
The precise terms and conditions for copying, distribution and
modification follow.
-
- GNU GENERAL PUBLIC LICENSE
+
+ GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
@@ -110,7 +110,7 @@
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
-
+
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
@@ -168,7 +168,7 @@
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
-
+
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
@@ -225,7 +225,7 @@
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
-
+
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
@@ -255,7 +255,7 @@
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
- NO WARRANTY
+ NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
@@ -277,9 +277,9 @@
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
@@ -291,7 +291,7 @@
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
- Copyright (C) 19yy <name of author>
+ Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -303,17 +303,16 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
- Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
@@ -336,5 +335,5 @@
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Library General
+library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
|
[-]
[+]
|
Changed |
getmail-4.30.1.tar.bz2/docs/configuration.html
^
|
@@ -570,6 +570,28 @@
Note that the format for hierarchical folder names is determined by the
IMAP server, not by getmail. Consult your server's documentation or
postmaster if you're unsure what form your server uses.
+ If your mailbox names contain non-ASCII characters, ensure that your
+ getmailrc file is stored with UTF-8 encoding so that getmail can
+ correctly determine the unicode character names that need to be quoted
+ in IMAP's modified UTF-7 encoding; if you do not do this, the mailbox
+ names will not match what the server expects them to be, or will cause
+ UnicodeErrors when attempting to load your getmailrc file.
+ As a special case, in getmail version 4.29.0 and later, the unquoted
+ base (non-tuple) value
+ <span class="file">ALL</span> (case-sensitive) means to retrieve mail
+ from all selectable IMAP mailboxes in the account. To retrieve messages
+ from all mailboxes, you would use:
+ <pre class="example">
+mailboxes = ALL
+ </pre>
+ </li>
+ <li>
+ use_peek
+ (<a href="#parameter-boolean">boolean</a>)
+ — whether to use PEEK to retrieve the message; the default is
+ True. IMAP servers typically mark a message as seen if PEEK is not used
+ to retrieve the message content. Versions of getmail prior to 4.26.0
+ did not use PEEK to retrieve messages.
</li>
<li>
move_on_delete
|
[-]
[+]
|
Changed |
getmail-4.30.1.tar.bz2/docs/configuration.txt
^
|
@@ -280,7 +280,25 @@
Note that the format for hierarchical folder names is determined by
the IMAP server, not by getmail. Consult your server's documentation
- or postmaster if you're unsure what form your server uses.
+ or postmaster if you're unsure what form your server uses. If your
+ mailbox names contain non-ASCII characters, ensure that your getmailrc
+ file is stored with UTF-8 encoding so that getmail can correctly
+ determine the unicode character names that need to be quoted in IMAP's
+ modified UTF-7 encoding; if you do not do this, the mailbox names will
+ not match what the server expects them to be, or will cause
+ UnicodeErrors when attempting to load your getmailrc file. As a
+ special case, in getmail version 4.29.0 and later, the unquoted base
+ (non-tuple) value ALL (case-sensitive) means to retrieve mail from all
+ selectable IMAP mailboxes in the account. To retrieve messages from
+ all mailboxes, you would use:
+
+ mailboxes = ALL
+
+
+ * use_peek (boolean) -- whether to use PEEK to retrieve the message; the
+ default is True. IMAP servers typically mark a message as seen if PEEK
+ is not used to retrieve the message content. Versions of getmail prior
+ to 4.26.0 did not use PEEK to retrieve messages.
* move_on_delete (string) -- if set, messages are moved to the named
mail folder before being deleted from their original location. Note
that if you configure getmail not to delete retrieved messages (the
|
[-]
[+]
|
Changed |
getmail-4.30.1.tar.bz2/getmail
^
|
@@ -76,13 +76,18 @@
#######################################
def blurb():
log.info('getmail version %s\n' % __version__)
- log.info('Copyright (C) 1998-2009 Charles Cazabon. Licensed under the '
+ log.info('Copyright (C) 1998-2012 Charles Cazabon. Licensed under the '
'GNU GPL version 2.\n')
#######################################
def go(configs):
+ """Main code.
+
+ Returns True if all goes well, False if any error condition occurs.
+ """
blurb()
summary = []
+ errorexit = False
for (configfile, retriever, _filters, destination, options) in configs:
oplevel = options['verbose']
logverbose = options['message_log_verbose']
@@ -101,156 +106,172 @@
syslog.syslog(syslog.LOG_INFO, logline)
retriever.initialize(options)
destination.retriever_info(retriever)
- nummsgs = len(retriever)
- fmtlen = len(str(nummsgs))
- for (msgnum, msgid) in enumerate(retriever):
- log.debug(' message %s ...\n' % msgid)
- msgnum += 1
- retrieve = False
- reason = 'seen'
- delete = False
- timestamp = retriever.oldmail.get(msgid, None)
- size = retriever.getmsgsize(msgid)
- info = ('msg %*d/%*d (%d bytes)'
- % (fmtlen, msgnum, fmtlen, nummsgs, size))
- logline = '%s msgid %s' % (info, msgid)
- if options['read_all'] or timestamp is None:
- retrieve = True
- if (options['max_message_size']
- and size > options['max_message_size']):
- retrieve = False
- reason = 'oversized'
- if (options['max_bytes_per_session']
- and (bytes_retrieved + size)
- > options['max_bytes_per_session']):
- retrieve = False
- reason = 'would surpass max_bytes_per_session'
+ for mailbox in retriever.mailboxes:
+ if mailbox:
+ # For POP this is None and uninteresting
+ log.debug(' checking mailbox %s ...\n' % mailbox)
try:
- if retrieve:
- try:
- msg = retriever.getmsg(msgid)
- except getmailRetrievalError, o:
- log.error(
- 'Retrieval error: server for %s is broken; '
- 'offered message %s but failed to provide it. '
- 'Please notify the administrator of the '
- 'server. Skipping message...\n'
- % (retriever, msgid)
- )
- continue
- msgs_retrieved += 1
- bytes_retrieved += size
- if oplevel > 1:
- info += (' from <%s>'
- % address_no_brackets(msg.sender))
+ retriever.select_mailbox(mailbox)
+ except getmailMailboxSelectError, o:
+ errorexit = True
+ log.info(' mailbox %s not selectable (%s) - verify the '
+ 'mailbox exists and you have sufficient '
+ 'permissions\n' % (mailbox, o))
+ continue
+ nummsgs = len(retriever)
+ fmtlen = len(str(nummsgs))
+ for (msgnum, msgid) in enumerate(retriever):
+ log.debug(' message %s ...\n' % msgid)
+ msgnum += 1
+ retrieve = False
+ reason = 'seen'
+ delete = False
+ timestamp = retriever.oldmail.get(msgid, None)
+ size = retriever.getmsgsize(msgid)
+ info = ('msg %*d/%*d (%d bytes)'
+ % (fmtlen, msgnum, fmtlen, nummsgs, size))
+ logline = '%s msgid %s' % (info, msgid)
+ if options['read_all'] or timestamp is None:
+ retrieve = True
+ if (options['max_message_size']
+ and size > options['max_message_size']):
+ retrieve = False
+ reason = 'oversized'
+ if (options['max_bytes_per_session']
+ and (bytes_retrieved + size)
+ > options['max_bytes_per_session']):
+ retrieve = False
+ reason = 'would surpass max_bytes_per_session'
+ try:
+ if retrieve:
+ try:
+ msg = retriever.getmsg(msgid)
+ except getmailRetrievalError, o:
+ errorexit = True
+ log.error(
+ 'Retrieval error: server for %s is broken; '
+ 'offered message %s but failed to provide it. '
+ 'Please notify the administrator of the '
+ 'server. Skipping message...\n'
+ % (retriever, msgid)
+ )
+ continue
+ msgs_retrieved += 1
+ bytes_retrieved += size
+ if oplevel > 1:
+ info += (' from <%s>'
+ % address_no_brackets(msg.sender))
+ if msg.recipient is not None:
+ info += (' to <%s>'
+ % address_no_brackets(msg.recipient))
+ logline += (' from <%s>'
+ % address_no_brackets(msg.sender))
if msg.recipient is not None:
- info += (' to <%s>'
- % address_no_brackets(msg.recipient))
- logline += (' from <%s>'
- % address_no_brackets(msg.sender))
- if msg.recipient is not None:
- logline += (' to <%s>'
- % address_no_brackets(msg.recipient))
-
- for mail_filter in _filters:
- log.debug(' passing to filter %s\n'
- % mail_filter)
- msg = mail_filter.filter_message(msg, retriever)
- if msg is None:
- log.debug(' dropped by filter %s\n'
+ logline += (' to <%s>'
+ % address_no_brackets(msg.recipient))
+
+ for mail_filter in _filters:
+ log.debug(' passing to filter %s\n'
% mail_filter)
- info += (' dropped by filter %s'
- % mail_filter)
- logline += (' dropped by filter %s'
- % mail_filter)
+ msg = mail_filter.filter_message(msg, retriever)
+ if msg is None:
+ log.debug(' dropped by filter %s\n'
+ % mail_filter)
+ info += (' dropped by filter %s'
+ % mail_filter)
+ logline += (' dropped by filter %s'
+ % mail_filter)
+ retriever.delivered(msgid)
+ break
+
+ if msg is not None:
+ r = destination.deliver_message(msg,
+ options['delivered_to'], options['received'])
+ log.debug(' delivered to %s\n' % r)
+ info += ' delivered'
+ if oplevel > 1:
+ info += (' to %s' % r)
+ logline += (' delivered to %s' % r)
retriever.delivered(msgid)
- break
-
- if msg is not None:
- r = destination.deliver_message(msg,
- options['delivered_to'], options['received'])
- log.debug(' delivered to %s\n' % r)
- info += ' delivered'
+ if options['delete']:
+ delete = True
+ else:
+ logline += ' not retrieved (%s)' % reason
+ msgs_skipped += 1
+ log.debug(' not retrieving (timestamp %s)\n'
+ % timestamp)
if oplevel > 1:
- info += (' to %s' % r)
- logline += (' delivered to %s' % r)
- retriever.delivered(msgid)
- if options['delete']:
+ info += ' not retrieved (%s)' % reason
+
+ if (options['delete_after'] and timestamp
+ and (now - timestamp) / 86400
+ >= options['delete_after']):
+ log.debug(
+ ' older than %d days (%s seconds), will delete\n'
+ % (options['delete_after'], (now - timestamp))
+ )
delete = True
- else:
- logline += ' not retrieved (%s)' % reason
- msgs_skipped += 1
- log.debug(' not retrieving (timestamp %s)\n'
- % timestamp)
- if oplevel > 1:
- info += ' not retrieved (%s)' % reason
- if (options['delete_after'] and timestamp
- and (now - timestamp) / 86400
- >= options['delete_after']):
- log.debug(
- ' older than %d days (%s seconds), will delete\n'
- % (options['delete_after'], (now - timestamp))
- )
- delete = True
+ if options['delete'] and timestamp:
+ log.debug(' will delete\n')
+ delete = True
- if options['delete'] and timestamp:
- log.debug(' will delete\n')
- delete = True
-
- if not retrieve and timestamp is None:
- # We haven't retrieved this message. Don't delete it.
- log.debug(' not yet retrieved, not deleting\n')
- delete = False
-
- if delete:
- retriever.delmsg(msgid)
- log.debug(' deleted\n')
- info += ', deleted'
- logline += ', deleted'
-
- except getmailDeliveryError, o:
- log.error('Delivery error (%s)\n' % o)
- info += ', delivery error (%s)' % o
- if options['logfile']:
- options['logfile'].write('Delivery error (%s)' % o)
- if options['message_log_syslog']:
- syslog.syslog(syslog.LOG_ERR,
- 'Delivery error (%s)' % o)
-
- except getmailFilterError, o:
- log.error('Filter error (%s)\n' % o)
- info += ', filter error (%s)' % o
- if options['logfile']:
- options['logfile'].write('Filter error (%s)' % o)
- if options['message_log_syslog']:
- syslog.syslog(syslog.LOG_ERR,
- 'Filter error (%s)' % o)
-
- if (retrieve or delete or oplevel > 1):
- log.info(' %s\n' % info)
- if options['logfile'] and (retrieve or delete or logverbose):
- options['logfile'].write(logline)
- if options['message_log_syslog'] and (retrieve or delete
- or logverbose):
- syslog.syslog(syslog.LOG_INFO, logline)
-
- if (options['max_messages_per_session']
- and msgs_retrieved >=
- options['max_messages_per_session']):
- log.debug('hit max_messages_per_session (%d), breaking\n'
- % options['max_messages_per_session'])
- if oplevel > 1:
- log.info(' max messages per session (%d)\n'
- % options['max_messages_per_session'])
- raise StopIteration('max_messages_per_session %d'
- % options['max_messages_per_session'])
+ if not retrieve and timestamp is None:
+ # We haven't retrieved this message. Don't delete it.
+ log.debug(' not yet retrieved, not deleting\n')
+ delete = False
+
+ if delete:
+ retriever.delmsg(msgid)
+ log.debug(' deleted\n')
+ info += ', deleted'
+ logline += ', deleted'
+
+ except getmailDeliveryError, o:
+ errorexit = True
+ log.error('Delivery error (%s)\n' % o)
+ info += ', delivery error (%s)' % o
+ if options['logfile']:
+ options['logfile'].write('Delivery error (%s)' % o)
+ if options['message_log_syslog']:
+ syslog.syslog(syslog.LOG_ERR,
+ 'Delivery error (%s)' % o)
+
+ except getmailFilterError, o:
+ errorexit = True
+ log.error('Filter error (%s)\n' % o)
+ info += ', filter error (%s)' % o
+ if options['logfile']:
+ options['logfile'].write('Filter error (%s)' % o)
+ if options['message_log_syslog']:
+ syslog.syslog(syslog.LOG_ERR,
+ 'Filter error (%s)' % o)
+
+ if (retrieve or delete or oplevel > 1):
+ log.info(' %s\n' % info)
+ if options['logfile'] and (retrieve or delete or logverbose):
+ options['logfile'].write(logline)
+ if options['message_log_syslog'] and (retrieve or delete
+ or logverbose):
+ syslog.syslog(syslog.LOG_INFO, logline)
+
+ if (options['max_messages_per_session']
+ and msgs_retrieved >=
+ options['max_messages_per_session']):
+ log.debug('hit max_messages_per_session (%d), breaking\n'
+ % options['max_messages_per_session'])
+ if oplevel > 1:
+ log.info(' max messages per session (%d)\n'
+ % options['max_messages_per_session'])
+ raise StopIteration('max_messages_per_session %d'
+ % options['max_messages_per_session'])
except StopIteration:
pass
except socket.timeout, o:
- retriever.write_oldmailfile(forget_deleted=False)
+ errorexit = True
+ retriever.abort()
if type(o) == tuple and len(o) > 1:
o = o[1]
log.error('%s: timeout (%s)\n' % (configfile, o))
@@ -258,13 +279,15 @@
options['logfile'].write('timeout error (%s)' % o)
except (poplib.error_proto, imaplib.IMAP4.abort), o:
- retriever.write_oldmailfile(forget_deleted=False)
+ errorexit = True
+ retriever.abort()
log.error('%s: protocol error (%s)\n' % (configfile, o))
if options['logfile']:
options['logfile'].write('protocol error (%s)' % o)
except socket.gaierror, o:
- retriever.write_oldmailfile(forget_deleted=False)
+ errorexit = True
+ retriever.abort()
if type(o) == tuple and len(o) > 1:
o = o[1]
log.error('%s: error resolving name (%s)\n' % (configfile, o))
@@ -272,15 +295,24 @@
options['logfile'].write('gaierror error (%s)' % o)
except socket.error, o:
- retriever.write_oldmailfile(forget_deleted=False)
+ errorexit = True
+ retriever.abort()
if type(o) == tuple and len(o) > 1:
o = o[1]
log.error('%s: socket error (%s)\n' % (configfile, o))
if options['logfile']:
options['logfile'].write('socket error (%s)' % o)
+ except getmailCredentialError, o:
+ errorexit = True
+ retriever.abort()
+ log.error('%s: credential/login error (%s)\n' % (configfile, o))
+ if options['logfile']:
+ options['logfile'].write('credential/login error (%s)' % o)
+
except getmailOperationError, o:
- retriever.write_oldmailfile(forget_deleted=False)
+ errorexit = True
+ retriever.abort()
log.error('%s: operation error (%s)\n' % (configfile, o))
if options['logfile']:
options['logfile'].write('getmailOperationError error (%s)' % o)
@@ -303,6 +335,7 @@
try:
retriever.quit()
except getmailOperationError, o:
+ errorexit = True
log.debug('%s: operation error during quit (%s)\n'
% (configfile, o))
if options['logfile']:
@@ -315,6 +348,9 @@
log.info('Retrieved %d messages (%s bytes) from %s\n'
% (msgs_retrieved, bytes_retrieved, retriever))
+ return (not errorexit)
+
+
#######################################
def main():
try:
@@ -682,7 +718,9 @@
sys.exit()
# Go!
- go(configs)
+ success = go(configs)
+ if not success:
+ raise SystemExit(127)
except KeyboardInterrupt:
log.warning('Operation aborted by user (keyboard interrupt)\n')
|
[-]
[+]
|
Changed |
getmail-4.30.1.tar.bz2/getmail.spec
^
|
@@ -2,7 +2,7 @@
Summary: POP3 mail retriever with reliable Maildir delivery
Name: getmail
-Version: 4.26.0
+Version: 4.30.1
Release: 1
License: GPL
Group: Applications/Internet
@@ -52,6 +52,33 @@
%{python_sitelib}/getmailcore/
%changelog
+* Thu Jun 21 2012 Charles Cazabon <charlesc-getmail-rpm@pyropus.ca>
+-update to version 4.30.1
+
+* Thu Jun 21 2012 Charles Cazabon <charlesc-getmail-rpm@pyropus.ca>
+-update to version 4.30.1
+
+* Thu Jun 21 2012 Charles Cazabon <charlesc-getmail-rpm@pyropus.ca>
+-update to version 4.30.0
+
+* Thu Jun 21 2012 Charles Cazabon <charlesc-getmail-rpm@pyropus.ca>
+-update to version 4.30.0
+
+* Thu Jun 21 2012 Charles Cazabon <charlesc-getmail-rpm@pyropus.ca>
+-update to version 4.30.0
+
+* Tue Jun 19 2012 Charles Cazabon <charlesc-getmail-rpm@pyropus.ca>
+-update to version 4.29.0
+
+* Tue Jun 19 2012 Charles Cazabon <charlesc-getmail-rpm@pyropus.ca>
+-update to version 4.29.0
+
+* Sun May 20 2012 Charles Cazabon <charlesc-getmail-rpm@pyropus.ca>
+-update to version 4.27.0
+
+* Sun May 20 2012 Charles Cazabon <charlesc-getmail-rpm@pyropus.ca>
+-update to version 4.27.0
+
* Sat Apr 14 2012 Charles Cazabon <charlesc-getmail-rpm@pyropus.ca>
-update to version 4.26.0
|
[-]
[+]
|
Changed |
getmail-4.30.1.tar.bz2/getmail_fetch
^
|
@@ -29,7 +29,7 @@
#######################################
def blurb():
log.info('getmail_fetch version %s\n' % __version__)
- log.info('Copyright (C) 1998-2009 Charles Cazabon. Licensed under the '
+ log.info('Copyright (C) 1998-2012 Charles Cazabon. Licensed under the '
'GNU GPL version 2.\n')
#######################################
@@ -88,6 +88,9 @@
except poplib.error_proto, o:
error_exit(11, 'Protocol error: %s' % o)
+ except getmailCredentialError, o:
+ error_exit(13, 'Credential error: %s' % o)
+
except getmailOperationError, o:
error_exit(12, 'Operational error: %s' % o)
|
[-]
[+]
|
Changed |
getmail-4.30.1.tar.bz2/getmail_maildir
^
|
@@ -3,7 +3,7 @@
Reads a message from stdin and delivers it to a maildir specified as
a commandline argument. Expects the envelope sender address to be in the
environment variable SENDER.
-Copyright (C) 2001-2009 Charles Cazabon <charlesc-getmail @ pyropus.ca>
+Copyright (C) 2001-2012 Charles Cazabon <charlesc-getmail @ pyropus.ca>
This program is free software; you can redistribute it and/or modify it under
the terms of version 2 (only) of the GNU General Public License as published by
@@ -15,8 +15,8 @@
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
-this program; if not, write to the Free Software Foundation, Inc., 59 Temple
-Place - Suite 330, Boston, MA 02111-1307, USA.
+this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+Street, Fifth Floor, Boston, MA 02110-1301 USA.
'''
import sys
|
[-]
[+]
|
Changed |
getmail-4.30.1.tar.bz2/getmail_mbox
^
|
@@ -3,7 +3,7 @@
Reads a message from stdin and delivers it to an mbox file specified as
a commandline argument. Expects the envelope sender address to be in the
environment variable SENDER.
-Copyright (C) 2001-2009 Charles Cazabon <charlesc-getmail @ pyropus.ca>
+Copyright (C) 2001-2012 Charles Cazabon <charlesc-getmail @ pyropus.ca>
This program is free software; you can redistribute it and/or modify it under
the terms of version 2 (only) of the GNU General Public License as published by
@@ -15,8 +15,8 @@
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
-this program; if not, write to the Free Software Foundation, Inc., 59 Temple
-Place - Suite 330, Boston, MA 02111-1307, USA.
+this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+Street, Fifth Floor, Boston, MA 02110-1301 USA.
'''
import sys
|
[-]
[+]
|
Changed |
getmail-4.30.1.tar.bz2/getmailcore/__init__.py
^
|
@@ -16,14 +16,16 @@
raise ImportError('getmail version 4 requires Python version 2.3.3'
' or later')
-__version__ = '4.26.0'
+__version__ = '4.30.1'
__all__ = [
'baseclasses',
+ 'compatibility',
'constants',
'destinations',
'exceptions',
'filters',
+ 'imap_utf7',
'logging',
'message',
'retrievers',
|
[-]
[+]
|
Changed |
getmail-4.30.1.tar.bz2/getmailcore/_retrieverbases.py
^
|
@@ -52,13 +52,15 @@
except ImportError:
pass
-from getmailcore.exceptions import *
from getmailcore.compatibility import *
+from getmailcore.exceptions import *
from getmailcore.constants import *
from getmailcore.message import *
from getmailcore.utilities import *
from getmailcore._pop3ssl import POP3SSL, POP3_ssl_port
from getmailcore.baseclasses import *
+import getmailcore.imap_utf7 # registers imap4-utf-7 codec
+
NOT_ENVELOPE_RECIPIENT_HEADERS = (
'to',
@@ -83,27 +85,31 @@
# Kerberos authentication state constants
(GSS_STATE_STEP, GSS_STATE_WRAP) = (0, 1)
-#
-# Bugfix classes
-#
+# For matching imap LIST responses
+IMAP_LISTPARTS = re.compile(
+ r'^\s*'
+ r'\((?P<attributes>[^)]*)\)'
+ r'\s+'
+ r'"(?P<delimiter>[^"]+)"'
+ r'\s+'
+ # I *think* this should actually be a double-quoted string "like/this"
+ # but in testing we saw an MSexChange response that violated that
+ # expectation:
+ # (\HasNoChildren) "/" Calendar"
+ # i.e. the leading quote on the mailbox name was missing. The following
+ # works for both by treating the leading/trailing double-quote as optional,
+ # even when mismatched.
+ r'("?)(?P<mailbox>.+?)("?)'
+ r'\s*$'
+)
+
+
+# Constants used in socket module
+NO_OBJ = object()
+EAI_NONAME = getattr(socket, 'EAI_NONAME', NO_OBJ)
+EAI_NODATA = getattr(socket, 'EAI_NODATA', NO_OBJ)
+EAI_FAIL = getattr(socket, 'EAI_FAIL', NO_OBJ)
-if sys.version_info < (2, 5, 3):
- # A serious imaplib bug (http://bugs.python.org/issue1389051) was
- # fixed in 2.5.3. Earlier Python releases need a work-around.
- orig_imap4_ssl = imaplib.IMAP4_SSL
- class IMAP4_SSL(orig_imap4_ssl):
- def read(self, size):
- """Read 'size' bytes from remote."""
- # sslobj.read() sometimes returns < size bytes
- chunks = []
- read = 0
- while read < size:
- data = self.sslobj.read(min(size-read, 16384))
- read += len(data)
- chunks.append(data)
- return ''.join(chunks)
-
- imaplib.IMAP4_SSL = IMAP4_SSL
#
# Mix-in classes
@@ -121,7 +127,8 @@
except poplib.error_proto, o:
raise getmailOperationError('POP error (%s)' % o)
except socket.timeout:
- raise getmailOperationError('timeout during connect')
+ raise
+ #raise getmailOperationError('timeout during connect')
except socket.gaierror, o:
raise getmailOperationError(
'error resolving name %s during connect (%s)'
@@ -165,7 +172,8 @@
except poplib.error_proto, o:
raise getmailOperationError('POP error (%s)' % o)
except socket.timeout:
- raise getmailOperationError('timeout during connect')
+ #raise getmailOperationError('timeout during connect')
+ raise
except socket.gaierror, o:
raise getmailOperationError(
'error resolving name %s during connect (%s)'
@@ -210,7 +218,8 @@
except poplib.error_proto, o:
raise getmailOperationError('POP error (%s)' % o)
except socket.timeout:
- raise getmailOperationError('timeout during connect')
+ #raise getmailOperationError('timeout during connect')
+ raise
except socket.gaierror, o:
raise getmailOperationError('socket error during connect (%s)' % o)
except socket.sslerror, o:
@@ -233,13 +242,16 @@
except imaplib.IMAP4.error, o:
raise getmailOperationError('IMAP error (%s)' % o)
except socket.timeout:
- raise getmailOperationError('timeout during connect')
+ #raise getmailOperationError('timeout during connect')
+ raise
except socket.gaierror, o:
raise getmailOperationError('socket error during connect (%s)' % o)
self.log.trace('IMAP connection %s established' % self.conn
+ os.linesep)
+
+
#######################################
class IMAPSSLinitMixIn(object):
'''Mix-In class to do IMAP over SSL initialization.
@@ -274,9 +286,23 @@
except imaplib.IMAP4.error, o:
raise getmailOperationError('IMAP error (%s)' % o)
except socket.timeout:
- raise getmailOperationError('timeout during connect')
+ #raise getmailOperationError('timeout during connect')
+ raise
except socket.gaierror, o:
- raise getmailOperationError('socket error during connect (%s)' % o)
+ errcode = o[0]
+ if errcode in (EAI_NONAME, EAI_NODATA):
+ # No such DNS name
+ raise getmailDnsLookupError(
+ 'no address for %s (%s)' % (self.conf['server'], o)
+ )
+ elif errcode == EAI_FAIL:
+ # DNS server failure
+ raise getmailDnsServerFailure(
+ 'DNS server failure looking up address for %s (%s)'
+ % (self.conf['server'], o)
+ )
+ else:
+ raise getmailOperationError('socket error during connect (%s)' % o)
except socket.sslerror, o:
raise getmailOperationError(
'socket sslerror during connect (%s)' % o
@@ -345,20 +371,24 @@
'''
def __init__(self, **args):
- self.msgnum_by_msgid = {}
- self.msgid_by_msgnum = {}
- self.sorted_msgnum_msgid = ()
- self.msgsizes = {}
self.headercache = {}
- self.oldmail = {}
self.deleted = {}
- self.__delivered = {}
self.timestamp = int(time.time())
self.__oldmail_written = False
self.__initialized = False
self.gotmsglist = False
+ self._clear_state()
ConfigurableBase.__init__(self, **args)
+ def _clear_state(self):
+ self.msgnum_by_msgid = {}
+ self.msgid_by_msgnum = {}
+ self.sorted_msgnum_msgid = ()
+ self.msgsizes = {}
+ self.oldmail = {}
+ self.__delivered = {}
+ self.mailbox_selected = False
+
def setup_received(self, sock):
serveraddr = sock.getpeername()
if len(serveraddr) == 2:
@@ -374,14 +404,16 @@
self.received_from = '%s (%s)' % (self.conf['server'],
self.remoteaddr)
- def __del__(self):
- self.log.trace()
- self.write_oldmailfile()
-
def __str__(self):
self.log.trace()
return str(self.conf)
+ def list_mailboxes(self):
+ raise NotImplementedError('virtual')
+
+ def select_mailbox(self, mailbox):
+ raise NotImplementedError('virtual')
+
def __len__(self):
self.log.trace()
return len(self.msgnum_by_msgid)
@@ -392,106 +424,93 @@
raise getmailOperationError('not initialized')
return self.sorted_msgnum_msgid[i][1]
- def _read_oldmailfile(self):
- '''Read contents of oldmail file.'''
- self.log.trace()
- # Read separate oldmail file for each folder (IMAP).
- mailboxes = self.conf.get('mailboxes', (None, ))
- for mailbox in mailboxes:
- filename = self.oldmail_filename
- mailbox_logstr = ''
- if mailbox:
- mailbox_logstr = 'mailbox %s in ' % mailbox
- filename += '-' + re.sub(STRIP_CHAR_RE, '.', mailbox)
- if not os.path.isfile(filename):
- # use existing oldmail file, to enable smooth migration to
- # oldmail filenames with appended mailbox name
- self.log.info(
- 'oldmail file %s not found, reverting to %s%s'
- % (filename, self.oldmail_filename, os.linesep)
- )
- filename = self.oldmail_filename
+ def _oldmail_filename(self, mailbox):
+ assert (mailbox is None
+ or (isinstance(mailbox, (str, unicode)) and mailbox)), (
+ 'bad mailbox %s (%s)' % (mailbox, type(mailbox))
+ )
+ if isinstance(mailbox, str):
+ mailbox = mailbox.decode('utf-8')
+ filename = self.oldmail_filename
+ if mailbox is None:
+ # No mailbox (POP), use above with no extension
+ pass
+ else:
+ # Use oldmail file per IMAP folder
+ filename += '-' + re.sub(STRIP_CHAR_RE, '.', mailbox)
+ return filename
+
+ def oldmail_exists(self, mailbox):
+ '''Test whether an oldmail file exists for a specified mailbox.'''
+ return os.path.isfile(self._oldmail_filename(mailbox))
+
+ def read_oldmailfile(self, mailbox):
+ '''Read contents of an oldmail file. For POP, mailbox must be
+ explicitly None.
+ '''
+ assert not self.oldmail, (
+ 'still have %d unflushed oldmail' % len(self.oldmail)
+ )
+ self.log.trace('mailbox=%s' % mailbox)
+
+ filename = self._oldmail_filename(mailbox)
+ logname = '%s:%s' % (self, mailbox or '')
+ try:
+ f = open(filename, 'rb')
+ except IOError:
+ self.log.moreinfo('no oldmail file for %s%s'
+ % (logname, os.linesep))
+ return
+
+ for line in f:
+ line = line.strip()
+ if not line or not '\0' in line:
+ # malformed
+ continue
try:
- oldlength = len(self.oldmail)
- for line in open(filename, 'rb'):
- line = line.strip()
- if not line or not '\0' in line:
- # malformed
- continue
- try:
- (msgid, timestamp) = line.split('\0', 1)
- self.oldmail[msgid] = int(timestamp)
- except ValueError:
- # malformed
- self.log.info(
- 'skipped malformed line "%r" for %s%s'
- % (line, self, os.linesep)
- )
- self.log.moreinfo(
- 'read %i uids for %s%s%s'
- % (len(self.oldmail) - oldlength, mailbox_logstr, self,
- os.linesep)
+ (msgid, timestamp) = line.split('\0', 1)
+ self.oldmail[msgid] = int(timestamp)
+ except ValueError:
+ # malformed
+ self.log.info(
+ 'skipped malformed line "%r" for %s%s'
+ % (line, logname, os.linesep)
)
- except IOError:
- self.log.moreinfo('no oldmail file for %s%s%s'
- % (mailbox_logstr, self, os.linesep))
+ self.log.moreinfo(
+ 'read %i uids for %s%s'
+ % (len(self.oldmail), logname, os.linesep)
+ )
self.log.moreinfo('read %i uids in total for %s%s'
- % (len(self.oldmail), self, os.linesep))
+ % (len(self.oldmail), logname, os.linesep))
- def write_oldmailfile(self, forget_deleted=True):
+ def write_oldmailfile(self, mailbox):
'''Write oldmail info to oldmail file.'''
- self.log.trace()
- if (self.__oldmail_written or not self.__initialized
- or not self.gotmsglist):
- return
- mailboxes = self.conf.get('mailboxes', (None,))
- wrote = {}
- for mailbox in mailboxes:
- wrote[mailbox] = 0
- f = {}
- try:
- # Write each folders msgids/data to a separate oldmail file.
- for mailbox in mailboxes:
- filename = self.oldmail_filename
- if mailbox:
- filename += '-' + re.sub(STRIP_CHAR_RE, '.', mailbox)
- f[mailbox] = updatefile(filename)
- msgids = frozenset(
- self.__delivered.keys()
- ).union(frozenset(self.oldmail.keys()))
+ self.log.trace('mailbox=%s' % mailbox)
+
+ filename = self._oldmail_filename(mailbox)
+ logname = '%s:%s' % (self, mailbox or '')
+
+ oldmailfile = None
+ wrote = 0
+ msgids = frozenset(
+ self.__delivered.keys()
+ ).union(frozenset(self.oldmail.keys()))
+ try:
+ oldmailfile = updatefile(filename)
for msgid in msgids:
self.log.debug('msgid %s ...' % msgid)
- if forget_deleted and msgid in self.deleted:
- # Already deleted, don't remember this one
- self.log.debug(' was deleted, skipping' + os.linesep)
- continue
- # else:
- # not deleted, remember this one's time stamp
t = self.oldmail.get(msgid, self.timestamp)
self.log.debug(' timestamp %s' % t + os.linesep)
- mailbox = None
- match = re.match('^\d+/(.+?)/\d+$', msgid)
- if match:
- mailbox = match.group(1)
- if mailbox in mailboxes:
- f[mailbox].write('%s\0%i%s' % (msgid, t, os.linesep))
- wrote[mailbox] += 1
- else:
- self.log.info('mailbox %s unknown for msgid %s%s'
- % (mailbox, msgid, os.linesep))
- for mailbox in mailboxes:
- f[mailbox].close()
- mailbox_logstr = ''
- if mailbox != None:
- mailbox_logstr = 'mailbox %s in ' % mailbox
- self.log.moreinfo('wrote %i uids for %s%s%s'
- % (wrote[mailbox], mailbox_logstr, self,
- os.linesep))
+ oldmailfile.write('%s\0%i%s' % (msgid, t, os.linesep))
+ wrote += 1
+ oldmailfile.close()
+ self.log.moreinfo('wrote %i uids for %s%s'
+ % (wrote, logname, os.linesep))
except IOError, o:
self.log.error('failed writing oldmail file for %s (%s)'
- % (self, o) + os.linesep)
- for file in f:
- file.abort()
+ % (logname, o) + os.linesep)
+ if oldmailfile:
+ oldmailfile.abort()
self.__oldmail_written = True
def initialize(self, options):
@@ -505,6 +524,8 @@
else:
# Explicitly set to None in case it was previously set
socket.setdefaulttimeout(None)
+
+ # Construct base filename for oldmail files.
# strip problematic characters from oldmail filename. Mostly for
# non-Unix systems; only / is illegal in a Unix path component
oldmail_filename = re.sub(
@@ -513,13 +534,22 @@
)
self.oldmail_filename = os.path.join(self.conf['getmaildir'],
oldmail_filename)
- self._read_oldmailfile()
+
self.received_from = None
-
self.app_options = options
-
self.__initialized = True
+ def quit(self):
+ if self.mailbox_selected is not False:
+ self.write_oldmailfile(self.mailbox_selected)
+ self._clear_state()
+
+ def abort(self):
+ '''On error conditions where you do not want modified state to be saved,
+ call this before .quit().
+ '''
+ self._clear_state()
+
def delivered(self, msgid):
self.__delivered[msgid] = None
@@ -557,8 +587,20 @@
RetrieverSkeleton.__init__(self, **args)
self.log.trace()
- def __del__(self):
- RetrieverSkeleton.__del__(self)
+ def select_mailbox(self, mailbox):
+ assert mailbox is None, (
+ 'POP does not support mailbox selection (%s)' % mailbox
+ )
+ if self.mailbox_selected is not False:
+ self.write_oldmailfile(self.mailbox_selected)
+
+ self._clear_state()
+
+ if self.oldmail_exists(mailbox):
+ self.read_oldmailfile(mailbox)
+ self.mailbox_selected = mailbox
+
+ self._getmsglist()
def _getmsgnumbyid(self, msgid):
self.log.trace()
@@ -614,6 +656,19 @@
# response above. Ignore it and we'll get it next time.
if msgid is not None:
self.msgsizes[msgid] = msgsize
+
+ # Remove messages from state file that are no longer in mailbox,
+ # but only if the timestamp for them are old (30 days for now).
+ # This is because IMAP users can have one state file but multiple
+ # IMAP folders in different configuration rc files.
+ for msgid in self.oldmail.keys():
+ timestamp = self.oldmail[msgid]
+ age = self.timestamp - timestamp
+ if not self.msgsizes.has_key(msgid) and age > VANISHED_AGE:
+ self.log.debug('removing vanished old message id %s' % msgid
+ + os.linesep)
+ del self.oldmail[msgid]
+
except poplib.error_proto, o:
raise getmailOperationError(
'POP error (%s) - if your server does not support the UIDL '
@@ -652,6 +707,8 @@
def initialize(self, options):
self.log.trace()
+ # POP doesn't support different mailboxes
+ self.mailboxes = (None, )
# Handle password
if self.conf.get('password', None) is None:
self.conf['password'] = get_password(
@@ -681,6 +738,7 @@
def abort(self):
self.log.trace()
+ RetrieverSkeleton.abort(self)
try:
self.conn.rset()
self.conn.quit()
@@ -689,8 +747,8 @@
del self.conn
def quit(self):
+ RetrieverSkeleton.quit(self)
self.log.trace()
- self.write_oldmailfile()
if not getattr(self, 'conn', None):
return
try:
@@ -768,12 +826,23 @@
def __init__(self, **args):
RetrieverSkeleton.__init__(self, **args)
self.log.trace()
- self.mailbox = None
- self.uidvalidity = None
self.gss_step = 0
self.gss_vc = None
self.gssapi = False
+ def _clear_state(self):
+ RetrieverSkeleton._clear_state(self)
+ self.mailbox = None
+ self.uidvalidity = None
+ self.msgnum_by_msgid = {}
+ self.msgid_by_msgnum = {}
+ self.sorted_msgnum_msgid = ()
+ self._mboxuids = {}
+ self._mboxuidorder = []
+ self.msgsizes = {}
+ self.oldmail = {}
+ self.__delivered = {}
+
def checkconf(self):
RetrieverSkeleton.checkconf(self)
if self.conf['use_kerberos'] and not HAVE_KERBEROS_GSS:
@@ -805,23 +874,24 @@
if not response:
response = ''
return response.decode('base64')
-
- def __del__(self):
- RetrieverSkeleton.__del__(self)
-
+
def _getmboxuidbymsgid(self, msgid):
self.log.trace()
if not msgid in self.msgnum_by_msgid:
raise getmailOperationError('no such message ID %s' % msgid)
- mailbox, uid = self._mboxuids[msgid]
- return mailbox, uid
+ uid = self._mboxuids[msgid]
+ return uid
def _parse_imapcmdresponse(self, cmd, *args):
self.log.trace()
try:
result, resplist = getattr(self.conn, cmd)(*args)
except imaplib.IMAP4.error, o:
- raise getmailOperationError('IMAP error (%s)' % o)
+ if cmd == 'login':
+ # Percolate up
+ raise
+ else:
+ raise getmailOperationError('IMAP error (%s)' % o)
if result != 'OK':
raise getmailOperationError(
'IMAP error (command %s returned %s %s)'
@@ -842,7 +912,11 @@
try:
result, resplist = self.conn.uid(cmd, *args)
except imaplib.IMAP4.error, o:
- raise getmailOperationError('IMAP error (%s)' % o)
+ if cmd == 'login':
+ # Percolate up
+ raise
+ else:
+ raise getmailOperationError('IMAP error (%s)' % o)
if result != 'OK':
raise getmailOperationError(
'IMAP error (command %s returned %s %s)'
@@ -879,25 +953,64 @@
self.log.trace('got %s' % r + os.linesep)
return r
- def _selectmailbox(self, mailbox):
+ def list_mailboxes(self):
+ '''List (selectable) IMAP folders in account.'''
+ mailboxes = []
+ cmd = ('LIST', )
+ resplist = self._parse_imapcmdresponse(*cmd)
+ for item in resplist:
+ m = IMAP_LISTPARTS.match(item)
+ if not m:
+ raise getmailOperationError(
+ 'no match for list response "%s"' % item
+ )
+ g = m.groupdict()
+ attributes = g['attributes'].split()
+ if r'\Noselect' in attributes:
+ # Can't select this mailbox, don't include it in output
+ continue
+ try:
+ mailbox = g['mailbox'].decode('imap4-utf-7')
+ mailboxes.append(mailbox)
+ #log.debug(u'%20s : delimiter %s, attributes: %s',
+ # mailbox, g['delimiter'], ', '.join(attributes))
+ except Exception, o:
+ raise getmailOperationError('error decoding mailbox "%s"'
+ % g['mailbox'])
+ return mailboxes
+
+ def select_mailbox(self, mailbox):
self.log.trace()
- if mailbox == self.mailbox:
- return
- if self.mailbox is not None:
+ assert mailbox in self.mailboxes, (
+ 'mailbox not in config (%s)' % mailbox
+ )
+ if self.mailbox_selected is not False:
# Close current mailbox so deleted mail is expunged.
# Except one user reports that an explicit expunge is needed with
# his server, or else the mail is never removed from the mailbox.
self.conn.expunge()
self.conn.close()
+ self.write_oldmailfile(self.mailbox_selected)
+
+ self._clear_state()
+
+ if self.oldmail_exists(mailbox):
+ self.read_oldmailfile(mailbox)
+
self.log.debug('selecting mailbox "%s"' % mailbox + os.linesep)
try:
- #response = self._parse_imapcmdresponse('SELECT', mailbox)
- #count = int(response[-1]) # use *last* EXISTS returned
if self.app_options['delete'] or self.app_options['delete_after']:
read_only = False
else:
read_only = True
- status, count = self.conn.select(mailbox, read_only)
+ (status, count) = self.conn.select(mailbox.encode('imap4-utf-7'),
+ read_only)
+ if status == 'NO':
+ # Specified mailbox doesn't exist, no permissions, etc.
+ raise getmailMailboxSelectError(mailbox)
+
+ self.mailbox_selected = mailbox
+ # use *last* EXISTS returned
count = int(count[-1])
uidvalidity = self.conn.response('UIDVALIDITY')[1][0]
except imaplib.IMAP4.error, o:
@@ -911,34 +1024,43 @@
% (mailbox, count) + os.linesep)
self.mailbox = mailbox
self.uidvalidity = uidvalidity
+
+ self._getmsglist(count)
+
return count
- def _getmsglist(self):
+ def _getmsglist(self, msgcount):
self.log.trace()
- self.msgnum_by_msgid = {}
- self._mboxuids = {}
- self._mboxuidorder = []
- self.msgsizes = {}
- for mailbox in self.conf['mailboxes']:
- try:
- # Get number of messages in mailbox
- msgcount = self._selectmailbox(mailbox)
- if msgcount:
- # Get UIDs and sizes for all messages in mailbox
- response = self._parse_imapcmdresponse(
- 'FETCH', '1:%d' % msgcount, '(UID RFC822.SIZE)'
+ try:
+ if msgcount:
+ # Get UIDs and sizes for all messages in mailbox
+ response = self._parse_imapcmdresponse(
+ 'FETCH', '1:%d' % msgcount, '(UID RFC822.SIZE)'
+ )
+ for line in response:
+ r = self._parse_imapattrresponse(line)
+ msgid = (
+ '%s/%s' % (self.uidvalidity, r['uid'])
)
- for line in response:
- r = self._parse_imapattrresponse(line)
- msgid = (
- '%s/%s/%s' % (self.uidvalidity, mailbox, r['uid'])
- )
- self._mboxuids[msgid] = (mailbox, r['uid'])
- self._mboxuidorder.append(msgid)
- self.msgnum_by_msgid[msgid] = None
- self.msgsizes[msgid] = int(r['rfc822.size'])
- except imaplib.IMAP4.error, o:
- raise getmailOperationError('IMAP error (%s)' % o)
+ self._mboxuids[msgid] = r['uid']
+ self._mboxuidorder.append(msgid)
+ self.msgnum_by_msgid[msgid] = None
+ self.msgsizes[msgid] = int(r['rfc822.size'])
+
+ # Remove messages from state file that are no longer in mailbox,
+ # but only if the timestamp for them are old (30 days for now).
+ # This is because IMAP users can have one state file but multiple
+ # IMAP folders in different configuration rc files.
+ for msgid in self.oldmail.keys():
+ timestamp = self.oldmail[msgid]
+ age = self.timestamp - timestamp
+ if not self.msgsizes.has_key(msgid) and age > VANISHED_AGE:
+ self.log.debug('removing vanished old message id %s' % msgid
+ + os.linesep)
+ del self.oldmail[msgid]
+
+ except imaplib.IMAP4.error, o:
+ raise getmailOperationError('IMAP error (%s)' % o)
self.gotmsglist = True
def __getitem__(self, i):
@@ -947,8 +1069,8 @@
def _delmsgbyid(self, msgid):
self.log.trace()
try:
- mailbox, uid = self._getmboxuidbymsgid(msgid)
- self._selectmailbox(mailbox)
+ uid = self._getmboxuidbymsgid(msgid)
+ #self._selectmailbox(mailbox)
# Delete message
if self.conf['move_on_delete']:
self.log.debug('copying message to folder "%s"'
@@ -966,8 +1088,7 @@
def _getmsgpartbyid(self, msgid, part):
self.log.trace()
try:
- mailbox, uid = self._getmboxuidbymsgid(msgid)
- self._selectmailbox(mailbox)
+ uid = self._getmboxuidbymsgid(msgid)
# Retrieve message
self.log.debug('retrieving body for message "%s"' % uid
+ os.linesep)
@@ -1022,6 +1143,7 @@
def initialize(self, options):
self.log.trace()
+ self.mailboxes = self.conf.get('mailboxes', ('INBOX', ))
# Handle password
if (self.conf.get('password', None) is None
and not (HAVE_KERBEROS_GSS and self.conf['use_kerberos'])):
@@ -1034,17 +1156,25 @@
try:
self.log.trace('trying self._connect()' + os.linesep)
self._connect()
- self.log.trace('logging in' + os.linesep)
- if self.conf['use_kerberos'] and HAVE_KERBEROS_GSS:
- self.conn.authenticate('GSSAPI', self.gssauth)
- elif self.conf['use_cram_md5']:
- self._parse_imapcmdresponse(
- 'login_cram_md5', self.conf['username'],
- self.conf['password']
- )
- else:
- self._parse_imapcmdresponse('login', self.conf['username'],
- self.conf['password'])
+ try:
+ self.log.trace('logging in' + os.linesep)
+ if self.conf['use_kerberos'] and HAVE_KERBEROS_GSS:
+ self.conn.authenticate('GSSAPI', self.gssauth)
+ elif self.conf['use_cram_md5']:
+ self._parse_imapcmdresponse(
+ 'login_cram_md5', self.conf['username'],
+ self.conf['password']
+ )
+ else:
+ self._parse_imapcmdresponse('login', self.conf['username'],
+ self.conf['password'])
+ except imaplib.IMAP4.abort, o:
+ raise getmailLoginRefusedError(o)
+ except imaplib.IMAP4.error, o:
+ raise getmailCredentialError(o)
+
+ self.log.trace('logged in' + os.linesep)
+ """
self.log.trace('logged in, getting message list' + os.linesep)
self._getmsglist()
self.log.debug('msgids: %s'
@@ -1061,11 +1191,18 @@
self.log.debug('removing vanished old message id %s' % msgid
+ os.linesep)
del self.oldmail[msgid]
+ """
+
+ if self.mailboxes == ('ALL', ):
+ # Special value meaning all mailboxes in account
+ self.mailboxes = tuple(self.list_mailboxes())
+
except imaplib.IMAP4.error, o:
raise getmailOperationError('IMAP error (%s)' % o)
def abort(self):
self.log.trace()
+ RetrieverSkeleton.abort(self)
try:
self.quit()
except (imaplib.IMAP4.error, socket.error), o:
@@ -1073,16 +1210,22 @@
def quit(self):
self.log.trace()
- self.write_oldmailfile()
if not getattr(self, 'conn', None):
return
try:
- self.conn.close()
+ if self.mailbox_selected is not False:
+ # Close current mailbox so deleted mail is expunged.
+ # Except one user reports that an explicit expunge is needed
+ # with his server, or else the mail is never removed from the
+ # mailbox.
+ self.conn.expunge()
+ self.conn.close()
self.conn.logout()
self.conn = None
except imaplib.IMAP4.error, o:
#raise getmailOperationError('IMAP error (%s)' % o)
self.log.warning('IMAP error during logout (%s)' % o + os.linesep)
+ RetrieverSkeleton.quit(self)
#######################################
class MultidropIMAPRetrieverBase(IMAPRetrieverBase):
|
[-]
[+]
|
Changed |
getmail-4.30.1.tar.bz2/getmailcore/baseclasses.py
^
|
@@ -11,6 +11,7 @@
'ConfBool',
'ConfInt',
'ConfTupleOfStrings',
+ 'ConfTupleOfUnicode',
'ConfTupleOfTupleOfStrings',
'ConfPassword',
'ConfDirectory',
@@ -114,6 +115,40 @@
result = [str(item) for item in val]
return tuple(result)
+class ConfTupleOfUnicode(ConfString):
+ def __init__(self, name, default=None, required=True, allow_specials=()):
+ ConfString.__init__(self, name, default=default, required=required)
+ self.specials = allow_specials
+
+ def validate(self, configuration):
+ _locals = dict([(v, v) for v in self.specials])
+ val = ConfItem.validate(self, configuration)
+ try:
+ if not val:
+ val = '()'
+ tup = eval(val, {}, _locals)
+ if tup in self.specials:
+ val = [tup]
+ else:
+ if type(tup) != tuple:
+ raise ValueError('not a tuple')
+ vals = []
+ for item in tup:
+ item = str(item)
+ try:
+ vals.append(item.decode('ascii'))
+ except UnicodeError, o:
+ try:
+ vals.append(item.decode('utf-8'))
+ except UnicodeError, o:
+ raise ValueError('not ascii or utf-8: %s' % item)
+ val = vals
+ except (ValueError, SyntaxError), o:
+ raise getmailConfigurationError(
+ '%s: incorrect format (%s)' % (self.name, o)
+ )
+ return tuple(val)
+
class ConfTupleOfTupleOfStrings(ConfString):
def __init__(self, name, default=None, required=True):
ConfString.__init__(self, name, default=default, required=required)
|
[-]
[+]
|
Changed |
getmail-4.30.1.tar.bz2/getmailcore/compatibility.py
^
|
@@ -9,8 +9,11 @@
]
import sys
+import imaplib
+import new
-if sys.hexversion < 0x2040000:
+
+if sys.version_info < (2, 4, 0):
# set/frozenset not built-in until Python 2.4
import sets
set = sets.Set
@@ -18,3 +21,59 @@
set = set
frozenset = frozenset
+
+if sys.version_info < (2, 5, 0):
+ # Python < 2.5.0 has a bug with the readonly flag on imaplib's select().
+ # Monkey-patch it in.
+
+ def py25_select(self, mailbox='INBOX', readonly=False):
+ """Select a mailbox.
+
+ Flush all untagged responses.
+
+ (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
+
+ 'data' is count of messages in mailbox ('EXISTS' response).
+
+ Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
+ other responses should be obtained via <instance>.response('FLAGS') etc.
+ """
+ self.untagged_responses = {} # Flush old responses.
+ self.is_readonly = readonly
+ if readonly:
+ name = 'EXAMINE'
+ else:
+ name = 'SELECT'
+ typ, dat = self._simple_command(name, mailbox)
+ if typ != 'OK':
+ self.state = 'AUTH' # Might have been 'SELECTED'
+ return typ, dat
+ self.state = 'SELECTED'
+ if 'READ-ONLY' in self.untagged_responses \
+ and not readonly:
+ if __debug__:
+ if self.debug >= 1:
+ self._dump_ur(self.untagged_responses)
+ raise self.readonly('%s is not writable' % mailbox)
+ return typ, self.untagged_responses.get('EXISTS', [None])
+
+ imaplib.IMAP4.select = new.instancemethod(py25_select, None, imaplib.IMAP4)
+
+
+if sys.version_info < (2, 5, 3):
+ # A serious imaplib bug (http://bugs.python.org/issue1389051) was
+ # fixed in 2.5.3. Earlier Python releases need a work-around.
+ # Monkey-patch it in.
+ def fixed_read(self, size):
+ """Read 'size' bytes from remote."""
+ # sslobj.read() sometimes returns < size bytes
+ chunks = []
+ read = 0
+ while read < size:
+ data = self.sslobj.read(min(size-read, 16384))
+ read += len(data)
+ chunks.append(data)
+ return ''.join(chunks)
+
+ imaplib.IMAP4_SSL.read = new.instancemethod(fixed_read, None,
+ imaplib.IMAP4_SSL)
|
[-]
[+]
|
Changed |
getmail-4.30.1.tar.bz2/getmailcore/exceptions.py
^
|
@@ -5,10 +5,15 @@
__all__ = [
'getmailError',
'getmailConfigurationError',
+ 'getmailDnsLookupError',
+ 'getmailDnsServerFailure',
'getmailOperationError',
'getmailFilterError',
'getmailRetrievalError',
'getmailDeliveryError',
+ 'getmailCredentialError',
+ 'getmailLoginRefusedError',
+ 'getmailMailboxSelectError',
]
# Base class for all getmail exceptions
@@ -41,3 +46,32 @@
Subclass of getmailOperationError.
'''
pass
+
+class getmailDnsError(getmailOperationError):
+ '''Base class for errors looking up hosts in DNS to connect to.'''
+ pass
+
+class getmailDnsLookupError(getmailDnsError):
+ '''No such DNS name, or name found but no address records for it.'''
+ pass
+
+class getmailDnsServerFailure(getmailDnsError):
+ '''DNS server failed when trying to look up name.'''
+ pass
+
+class getmailCredentialError(getmailOperationError):
+ '''Error raised when server says "bad password", "no such user", etc
+ (when that is possible to detect).'''
+ pass
+
+class getmailLoginRefusedError(getmailOperationError):
+ '''Error raised when the server is just refusing logins due to reasons
+ other than credential problems (when that is possible to detect): server
+ too busy, service shutting down, etc.'''
+ pass
+
+class getmailMailboxSelectError(getmailOperationError):
+ '''Error raised when the server responds NO to an (IMAP) select mailbox
+ command -- no such mailbox, no permissions, etc.
+ '''
+ pass
|
[-]
[+]
|
Added |
getmail-4.30.1.tar.bz2/getmailcore/imap_utf7.py
^
|
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+"""
+Modified utf-7 encoding as used in IMAP v4r1 for encoding mailbox names.
+Code from here; couldn't find a license statement:
+http://www.koders.com/python/fid744B4E448B1689C0963942A7928FA049084FAC86.aspx
+
+From the RFC:
+
+5.1.3. Mailbox International Naming Convention
+
+ By convention, international mailbox names are specified using a
+ modified version of the UTF-7 encoding described in [UTF-7]. The
+ purpose of these modifications is to correct the following problems
+ with UTF-7:
+
+ 1) UTF-7 uses the "+" character for shifting; this conflicts with
+ the common use of "+" in mailbox names, in particular USENET
+ newsgroup names.
+
+ 2) UTF-7's encoding is BASE64 which uses the "/" character; this
+ conflicts with the use of "/" as a popular hierarchy delimiter.
+
+ 3) UTF-7 prohibits the unencoded usage of "\"; this conflicts with
+ the use of "\" as a popular hierarchy delimiter.
+
+ 4) UTF-7 prohibits the unencoded usage of "~"; this conflicts with
+ the use of "~" in some servers as a home directory indicator.
+
+ 5) UTF-7 permits multiple alternate forms to represent the same
+ string; in particular, printable US-ASCII chararacters can be
+ represented in encoded form.
+
+ In modified UTF-7, printable US-ASCII characters except for "&"
+ represent themselves; that is, characters with octet values 0x20-0x25
+ and 0x27-0x7e. The character "&" (0x26) is represented by the two-
+ octet sequence "&-".
+
+ All other characters (octet values 0x00-0x1f, 0x7f-0xff, and all
+ Unicode 16-bit octets) are represented in modified BASE64, with a
+ further modification from [UTF-7] that "," is used instead of "/".
+ Modified BASE64 MUST NOT be used to represent any printing US-ASCII
+ character which can represent itself.
+
+ "&" is used to shift to modified BASE64 and "-" to shift back to US-
+ ASCII. All names start in US-ASCII, and MUST end in US-ASCII (that
+ is, a name that ends with a Unicode 16-bit octet MUST end with a "-
+ ").
+"""
+import binascii
+import codecs
+
+#
+# encoding
+#
+def modified_base64(s):
+ s = s.encode('utf-16be')
+ return binascii.b2a_base64(s).rstrip('\n=').replace('/', ',')
+
+def doB64(_in, r):
+ if _in:
+ r.append('&%s-' % modified_base64(''.join(_in)))
+ del _in[:]
+
+def encoder(s):
+ r = []
+ _in = []
+ for c in s:
+ ordC = ord(c)
+ if 0x20 <= ordC <= 0x25 or 0x27 <= ordC <= 0x7e:
+ doB64(_in, r)
+ r.append(c)
+ elif c == '&':
+ doB64(_in, r)
+ r.append('&-')
+ else:
+ _in.append(c)
+ doB64(_in, r)
+ return (str(''.join(r)), len(s))
+
+#
+# decoding
+#
+def modified_unbase64(s):
+ b = binascii.a2b_base64(s.replace(',', '/') + '===')
+ return unicode(b, 'utf-16be')
+
+def decoder(s):
+ r = []
+ decode = []
+ for c in s:
+ if c == '&' and not decode:
+ decode.append('&')
+ elif c == '-' and decode:
+ if len(decode) == 1:
+ r.append('&')
+ else:
+ r.append(modified_unbase64(''.join(decode[1:])))
+ decode = []
+ elif decode:
+ decode.append(c)
+ else:
+ r.append(c)
+ if decode:
+ r.append(modified_unbase64(''.join(decode[1:])))
+ bin_str = ''.join(r)
+ return (bin_str, len(s))
+
+
+class StreamReader(codecs.StreamReader):
+ def decode(self, s, errors='strict'):
+ return decoder(s)
+
+
+class StreamWriter(codecs.StreamWriter):
+ def decode(self, s, errors='strict'):
+ return encoder(s)
+
+
+def imap4_utf_7(name):
+ if name == 'imap4-utf-7':
+ return (encoder, decoder, StreamReader, StreamWriter)
+codecs.register(imap4_utf_7)
+
|
[-]
[+]
|
Changed |
getmail-4.30.1.tar.bz2/getmailcore/retrievers.py
^
|
@@ -134,7 +134,7 @@
duplicated IDs are always treated as new messages.'''
self.log.trace()
- def write_oldmailfile(self, **kwargs):
+ def write_oldmailfile(self, unused, **kwargs):
'''Short-circuit writing the oldmail file.'''
self.log.trace()
@@ -356,8 +356,9 @@
ConfInt(name='port', required=False, default=imaplib.IMAP4_PORT),
ConfString(name='username'),
ConfPassword(name='password', required=False, default=None),
- ConfTupleOfStrings(name='mailboxes', required=False,
- default="('INBOX', )"),
+ ConfTupleOfUnicode(name='mailboxes', required=False,
+ default="('INBOX', )", allow_specials=('ALL',)),
+ ConfBool(name='use_peek', required=False, default=True),
ConfString(name='move_on_delete', required=False, default=None),
# imaplib.IMAP4.login_cram_md5() requires the (unimplemented)
# .authenticate(), so we can't do this yet (?).
@@ -395,8 +396,9 @@
ConfInt(name='port', required=False, default=imaplib.IMAP4_SSL_PORT),
ConfString(name='username'),
ConfPassword(name='password', required=False, default=None),
- ConfTupleOfStrings(name='mailboxes', required=False,
- default="('INBOX', )"),
+ ConfTupleOfUnicode(name='mailboxes', required=False,
+ default="('INBOX', )", allow_specials=('ALL',)),
+ ConfBool(name='use_peek', required=False, default=True),
ConfString(name='move_on_delete', required=False, default=None),
ConfFile(name='keyfile', required=False, default=None),
ConfFile(name='certfile', required=False, default=None),
@@ -435,8 +437,9 @@
ConfInt(name='port', required=False, default=imaplib.IMAP4_PORT),
ConfString(name='username'),
ConfPassword(name='password', required=False, default=None),
- ConfTupleOfStrings(name='mailboxes', required=False,
- default="('INBOX', )"),
+ ConfTupleOfUnicode(name='mailboxes', required=False,
+ default="('INBOX', )", allow_specials=('ALL',)),
+ ConfBool(name='use_peek', required=False, default=True),
ConfString(name='move_on_delete', required=False, default=None),
# imaplib.IMAP4.login_cram_md5() requires the (unimplemented)
# .authenticate(), so we can't do this yet (?).
@@ -475,8 +478,9 @@
ConfInt(name='port', required=False, default=imaplib.IMAP4_SSL_PORT),
ConfString(name='username'),
ConfPassword(name='password', required=False, default=None),
- ConfTupleOfStrings(name='mailboxes', required=False,
- default="('INBOX', )"),
+ ConfTupleOfUnicode(name='mailboxes', required=False,
+ default="('INBOX', )", allow_specials=('ALL',)),
+ ConfBool(name='use_peek', required=False, default=True),
ConfString(name='move_on_delete', required=False, default=None),
ConfFile(name='keyfile', required=False, default=None),
ConfFile(name='certfile', required=False, default=None),
|