From 36bfe4238a6fe8c986579c27238ec4327ca72549 Mon Sep 17 00:00:00 2001 From: Nick Bebout Date: Sat, 12 Mar 2011 02:44:43 +0000 Subject: [PATCH] 1.267 --- BUG_IMAPClient_3.xx | 4 + ChangeLog | 20 +- Mail-IMAPClient-3.10/COPYRIGHT | 401 + Mail-IMAPClient-3.10/Changes | 1720 ++ Mail-IMAPClient-3.10/INSTALL | 82 + Mail-IMAPClient-3.10/MANIFEST | 39 + Mail-IMAPClient-3.10/META.yml | 26 + Mail-IMAPClient-3.10/Makefile.PL | 111 + Mail-IMAPClient-3.10/README | 111 + Mail-IMAPClient-3.10/TODO | 68 + Mail-IMAPClient-3.10/examples/build_dist.pl | 172 + Mail-IMAPClient-3.10/examples/build_ldif.pl | 235 + Mail-IMAPClient-3.10/examples/cleanTest.pl | 64 + Mail-IMAPClient-3.10/examples/copy_folder.pl | 147 + Mail-IMAPClient-3.10/examples/cyrus_expire.pl | 111 + .../examples/cyrus_expunge.pl | 85 + .../examples/find_dup_msgs.pl | 217 + Mail-IMAPClient-3.10/examples/imap_to_mbox.pl | 217 + .../examples/imtestExample.pl | 226 + .../examples/migrate_mail2.pl | 326 + Mail-IMAPClient-3.10/examples/migrate_mbox.pl | 131 + .../examples/populate_mailbox.pl | 319 + Mail-IMAPClient-3.10/examples/sharedFolder.pl | 88 + Mail-IMAPClient-3.10/lib/Mail/IMAPClient.pm | 2827 +++ Mail-IMAPClient-3.10/lib/Mail/IMAPClient.pod | 3723 ++++ .../lib/Mail/IMAPClient/BodyStructure.pm | 557 + .../IMAPClient/BodyStructure/Parse.grammar | 188 + .../Mail/IMAPClient/BodyStructure/Parse.pm | 16425 ++++++++++++++++ .../Mail/IMAPClient/BodyStructure/Parse.pod | 15 + .../lib/Mail/IMAPClient/MessageSet.pm | 280 + .../lib/Mail/IMAPClient/Thread.grammar | 18 + .../lib/Mail/IMAPClient/Thread.pm | 1014 + .../lib/Mail/IMAPClient/Thread.pod | 14 + Mail-IMAPClient-3.10/prepare_dist | 37 + Mail-IMAPClient-3.10/sample.perldb | 1 + Mail-IMAPClient-3.10/t/basic.t | 303 + Mail-IMAPClient-3.10/t/bodystructure.t | 53 + Mail-IMAPClient-3.10/t/messageset.t | 32 + Mail-IMAPClient-3.10/t/pod.t | 9 + Mail-IMAPClient-3.10/t/thread.t | 31 + Mail-IMAPClient-3.10/test_template.txt | 5 + README | 4 +- TODO | 14 +- VERSION | 2 +- imapsync | 55 +- tests.sh | 202 +- 46 files changed, 30656 insertions(+), 73 deletions(-) create mode 100644 Mail-IMAPClient-3.10/COPYRIGHT create mode 100644 Mail-IMAPClient-3.10/Changes create mode 100644 Mail-IMAPClient-3.10/INSTALL create mode 100644 Mail-IMAPClient-3.10/MANIFEST create mode 100644 Mail-IMAPClient-3.10/META.yml create mode 100644 Mail-IMAPClient-3.10/Makefile.PL create mode 100644 Mail-IMAPClient-3.10/README create mode 100644 Mail-IMAPClient-3.10/TODO create mode 100755 Mail-IMAPClient-3.10/examples/build_dist.pl create mode 100755 Mail-IMAPClient-3.10/examples/build_ldif.pl create mode 100755 Mail-IMAPClient-3.10/examples/cleanTest.pl create mode 100644 Mail-IMAPClient-3.10/examples/copy_folder.pl create mode 100755 Mail-IMAPClient-3.10/examples/cyrus_expire.pl create mode 100644 Mail-IMAPClient-3.10/examples/cyrus_expunge.pl create mode 100644 Mail-IMAPClient-3.10/examples/find_dup_msgs.pl create mode 100755 Mail-IMAPClient-3.10/examples/imap_to_mbox.pl create mode 100755 Mail-IMAPClient-3.10/examples/imtestExample.pl create mode 100755 Mail-IMAPClient-3.10/examples/migrate_mail2.pl create mode 100755 Mail-IMAPClient-3.10/examples/migrate_mbox.pl create mode 100755 Mail-IMAPClient-3.10/examples/populate_mailbox.pl create mode 100755 Mail-IMAPClient-3.10/examples/sharedFolder.pl create mode 100644 Mail-IMAPClient-3.10/lib/Mail/IMAPClient.pm create mode 100644 Mail-IMAPClient-3.10/lib/Mail/IMAPClient.pod create mode 100755 Mail-IMAPClient-3.10/lib/Mail/IMAPClient/BodyStructure.pm create mode 100755 Mail-IMAPClient-3.10/lib/Mail/IMAPClient/BodyStructure/Parse.grammar create mode 100644 Mail-IMAPClient-3.10/lib/Mail/IMAPClient/BodyStructure/Parse.pm create mode 100755 Mail-IMAPClient-3.10/lib/Mail/IMAPClient/BodyStructure/Parse.pod create mode 100644 Mail-IMAPClient-3.10/lib/Mail/IMAPClient/MessageSet.pm create mode 100644 Mail-IMAPClient-3.10/lib/Mail/IMAPClient/Thread.grammar create mode 100644 Mail-IMAPClient-3.10/lib/Mail/IMAPClient/Thread.pm create mode 100755 Mail-IMAPClient-3.10/lib/Mail/IMAPClient/Thread.pod create mode 100755 Mail-IMAPClient-3.10/prepare_dist create mode 100755 Mail-IMAPClient-3.10/sample.perldb create mode 100755 Mail-IMAPClient-3.10/t/basic.t create mode 100755 Mail-IMAPClient-3.10/t/bodystructure.t create mode 100755 Mail-IMAPClient-3.10/t/messageset.t create mode 100755 Mail-IMAPClient-3.10/t/pod.t create mode 100755 Mail-IMAPClient-3.10/t/thread.t create mode 100755 Mail-IMAPClient-3.10/test_template.txt diff --git a/BUG_IMAPClient_3.xx b/BUG_IMAPClient_3.xx index 5826e11..95c316c 100644 --- a/BUG_IMAPClient_3.xx +++ b/BUG_IMAPClient_3.xx @@ -6,4 +6,8 @@ BUGS found with Mail-IMAPClient-3.05/ 30 timeout. + 2) --expunge2 does not expunge anything. + +Fixed in Mail-IMAPClient-3.10/ + diff --git a/ChangeLog b/ChangeLog index de5188f..7684596 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,17 +1,29 @@ RCS file: RCS/imapsync,v Working file: imapsync -head: 1.264 +head: 1.267 branch: locks: strict - gilles: 1.264 access list: symbolic names: keyword substitution: kv -total revisions: 264; selected revisions: 264 +total revisions: 267; selected revisions: 267 description: ---------------------------- -revision 1.264 locked by: gilles; +revision 1.267 +date: 2008/10/07 11:36:02; author: gilles; state: Exp; lines: +14 -10 +Better test to check non existing folders on destination +server. +---------------------------- +revision 1.266 +date: 2008/10/07 05:56:52; author: gilles; state: Exp; lines: +27 -10 +*** empty log message *** +---------------------------- +revision 1.265 +date: 2008/08/30 14:20:38; author: gilles; state: Exp; lines: +7 -7 +carp to warn +---------------------------- +revision 1.264 date: 2008/08/27 15:19:05; author: gilles; state: Exp; lines: +7 -8 Archiveopteryx 3.0.0 ---------------------------- diff --git a/Mail-IMAPClient-3.10/COPYRIGHT b/Mail-IMAPClient-3.10/COPYRIGHT new file mode 100644 index 0000000..ebc36eb --- /dev/null +++ b/Mail-IMAPClient-3.10/COPYRIGHT @@ -0,0 +1,401 @@ +COPYRIGHT + + Copyright 1999, 2000, 2001, 2002 , 2003 The Kernen Group, Inc. + All rights reserved. + +This program is free software; you can redistribute it and/or modify it +under the terms of either: + + +a) the "Artistic License" which comes with this Kit, or + +b) the GNU General Public License as published by the Free Software +Foundation; either version 1, or (at your option) any later version. + + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the GNU +General Public License or the Artistic License for more details. All your +base are belong to us. + +============= + + The "Artistic License" + + Preamble + +The intent of this document is to state the conditions under which a +Package may be copied, such that the Copyright Holder maintains some +semblance of artistic control over the development of the package, +while giving the users of the package the right to use and distribute +the Package in a more-or-less customary fashion, plus the right to make +reasonable modifications. + +Definitions: + + "Package" refers to the collection of files distributed by the + Copyright Holder, and derivatives of that collection of files + created through textual modification. + + "Standard Version" refers to such a Package if it has not been + modified, or has been modified in accordance with the wishes + of the Copyright Holder as specified below. + + "Copyright Holder" is whoever is named in the copyright or + copyrights for the package. + + "You" is you, if you're thinking about copying or distributing + this Package. + + "Reasonable copying fee" is whatever you can justify on the + basis of media cost, duplication charges, time of people involved, + and so on. (You will not be required to justify it to the + Copyright Holder, but only to the computing community at large + as a market that must bear the fee.) + + "Freely Available" means that no fee is charged for the item + itself, though there may be fees involved in handling the item. + It also means that recipients of the item may redistribute it + under the same conditions they received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you +duplicate all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications +derived from the Public Domain or from the Copyright Holder. A Package +modified in such a way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided +that you insert a prominent notice in each changed file stating how and +when you changed that file, and provided that you do at least ONE of the +following: + + a) place your modifications in the Public Domain or otherwise make them + Freely Available, such as by posting said modifications to Usenet or + an equivalent medium, or placing the modifications on a major archive + site such as uunet.uu.net, or by allowing the Copyright Holder to include + your modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation or organization. + + c) rename any non-standard executables so the names do not conflict + with standard executables, which must also be provided, and provide + a separate manual page for each non-standard executable that clearly + documents how it differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or +executable form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library files, + together with instructions (in the manual page or equivalent) on where + to get the Standard Version. + + b) accompany the distribution with the machine-readable source of + the Package with your modifications. + + c) give non-standard executables non-standard names, and clearly + document the differences in manual pages (or equivalent), together + with instructions on where to get the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this +Package. You may charge any fee you choose for support of this +Package. You may not charge a fee for this Package itself. However, +you may distribute this Package in aggregate with other (possibly +commercial) programs as part of a larger (possibly commercial) software +distribution provided that you do not advertise this Package as a +product of your own. You may embed this Package's interpreter within +an executable of yours (by linking); this shall be construed as a mere +form of aggregation, provided that the complete Standard Version of the +interpreter is so embedded. + +6. The scripts and library files supplied as input to or produced as +output from the programs of this Package do not automatically fall +under the copyright of this Package, but belong to whoever generated +them, and may be sold commercially, and may be aggregated with this +Package. If such scripts or library files are aggregated with this +Package via the so-called "undump" or "unexec" methods of producing a +binary executable image, then distribution of such an image shall +neither be construed as a distribution of this Package nor shall it +fall under the restrictions of Paragraphs 3 and 4, provided that you do +not represent such an executable image as a Standard Version of this +Package. + +7. C subroutines (or comparably compiled subroutines in other +languages) supplied by you and linked into this Package in order to +emulate subroutines and variables of the language defined by this +Package shall not be considered part of this Package, but are the +equivalent of input as in Paragraph 6, provided these subroutines do +not change the language in any way that would cause it to fail the +regression tests for the language. + +8. Aggregation of this Package with a commercial distribution is always +permitted provided that the use of this Package is embedded; that is, +when no overt attempt is made to make this Package's interfaces visible +to the end user of the commercial distribution. Such use shall not be +construed as a distribution of this Package. + +9. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + + The End + +============= + + GNU GENERAL PUBLIC LICENSE + Version 1, February 1989 + + Copyright (C) 1989 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The license agreements of most software companies try to keep users +at the mercy of those companies. By contrast, our General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. The +General Public License applies to the Free Software Foundation's +software and to any other program whose authors commit to using it. +You can use it for your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Specifically, the General Public License is designed to make +sure that you have the freedom to give away or sell copies of free +software, that you receive source code or can get it if you want it, +that you can change the software or use pieces of it in new free +programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of a such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must tell them their rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any program or other work which +contains a notice placed by the copyright holder saying it may be +distributed under the terms of this General Public License. The +"Program", below, refers to any such program or work, and a "work based +on the Program" means either the Program or any work containing the +Program or a portion of it, either verbatim or with modifications. Each +licensee is addressed as "you". + + 1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this +General Public License and to the absence of any warranty; and give any +other recipients of the Program a copy of this General Public License +along with the Program. You may charge a fee for the physical act of +transferring a copy. + + 2. You may modify your copy or copies of the Program or any portion of +it, and copy and distribute such modifications under the terms of Paragraph +1 above, provided that you also do the following: + + a) cause the modified files to carry prominent notices stating that + you changed the files and the date of any change; and + + b) cause the whole of any work that you distribute or publish, that + in whole or in part contains the Program or any part thereof, either + with or without modifications, to be licensed at no charge to all + third parties under the terms of this General Public License (except + that you may choose to grant warranty protection to some or all + third parties, at your option). + + c) If the modified program normally reads commands interactively when + run, you must cause it, when started running for such interactive use + in the simplest and most usual way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this General + Public License. + + d) You may charge a fee for the physical act of transferring a + copy, and you may at your option offer warranty protection in + exchange for a fee. + +Mere aggregation of another independent work with the Program (or its +derivative) on a volume of a storage or distribution medium does not bring +the other work under the scope of these terms. + + 3. You may copy and distribute the Program (or a portion or derivative of +it, under Paragraph 2) in object code or executable form under the terms of +Paragraphs 1 and 2 above provided that you also do one of the following: + + a) accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of + Paragraphs 1 and 2 above; or, + + b) accompany it with a written offer, valid for at least three + years, to give any third party free (except for a nominal charge + for the cost of distribution) a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of + Paragraphs 1 and 2 above; or, + + c) accompany it with the information you received as to where the + corresponding source code may be obtained. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form alone.) + +Source code for a work means the preferred form of the work for making +modifications to it. For an executable file, complete source code means +all the source code for all modules it contains; but, as a special +exception, it need not include source code for modules which are standard +libraries that accompany the operating system on which the executable +file runs, or for standard header files or definitions files that +accompany that operating system. + + 4. You may not copy, modify, sublicense, distribute or transfer the +Program except as expressly provided under this General Public License. +Any attempt otherwise to copy, modify, sublicense, distribute or transfer +the Program is void, and will automatically terminate your rights to use +the Program under this License. However, parties who have received +copies, or rights to use copies, from you under this General Public +License will not have their licenses terminated so long as such parties +remain in full compliance. + + 5. By copying, distributing or modifying the Program (or any work based +on the Program) you indicate your acceptance of this license to do so, +and all its terms and conditions. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these +terms and conditions. You may not impose any further restrictions on the +recipients' exercise of the rights granted herein. + + 7. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of the license which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +the license, you may choose any version ever published by the Free Software +Foundation. + + 8. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: 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 humanity, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + + To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + 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 + the Free Software Foundation; either version 1, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + 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. + +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) 19xx 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. + +The hypothetical commands `show w' and `show c' should show the +appropriate parts of the General Public License. Of course, the +commands you use may be called something other than `show w' and `show +c'; they could even be mouse-clicks or menu items--whatever suits your +program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (a program to direct compilers to make passes + at assemblers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/Mail-IMAPClient-3.10/Changes b/Mail-IMAPClient-3.10/Changes new file mode 100644 index 0000000..5d9d819 --- /dev/null +++ b/Mail-IMAPClient-3.10/Changes @@ -0,0 +1,1720 @@ + +== Revision History for Mail::IMAPClient +All changes from 2.99_01 upward are made by Mark Overmeer. The changes +before that are applied by David Kernen + +version 3.10: Sun Aug 24 21:26:27 CEST 2008 + + Fixes: + + - INET socket scope error, introduced by 3.09 + rt.cpan.org#38689 [Matt Moen] + +version 3.09: Fri Aug 22 16:38:25 CEST 2008 + + Fixes: + + - return status of append_message reversed. + rt.cpan.org#36726 [Jakob Hirsch] + + - no line-breaks in base64 encoded strings when logging-in + rt.cpan.org#36879 [David Jonas] + + - fix MD5 authentication. + rt.cpan.org#38654 [Thomas Jarosch] + + Improvements: + + - extensions and clean-ups in examples/imap_to_mbox.pl by + [Ralph Sobek] + + - an absolute path as Server setting will open a local ::UNIX + socket, not an ::INET + rt.cpan.org#38655 [Thomas Jarosch] + +version 3.08: Tue Jun 3 09:36:24 CEST 2008 + + Fixes: + + - message_to_file used wrong command. + rt.cpan.org#36184 [Parse Int] + + - oops, distribution released with OODoc/oodist, not make dist. + [Randy Harmon] + + - fix parsing of body-structure information for multi-parts. + rt.cpan.org#36279 [Doug Claar] + + Improvements: + + - Updated README and TODO (Was 'Todo') + +version 3.07: Mon Apr 28 09:17:30 CEST 2008 + + Fixes: + + - expunge with no folder specified produced "use of undef" + error. Fixed by [André Warnier] + + - additional arguments for create [Michael Bacon] + + - accepts LIST answer with multiple lines [Michael Bacon] + + - ::BodyStructure::_address() should be _addresses() + Fixed by rt.cpan.org#35471 [Brian Kelly] + +version 3.06: Mon Apr 14 23:44:03 CEST 2008 + + Fixes: + + - expunge without argument must use selected folder. [John W] + + - expunge with folder does not select it. [John W] + + - the documentation still spoke about "autogenerated methods", + but they were removed with 2.99 [John W] + + - append_string needs LF -> CRLF translations, for some + servers. rt.cpan.org #35031 [Jonathan Kamens] + + Improvements: + + - added ::setquota(), thanks to [Jappe Reuling] + +version 3.05: Wed Feb 20 08:59:37 CET 2008 + + Fixes: + + - match ENVELOPE and BODYSTRUCTURE more strict in the + grammar, to avoid confusion. [Zach Levow] + + - get_envelope and get_bodystructure failed for servers which + did not return the whole answer in one piece. [Zach Levow] + + - do not produce parser errors when get_envelope does not + return an envelope. [Zach Levow] + + - PLAIN login response possibly solely a '+' [Zach] and [Nick] + +version 3.04: Fri Jan 25 09:25:51 CET 2008 + + Fixes: + + - read_header fix for UID on Windows Server 2003. + rt.cpan.org#32398 [Michiel Stelman] + + Improvements: + + - doc update on authentication, by [Thomas Jarosch] + +version 3.03: Wed Jan 9 22:11:36 CET 2008 + + Fixes: + + - LIST (f.i. used by folders()) did not return anything when the + passed argument had a trailing separator. [Gunther Heintze] + + - Rfc2060_datetime() must include a zone. + rt.cpan.org#31971 [David Golden] + + - folders() uses LIST, and then calls a STATUS on each of the + names found. This is superfluous, and will cause problems when + the STATUS fails... for instance because of ACL limitations + on the sub-folder. + rt.cpan.org#31962 [Thomas Jarosch] + + - fixed a zillion of problems in the BodyStructure parser. The + original author did not understand parsing, nor Perl. + + - part numbering wrong when nested messages contained multiparts + + Improvements: + + - implementation of DIGEST-MD5 authentication [Thomas Jarosch] + + - removed call for status() in Massage(), which hopefully speeds-up + things without destroying anything. It removed a possible deep + recursion, which no-one reported (so should be ok to remove it) + + - simplified folders() algorithm. + + - merged folder commands, like subscribe into one. + + - added unsubscribe() + rt.cpan.org#31268 [G Miller] + + - lazy-load Digest::HMAC_MD5 + +version 3.02: Wed Dec 5 21:33:17 CET 2007 + + Fixes: + + - Another attempt to get get FETCH UID right. Patch by [David Golden] + +version 3.01: Wed Dec 5 09:55:43 CET 2007 + + Changes: + + - removed version number from ::BodyStructure + + Fixes: + + - quote password at login. + rt.cpan.org#31035 [Andy Harriston] + + - empty return of flags command should be empty list, not undef. + rt.cpan.org#31195 [David Golden] + + - UID command does not work with folder management commands + rt.cpan.org#31182 [Robbert Norris] + + - _read_line simplifications avoids timeouts. + rt.cpan.org#31221 [Robbert Norris] + + - FETCH did not detect the UID of a message anymore. + [David Golden] + + Improvements: + + - proxyauth for SUN/iPlanet/NetScape IMAP servers. + patch by rt.cpan.org#31152 [Robbert Norris] + + - use grep in stead of map in one occasion in MessageSet.pm + [Yves Orton] + +version 3.00: Wed Nov 28 09:56:54 CET 2007 + + Fixes: + + - "${peek}[]" should be "$peek\[]" for perl 5.6.1 + rt.cpan.org#30900 [Gerald Richter] + +version 2.99_07: Wed Nov 14 09:54:46 CET 2007 + + Fixes: + + - forgot to update the translate grammar. + +version 2.99_06: Mon Nov 12 23:21:58 CET 2007 + + Fixes: + + - body structure can have any number of optional parameters. + Patch by [Gerald Richter]. + + - get_bodystructure did not take the output correctly [Gerald Richter] + + - parser of body-structure did not handle optional body parameters + Patch by [Gerald Richter], rt.cpan.org#4479 [Geoffrey D. Bennet] + +version 2.99_05: Mon Nov 12 00:17:42 CET 2007 + + Fixes: + + - pod error in MessageSet.pm + + - folders() without argument failed. [Gerald Richter] + + Improvements: + + - better use of format syntax in date formatting. + + - Rfc2060_datetime also contains the time. + + - append_file() now has options to pass flags and time of file + in one go. [Thomas Jarosch] + +version 2.99_04: Sat Nov 10 20:55:18 CET 2007 + + Changes: + + - Simplified initiation of IMAP object with own Socket with a new + option: RawSocket [Flavio Poletti] + + Fixes: + + - fixed read_line [Flavio Poletti] + + - fixed test-run in t/basic.t [Flavio Poletti] + +version 2.99_03: Thu Nov 1 12:36:44 CET 2007 + + Fixes: + + - Remove note about optional Parse::RecDescent by Makefile.PL; + it is not optional anymore + + Improvements: + + - When syswrite() returns 0, that might be caused by an error + as well. Take the timeout/maxtemperrors track. + rt.cpan.org#4701 [C Meyer] + + - add NTLM support for logging-in, cleanly intergrated. Requires + the user to install Authen::NTLM. + +version 2.99_02: Fri Oct 26 11:47:35 CEST 2007 + + The whole Mail::IMAPClient was rewritten, hopefully without + breaking the interface. Nearly no line was untouched. + + The following things happened: + - use warnings, use strict everywhere + - removed many lines which were commented out, over the years + - $self->_debug if $self->Debug checked debug flag twice + - $self->LogError calls where quite inconsequent wrt $@ and carp + - consequent layout, changed sporadic tabs in blanks + - consequent calling convensions + - \0x0d\0x0a is always \r\n + - zillions of minor syntactical improvements + - a few major algorithmic rewrites to simplify the code, still + many oppotunities for improvements. + - expanded "smart" accessor methods, search abbreviations, + and autoloaded methods into separate subs. In total much + shorter, and certainly better understandable! + - fixed many potential bugs. + - labeled some weird things with #???? + Over 1000 lines (30%!) and 25kB smaller in size + Needs to be tested!!!! Volunteers? + + Fixes: + + - Exchange 2007 only works with new parameter: IgnoreSizeErrors + rt.cpan.org#28933 [Dregan], #5297 [Kevin P. Fleming] + + - Passed socket did not get selected. + debian bug #401144, rt.cpan.org# [Alexander Zanger], + #8480 [Karl Gaissmaier], #8481 [Karl Gaissmaier], + #7298 [Herbert Engelmann] + http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=401144 + + - Seperator not correctly extracted from list command. + rt.cpan.org#9236 [Eugene Koontz], #4662 [Rasjid] + + - migrate() Massage'd foldername twice + rt.cpan.org#20703 [Peter J. Holzer] + + - migrate() could loop because error in regexp. + rt.cpan.org#20703 [Peter J. Holzer] + + - migrate() append_string result not tested. + rt.cpan.org#8577 [guest] + + - Failing fetch() returned undef, not empty list. + rt.cpan.org#18361 [Robert Terzi] + + - Fix "use of uninitialised" warning when expunge is called + rt.cpan.org#15002 [Matt Jackson] + + - Fix count subfolders in is_parent, regexp did not take care + of regex special characters in foldername and seperator. + rt.cpan.org#12883 [Mike Porter] + + - In fetch_hash(), the capturing of UID was too complicated + (and simply wrong) + rt.cpan.org#9341 [Gilles Lamiral] + + - overload in MessageSet treated the 3rd arg (reverse) as + message-set. + + - do not send the password on a different line as the username + in LOGIN. Suggested by many people, amongst them + rt.cpan.org#4449 [Lars Uffmann] + + - select() with $timeout==0 (no timeout) returns immediately. + Should be 'undef' as 4th select parameter. + rt.cpan.org#5962 [Colin Robertson] and [Jules Agee] + + - examine() remembers Massage()d folder name, not the unescaped + version. rt.cpan.org#7859 [guest] + + Improvements: + + - PREAUTH support by rt.cpan.org#17693 [Danny Siu] + + - Option "SupportedFlags", useful when the source supports + different flags than the peer in migrate(). + Requested by rt.cpan.org#12961 [Don Christensen] + + - Fast_io did not clear $@ on unimportant errors. + rt.cpan.org#9835 [guest] and #11220 [Brian Helterline] + + - Digest::HMAC_MD5 and MIME::Base64 are now prerequisits. + rt.cpan.org#6391 [David Greaves] + + - PLAIN (SASL) authentication added, option Proxy + rt.cpan.org#5706 [Carl Provencher] + + - removed Bodystructure.grammar and IMAPClient.cleanup from dist. + + - reworked Bodystructure and MessageSet as well. + + - EnableServerResponseInLiteral now autodetect (hence ignored) + +version 2.99_01: + + After 4 years of silence, Mark Overmeer took maintenance. David + Kernen could not be reached. Please let him contact the new + maintainer. + + A considerable clean-up took place, fixing bug and adapting the + distribution to current best practices. + + - use "prompt" in Makefile.PL, to please CPAN-testers + + - removed old Parse::RecDescent grammars + + - include Artistic and Copying (GPL) into COPYRIGHT file + + - remove INSTALL_perl5.80 + + - removed all the seperate Makefile.PLs and test directories + + - removed the hard-copy of all involved RFCs: there are better + sources for those. + + - converted tests to use "Test::More" + + - Authmechanism eq 'LOGIN' understood. + + - test for CRAM-MD5 removed, because conflicts with test params + from Makefile.PL + + - test for fast-io removed, it is Perl core functionality + + - require IO::Socket::INET 1.26 to avoid Port number work-around. + + - Parse::RecDescent is required, and the grammars are pre-parsed + in the distribution. This makes the whole installation process + a lot easier. + + - Update Todo, and many other texts. + + - added pod tester in t/pod.t + + - cleaned-up the rt.cpan.org bug-list from spam. The next + release will contain fixes for the real reports. + +Changes in version 2.2.9 +------------------------ +Fixed problem in migrate that caused problems in versions of perl earlier +than 5.6. Thanks go to Steven Roberts for reporting the problem and +identifying its cause. + +Fixed problem in the make process that caused tests for BodyStructure +subclass to fail if the grammer had been compiled under a different +version of Parse::RecDescent. This problem was detected by the dedicated +people at testers@cpan.org. + +Fixed a compatibility problem using Parse::RecDescent version 1.94. +This caused BodyStructure and Thread to fail for 5.8.x users. A number of +people reported this bug to CPAN but it took me a while to realize what +was going on. Really it took me a while to realize my Parse::RecDescent +was out of date. ;-) Now this module is delivered with two versions of +each of the affected grammars and Makefile.PL determines which version +to use. Upgrading to Parse::RecDescent 1.94 will require you to re-run +Makefile.PL and reinstall Mail::IMAPClient. + +Changes in version 2.2.8 +------------------------ +Change the login method so that it always send password as a literal +to get around problem 2544 reported by Phil Tracy which caused +passwords containing asterisks to fail on some systems (but not any of +mine...). Good catch, Phil. + +Added a new example that demonstrates the use of imtest (a utility +that comes with Cyrus IMAP) and Mail::IMAPClient together. The +example uses imtest to do secure authentication and then "passes" the +connection over to Mail::IMAPClient (but imtest is still brokering +the encryption/decryption). This example comes from an idea of +Tara L. Andrews', whose brainstorm it was to use imtest to broker +secure connections. (But I still want to get encryption working with +Mail::IMAPClient some day!) + +Fixed an error in which a "+" was used as a conncatenation error instead +of a ".". Thanks to Andrew Bramble for reporting this, even though he +mistakenly identified it as a "typo". It is not a typo; a plus sign is the +correct concatenation operator, as any decent Java book will tell you ;-) + +Fixed an error in the login method when the password contains a special +character (such as an asterisk.) Thanks to Phil Tracey for reporting +this bug. + +Fixed some bugs in _send_line (the "O" side of the I/O engine) that were +reported by Danny Smith. + +Fixed a bug in the migrate method in the optimization code (which +gets called when socket writes are delayed due to a slow or busy target +host, aka EAGAIN errors). Thanks to Pedro Carvalho for identifying +this bug and its cause. + +Fixed a bug in migrate that caused migration of unread messages to fail. +This was due to the way Mail::IMAPClient's migrate method would try to send +an empty list of flags to the target server in the APPEND. Thanks to +Stephen Fralich at Syracuse University and for reporting this bug. + +Fixed another bug in the migrate method that caused flags to get lost. Thanks +go to Jean-Michel Besnard for reporting this. + +Fixed a bug in migrate that caused +Fixed a bug in get_envelope that caused it to fail under certain conditions. +Thanks go to Bob Brown for reporting this bug. + + +Changes in version 2.2.7 +------------------------ + +Added some new parameters to support alternate authentication mechanisms: + + Prewritemethod + Readmethod + +Mail::IMAPClient has supported cram-md5 authentication "out of the box" +as of 2.2.6 (courtesy of Ville Skyttä). I also have digest-md5 working +in my lab with quality of protection levels "auth" and "integrity", but +not "confidentiality". I'm hoping to get the confidentiality part working +soon but so far have only managed to authenticate, send an encrypted command, +and receive and decrypt the response. This may sound like enough but I can't +seem to send a second command or receive a second response;-( In any event +2.2.8 will support at least qop=auth and qop=auth-int but maybe not +qop=auth-conf. + +Fixed a bug reported by Adrian that caused get_bodystructure to +fail if the server returned a bodystructure with an embedded +literal. Also fixed the same bug in get_envelope, so I guess now +everyone knows that get_envelope was just a tinkered-with copy of +get_bodystructure... + +Fixed two related bugs in Parser.pm that caused +get_bodystructure and get_envelope to fail if the +UID nnnnn part of a fetch response follows all the +other stuff. Thanks to Raphaël Langella for reporting this bug. + +Enhanced several methods to use MessageSets when the +Ranges parameter is true. There are still more methods that +need to be retrofitted to take advantage of the Range method +(and its underlying MessageSet object). In the meantime, if you +need to get the functionality of the shorter message ranges provided +by the Range method from a method that does not honor the Ranges +parameter, then you should a) create a message set by passing the +messages to the Range method and then pass the scalar as a string +to the method you want to use. For example, if you want to move +a whole lot of messages to Trash, do something like this: +> +>my $range = $imap->Range(scalar($imap->search("SentBefore", "01-Jan-2000"))); +>$imap->move("Trash","$range"); +> +This will cause the range object to stringify out to what looks like +a non-reference scalar before the move method gets the argument. If you +omit the quotes around "$range" then this won't work. + +Fixed a bug in the list method that caused LIST "" "" to fail miserably. +Thanks to John W Sopko Jr. for reporting this bug. + +Fixed a bug in the test suite that caused the cram-md5 tests to fail +if you are not running the extended tests. (Introduced in 2.2.6) + +Fixed a bug that affected users on platforms that do not support +fcntl (i.e. NT). Thanks to Raphaël Langella for reporting this bug. + +Changes in version 2.2.6 +------------------------ + +Fixed a bug in the migrate method that caused the internaldate +of migrated messages to sometimes be wrong. Credit goes to Jen Wu +for identifying both bug and fix. + +Added a new method, "get_header", to provide a short-cut for a common +use of parse_headers. Added two other methods, "subject" and "date", +to provide shortcuts to get_header. + +Changed the Mail::IMAPClient::MessageSet module to override array +dereferencing. (See below.) + +Changed fetch and search methods to use the Range method (and thus the +Mail::IMAPClient::MessageSet module) for messages. The fetch method will +use MessageSet objects all the time, but the search method will only +return MessageSet objects if you specify "Ranges => 1" (with Ranges being +a new parameter). The default will be "Ranges => 0" (which preserves +the old behavior) but this default will go away in some future release. +There should be no need to override the fetch method's new behavior, since +it will be transparent to you unless you tend to fetch a lot of messages +at once, in which case your fetches may be faster and perhaps less likely +to fail due to the request exceeding your server's line limit. If you set +the Ranges parameter to true, then you still should not see a difference, +because a) when fetch is called in a list context then you will not get +a MessageSet object, you'll get the same list as always, and b) the +MessageSet objects now override array de-referencing operations, so if you +treat the returned MessageSet object as if it were an array then the object +will humour you and act like a reference to an array of messages sequence +numbers or message uids. + +Also changed the flags method to use the Range method. This should also +be transparent since the methods arguments and return values do not change. + +Added built-in support for CRAM-MD5 authentication. This authentication +method will in this release be used only when requested. In future releases +the default authentication will probably be the strongest authentication +supported "out of the box" that is available on your server. Since CRAM-MD5 +is the only authentication other than plain text that is currently supported +"out of the box", it will be the default authentication mechanism for any +server that supports it. See the pod for the Authmechanism and Authcallback +parameters (which were also added in this release) and the doc for the +authenticate method (which has been around a while). Many thanks to Ville Skyttä +for providing the code that makes up the heart of this new support, as well +as to Gisle Aas for the Digest::HMAC_MD5 and MIME::Base64. + +Made minor tweaks to the documentation. Again. (Will it ever be 100% right?) + +Changes in version 2.2.5 +------------------------ +Added the Range method to convert a bunch of message UID's or sequence numbers +into compact ranges. Also added a supporting class for the returned range +objects with overloaded operators that support stringifying, adding to, and +deleting from a range object's message set (Mail::IMAPClient::MessageSet). +I also wrote documentation for same, so check it out. In future releases, +I will probably enhance the base module to use MessageSet objects when +feasible (i.e. whenever I know that the argument in question should in fact +be a message specification). But I'll let you find all the bugs in the +MessageSet module first ;-) Thanks goes to Stefan Schmidt, who is the first +to report using a server that restricted the size of a client request to +something smaller than what Mail::IMAPClient was generating for him. +(Originally the Range method was just supposed condense a message set into +the shortest possible RFC2060-compliant string, but then I got all happy and +started adding features. You know how it is...) + + +Changes in version 2.2.4 +------------------------- +Fixed a bug in the done method (new in 2.2.3). + +Added tests for idle and done. (That's how I found the bug in the done method, above.) + +Fixed minor bugs in test suite. (The test suite worked but wasn't always using the options +I wanted tested. ) + + +Changes in version 2.2.3 +------------------------- + +NOTE: This version was distributed to beta testers only. + +Fixed the "Changes in version 2.2.2" section so that it correctly specifies +version 2.2.2 (instead of being yet another 2.2.1 section). + +Fixed a bug in the migrate method that affected folders with spaces in their +names. + +Fixed a bug in the Massage method that affected folders with braces ({}) in +their names. + +Added a new class method, "Quote", that will quote your arguments for you. (So you +no longer have to worry so much about quoting your quotes. + +Added optimizations to the migrate method and to the core I/O engine inspired +by Jules Agee. (Actually they were not so much inspired by him as they were +lifted right out of a patch he had out on sourceForge.net. I had to refit them +for this version, and reformat his comments so they could fit in my window. Thanks +Jules, wherever you are.) + +Added the fetch_hash method, which will fetch an entire folder's contents into a +hash indexed by message UID (or message sequence number if that's all you've got). + +Added a new example to the examples subdirectory, and corrected some minor bugs +in existing examples. + +Added the idle and done methods, which together implement the IMAP IDLE extension +(RFC2177), at John Rudd's suggestion. + +Changes in version 2.2.2 +------------------------ +Fixed a bug in Massage method (generally only used by other IMAPClient methods) +that broke folder names with parens. + +Updated bug reporting procedures. Also added a section in the documentation +for REPORTING THINGS THAT ARE NOT BUGS. Bug tracking is now done via +rt.cpan.org, which I stumbled upon quite by accident and with which I am +really pleased. A lot of credit goes to _somebody_ for putting this +out on CPAN. Unfortunately as of this writing I don't whom. + +Fixed a bug in the documentation regarding the logoff method, which is never +implicitly invoked anymore; I gave up on that because the DESTROY method would +sometimes be called after the Socket handle was already destroyed. (This is +especially likely at program exit, when everything still in scope goes out of +scope at the same time.) You should always log off explicitly if you want to +be a well behaviod IMAP client. + +Changes in version 2.2.1 +------------------------ +Updated append_string to wrap the date argument in double quotes if the argument was +provided without quotes. Thanks to Grant Waldram for pointing out that some IMAP +servers require this behavior. + +Added a new method, selectable, which returns a true value if a folder is selectable. + +Documented in this Changes file a change that was actually made for 2.2.0, in which +newlines are chomped off of $@ (but not LastError). + +Added pointers in the documentation to point to Mark Bush's Authen::NTLM module. This +module will allow you to use NTML authentication with Mail::IMAPClient connections. +Also changed the authenticate method so that it will work with Authen::NTML without +the update mentioned in NTLM::Authen's README. + +Added a second example on using the new migrate method, +migrate_mail2.pl. This example demonstrates more advanced techniques +then the first, such as using the separator method to massage folder +names and stuff like that. + +Added support for the IMAP THREAD extension. Added +Mail::IMAPClient::Thread.pm to support this. (This pm file is generated +during make from Thread/Thread.grammar.) This new function should be +considered experimental. Note also that this extension has nothing to do +with threaded perl or anything like that. This is still on the TODO list. + +Updated the search, sort, and thread methods to set $@ to "" before +attempting their respective operations so that text in $@ won't be left +over from some other error and therefore always indicative of an error +in search, sort, or thread, respectively. + +Made many many tweaks to the documentation, including adding more examples +(albeit simple ones) and fixing some errors. + +Changes in version 2.2.0 +------------------------ +Fixed some tests so that they are less likely to give false negatives. For +example, test 41 would fail if the test account happened to have an +empty inbox. + +Made improvements to Mail::IMAPClient::BodyStructure and renamed +Mail::IMAPClient::Parse to Mail::IMAPClient::BodyStructure::Parse. (This +should be transparent to apps since the ...Parse helper module is +used by BodyStructure.pm only.) I also resumed my earlier practice of +using ...Parse.pm from within BodyStructure.pm to avoid the overhead of +compiling the grammar every time you use BodyStructure.pm. (Parse.pm is +just the output from saving the compiled Parse::RecDescent grammar.) In a +related change, I've moved the grammar into its own file (Parse.grammar) +and taught Makefile.PL how to write a Makefile that converts the .grammar +file into a .pm file. This work includes a number of fixes to how a body +structure gets parsed and the parts list returned by the parts method, +among other things. I was able to successfully parse every bodystructure +I could get my hands on, and that's a lot. + +Also added a bunch of new methods to Mail::IMAPClient::BodyStructure +and its child classes. The child classes don't even have files of their +own yet; they still live with their parent class! Notable amoung these +changes is support for the FETCH ENVELOPE IMAP command (which was easy +to build in once the BODYSTRUCTURE stuff was working) and some helper +modules to get at the envelope info (as well as envelope information +for MESSAGE/RFC822 attachments from the BODYSTRUCTURE output). Have a +look at the documentation for Mail::IMAPClient::BodyStructure for more +information. + +Fixed a bug in the folders method regarding quotes and folders with +spaces in the names. The bug must have been around for a while but +rarely manifested itself because of the way methods that take folder +name arguments always try to get the quoting right anyway but it was +still there. Noticing it was the hard part (none of you guys reported +it to me!). + +Fixed a bug reported by Jeremy Hinton regarding how the search method +handles dates. It was screwing it all up but it should be much better now. + +Added the get_envelope method which is like the get_bodystructure method +except for in ways in which it's different. + +Added the messages method (a suggestion from Danny Carroll), which is +functionally equivalent to $imap->search("ALL") but easier to type. + +Added new arguments to the bodypart_string method so that you can get +just a part of a part (or a part of a subpart for that matter...) I did +this so I could verify BodyStructure's parts method by fetching the first +few bytes of a part (just to prove that the part has a valid part number). + +Added new tests to test the migrate function and to do more thorough +testing of the BodyStructure stuff. Also added a test to make sure that +searches that come up empty handed return an undef instead of an empty +array (reference), regardless of context. Which reminds me... + +Fixed a bug in which searches that don't find any hits would return a +reference to an empty array instead of undef when called in a scalar +context. This bug sounds awfully familiar, which is why I added the test +mentioned above... + + +Changes in version 2.1.5 +------------------------ +Fixed the migrate method so now it not only works, but also works +as originally planned (i.e. without requiring source messages to +be read entirely into memory). If the message is smaller than +the value in the Buffer parameter (default is 4096) then a normal +$imap2->append($folder,$imap1->message_string) is done. However, if +the message is over the buffer size then it is retrieved and written a +bufferful at a time until the whole message has been read and sent. (The +receiving server still expects the entire message at once, but it +will have to wait because the message is being read from the source in +smaller chunks and then written to the destination a chunk at a time.) +This needs extensive testing before I'd be willing to trust it (or at +least extensive logging so you know when something has gone terribly +wrong) and I consider this method to be in BETA in this release. (Numerous +people wrote complaining that migrate didn't work, and some even included +patches to make it work, but the real bug in the last release wasn't +that migrate was broken but that I had inadvertently included the pod for +the method which I knew perfectly well was not ready to be released. My +apologies to anyone who was affected by this.) The migrate method does +seem to work okay on iPlanet (i.e. Netscape) Messenger Server 4.x. Please +let me know if you have any issues on this or any other platform. + +Added a new example, migrate_mbox.pl, which will demonstrate the migrate method. + +Fixed a bug that will cause Mail::IMAPClient's message reading methods to misbehave if +the last line of the email message starts with a number followed by a space and either +"OK", "NO", or "BAD". This bug was originally introduced in 1.04 as a fix for another +bug, but since the fix supports noncompliant behavior I'm disabling this behavior by +default. If your IMAP clients start hanging every time you try to read literal text +(i.e. a message's test, or a folder name with spaces or funky characters) then you +may want to turn this on with the EnableServerResponseInLiteral parameter. Thanks go +to Manpreet Singh for reporting this bug. + +Fixed a bug in imap_to_mbox.pl that has been there since 2.0.0 (when the Uid +parameter started defaulting to "True"). Thanks to Christoph Viethen for reporting +the bug and suggesting the fix. BUT NOTE THIS: I often don't test the example programs, +so you should think of them as examples and not free production programs. Eventually +I would like to add tests to my test suite (either the 'make test' test suite that you +run or my own more extensive test suite) but it's not a super high priority right now. + +Significant improvements to the whole Mail::IMAPClient::BodyStructure module +were contributed by Pedro Melo Cunha. It's really much better now. + +Bullet-proofing added to some private methods. (Private meaning they are undocumented +and not part of the module's API. This is perl not java.) + +Fix applied to unset_flag to support user-defined flags (thanks to E.Priogov +for submitting the bug report and patch). + + +Changes in version 2.1.4 +------------------------ +Added Paul Warren's bugfix to the sort method. + +Added Mike Halderman's bugfix for the get_bodystructure method. + +Fixed a localization problem reported by Ivo Panecek. Because of this fix, +the Errno.pm file is now a prerequisite to this module. This way I can just +test to see if the error is an "EAGAIN" error (as defined in sys/errno.h and thus +Errno.pm) instead of awkwardly checking the string value of $!. + +I also renamed the MaxTempErrors parameter to Maxtemperrors in response the same +bug report. Added a "MaxTempErrors" accessor method that will set and return +Maxtemperrors for backwards compatibility. Also, the number of temporary errors +gets reset after each successful I/O, so that the socket i/o operation fails only if +you if your temporary I/O errors happen more than "Maxtemperrors" times in a row. +The old behavior was to continue incrementing the count of temporary errors until +either the entire message was written or until a total of Maxtemperrors had occurred, +regardless of how many intervening successful syswrites occurred. This was a bug, but +Ivo politely suggested the new behavior as an enhancement. ;-) Also, you can now +specify "UNLIMITED" as the Maxtemperrors, in which case these errors will be ignored. +And the default for Maxtemperrors is now 100, but I'm open to any feedback you may +have in this regard. + +I also fixed the operator precedence problem that was reported by many folks in that +very same part of the code. (As you may have guessed, that code was new in the last +version!) + +One of the people who reported the precedence problem was Jules Agee, who also submitted +a patch that may in the end provide an optimal solution to handling EAGAIN errors. +Unfortunately I have not had time to retrofit his patch into the current version of the +module. But if I can manage to do this soon and it tests well I'll include it in the next +release, in which case the Maxtemperrors parameter will be of interest only to historians. + +I also received a patch from John Ello that adds support for Netscape's proprietary +PROXYAUTH IMAP client command. I haven't included that support in this release because +you can already use the proxyauth method. It's one of those famous "default" methods +that, despite their fame and my documentation, nobody seems to know about. But you +can always say "$imap->proxyauth($uid)", for example, providing that $imap and $uid +are already what they're supposed to be. (I've been doing this myself for years.) + +However, John's patch does provide a cleaner interface (it remembers who you are as +well as who you were, for example) so I may include it later as part of a separate +module that extends Mail::IMAPClient. This would also give me an excuse for providing +the framework for plugging in Administrative methods that are proprietary to other imap +servers, so if you have a technique for acquiring administrative access to your users' +mailboxes (besides proxyauth) please let me know what it is. Perhaps we'll get something cool out of it, like a document on how to write administrative scripts for various +platforms and a suite of supporting methods for each. + +Changes in version 2.1.3 +------------------------ +Added the new method append_string. It works similarly to append but will allow extra +arguments to supply the flags and internal date of the appended message. See the pod +for more details. + +(Thanks to Federico Edelman Anaya for suggesting this fix.) + +Fixed a bug in the AUTOLOAD subroutine that caused "myrights" (and possibly other +non-existant methods) to fail. Thanks go to Larry Rosenbaum for reporting the bug +and identifying the fix. + +Added the new method Escaped_results, which preprocesses results so that data +containing certain special characters are returned quoted with special characters +(like quotes!) escaped. (I needed this for the bodystructure stuff, below.) + +NEW! Added support for parsing bodystructures (as provided in the server response to +FETCH BODYSTRUCTURE). This support requires Parse::RecDescent and is implemented via two +new modules, Mail::IMAPClient::BodyStructure and Mail::IMAPClient::Parse. Note that +the latter module is used by the former; your programs need not and should not use it +directly so don't. Also, these modules are ALPHA and EXPERIMENTAL so no screaming when +they don't work. (Polite bug reports will of course be gratefully accepted.) Many +thanks to Damian Conway, the author of Parse::RecDescent, without which this feature +would not have been possible (or at least not very likely). + +Enhanced support for DOS systems (and DOS's offspring, such as windows) by removing +the "\c\n"s and replacing them with "\x0d\x0a". Thanks go to Marcio Marchini for his +help with this effort. + +Fixed the list of symbols imported along with Fcntl.pm. (Paul Linder asked me to put +this in the last release but I forgot.) + +Changes in version 2.1.2 +------------------------ + +Fixed a bug in the is_parent method which made it inaccurate on some servers. + +Added new method "sort", which implements the SORT extenstion and which was contributed +by Josh Rotenberg. The SORT extension is documented at +http://search.ietf.org/internet-drafts/draft-ietf-imapext-sort-06.txt. A copy of the +draft is also included with the Mail::IMAPClient distribution, which means I also: + +Added draft-ietf-imapext-sort-06.txt to the docs subdirectory of the distribution. + +Fixed a bug in the folders method and the subscribed method (same bug, appeared twice) +which broke these methods under some conditions. Thanks again Josh Rotenberg for supplying the fix. + +Fixed bugs in getacl and listacl. Changed the interface for getacl significantly; +existing scripts using getacl will not behave the same way. But then on the other hand, +getacl was never documented before, so how could you be using it? + +Implemented improvements to reduce memory usage by up to 30%. Thanks go Paul Linder, +who developed the memory usage patch after a considerable amount of analysis. The +improvements include the use of 'use constant', so your perl needs to support that +pragma in order to use Mail::IMAPClient. + +Added a new parameter, MaxTempErrors, which allows the programmer to control the number +of consecutive "Resource Temporarily Unavailable" errors that can occur before a write +to the server will fail. Also changed the behavior of the client when one of these +errors occurs. Previously, Mail::IMAPClient waited .25 seconds (a quarter of one +second) before retrying the read operation. Now it will wait (.25 * the number of +consecutive temporary errors) seconds before retrying the read. + +Documented the "Buffer" parameter, which has been secretly available for some time. I +just forgot to document it. It sets the size of the read buffer when Fast_io is turned +on. (NOTE: As of version 2.1.5 it also controls the size of the buffer used by the +migrate method.) + +Updated the Todo file. It was nice to see that a number of lines in the "Todo" file were now deletable. It was depressing to see that a number of original lines need to stay +in there. + + +Changes in version 2.1.1 +------------------------ +Added the "mark", "unmark", and imap4rev1 methods. + +Updated the documentation to include the new methods and to document "create", "store", +and "delete". + +Updated "message_string" to be smart about whether you're using IMAP4 or IMAP4REV1. + +Updated "message_to_file" to be smart about whether you're using IMAP4 or IMAP4REV1. + +Added several bug fixes to authenticate method. Many thanks to Daniel Wright who +reported these bugs and provided the information necessary to fix them. + + +Changes in version 2.1.0 +------------------------ + +Fixed a serious bug introduced in 2.0.9 when appending large messages. + +Made minor changes to improve the cyrus_expunge.pl example script. + +Made the set_flags routine RFC2060-compliant. Previously it prepended flag names with +backslashes, even if the flags were not reserved flags. This broke support for +user-defined flags, which I didn't realize was supposed to even be there until Scott +Renner clued me in. (Thanks, Scott.) + +Promoted the release level to "1". + +Added a new 'internaldate' method. (Thanks to the folks at jwm3.org for donating the +code!) + +Added a new example, cyrus_expire.pl. + +Changes in version 2.0.8/2.0.9 +------------------------------ +Made minor changes to the tests in t/basic.t so that folders are explicitly closed +before they are deleted. (Don't worry, only folders created by the tests are +deleted. :-) Thanks go to Alan Young for reporting that some servers require this. + +Changed the routine that massages folder names into IMAP-compliant strings so that +single-quotes in a name do not force the folder to go through as "LITERAL" strings +(as defined in RFC2060). This shouldn't cause a problem for anybody (and in fact +should make life easier for some folks) but if you do have any trouble with +single-quotes in folder names PLEASE LET ME KNOW ASAP!! + +Divided the sending of literal strings into two I/O operations (as required by RFC2060). +This should correct problems with sending literals to some servers that will not read +any data sent before they reply with the "+ go ahead" message. (Thanks go to Keith Clay, +who reported seeing this problem with the M-Store IMAP server.) + +Changed the "create" method so that it will autoquote the first argument to create +rather than the last. Normally the first argument is the last, but Cyrus users can +specify an optional 2nd argument, except when using pre-2.0.8 versions of +Mail::IMAPClient ;-) Thank you Chris Stratford for reporting this bug and +identifying its cause. + +Fixed a bug in body_string when the message is empty. (Thanks go to Vladimir Jebelev for +finding this bug and providing the fix.) + +Added a new example to the examples subdirectory. cyrus_expunge.pl is a script you +can use (after making minor tweaks) to periodically expunge your server's mail store. + +Changes in version 2.0.7 +------------------------ +Fixed a bug in message_count. Thanks go to Alistair Adams for reporting this bug. + +Fixed a bug in folders that caused some foldernames to not be reported in the +returned array. + +Changes in version 2.0.6 +------------------------ + +Applied patches from Phil Lobbe to tighten up sysreads and 'writes and to correct a +bug in the I/O engine. + +Changes in version 2.0.5 +------------------------ + +Fixed bug in parse_headers so that RFC822 headers now match the pattern /(\S*):\s*/ +instead of /(\S*): /. Thanks go to Paul Warren for reporting this bug and providing the +fix. + +Added more robust error checking to prevent infinite loops during read attempts and +fixed bugs in parse_headers. Thanks go to Phil Lobbes, who provided several useful +patches and who performed valuable pre-release testing. + +Changes in version 2.0.4 +------------------------ + +Fixed bug in parse_headers when connected to an Exchange server with UID=>1. (Kudos to +Wilber Pol for that fix.) + +Fixed bugs in parse_headers and tightened reliability of I/O engine by implementing +many improvements suggested by Phil Lobbes, who also provided code for same. + +Added bugfix that under certain conditions caused server responses to be "repeated" +when fast_io is turned on. Thanks to Jason Hellman for providing bug report and +diagnostic data to fix this. + +Added a "LastIMAPCommand" method, which returns the last IMAP client command that +was sent to the server. + +Removed the "=begin debugging" paragraph that somehow got included in CPAN's +html pages (even though it shouldn't have). + +Began a process of redesigning the documentation. I would like to be able to present +a more formal syntax for the various methods and hope to have that ready for the next +release. + +Tested successfully against Cyrus v 2.0.7. + +Tested unsuccessfully against mdaemon. This appears to be due to mdaemon's +noncompliance with rfc2060 so future support for mdaemon should not be expected +any time soon. ;-( + + +Changes in version 2.0.3 +------------------------ + +Did major rewrite of message_string method, which should now be both cleaner +and more reliable. + +Fixed bug in move method that caused some folders to be incorrectly quoted. +Thanks go to Felix Finch for reporting this bug. Also, at his suggestion I +added information to move documentation explaining the need to expunge. + +Made many fixes and tweaks to pod text. + +Added a new method, Rfc2060_date, which takes times in the "seconds since 1/1/1970" +format and returns a string in RFC2060's "dd-Mon-yyyy" format (which is the format +you need to use in IMAP SEARCH commands). + +Changes in version 2.0.2 +------------------------ +Fixed bug that caused a compile error on some earlier versions of perl5. + +Noticed that some older versions of perl give spurious "Ambiguous use" warnings +here and there, mostly because I'm not quoting the name of the "History" member +of the underlying Mail::IMAPClient hash. These warnings will go away when you upgrade +perl. (I may fix them later, or maybe not. Depends on if I have time.) + +Added new parameter (and eponymous method) Peek, along with new tests for 'make test' +for same. See the pod for further info. + +Added some error checking to avoid trying to read or write with an +unconnected IMAPClient object. + +Made bug fixes to parse_headers and flags. + +Added missing documentation for the exciting new message_to_file method (oops). +Also cleaned up a few typos in the pod while I happened to be there. (I'm sure +there are still plenty left.) + +Fixed bugs in append and append_file. (Thanks to Mauro Bartolomeoli and to the people +at jwm3.org for reporting these bugs.) + +Made changes to call to syswrite to guarantee delivery of entire message. (Only affects +appends of very large messages.) + +Added the 'close' method to the list of lower-case-is-okay methods (see the section +under version 2.0.0 on "NEW ERROR MESSAGES"). + +Changes in version 2.0.1 +------------------------ +Several bug fixes related to the flags method and to spurious warning messages +when run with warnings turned on. + +A new method, message_to_file, writes message text directly into a file. This +bypasses saving the text in the history buffer and the overhead that entails, which +could be especially important when processing big ass messages. Of course the bad news +is that now you'll have to write all that shtuff out to a filehandle, but maybe you +wanted to do that anyway. Anyhow, between append_file and message_to_file, both +of which take filehandle arguments, there should be a way to "short circuit" the +copying of mail between two imap sessions. I just haven't got it completely figured +out yet how it would work. Got any ideas? Anyhow, this method is currently considered +experimental. + +A couple of new tests have been added to go along with our new little method. + +I've added a whole bunch more IMAP-related rfc's to the docs/ subdirectory. Trust me, +you are going to need them. + +Changes in version 2.0.0 +----------------------- +NEW I/O ENGINE +This version includes a major rewrite of the I/O engine. It's now cleaner and more +reliable. Also, output processing is less likely to match patterns that look like +server output but are really, say, message text contained in a literal or something +like that. Also, various problems with blank lines at the ends of messages either +magically appearing or disappearing should now go away. Basically, it's much better +is what I'm trying to say. + +NEW DEFAULT +The Uid parameter now defaults to true. This should be transparent to existing scripts +(except for those scripts that produce embarrassing results because someone forgot to +specify Uid=>1, in which case they'll magically start behaving somehow). + +NEW METHOD +The namespace method has been added, thus implementing RFC2342. If you have any scripts +that rely on the old, "default method" style of namespace implementation then you should +rename those method calls to be mixed case (thus forcing the AUTOLOADed default method). + +NEW ERROR MESSAGES +Mail::IMAPClient now issues a lot more warning messages when run in warn mode +(i.e. $^W is true). Of particular interest are methods implemented via the "default +method" AUTOLOAD hack. They will generate a warning telling you to use mixed- or +upper-case method names (but only if warnings are turned on, say with the -w switch +or $^W++ or something). The exceptions are certain unimplemented yet quite popular +methods that, if ever explicitly implemented, will behave the same way as they do via +the default method. (Or at least they will remain downwardly compatible. I may add +bells and whistles by not by default.) Those methods are listed in the pod and right +here: store, copy, subscribe, close, create, delete and expunge. + +NEW VERSION NUMBERING SCHEME +Changed the version numbering scheme to match perl's (as of perl v5.6.0). + +NEW INSTALLATION TESTS +Added a few new tests to the test suite. (Still need more, though.) Also changed fast_io +and uidplus test suites so that they just "do" the basic tests but with different +options set (i.e. Fast_io and Uid, respectively). + +OTHER CHANGES +- The expunge method now optionally accepts the name of the folder to be expunged. It's +also been documented, even though it technically doesn't exist. (That won't stop it from +working, though.) Since expunge deletes messages that you thought were already deleted, +it's only appropriate to use a method that you thought existed but really doesn't, don't +you think? And if you're wondering how I managed to change the behavior of a method that +doesn't exist, well, I don't want to talk about it. + +- Speaking of methods that don't exist (also known as methods implemented via "the +default method"), effective with this release there are a number of unimplemented +methods that are guaranteed to always exhibit their current behavior. In other words, +even if I do eventually implement these methods explicitly, they will continue to +accept the same arguments and return the same results that they do now via the default +method. (Why I would even bother to do that is specifically not addressed in this +document.) Currently this means that these methods will not trigger warnings when +called via all-lowercase letters (see "NEW ERROR MESSAGES", above). In the future I +hope that it will also mean that these non-existant but functioning methods will also +be documented in the pod. + +- Fixed a bug in the flags method introduced in 1.19. (Thanks to the people at jwm3.org +for reporting this!) + + +Changes in version 1.19 +----------------------- +Fixed a bug in which the Folder parameter returned quoted folder names, which sometimes +caused other methods to requote the folders an extra time. (The IMAP protocol is real +picky about that.) Thanks go to Felix Finch for both reporting the bug and identifying +the fix. + +Siggy Thorarinsson contributed the new "unseen_count" method and suggested a new +"peek mode" parameter. I have not yet gotten around to implementing the new parameter +but have included the unseen_count method, since a) he was kind enough to write it, and +b) it tests well. + +In the meantime, you cannot tell methods like "parse_headers" and "message_string" and +so forth whether or not you want them to mark messages as "\Seen". So, to make life +easier for you in particular I added a bunch of new methods: set_flag, unset_flag, +see, and deny_seeing. The latter two are derivitives of the former two, respectively, +which should make this sentence almost as difficult to parse as an IMAP conversation. + +Fixed bug in which "BAD" "OK" or "NO" lines prefixed by an asterisk (*) instead of the +tag are not handled correctly. This is especially likely when LOGIN to a UW IMAP server +fails. Thanks go to Phil Lobbes for squashing this bug. + +Fixed bug in logout that caused the socket handle to linger. Credit goes to +Jean-Philippe Bouchard for reporting this bug and for identifying the fix. + +Fixed bug in uidvalidity method where folder has special characters in it. + +Made several bug fixes to the example script examples/find_dup_msgs.pl. Thanks to Steve +Mayer for identifying these bugs. + +Changed Fast_io to automatically turn itself off if running on a platform that does +not provide the necessary fcntl macros (I won't mention any names, but it's initials +are "NT"). This will occur silently unless warnings are turned on or unless the Debug +parameter is set to true. Previously scripts running on this platform had to turn off +fast_io by hand, which is lame. (Thank you Kevin Cutts for reporting this problem.) + +Updated logic that X's out login credentials when printing debug output so that funky +characters in "User" or "Password" parameters won't break the regexp. (Kevin Cutts found +this one, too.) + +Tinkered with the Strip_cr method so it can accept multiple arguments OR an array +reference as an argument. See the updated pod for more info. + +Fixed a typo in the documentation in the section describing the fetch method. There +has been an entire paragraph missing from this section for who knows how long. Thanks +to Adam Wells, who reported this documentation error. + +Fixed bug in seen, recent, and unseen methods that caused them to return empty arrays +erroneously under certain conditions. + +Changes in version 1.18 +----------------------- +Timeouts during read operations now work correctly. + +Fixed several bugs in the I/O engine. This should correct various problems with Fast_io +turned on (which is now the default). + +Reworked message_string and body_string methods to avoid bugs when Uid set to true. + +Changes in version 1.17 +----------------------- + +Added support for the Oracle IMAP4r1 server. + +Tinkered with the DESTROY method so that it does a local($@) before doing its evals. +This will perserve the value of $@ when the "new" method fails during a login but the +DESTROY's "logout" succeeds. The module was setting the $@ variable, but on some +versions of perl the DESTROY method would clobber $@ before anything useful could be +done with it! Thanks to Kimmo Hovi for reporting this problem, which was harder to +debug than you might think. + +Changes in version 1.16 +----------------------- + +IMPORTANT: Made Fast_IO the default. You must specify Fast_io => 0 in your new method +call or invoke the Fast_io method (and supply 0 as an arg) to get the old behavior. +(This should be transparent to most users, but as always your mileage may vary.) + +Reduced the number of debug msgs printed in the _read_line internal method and added a +debug msg to report perl and Mail::IMAPClient versions. + +The message_count method will now return the number of messages in the currently select +folder if no folder argument is supplied. + +The message_string method now does an IMAP FETCH RFC822 (instead of a +FETCH RFC822.HEADERS and a FETCH RFC822.TEXT), which should eliminate missing blank +lines at the ends of some messages on some IMAP server platforms. It also returns undef +if for some reason the underlying FETCH fails (i.e. there is no folder selected), +thanks to a suggestion by Pankaj Garg. It has also been slightly re-worked to support +the changes in the I/O engine from version 1.14. + +Re-worked the body_string method to support the I/O engine changes from v1.14. + +Fixed a bug in parse_headers when used with multiple headers and the Uid parameter set +to a true value. + +Documented in this file a fix for a bug in the flags method with the Uid parameter +turned on. (Belated thanks to Michael Lieberman for reporting this bug.) + +Changes in version 1.15 +----------------------- +Fixes the test suite, which in v1.14 had an "exit" stmt that caused early termination +of the tests. (I had put that "exit" in there on purpose, and left it in there by +accident.) + +Changes in version 1.14 +----------------------- +Fixed a bug in the _readline subroutine (part of the I/O engine) that was caused by my +less-than-perfect interpretation of RFC2060. This fix will allow the Mail::IMAPClient +module to function correctly with servers that imbed literal datatypes in the middle +of response lines (rather than just at the end of them). Thanks to Pankaj Garg for +reporting this problem and providing the debugging output necessary to correct it. + +Fixed a bug in parse_headers that was introduced with the fix to the I/O engine +described above. + +Changes in version 1.13 +----------------------- +Changed the parse_headers method so that it uses BODY.PEEK instead of BODY. This +prevents the parse_headers method from implicitly setting the "\Seen" flag for messages +that have not been otherwise read. This change could produce an incompatibility in +scripts that relied on the parse_headers previous behavior. + +Fixed a bug in the flags method with the Uid parameter turned on. (Thanks to Michael +Lieberman for reporting this bug.) + +Changes in version 1.12 +----------------------- +Fixed a bug in the folders method when called first with a second arg and then without +a second arg. + +Tested sucessfully with perl-5.6.0. + +Added a section to the pod documentation on how to report bugs. I've had to ask for +output from scripts with "Debug => 1" so many times that I eventually decided to +include the procedure for documenting bugs in the distribution. (Duh! It only took me +11 releases to come up with that brainstorm.) Often following the procedures to obtain +the documentation is enough; once people see what's going on (by turning on Debug =>1) +they no longer want to report a bug. + +Did I mention it's a good idea to turn on debugging when trying to figure out why a +script isn't working? (It is.) + +In order to make the Debug parameter friendlier, it now prints to STDERR by default. +You can override this by supplying the spanking brand new Debug_fh parameter, which +if supplied had better well point to a filehandle (either by glob or by reference), +and by 'filehandle' I mean something besides STDIN! + +Debugging mode will now also X-out the login credentials used to login. This will make +it easier to share your debugging output. + +Added documentation for the State parameter, which must be set manually by programmers +who are not using Mail::IMAPClient's connect and/or login methods but who are instead +making their own connections and then using the Socket parameter to turn their +connections into IMAP clients. + +Fixed bug in parse_headers with Uid turned on. + +Fixed bug in parse_headers when using the argument "ALL". + +Changes in version 1.11 +----------------------- +Added new example script, copy_folder.pl, to demonstrate one way to copy entire +folders between imap accounts (which may or may not be on the same server). This +example is right next to all the others, in the examples/ subdirectory of the +distribution. + +Changed error handling slightly. $@ now contains pretty much the same stuff as what +gets returned by LastError, even when LastError won't work (i.e. when an implicit +connect or login fails and so no object reference is returned by new). You can thank +John Milton for the friendly nagging that got me to do this. + +Added new test suite for the fast_io engine. This should make it easier to determine +whether or not the fast_io engine will work on your platform. + +Implemented a work-around to allow the Port parameter to default despite a known bug in +IO::Socket::INET version 1.25 (distributed with perl 5.6.0). + +Fixed a bug in the message_string method in which the resulting text string for some +mime messages to be incompatible with append. + +Fixed a bug in the Fast_io i/o engine that could cause hangs during an append operation. + +Changed a number of regular expressions to accept mixed-case "Ok", "No" or "Bad" +responses from the server and to do multi-line matching. + +Fixed a bug in the append method that was causing extra carriage returns to appear in +messages whose lines were already terminated with the CR-LF sequence. Thanks to Heather +Adkins for reporting this bug. + +Enhanced the parse_headers routine so that it is less sensitive to variations of +case in message headers. Now, the case of the returned key matches the case of the +field as specified in the parse_headers method's arguments, regardless of its case +in the message being parsed. (You can thank Heather Atkins for this suggestion as +well.) See below for more changes to parse_headers in this release. + +Improved the append method so that it has better error handling and error recovery. +Thanks to Mark Keisler for pointing out some bugs in the error handling code in +this method. + +Added the append_file method, which is like the append method but it works on files +instead of strings. The file provided to append must contain an RFC822-formatted +message. Use of the append_file method avoids having to stuff huge messages into +variables before appending them. Thanks to jwmIII (http://jwm3.org) for suggesting +this method. + +Changed the flags method and the parse_headers method so that a reference to an array +of message sequence numbers (or message UIDS if the Uid parameter is turned on) can +optionally be passed instead of a single message sequence number (or UID). Use of this +enhancement will change your return values so be sure to read the pod. Thanks to +Adrian Smith (adrian.smith@ucpag.com) for delivering this enhancement. + +Fixed a bug in "message_string" that caused the blank lines between headers and body +to fall out of the string. + +Tinkered with the undocumented _send_line method to permit an optional argument +to suppress the automatic insertion of at the end of strings being sent. +(NOTE: I'm telling you this because I'm a nice guy. This doesn't mean that _send_line +is now a programming interface.) + +Changes in version 1.10 +----------------------- + +Added two new methods, lsub and subscribed. lsub replaces the behavior of the default +method and should be downwardly compatible. The subscribed method works like the +folders method but the results include only subscribed folders. Thanks to Alexei +Kharchenko for providing the code for lsub (which is the foundation upon which +'subscribed' was built). + +Changes in version 1.09 +----------------------- + +Changed login method so that values for the User parameter that do not start and end +with quotes will be quoted when sent to the server. This is to support user id's +with embedded spaces, which are legal on some platforms. + +Changed name of test input file created by perl Makefile.PL and used by 'make test' +from .test to test.txt to support weird, offbeat OS platforms that cannot handle +filenames beginning with a dot. + +Fixed bugs in seen, unseen, and recent methods. (These are almost the same method +anyway; they are dynamically created at compile time from the same code, with +variable substitution filling in the places where "seen", "unseen", or "recent" +belong.) The bug caused these methods to return the transaction number of the +search as if it were the last message sequence number (or message uid) in +the result set. + +Added the 'since' method, which accepts a date in either standard perl format (seconds +since 1/1/1970, or as output by time and as accepted by localtime) or in the date_text +format as defined in RFC2060 (dd-Mon-yyyy, where Mon is the English-language +three-letter abbreviation for the month). It searches for items in the currently +selected folder for messages sent since the day whose date is provided as an argument. + +Added 'sentsince', 'senton', 'sentbefore', 'on', and 'before' methods which are +totally 100% just like the 'since' method, except that they run different searches. +(Did I mention that it's useful to have RFC2060 handy when writing IMAP clients?) + +Added two new methods, run and tag_and_run, to allow IMAP client programmers finer +control over the IMAP conversation. These methods allow the programmer to compose +the entire IMAP command string and pass it as-is to the IMAP server. The difference +between these two methods is that the run method requires that the string include +the tag while the tag_and_run method requires that it does not. + +To a similar end, the pre-existing Socket parameter and eponymous accessor method +has been documented to allow direct access to the IMAP socket handle and to allow +the socket handle to be replaced with some other file handle, presumably one derived +from a more interesting technology (such as SSL). + +Fixed a bug that caused blank lines to be removed from 'literal' output (as defined +in RFC2060) when fast_io was not used. This bug was especially likely to show up in +routines that fetched a message's body text. The fact that this bug did not occur +in the newer fast_io code may indicate that I've learned something, but on the other +hand we shouldn't jump to rash conclusions. + +I've run benchmarks on the fast_io code to determine whether or not it is faster and, +if so, under what circumstances. It appears that the fast_io code is quite faster, +except when reading large 'literal' strings (i.e. message bodies), in which case it +appears to take the same amount of time as the older i/o code but at the cost of +more cpu cycles (which means it may actually be slower on cpu-constrained systems). +The reason for this is that reads of literal strings are by their nature already +optimized, but without the overhead of fcntl calls. So if you expect to be doing +lots of message text (or multipart message body parts) fetching you should not use +fast_io, but in pretty much any other case you should go ahead and use it. In any +event, a number of people have tested fast_io so I no longer consider it +experimental, unless you're running perl on NT or CP/M or something funky like that, +in which case let me know how you make out! + +Changes in version 1.08 +----------------------- + +Maintenance release 1.08a fixes a bug in the folders method when supplying the +optional argument (see "Enhanced folders method..." below) with some IMAP servers. + +Added option to build_ldif.pl (in the examples subdirectory) to allow new options and +to better handle quoted comments in e-mail addresses. Thanks to Jeffrey Fiedl, +whose book _Mastering Regular Expressions_ (O'Reilly) helped me to figure out a +good way to do this. + +Fixed documentation error that failed to mention constraints on when the append +method will return the uid of the appended message. (This feature only works with +servers that have the UIDPLUS capability.) + +Added/improved documentation somewhat. + +The copy method now returns a comma-separated list of uids if successful and if the +IMAP server supports UIDPLUS extentions. The move method now works similarly. + +Added new method uidnext, which accepts the name of a folder as an argument and returns +the next available message UID for that folder. + +The exists and append methods now will handle unquoted foldernames with embedded +spaces or quotes or whatever. Including quotes as part of the argument string is no +longer required but is still supported for backwards compatibility reasons. In other +words, $imap->exists(q("Some Folder")) is now no longer necessary (but will still work). $imap->exists(some folder) is good enough. + +Mail::IMAPClient has been tested successfully on Mirapoint 2.0.2. (Thanks to Jim +Hickstein.) + +I've now installed the UW imapd IMAP4rev1 v12.264 on one of my machines so I'm better +able to certify that platform. All the tests in 'make test' work there (or are at least +gently skipped). + +Fixed bug in getacl in which folder names were quoted twice. (Thanks to Albert Chin for +squashing this bug.) Similar bugs existed in the other ACL methods and were similarly +fixed. + +Fixed a bug in message_uid that basically caused it to not work. Muchos gracias to +Luvox (aka fluvoxamine hydrochloride) for providing me with just the help I needed to +discover and fix this bug. + +Enhanced folders method to allow an argument. If an argument is supplied, then +the folders method will restrict its results to subfolders of the supplied argument +(which should be the name of a parent folder, IMHO). This is implemented by supplying +arguments to the LIST IMAP Client command so we are optimizing network I/O at the +expense of possible server incompatibilities. If you find server incompatibilities +with this then please let me know, and in the meantime you can always +grep(/^parent/,$imap->folders) or something. Or re-implement the folders +method yourself. + + +Changes in version 1.07 +----------------------- +Added a new parameter, Fast_io, which, if set to a true value, will attempt to +implement a faster I/O engine. USE THIS AT YOUR OWN RISK. It is alpha code. I don't +even know yet if it even helps. + +Added support for spaces in folder names for the autoloaded subscribe method. + +Added new methods setacl, getacl, deleteacl, and listrights. These methods are not yet +fully tested and should be considered beta for this release. + +Enhanced support for the myrights method (which is implemented via the default method). + +Fixed bug in append method that caused it to hang if server replied to original APPEND +with a NO (because, say, the mailbox's quota has been exceeded). + +Removed the autodiscovery of the folder hierarchy from the login method. This will +speed up logging in but may delay certain other methods later (but see the next item, +below). + +Updated the exists method to issue a "STATUS" IMAP Client command, rather than depend +on the folder hierarchy being discovered via 'LIST "" "*"'. Apparently this speeds +things up a lot for some configurations, although the difference will be negligable to +many. + +Updated Makefile.PL to support the PREFIX=~/ directive. Thanks to Henry C. Barta +(hbarta@wwa.com) for this fix. + +Added the Timeout parameter and eponymous accessor method, which, if set to a true +value, causes reads to time out after the number of seconds specified in the Timeout +parameter. The value can be in fractions of a second. This has not been fully tested +though, so use of this parameter is strictly "Beta". + +Enhanced support for the UID IMAP client command. Setting the new Uid parameter to a +true value will now cause the object to treat all message numbers as message UID +numbers rather than message sequence numbers. Setting the Uid parameter to a false +value will turn off this behavior again. + +Updated test suite to handle servers that cannot do UIDPLUS and to add tests for +the Uid parameter. + +Incorporated bug fixes for recent_count and message_count in which some servers are +sticking in extra \r's, and updated DESTROY to remove spurious warning messages under +some versions of perl (thanks to Scott Wilson for catching and killing these bugs). + + +Changes in version 1.06 +----------------------- +Changed folders method so that it correctly handles mail folders whose names start and +end with quotes. + +Changed append method so that it returns the uid of the newly appended message if +successful. Since the uid is a "true" value this should not affect the behavior of +existing scripts, although it may enhance the behavior of new scripts ;-) + +Fixed bug in parse_headers that could cause script to die if there were no headers of +the type requested and if there was a space on the blank line returned from FETCH. +(Some blank lines are blanker than others...) + +Added the "flags" method, which returns an array (or array reference if called in scalar +context) containing the flags that have been set for the message whose sequence number +has been provided as the argument to the method. + +Added the "message_string" method, which accepts a message sequence number as an +argument and returns the contents of the message (including RFC822 headers) as a +single string. + +Added the "body_string" method, which accepts a message sequence number as an argument +and returns the contents of the message (not including RFC822 headers) as a single +string. + +Changes in version 1.05 +----------------------- + +Patched the 'make test' basic test to work correctly on systems that do not +support double quotes in folder names. Thanks to Rex Walters for this fix. + +Added a new example script, build_dist.pl, that rumages through a folder +(specified on the command line) and collects the "From:" address, and then +appends a message to that folder with all those addresses in both the To: field +and the text, to facilitate cuting and pasting (or dragging and dropping) +into address books and so forth. (Note that the message doesn't actually get +sent to all those people; it just kind of looks that way.) + +Also added another example, build_ldif.pl, that is similar to build_dist.pl +except that instead of listing addresses in the message text, it creates a +MIME attachment and attaches a text file in LDIF format, which can then be +imported into any address book that supports LDIF as an import file format. +This example requires the MIME::Lite module. MIME::Lite was written by Eryq +(okay, Erik Dorfman is his legal name), and is totally available on CPAN. + +This distribution has now been tested on Mirapoint Message Server Appliances +(versions 1.6.1 and 1.7.1). Many thanks to Rex Walters for certifying this +platform and for providing a test account for future releases. + +Changes in version 1.04 +----------------------- + +Fixed situation in which servers that include the " OK\r\n" line +as part of a literal (i.e. text delivered via {}\r\n bytes\r\n) +caused the module to hang. This situation is pretty rare; I've only run across +one server that does it. I'm sure it's a bug; I'm not sure whose. ;-} +Many thanks to Thomas Stromberg for 1) pointing out this bug and 2) providing +me with facilities to find and fix it! + +Fixed potential bug in I/O engine that could cause module to hang when reading +a literal if the first read did not capture the entire literal. + +Cleaned up some unnecessary runtime warnings when a script is executed with +the -w switch. + +Added new tests to 'make test'. I just can't keep my hands off it! ;-) + +Enhanced the append method and several tests in 'make test' to be more widely +compatible. Successfully tested on UW-IMAP, Cyrus v1.5.19, Netscape Messenger +4.1, and Netscape Messenger v3.6. If you know of others please add them to +the list! + +Fixed a bug in the separator method (new in 1.03) that caused it to fail if +'inbox' was specified in lowercase characters as the method's argument. + +Added a new example, imap_to_mbox.pl, contributed by Thomas Stromberg. This +example converts a user's IMAP folders on an IMAP server into mbox format. + +Changes in version 1.03 +----------------------- +Reworked several methods to support double-quote characters within folder +names. This was kind of hard. This has been successfully tested with create, +delete, select, and folders, to name the ones that come to mind. + +Reworked the undocumented method that reads the socket to accept and handle +more gracefully lines ending in {nnn}\r\n ( where nnn is a number of +characters to read). This seems to be part of the IMAP protocol although I +am at a total loss as to where it's explained, other than a brief description +of a "literal's" bnf syntax, which hardly counts. + +Added separator object method, which returns the separator character in use +by the current server. + +Added is_parent method, which returns 1, 0, or undef depending on whether a +folder has children, has no children, or is not permitted to have children. + +Added tests to 'make test' to test new function. Also changed 'make test' to +support IMAP systems that allow folders to be created only in the user's INBOX +(which is the exact opposite of what my IMAP server allows...oh, well). + +Fixed a bug that caused search to return an array of one undef'ed element +rather than undef if there were no hits. + +Changes in version 1.02 +----------------------- +Fixed bugs in search and folders methods. + +Fixed bug in new method that ignored Clear => 0 when specified as arguments to +new. + +Changes in version 1.01 +----------------------- +Fixed a bug in test.pl that caused tests to fail if the extended tests were not used. + +Added method 'parse_headers' to parse the header fields of a message in the +IMAP store into a perl data structure. + +Changes in version 1.00 +----------------------- +Made cosmetic changes to documentation. + +Fixed a bug introduced into the 'folders' method in .99. + +Changed 'new' method so that it returns undef if an implicit connection or +login is attempted but fails. Previous releases returned a Mail::IMAPClient +object that was not connected or not logged in, depending on what failed. + +Changed installation script so that it reuses the parameter file for test.pl +if it finds one. Installation can be run in the background if the test.txt file +exists. Touching it is good enough to prevent prompts; having a correctly +formatted version (as described in test_template.txt) is even better, as it will +allow you to do a thorough 'make test'. + +Changes in version .99 +---------------------- +Added the Rfc822_date class method to create RFC822-compliant date fields in +messages being appended with the append method. + +Added the recent, seen, and unseen methods to return an array of sequence +numbers from a SEARCH RECENT, SEARCH SEEN, or SEARCH UNSEEN method call. +These methods are shortcuts to $imap->search("RECENT"), etc. + +Added the recent_count method to return the number of RECENT messages in a +folder. Contributed by Rob Deker. + +Added 'use strict' compliance, courtesy of Mihai Ibanescu. + +Fixed a bug in the search method that resulted in a list with one empty member +being returned if a search had no hits. The search method now returns undef +if there are no hits. + +Added 'authenticate' method to provide very crude support for the IMAP +AUTHENTICATE command. The previous release didn't support AUTHENTICATE at all, +unless you used very low-level (and undocumented) methods. With the +'authenticate' method, the programmer still has to figure out how to +respond to the server's challenge. I hope to make it friendlier in the +next release. Or maybe the one after that. This method is at least a start, +albeit a pretty much untested one. + +Added Rfc822_date class method to facilitate creation of "Date:" header +field when creating text for the "append" method, although the method may +come in handy whenever you're creating a Date: header, even if it's not +in conjuction with an IMAP session. + +Added more tests, which will optionally run at 'make test' time, provided all +the necessary data (like username, hostname, password for testing an IMAP +session) are available. + + +Changes in version 0.09 +----------------------- +Thu Aug 26 14:10:03 1999 - original version; created by h2xs 1.19 + +# $Id: Changes,v 20001010.18 2003/06/12 21:35:48 dkernen Exp $ diff --git a/Mail-IMAPClient-3.10/INSTALL b/Mail-IMAPClient-3.10/INSTALL new file mode 100644 index 0000000..1b74934 --- /dev/null +++ b/Mail-IMAPClient-3.10/INSTALL @@ -0,0 +1,82 @@ +Mail::IMAPClient Installation + +The Mail::IMAPClient is written entirely in Perl, so it should install +on any reasonably recent version of Perl. See the README file for a perl +one-liner that you can run to verify that your perl has what it takes +to run Mail::IMAPClient. + +The installation is standard: + + 0) cd to installation directory + 1) perl Makefile.PL (and reply to the prompts) + 2) make (optional) + 3) make test (optional) + 4) make install + +The 'make install' and 'make test' will both do step 2 ('make') if you +haven't done it already. Currently the test script is lame (although +not as lame as in the last release!) but I hope to incorporate more +thorough testing in a future version. You should at least try it and +let me know if your tests fail. + +Version 1.0 changed the installation script so that it reuses the +parameter file for the tests if it finds one. Installation can be run in +the background if the test.txt file exists. Touching it is good enough +to prevent prompts; having a correctly formatted version (as shown in +test_template.txt) is even better, as it will allow you to do a thorough +'make test'. Invalid data in test.txt (either from precreating it or from +responding inaccurately to prompts) will cause 'make test' to report 'not +ok' results but won't break anything important (like the IMAPClient.pm +file, or your car). + +If you have tests that fail it may be more illuminating to run the +tests by hand. IE: perl -I./blib/lib t/basic.t from the installation +dir will pinpoint the failing test. Better yet, supply an argument to +basic/t (any 'true' argument will do; I use '1') to turn on debugging, +which will be placed in your installation directory in 'imap1.debug' +and 'imap2.debug'. E-mail me the results. + +If you don't have a test.txt file in your installation directory then you +will have to answer at least one prompt. If you do have a test.txt file, +and you run 'make clean', then you won't have a test.txt file anymore, +so take precautions. + +If you do have a test.txt file and you don't run 'make clean' then +a text file will be sitting around containing logon credentials, so, +again, take precautions. (It's just a test account anyway, right?) + +If, when replying to the "perl Makefile.PL" prompts, you supply server, +id, and password credentials for an id that has a ridiculously huge number +of folders and subfolders then the 'make test' may run approximately +forever. Next time try an id with less stuff. + +For examples on using Mail::IMAPClient, check out the examples +subdirectory. If you have better examples, then why haven't you e-mailed +them to me? Also, I totally recommend that you have a copy of RFC2060 +handy when using this module, since the documentation for this module is +meant to compliment, not replace, RFC2060. In fact, I am so convinced that +you'll need the RFC that I've included a copy of it in the distribution, +under the "docs/" subdirectory. It's a smashing good read so have at +it. Other IMAP related rfcs are there as well. + +One of the examples in the examples/ subdirectory is called +cleanTest.pl. If you find your 'make test' has had trouble and left some +folders named "IMAPClient_*" in your test account, you can run this +example to clean up the account. But probably only after you've fixed +any problems encountered with 'make test'! + +This module uses Damian Conway's excellent Parse::RecDescent module +for some advanced features. If you don't have that module installed +then you can still install Mail::IMAPClient but you won't have the +full functionality. If you have Parse::RecDescent installed and then +upgrade it, you may find that some features in Mail::IMAPClient suddenly +start throwing compile-time errors. Just 'make clean' and then 'make', +'make test', and 'make install'. This happens because grammers compiled +under older releases of Parse::RecDescent are sometimes incompatible +with newer Parse::RecDescent runtime engines. This would never be a +problem if Mail::IMAPClient recompiled grammers at run time, but for +performance reasons it precompiles them at install time. TANSTAAFL. + +Now go and write IMAP clients. + +Dave Kernen diff --git a/Mail-IMAPClient-3.10/MANIFEST b/Mail-IMAPClient-3.10/MANIFEST new file mode 100644 index 0000000..224175c --- /dev/null +++ b/Mail-IMAPClient-3.10/MANIFEST @@ -0,0 +1,39 @@ +COPYRIGHT +Changes +INSTALL +MANIFEST +Makefile.PL +README +TODO +examples/build_dist.pl +examples/build_ldif.pl +examples/cleanTest.pl +examples/copy_folder.pl +examples/cyrus_expire.pl +examples/cyrus_expunge.pl +examples/find_dup_msgs.pl +examples/imap_to_mbox.pl +examples/imtestExample.pl +examples/migrate_mail2.pl +examples/migrate_mbox.pl +examples/populate_mailbox.pl +examples/sharedFolder.pl +lib/Mail/IMAPClient.pm +lib/Mail/IMAPClient.pod +lib/Mail/IMAPClient/BodyStructure.pm +lib/Mail/IMAPClient/BodyStructure/Parse.grammar +lib/Mail/IMAPClient/BodyStructure/Parse.pm +lib/Mail/IMAPClient/BodyStructure/Parse.pod +lib/Mail/IMAPClient/MessageSet.pm +lib/Mail/IMAPClient/Thread.grammar +lib/Mail/IMAPClient/Thread.pm +lib/Mail/IMAPClient/Thread.pod +prepare_dist +sample.perldb +t/basic.t +t/bodystructure.t +t/messageset.t +t/pod.t +t/thread.t +test_template.txt +META.yml Module meta-data (added by MakeMaker) diff --git a/Mail-IMAPClient-3.10/META.yml b/Mail-IMAPClient-3.10/META.yml new file mode 100644 index 0000000..bf5556c --- /dev/null +++ b/Mail-IMAPClient-3.10/META.yml @@ -0,0 +1,26 @@ +--- #YAML:1.0 +name: Mail-IMAPClient +version: 3.10 +abstract: IMAP4 client library +license: ~ +author: ~ +generated_by: ExtUtils::MakeMaker version 6.42 +distribution_type: module +requires: + Carp: 0 + Data::Dumper: 0 + Digest::HMAC_MD5: 0 + Errno: 0 + Fcntl: 0 + File::Temp: 0.18 + IO::File: 0 + IO::Select: 0 + IO::Socket: 0 + IO::Socket::INET: 1.26 + MIME::Base64: 0 + Parse::RecDescent: 1.94 + Test::More: 0 + Test::Pod: 0 +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.3.html + version: 1.3 diff --git a/Mail-IMAPClient-3.10/Makefile.PL b/Mail-IMAPClient-3.10/Makefile.PL new file mode 100644 index 0000000..58fc669 --- /dev/null +++ b/Mail-IMAPClient-3.10/Makefile.PL @@ -0,0 +1,111 @@ +use ExtUtils::MakeMaker; +use warnings; +use strict; + +sub set_test_data(); + +WriteMakefile + ( NAME => 'Mail::IMAPClient', + , ABSTRACT => 'IMAP4 client library' + , VERSION_FROM => 'lib/Mail/IMAPClient.pm' + , PREREQ_PM => + { 'Errno' => 0 + , 'IO::Socket' => 0 + , 'Fcntl' => 0 + , 'IO::Select' => 0 + , 'IO::File' => 0 + , 'Data::Dumper' => 0 + , 'Carp' => 0 + , 'IO::Socket::INET' => 1.26 + , 'Parse::RecDescent' => 1.94 + , 'Digest::HMAC_MD5' => 0 + , 'MIME::Base64' => 0 + + , 'Test::More' => 0 + , 'File::Temp' => 0.18 + , 'Test::Pod' => 0 + } + , clean => { FILES => 'test.txt' } + ); + +set_test_data(); + +exit 0; + +### +### HELPERS +### + +sub set_test_data() +{ unless(-f "lib/Mail/IMAPClient.pm") + { warn "ERROR: not in installation directory\n"; + return; + } + + return if -f "./test.txt"; + + print <<'__INTRO'; +You have the option of running an extended suite of tests during +'make test'. This requires an IMAP server name, user account, and +password to test with. + +__INTRO + + my $yes = prompt "Do you want to run the extended tests? (n/y)"; + return if $yes !~ /^[Yy](?:[Ee]:[Ss]?)?$/ ; + + unless(open TST, '>', "./test.txt") + { warn "ERROR: couldn't open ./test.txt: $!\n"; + return; + } + + my $server = ""; + until($server) + { $server = prompt "\nPlease provide the hostname or IP address of " + . "a host running an\nIMAP server (or QUIT to skip " + . "the extended tests)"; + chomp $server; + return if $server =~ /^\s*quit\s*$/i ; + } + + print TST "server=$server\n"; + + my $user = ""; + until($user) + { $user = prompt "\nProvide the username of an account on $server (or QUIT)"; + chomp $user; + return if $user =~ /^\s*quit\s*$/i ; + } + print TST "user=$user\n"; + + my $passed = ""; + until($passed) + { $passed = prompt "\nProvide the password for $user (or QUIT)"; + chomp $passed; + return if $passed =~ /^\s+$|^quit$/i ; + } + + print TST "passed=$passed\n"; + + my $port = prompt "\nPlease provide the port to connect to on $server " + . "to run the test\n(default is 143)"; + chomp $port; + $port ||= 143; + print TST "port=$port\n"; + + my $authmech = prompt "\nProvide the authentication mechanism to use " + . "on $server to\nrun the test (default is LOGIN)"; + + chomp $authmech; + $authmech ||= 'LOGIN'; + print TST "authmechanism=$authmech\n"; + close TST; + + print <<'__THANKS'; + +Gracias! The information you provided (including the password!) has +been stored in test.txt and SHOULD BE REMOVED (either by hand or by +'make clean') after testing. +__THANKS + +} diff --git a/Mail-IMAPClient-3.10/README b/Mail-IMAPClient-3.10/README new file mode 100644 index 0000000..45de3fb --- /dev/null +++ b/Mail-IMAPClient-3.10/README @@ -0,0 +1,111 @@ + Mail::IMAPClient + +Copyright 1999-2003 The Kernen Group, Inc. +Copyright 2007 Mark Overmeer +All rights reserved. + +This program is free software; you can redistribute it and/or modify +it under the terms of either: + + a) the "Artistic License" which comes with this Kit, or + + b) the GNU General Public License as published by the Free Software + Foundation; either version 1, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either +the GNU General Public License or the Artistic License for more details. + +DESCRIPTION + +This module provides perl routines that simplify a sockets connection +to and an IMAP conversation with an IMAP server. + +COMPATIBILITY + +[This paragraph has not been updated for many years] +This module was developed on Solaris 2.5.1 and 2.6 against Netscape IMAP +servers versions 3.6 and 4.1. However, since it is written in perl and +designed for flexibility, it should run on any OS with a TCP/IP stack and +a version of perl that includes the Socket and IO::Socket modules. It also +should be able to talk to any IMAP server, even those that have, um, +proprietary features (assuming that the programmer knows what those features +are). + +To date, I know that the test suite runs successfully with the following IMAP +servers: + +-Netscape Messenging Server v4.x +-Netscape Messenging Server v3.x +-UW-IMAP (I think it was 4.5) +-Cyrus IMAP4 v1.5.19 +-Mirapoint Message Server Appliances (OS versions 1.6.1, 1.7.1, and 2.0.2) + +Version 2.0.3 has been tested with the mdaemon server with mixed +results. It seems that mdaemon does not comply strictly with RFC2060 and +so you may have problems using this module with mdaemon, especially with +folder names with embedded spaces or embedded double quotes. You may be +able to get some simple tasks to work but you won't be able to run the +test suite successfully. Use with caution. + +If your server requires the use of the AUTHENTICATE IMAP client command +(say, for strong authentication) then you can still use this module, +provided you can come up with the appropriate responses to any challenges +offered by your server. Mark Bush's Authen::NTLM module can assist with +this if you specifically are interested in NTLM authentication. + +REPORING BUGS + +See http://rt.cpan.org/Public/Dist/Display.html?Name=Mail-IMAPClient + +INSTALLATION + +Generally, gunzipping and untarring the source file, running 'perl +Makefile.PL' and 'make install' are all it takes to install this +module. And if that's too much work you can always use the CPAN module! + +OVERVIEW OF FUNCTIONALITY + +Mail::IMAPClient.pm provides methods to simplify the connection to and +the conversation between a perl script and an IMAP server. Virtually +all IMAP Client commands (as defined in rfc2060) are supported, either +through IMAPClient object methods or the 'default method', which is an +AUTOLOAD hack that assumes a default syntax for IMAP Client commands of: + + tagvalue COMMAND [Arg1 [Arg2 [... Arg3]]]" + +By remarkable coincidence, AUTOLOAD's default syntax mimics the +general syntax of IMAP Client commands. This means that if a script +tries to use any undefined method then that method will be interpreted +as an unimplemented IMAP command, and the default syntax will be used +to create the command string. I did this as a short cut to writing a +bunch of methods that were practically the same. There are inheritance +implications because of this approach but as far as I can tell this is +not a serious limitation. However, if you decide to write modules that +inherit from this class that require AUTOLOAD logic of their own then you +will have to take the Mail::IMAPClient's AUTOLOAD strategy into account. + +Where methods are defined, they usually exist to add functionality, +perhaps by massaging output or by supplying default arguments. An example +is the search method, which accepts the same arguments as the SEARCH +IMAP Client command (as documented in RFC2060) but which massages the +results so that the return value is an array of message sequence numbers +matching the search criteria, rather than a line of text listing the +sequence numbers. + +Some methods exists solely to add functionality, such as the folders +method, which invokes the list method but then massages the results to +produce an array containing all folder names. The message_count and +delete_messsage methods are similarly examples of methods that add +function to "raw" IMAP Client commands. + +Further information is provided in the module's documentation, which you are +encouraged to read and enjoy. + +Good Luck! + +Dave Kernen +The Kernen Group, Inc. +DJKERNEN@cpan.org + diff --git a/Mail-IMAPClient-3.10/TODO b/Mail-IMAPClient-3.10/TODO new file mode 100644 index 0000000..47ff39a --- /dev/null +++ b/Mail-IMAPClient-3.10/TODO @@ -0,0 +1,68 @@ + +=== README + +Starting with release 2.99_01, I (Mark Overmeer) try to revive the +module. The original author David Kernen cannot be reached and didn't +release any fixes in four years. That is far too long. + +The code and installation procedure has been cleaned-up radically, + + and some minimal improvements in the code are made to +fix things people reported. + +=== wishlist: + +- A start was made in cleanup of the code in Mail/IMAPClient.pm + The file Mail/IMAPClient-cleanup shows the progress (30%) + But I lack the time (a weeks work at least) to complete this + task. There is a lot of code replication to be stripped. + If anyone buys me time, I will complete that task. + +=== wishlist from the original author: + +The following is a list of some items that I hope to include in a future +release: + +- Support for threaded perl programs (still pending as of version 2.2.0.). + +- Support for imaps (Imap via SSL). I don't have any way to test this + right now but if you get this to work or know someone who has I'd be + really interested in hearing from you. + +- Support for more authentication mechanisms. Currently plain + authentication and cram-md5 authentication are supported. I have + DIGEST-MD5 working at the AUTH qop, but haven't incorporated it into + a released version because I'm still trying to get at least the + integrity qop working, and maybe even privacy, but considering how + much trouble I'm having with just the integrity level I wouldn't + hold my breath if I were you ;-). + +- Currently a number of IMAP Client commands are implemented using the + 'default method', which is an AUTOLOAD hack. I'd like to reduce that + if possible to a bare minimum. (Some are still pending as of version 2.2.7.) + +- I'd like to see this module certified for more OS's and more IMAP servers. + This is (hopefully) just a matter of testing; the code should already + be compatible with the IMAP servers that are out there and with any OS + that allows the IO::Socket module to work. ** A number of platforms + have been added to the list of tested platforms since this was first + written. Please contact DJKernen@cpan.org if you have any to add. + +- Support for newer/older/other versions of IMAP. Currently only RFC2060 is + explicitly supported, although thanks to the 'default method' + (implemented via an AUTOLOAD hack) virtually any IMAP command is + supported, even proprietary commands, X- extensions, and so forth. But + not necessarily other authentication mechanisms... :-( (NOTE: the + AUTHENTICATE method partially addresses this issue.) + +- Support for piping output from (some?) imap commands directly to a + thingy of some sort (perhaps a coderef, a filehandle, or both). + +- Your thingy here!!! Send me your request, and I'll do it in the order of + ($popularity/$difficulty ). + +- Support for perl version 6. This will probably involve a rewrite that + will make portions of the Mail::IMAPClient module look more like the + Mail::IMAPClient::BodyStructure module. (Perl 6 will have built-in + support for semantics that look remarkably like Damian Conway's + Parse::RecDescent module, which will solve a lot of problems for me.) diff --git a/Mail-IMAPClient-3.10/examples/build_dist.pl b/Mail-IMAPClient-3.10/examples/build_dist.pl new file mode 100755 index 0000000..55b90c7 --- /dev/null +++ b/Mail-IMAPClient-3.10/examples/build_dist.pl @@ -0,0 +1,172 @@ +#!/usr/local/bin/perl +#$Id: build_dist.pl,v 19991216.7 2003/06/12 21:38:29 dkernen Exp $ + +use Mail::IMAPClient; + +=head1 DESCRIPTION + +B accepts the name of a target folder as an argument. It +then opens that folder and rummages through all the mail files in it, looking +for "Reply-to:" headers (or "From:" headers, where there is no "Reply-to:"). +It then appends a message into the folder containing all of the addresses in +thus found as a list of recipients. This message can be used to conveniently +drag and drop names into an address book, distribution list, or e-mail message, +using the GUI client of choice. + +The email appended to the folder specified in the I<-f> option will have the +subject "buid_dist.pl I Output". + +=head1 SYNTAX + +b I<-h> + +b I<-s servername -u username -p password -f folder [ -d ]> + +=over 4 + +=item -f The folder name to process. + +=item -s The servername of the IMAP server + +=item -u The user to log in as + +=item -p The password for the user specified in the I<-u> option + +=item -d Tells the IMAP client to turn on debugging info + +=item -h Prints out this document + +=back + +B You can supply defaults for the above options by updating the script. + +=cut + +use Getopt::Std; + +getopts('s:u:p:f:d'); + +# Update the following to supply defaults: + +$opt_f ||= "default folder"; +$opt_s ||= "default server"; +$opt_u ||= "default user"; +$opt_p ||= "default password"; # security risk: use with caution! + +# Let the compiler know we're serious about these two variables: +$opt_h = $opt_h or $opt_d = $opt_d ; + +exec "perldoc $0" if $opt_h; + +my $imap = Mail::IMAPClient->new( + Server => $opt_s , + User => $opt_u , + Password=> $opt_p , + Debug => $opt_d||0 , +) or die "can't connect to server\n"; + +$imap->select($opt_f); + +my @msgs = $imap->search("NOT SUBJECT",qq("buid_dist.pl $opt_f Output")); +my %list; +foreach my $m (@msgs) { + + my $ref = $imap->parse_headers($m,"Reply-to","From"); + + warn "Couldn't get recipient address from msg#$m\n" + unless scalar(@{$ref->{'Reply-to'}}) || + scalar(@{$ref->{'From'}}) ; + + my $from = scalar(@{$ref->{'Reply-to'}}) ? + $ref->{'Reply-to'}[0] : + $ref->{'From'}[0] ; + + my $addr = $from; + $addr =~ s/.*]//g; + $list{$addr} = $from unless exists $list{$addr}; +} + +$append = <<"EOMSG"; +To: ${\(join(",",values %list))} +From: $opt_u\@$opt_s +Date: ${\($imap->Rfc822_date(time))} +Subject: build_dist.pl $opt_f Output + +The above note was never actually sent to the following people: + +${\(join("\n",keys %list))} + +Interesting, eh? + +Love, +$opt_u + +EOMSG + +$imap->append($opt_f,$append) or warn "Couldn't append the message."; + +$imap->logout; + + +=head1 AUTHOR + +David J. Kernen + +The Kernen Group, Inc. + +imap@kernengroup.com + +=head1 COPYRIGHT + +This example and Mail::IMAPClient are Copyright (c) 2003 +by The Kernen Group, Inc. All rights reserved. + +This example is distributed with Mail::IMAPClient and +subject to the same licensing requirements as Mail::IMAPClient. + +imtest is a utility distributed with Cyrus IMAP server, +Copyright (c) 1994-2000 Carnegie Mellon University. +All rights reserved. + +=cut + + +# $Id: build_dist.pl,v 19991216.7 2003/06/12 21:38:29 dkernen Exp $ +# $Log: build_dist.pl,v $ +# Revision 19991216.7 2003/06/12 21:38:29 dkernen +# +# Preparing 2.2.8 +# Added Files: COPYRIGHT +# Modified Files: Parse.grammar +# Added Files: Makefile.old +# Makefile.PL Todo sample.perldb +# BodyStructure.pm +# Parse.grammar Parse.pod +# range.t +# Thread.grammar +# draft-crispin-imapv-17.txt rfc1731.txt rfc2060.txt rfc2062.txt +# rfc2221.txt rfc2359.txt rfc2683.txt +# +# Revision 19991216.6 2000/12/11 21:58:50 dkernen +# +# Modified Files: +# build_dist.pl build_ldif.pl copy_folder.pl find_dup_msgs.pl +# imap_to_mbox.pl populate_mailbox.pl +# to add CVS data +# +# Revision 19991216.5 1999/12/16 17:19:09 dkernen +# Bring up to same level +# +# Revision 19991124.3 1999/12/16 17:14:22 dkernen +# Incorporate changes for exists method performance enhancement +# +# Revision 19991124.02 1999/11/24 17:46:16 dkernen +# More fixes to t/basic.t +# +# Revision 19991124.01 1999/11/24 16:51:46 dkernen +# Changed t/basic.t to test for UIDPLUS before trying UID cmds +# +# Revision 1.8 1999/11/23 17:51:05 dkernen +# Committing version 1.06 distribution copy +# diff --git a/Mail-IMAPClient-3.10/examples/build_ldif.pl b/Mail-IMAPClient-3.10/examples/build_ldif.pl new file mode 100755 index 0000000..f040db5 --- /dev/null +++ b/Mail-IMAPClient-3.10/examples/build_ldif.pl @@ -0,0 +1,235 @@ +#!/usr/local/bin/perl +#$Id: build_ldif.pl,v 19991216.11 2003/06/12 21:38:30 dkernen Exp $ +use Mail::IMAPClient; +use MIME::Lite; +use Data::Dumper; + +=head1 DESCRIPTION + +B accepts the name of a target folder as an argument. It +then opens that folder and rummages through all the mail files in it, looking +for "Reply-to:" headers (or "From:" headers, where there is no "Reply-to:"). +It then prints to STDOUT a file in ldif format containing entries for all of +the addresses that it finds. It also appends a message into the specified folder containing +all of the addresses in both the B field of the message header and in an +LDIF-format attachment. + +B requires B. + +=head1 SYNTAX + +B I<-h> + +B I<-s servername -u username -p password -f folder [ -d ]> + +=over 4 + +=item -f The folder name to process. + +=item -s The servername of the IMAP server + +=item -t Include "To" and "Cc" fields as well as "From" + +=item -u The user to log in as + +=item -p The password for the user specified in the I<-u> option + +=item -d Tells the IMAP client to turn on debugging info + +=item -n Suppress delivering message to folder + +=item -h Prints out this document + +=back + +B You can supply defaults for the above options by updating the script. + +=cut + +use Getopt::Std; + +getopts('hs:u:p:f:dtn'); + +# Update the following to supply defaults: + +$opt_f ||= "default folder"; +$opt_s ||= "default server"; +$opt_u ||= "default user"; +$opt_p ||= "default password"; # security risk: use with caution! + +# Let the compiler know we're serious about these variables: +$opt_0 = ( $opt_h or $opt_d or $opt_t or $opt_n or $opt_0); + +exec "perldoc $0" if $opt_h; + +my $imap = Mail::IMAPClient->new( + Server => $opt_s , + User => $opt_u , + Password=> $opt_p , + Debug => $opt_d||0 , +) or die "can't connect to server\n"; + +$imap->select($opt_f); $imap->expunge; + +my @msgs = $imap->search("NOT SUBJECT",qq("buid_ldif.pl $opt_f Output")); +my %list; +foreach my $m (@msgs) { + + my $ref = $imap->parse_headers($m,"Reply-to","From"); + + warn "Couldn't get recipient address from msg#$m\n" + unless scalar(@{$ref->{'Reply-to'}}) || + scalar(@{$ref->{'From'}}) ; + + my $from = scalar(@{$ref->{'Reply-to'}}) ? + $ref->{'Reply-to'}[0] : + $ref->{'From'}[0] ; + my $name = $from ; + + $name =~ s/<.*// ; + if ($name =~ /\@/) { + $name = $from ; + $name =~ s/\@.*//; ; + } + $name =~ s/\"//g ; + $name =~ s/^\s+|\s+$//g ; + my $addr = $from ; + $addr =~ s/.*]//g ; + $list{lc($addr)} = [ $addr, $name ] + unless exists $list{lc($addr)} ; + if ($opt_t) { # Do "To" and "Cc", too + my $ref = $imap->parse_headers($m,"To","Cc") ; + my @array = ( @{$ref->{To}} , @{$ref->{Cc}} ) ; + my @members = () ; + foreach my $text (@array) { + while ( $text =~ / "([^"\\]*(\\.[^"\\]*)*"[^,]*),? | + ([^",]+),? | + , + /gx + ) { + push @members, defined($1)?$1:$3 ; + } + } + foreach my $to (@members) { + + my $name = $to ; + + $name =~ s/<.*// ; + if ($name =~ /\@/) { + $name = $to ; + $name =~ s/\@.*//; ; + } + $name =~ s/\"//g ; + $name =~ s/^\s+|\s+$//g ; + my $addr = $to ; + $addr =~ s/.*]//g ; + $list{lc($addr)} = [ $addr, $name ] + unless exists $list{lc($addr)} ; + } + + } +} + +my $text = join "",map { + qq{dn: cn="} . $list{$_}[1] . + qq{", mail=$list{$_}[0]\n} . + qq{cn: } . $list{$_}[1] . qq{\n} . + qq{mail: $list{$_}[0]\n} . + qq{objectclass: top\nobjectclass: person\n\n}; +} keys %list ; + +# Create a new multipart message: +my $msg = MIME::Lite->new( + From => $opt_u, + map({ ("To" => $list{$_}[0]) } keys %list), + Subject => "LDIF file from $opt_f", + Type =>'TEXT', + Data =>"Attached is the LDIF file of addresses from folder $opt_f." +); +$msg->attach( Type =>'text/ldif', + Filename => "$opt_f.ldif", + Data => $text , +); +print $text; +$imap->append($opt_f, $msg->as_string) unless $opt_n; +print Dumper($imap) if $opt_d; +$imap->logout; + + +=head1 AUTHOR + +David J. Kernen + +The Kernen Group, Inc. + +imap@kernengroup.com + +=head1 COPYRIGHT + +This example and Mail::IMAPClient are Copyright (c) 1999,2003 +by The Kernen Group, Inc. All rights reserved. + +This example is distributed with Mail::IMAPClient and +subject to the same licensing requirements as Mail::IMAPClient. + +imtest is a utility distributed with Cyrus IMAP server, +Copyright (c) 1994-2000 Carnegie Mellon University. +All rights reserved. + +=cut + +# $Id: build_ldif.pl,v 19991216.11 2003/06/12 21:38:30 dkernen Exp $ +# $Log: build_ldif.pl,v $ +# Revision 19991216.11 2003/06/12 21:38:30 dkernen +# +# Preparing 2.2.8 +# Added Files: COPYRIGHT +# Modified Files: Parse.grammar +# Added Files: Makefile.old +# Makefile.PL Todo sample.perldb +# BodyStructure.pm +# Parse.grammar Parse.pod +# range.t +# Thread.grammar +# draft-crispin-imapv-17.txt rfc1731.txt rfc2060.txt rfc2062.txt +# rfc2221.txt rfc2359.txt rfc2683.txt +# +# Revision 19991216.10 2002/05/24 15:47:18 dkernen +# Misc fixes +# +# Revision 19991216.9 2000/12/11 21:58:51 dkernen +# +# Modified Files: +# build_dist.pl build_ldif.pl copy_folder.pl find_dup_msgs.pl +# imap_to_mbox.pl populate_mailbox.pl +# to add CVS data +# +# Revision 19991216.8 2000/03/02 19:57:13 dkernen +# +# Modified Files: build_ldif.pl -- to support new option to all "To:" and "Cc:" to be included in ldif file +# +# Revision 19991216.7 2000/02/21 16:16:10 dkernen +# +# Modified Files: build_ldif.pl -- to allow for "To:" and "Cc:" header handling and +# to handle quoted names in headers +# +# Revision 19991216.6 1999/12/28 13:56:59 dkernen +# Fixed -h option (help). +# +# Revision 19991216.5 1999/12/16 17:19:10 dkernen +# Bring up to same level +# +# Revision 19991124.3 1999/12/16 17:14:24 dkernen +# Incorporate changes for exists method performance enhancement +# +# Revision 19991124.02 1999/11/24 17:46:18 dkernen +# More fixes to t/basic.t +# +# Revision 19991124.01 1999/11/24 16:51:48 dkernen +# Changed t/basic.t to test for UIDPLUS before trying UID cmds +# +# Revision 1.8 1999/11/23 17:51:05 dkernen +# Committing version 1.06 distribution copy +# diff --git a/Mail-IMAPClient-3.10/examples/cleanTest.pl b/Mail-IMAPClient-3.10/examples/cleanTest.pl new file mode 100755 index 0000000..a60f780 --- /dev/null +++ b/Mail-IMAPClient-3.10/examples/cleanTest.pl @@ -0,0 +1,64 @@ +#!/usr/local/bin/perl + +use Mail::IMAPClient; +use IO::File; +# +# Example that will also clean out your test account if interrupted 'make test' +# runs have left junk folders there. Run from installation dir, installation/examples +# subdir, or supply full path to the test.txt file (created during 'perl Makefile.PL' +# and left in the installation dir until 'make clean'). +# If you 've already run 'make clean' or said no to extended tests, +# then you don't have the file anyway; re-run 'perl Makefile.PL', reply 'y' to the +# extended tests prompt, then supply the test account's credentials as prompted. +# Then try this again. +# +if ( -f "./test.txt" ) { + $configFile = "./test.txt" +} elsif ( -f "../test.txt" ) { + $configFile = "../test.txt" +} elsif ( $ARGV[0] and -f "$ARGV[0]" ) { + $configFile = $ARGV[0]; +} else { + print STDERR "Can't find test.txt. Please run this from the installation directory ", + "or supply the full path to test.txt as an argument on the command line.\n"; +} +my $fh = IO::File->new("./test.txt") or die "./test.txt: $!\n"; +while (my $input = <$fh>) { + chomp $input; + my($k,$v) = split(/=/,$input,2); + $conf{$k}=$v; +} +my $imap = Mail::IMAPClient->new(Server=>$conf{server},User=>$conf{user}, + Password=>$conf{passed}) or die "Connecting to $conf{server}: $! $@\n"; + +for my $f ( grep(/^IMAPClient_/,$imap->folders) ) { + print "Deleting $f\n"; + $imap->select($f); + $imap->delete_messages(@{$imap->messages}) ; + $imap->close($f); + $imap->delete($f); +} + + +=head1 AUTHOR + +David J. Kernen + +The Kernen Group, Inc. + +imap@kernengroup.com + +=head1 COPYRIGHT + +This example and Mail::IMAPClient are Copyright (c) 2003 +by The Kernen Group, Inc. All rights reserved. + +This example is distributed with Mail::IMAPClient and +subject to the same licensing requirements as Mail::IMAPClient. + +imtest is a utility distributed with Cyrus IMAP server, +Copyright (c) 1994-2000 Carnegie Mellon University. +All rights reserved. + +=cut + diff --git a/Mail-IMAPClient-3.10/examples/copy_folder.pl b/Mail-IMAPClient-3.10/examples/copy_folder.pl new file mode 100644 index 0000000..736ffff --- /dev/null +++ b/Mail-IMAPClient-3.10/examples/copy_folder.pl @@ -0,0 +1,147 @@ +#!/usr/local/bin/perl +#$Id: copy_folder.pl,v 19991216.3 2003/06/12 21:38:30 dkernen Exp $ +++$|; +use Getopt::Std; +use Mail::IMAPClient; +use vars qw/$opt_r $opt_h $opt_t $opt_f/; + +getopts("t:f:F:N:rh"); +if ( $opt_h ) { + print &usage; + exit; +} + +my($to_id,$to_pass,$thost) = $opt_t =~ m{ + ([^/]+) # everything up to / is the id + / # then a slash + ([^@]+) # then everything up to @ is pswd + @ # then an @-sign + (.*) # then everything else is the host + }x ; +my($from_id,$from_pass,$fhost) = + $opt_f =~ m{ + ([^/]+) # everything up to / is the id + / # then a slash + ([^@]+) # then everything up to @ is pswd + @ # then an @-sign + (.*) # then everything else is the host + }x ; +$to_id and $from_id and $to_pass and $from_pass and $thost and $fhost + or die "Error: Must specify -t and -f (to and from)\n" . &usage; +$opt_F or + die "Error: Must specify '-F folder' or how will I know what folder to copy?\n" . + &usage ; + +$opt_N ||= $opt_F; + + +print "Copying folder $opt_F from $from_id\@$fhost to ${to_id}'s $opt_N folder on $thost.\n"; + +my ($from) = Mail::IMAPClient->new( Server => $fhost, + User => $from_id, + Password=> $from_pass, + Fast_IO => 1, + Uid => 1, + Debug => 0, +); + + +my ($to) = Mail::IMAPClient->new( Server => $thost, + User => $to_id, + Password=> $to_pass, + Fast_IO => 1, + Uid => 1, + Debug => 0, +); + +my @folders = $opt_r ? @{$from->folders($opt_F)} : ( $opt_F ) ; + +foreach my $fold (@folders) { + print "Processing folder $fold\n"; + $from->select($fold); + if ($opt_F ne $opt_N) { + $fold =~s/^$opt_F/$opt_N/o; + } + unless ($to->exists($fold)) { + $to->create($fold) or warn "Couldn't create $fold\n" and next; + } + $to->select($fold); + my @msgs = $from->search("ALL"); + # my %flaghash = $from->flags(\@msgs); + foreach $msg (@msgs) { + print "Processing message $msg in folder $fold.\n"; + my $string = $from->message_string($msg); + # print "String = $string\n"; + my $new_id = $to->append($fold,$string) + or warn "Couldn't append msg #$msg to target folder $fold.\n"; + + $to->store($new_id,"+FLAGS (" . join(" ",@{$from->flags($msg)}) . ")"); + } +} + +sub usage { + return "Syntax:\n\t$0 -t to_id/to_pass\@to.host -f from_id/from_pass\@from.host \\\n" . + "\t\t-F folder [-N New_Folder] [-r]\n". + "\tor\n\t$0 -h\n\n". + "\twhere:\n\t\t". + "to_id\t\tis the id to recieve the folder\n\t\t". + "to_pass\t\tis the password for to_id\n\t\t". + "from\t\tis the uid who currently has the folder\n\t\t". + "from_pass\tis the password for from_id\n\t\t". + "to.host\t\tis the optional host where the 'to' uid has a mailbox\n\t\t". + "from.host\tis the optional host where the 'from' uid has a mailbox\n\t\t". + "folder\t\tis the folder to copy from\n\t\t". + "New_Folder\tis the folder to copy to (defaults to 'folder')\n\t\t". + "-h\t\tprints this help message\n\t\t". + "-r\t\tspecifies a recursive copy (only works on systems that support the idea " . + "\n\t\t\t\tof recursive folders)\n\t\t". + "\n" + ; +} + + +=head1 AUTHOR + +David J. Kernen + +The Kernen Group, Inc. + +imap@kernengroup.com + +=head1 COPYRIGHT + +This example and Mail::IMAPClient are Copyright (c) 1999,2000,2003 +by The Kernen Group, Inc. All rights reserved. + +This example is distributed with Mail::IMAPClient and +subject to the same licensing requirements as Mail::IMAPClient. + +imtest is a utility distributed with Cyrus IMAP server, +Copyright (c) 1994-2000 Carnegie Mellon University. +All rights reserved. + +=cut + +# History: +# $Log: copy_folder.pl,v $ +# Revision 19991216.3 2003/06/12 21:38:30 dkernen +# +# Preparing 2.2.8 +# Added Files: COPYRIGHT +# Modified Files: Parse.grammar +# Added Files: Makefile.old +# Makefile.PL Todo sample.perldb +# BodyStructure.pm +# Parse.grammar Parse.pod +# range.t +# Thread.grammar +# draft-crispin-imapv-17.txt rfc1731.txt rfc2060.txt rfc2062.txt +# rfc2221.txt rfc2359.txt rfc2683.txt +# +# Revision 19991216.2 2000/12/11 21:58:51 dkernen +# +# Modified Files: +# build_dist.pl build_ldif.pl copy_folder.pl find_dup_msgs.pl +# imap_to_mbox.pl populate_mailbox.pl +# to add CVS data +# diff --git a/Mail-IMAPClient-3.10/examples/cyrus_expire.pl b/Mail-IMAPClient-3.10/examples/cyrus_expire.pl new file mode 100755 index 0000000..52a97d7 --- /dev/null +++ b/Mail-IMAPClient-3.10/examples/cyrus_expire.pl @@ -0,0 +1,111 @@ +#!/usr/local/bin/perl +#$Id + +use Mail::IMAPClient; # available from http://search.cpan.org/search?mode=module&query=IMAPClient +use IO::File; +use Getopt::Std; +use vars qw/ $opt_d $opt_s $opt_p $opt_u $opt_P $opt_h /; + +&getopts('d:s:u:p:P:h'); # -d days_to_keep -u cyrys_user -p cyrus_pswd -s cyrus_server -P port + +my $days_to_keep = $opt_d||365; # Delete msgs older than -d arg or 365 days +my $cutoff = time - ( $days_to_keep * 24 * 60 * 60 ) ; # time - arg * 24 * 60 * 60 = cutoff date in seconds + +# Change the following line (or replace it with something better): +$opt_h and die help()."\n"; +my $h = $opt_s || "localhost" ; +my $u = $opt_u || "cyrys" ; +my $p = $opt_p or die "Unable to continue. No password provided.\n" . help(); + +my $imap = Mail::IMAPClient->new( + Server => "$h", + User => "$u", # $u, + Password=> "$p", # $p, + Uid => 1, # True value + Port => $opt_P||143, # imapd + Debug => 0, # Make true to debug + Buffer => 4096*10, # True value; decrease on machines w/little memory + Fast_io => 1, # True value + Timeout => 30, # True value + # Debug_fh=> IO::File->new(">out.db"), # fhandle + ) +or die "$@"; + my $mcnt = my $fcnt = 0; +print "Deleting messages older than ",$imap->Rfc2060_date($cutoff),"\n"; +for my $f ( $imap->folders ) { + print "Expiring $f\n"; + unless ($imap->select($f) ) { + $imap->setacl($f,$u,"lrswipcda") or warn "Cannot setacl for $f: $@\n" and next; + $imap->select($f) or warn "Cannot select $f: $@" and next; + } + my @expired = $imap->search("SENTBEFORE",$imap->Rfc2060_date($cutoff)); + next unless @expired; + $mcnt += scalar(@expired); $fcnt ++; + print "Deleting ",scalar(@expired)," messages from $f\n"; + $imap->delete_message(@expired); + $imap->expunge; + $imap->close; +} + $imap->logout; + print "Deleted a total of $mcnt messages in $fcnt folders.\n"; +exit; + + +sub help { + return <<"EOHELP"; + +Usage: + + $0 [ -d days_to_keep ] [ -s mail_server ] [ -u cyrus_admin_id ] -p cyrus_password + $0 -h + + -h -- prints this here help message + -d days_to_keep -- $0 will delete messages older than "days_to_keep". (Default is 365) + -s mail_server -- hostname or IP Address of IMAP mail server (defaults to "localhost") + -u cyrus_admin_id -- user name of Unix account that owns Cyrus server (defaults to "cyrus") + -p cyrus_password -- password for the "cyrus_admin_id" user account (no default) + -P cyrus_port -- port where the cyrus imapd daemon is listening (defaults to value from + /etc/services or '143') + +EOHELP + +} + + +=head1 AUTHOR + +David J. Kernen + +The Kernen Group, Inc. + +imap@kernengroup.com + +=head1 COPYRIGHT + +This example and Mail::IMAPClient are Copyright (c) 2003 +by The Kernen Group, Inc. All rights reserved. + +This example is distributed with Mail::IMAPClient and +subject to the same licensing requirements as Mail::IMAPClient. + +imtest is a utility distributed with Cyrus IMAP server, +Copyright (c) 1994-2000 Carnegie Mellon University. +All rights reserved. + +=cut + +#$Log: cyrus_expire.pl,v $ +#Revision 19991216.2 2003/06/12 21:38:31 dkernen +# +#Preparing 2.2.8 +#Added Files: COPYRIGHT +#Modified Files: Parse.grammar +#Added Files: Makefile.old +# Makefile.PL Todo sample.perldb +# BodyStructure.pm +# Parse.grammar Parse.pod +# range.t +# Thread.grammar +# draft-crispin-imapv-17.txt rfc1731.txt rfc2060.txt rfc2062.txt +# rfc2221.txt rfc2359.txt rfc2683.txt +# diff --git a/Mail-IMAPClient-3.10/examples/cyrus_expunge.pl b/Mail-IMAPClient-3.10/examples/cyrus_expunge.pl new file mode 100644 index 0000000..d5e3f9a --- /dev/null +++ b/Mail-IMAPClient-3.10/examples/cyrus_expunge.pl @@ -0,0 +1,85 @@ +#!/usr/local/bin/perl +#$Id: cyrus_expunge.pl,v 19991216.3 2003/06/12 21:38:31 dkernen Exp $ + +use Mail::IMAPClient; +use IO::File; + +# Change the following line (or replace it with something better): +my($h,$u,$p) = ('cyrus_host','cyrus_admin_id','cyrus_admin_pswd'); + +my $imap = Mail::IMAPClient->new( Server => "$h", # imap host + User => "$u", # $u, + Password=> "$p", # $p, + Uid => 1, # True value + Port => 143, # Cyrus + Debug => 0, # True value + Buffer => 4096*10, # True value + Fast_io => 1, # True value + Timeout => 30, # True value + # Debug_fh=> IO::File->new(">out.db"), # fhandle + ) +or die "$@"; + +for my $f ( $imap->folders ) { + print "Expunging $f\n"; + unless ($imap->select($f) ) { + $imap->setacl($f,$u,"lrswipcda") or warn "Cannot setacl for $f: $@\n" and next; + $imap->select($f) or warn "Cannot select $f: $@" and next; + } + $imap->expunge; +} + + +=head1 AUTHOR + +David J. Kernen + +The Kernen Group, Inc. + +imap@kernengroup.com + +=head1 COPYRIGHT + +This example and Mail::IMAPClient are Copyright (c) 2003 +by The Kernen Group, Inc. All rights reserved. + +This example is distributed with Mail::IMAPClient and +subject to the same licensing requirements as Mail::IMAPClient. + +imtest is a utility distributed with Cyrus IMAP server, +Copyright (c) 1994-2000 Carnegie Mellon University. +All rights reserved. + +=cut + +# +#$Log: cyrus_expunge.pl,v $ +#Revision 19991216.3 2003/06/12 21:38:31 dkernen +# +#Preparing 2.2.8 +#Added Files: COPYRIGHT +#Modified Files: Parse.grammar +#Added Files: Makefile.old +# Makefile.PL Todo sample.perldb +# BodyStructure.pm +# Parse.grammar Parse.pod +# range.t +# Thread.grammar +# draft-crispin-imapv-17.txt rfc1731.txt rfc2060.txt rfc2062.txt +# rfc2221.txt rfc2359.txt rfc2683.txt +# +#Revision 1.1 2003/06/12 21:38:14 dkernen +# +#Preparing 2.2.8 +#Added Files: COPYRIGHT +#Modified Files: Parse.grammar +#Added Files: Makefile.old +# Makefile.PL Todo sample.perldb +# BodyStructure.pm +# Parse.grammar Parse.pod +# range.t +# Thread.grammar +# draft-crispin-imapv-17.txt rfc1731.txt rfc2060.txt rfc2062.txt +# rfc2221.txt rfc2359.txt rfc2683.txt +# +# diff --git a/Mail-IMAPClient-3.10/examples/find_dup_msgs.pl b/Mail-IMAPClient-3.10/examples/find_dup_msgs.pl new file mode 100644 index 0000000..949f7d3 --- /dev/null +++ b/Mail-IMAPClient-3.10/examples/find_dup_msgs.pl @@ -0,0 +1,217 @@ +#!/usr/local/bin/perl +# $Id: find_dup_msgs.pl,v 19991216.5 2003/06/12 21:38:32 dkernen Exp $ + +use Mail::IMAPClient; +use Mozilla::LDAP::Conn; +use Getopt::Std; +use vars qw/$rootdn $opt_a/; +use Data::Dumper; + +# It then connects to a user's mailhost and rummages around, +# looking for duplicate messages. +# It will optionally delete messages that are duplicates (based on +# msg-id header and number of bytes). +# For help, enter: +# find_dup_msgs.pl -h +# + +getopts('ahdtvf:F:u:s:p:P:'); + +if ( $opt_h ) { + print STDERR &usage; + exit; +} + +my $uid = $opt_u or die &usage; +$opt_s||='localhost'; +$opt_p or die &usage; +$opt_P||=143; + +$opt_t and + $opt_d and + die "ERROR: Don't specify -d and -t together.\n" . &usage; + + +my($pu,$pp) = get_admin(); + +print "Connecting to $host:$opt_P\n" if $opt_v; +my $imap = Imap->new( Server => $opt_s, + User => $opt_u, + Password=> $opt_p, + Port => $opt_P, + Fast_io => 1, +) or die "couldn't connect to $host port $opt_P: $!\n"; + +my %folders; my %counts; + +FOLDER: foreach my $f ( $opt_F ? $opt_F : $imap->folders ) { + next if $opt_t and $f eq 'Trash'; + $folders{$f} = 0; + $counts{$f} = $imap->message_count($f); + print "Processing folder $f\n" if $opt_v; + unless ( $imap->select($f)) { + warn "Error selecting $f: " . $imap->LastError . "\n"; + next FOLDER; + } + my @msgs = $imap->search("ALL"); + my %hash = (); + MESSAGE: foreach my $m (@msgs) { + my $mid; + if ($opt_a) { + my $h = $imap->parse_headers( + $m,"Date","Subject","From","Message-ID" + ) or next MESSAGE; + $mid = "$h->{'Date'}[0]$;$h->{'Subject'}[0]$;". + "$h->{'From'}[0]$;$h->{'Message-ID'}[0]"; + + } else { + $mid = $imap->parse_headers( + $m, + "Message-ID" + )->{'Message-ID'}[0] + or next MESSAGE; + } + my $size = $imap->size($m); + if ( exists $hash{$mid} and $hash{$mid} == $size ) { + if ($opt_f) { + open F,">>$opt_f" or + die "can't open $opt_f: $!\n"; + print F $imap->message_string($m), + "___END OF SAVED MESSAGE___","\n"; + close F; + } + $imap->move("Trash",$m) if $opt_t; + $imap->delete_message($m) if $opt_d; + $folders{$f}++; + print "Found a duplicate in ${f}; key = $mid\n" if $opt_v; + + } else { + + $hash{$mid} = $size; + } + } + print "$f hash:\n",Data::Dumper::Dumper(\%hash) if $opt_v; + $imap->expunge if ($opt_t or $opt_d); +} + +my $total; my $totms; +map { $total += $_} values %folders; +map { $totms += $_ } values %counts; +print "Found $total duplicate messages in ${uid}'s mailbox. ", + "The breakdown is:\n", + "\tFolder\tNumber of Duplicates\tNumber of Msgs in Folder\n", + "\t------\t--------------------\t------------------------\n", + map { "\t$_\t$folders{$_}\t$counts{$_}\n" } keys %folders, + "\tTOTAL\t$total\t$totms\n" +; + + +sub usage { + return "Usage:\n" . + "\t$0 [-d|-t] [-v] [-f filename] [-a] [-P port] \\\n". + "\t\t-s server -u user -p password\n\n" . + "\t-a\t\tdo an especially aggressive search for duplicates\n". + "\t-d\t\tdelete duplicates (default is to just report them)\n". + "\t-f file\t\tsave deleted messages in file named 'file'\n" . + "\t-F fldr\t\tOnly check the folder named 'fldr' (default is to check all folders)\n" . + "\t-h\t\tprint this help message (all other options are ignored)\n" . + "\t-p password\tspecify the target user's password\n" . + "\t-P port\t\tspecify the port to connect to (default is 143)\n" . + "\t-s server\tspecify the target mail server\n" . + "\t-u uid\t\tspecify the target user\n" . + "\t-t\t\tmove deleted messages to trash folder\n" . + "\t-v\t\tprint verbose status messages while processing\n". + "\n" ; +} + + +=head1 AUTHOR + +David J. Kernen + +The Kernen Group, Inc. + +imap@kernengroup.com + +=head1 COPYRIGHT + +This example and Mail::IMAPClient are Copyright (c) 2003 +by The Kernen Group, Inc. All rights reserved. + +This example is distributed with Mail::IMAPClient and +subject to the same licensing requirements as Mail::IMAPClient. + +imtest is a utility distributed with Cyrus IMAP server, +Copyright (c) 1994-2000 Carnegie Mellon University. +All rights reserved. + +=cut + +# History: +# $Log: find_dup_msgs.pl,v $ +# Revision 19991216.5 2003/06/12 21:38:32 dkernen +# +# Preparing 2.2.8 +# Added Files: COPYRIGHT +# Modified Files: Parse.grammar +# Added Files: Makefile.old +# Makefile.PL Todo sample.perldb +# BodyStructure.pm +# Parse.grammar Parse.pod +# range.t +# Thread.grammar +# draft-crispin-imapv-17.txt rfc1731.txt rfc2060.txt rfc2062.txt +# rfc2221.txt rfc2359.txt rfc2683.txt +# +# Revision 1.1 2003/06/12 21:38:14 dkernen +# +# Preparing 2.2.8 +# Added Files: COPYRIGHT +# Modified Files: Parse.grammar +# Added Files: Makefile.old +# Makefile.PL Todo sample.perldb +# BodyStructure.pm +# Parse.grammar Parse.pod +# range.t +# Thread.grammar +# draft-crispin-imapv-17.txt rfc1731.txt rfc2060.txt rfc2062.txt +# rfc2221.txt rfc2359.txt rfc2683.txt +# +# Revision 19991216.4 2002/08/23 14:34:51 dkernen +# +# Modified Files: Changes IMAPClient.pm Makefile Makefile.PL test.txt for version 2.2.0 +# Added Files: Makefile Makefile.PL Parse.grammar Parse.pm Parse.pod version 2.2.0 +# Added Files: parse.t for version 2.2.0 +# Added Files: bodystructure.t for 2.2.0 +# Modified Files: find_dup_msgs.pl for v2.2.0 +# +# Revision 1.6 2001/03/08 19:00:35 dkernen +# +# ---------------------------------------------------------------------- +# Modified Files: +# copy_folder.pl delete_mailbox.pl find_dup_msgs.pl +# mbox_check.pl process_orphans.pl rename_id.pl +# scratch_indexes.pl +# to get ready for nsusmsg02 upgrade +# ---------------------------------------------------------------------- +# +# Revision 1.5 2000/11/01 15:51:58 dkernen +# +# Modified Files: copy_folder.pl find_dup_msgs.pl restore_mbox.pl +# +# Revision 1.4 2000/04/13 21:17:18 dkernen +# +# Modified Files: find_dup_msgs.pl - to add -a switch (for aggressive dup search) +# Added Files: copy_folder.pl - a utility for copying a folder from one user's +# mailbox to another's +# +# Revision 1.3 2000/03/14 16:40:21 dkernen +# +# Modified Files: find_dup_msgs.pl -- to skip msgs with no message-id +# +# Revision 1.2 2000/03/13 19:05:50 dkernen +# +# Modified Files: +# delete_mailbox.pl find_dup_msgs.pl restore_mbox.pl -- to add cvs comments +# find_dup_msgs.pl -- to fix bug that occurred when -t (move-to-trash) switch is used +# diff --git a/Mail-IMAPClient-3.10/examples/imap_to_mbox.pl b/Mail-IMAPClient-3.10/examples/imap_to_mbox.pl new file mode 100755 index 0000000..2ea51c7 --- /dev/null +++ b/Mail-IMAPClient-3.10/examples/imap_to_mbox.pl @@ -0,0 +1,217 @@ +#!/usr/local/bin/perl +# (c) 1999 Thomas Stromberg, Research Triangle Commerce, Inc. +# This software is protected by the BSD License. No rights reserved anyhow. +# + +# DESC: Reads a users IMAP folders, and converts them to mbox +# Good for an interim switch-over from say, Exchange to Cyrus IMAP. + +# TODO: +# correct header printing From + +use Mail::IMAPClient; # a nice set of perl libs for imap +use IO::Socket::SSL; +use Getopt::Std; # for the command-line overrides. good for user +use File::Path; # create full file paths. (yummy!) +use File::Basename; # find a nice basename for a folder. +use Date::Manip; # to create From header date +$| = 1; + +sub connect_imap(); +sub find_folders(); +sub write_folder($$$$); + +# Config for the imap migration kit. + +getopts('u:p:P:s:i:f::b:c:W:Sdh'); + +$SSL = $opt_S || 0; +$SERVER = $opt_s || 'dell2'; +$USER = $opt_u || 'userid'; +$PASSWORD = $opt_p || 'password'; +$PORT = $opt_P || '143'; +$INBOX_PATH = $opt_i || "/var/mail/$USER"; +$FOLDERS_PATH = $opt_f || "./folders/$USER"; +$DONT_MOVE = $opt_m || '.mailboxlist|Trash|INBOXIIMAP|mlbxl'; +$READ_DELIMITER = $opt_r || '/'; +$WRITE_DELIMITER= $opt_w || '/'; +$WRITE_MODE = $opt_W || '>'; +$BANNED_CHARS = $opt_b || '.|^|%'; +$CR = $opt_c || "\r"; +$DELETE = $opt_D || 0; +$DEBUG = $opt_d || "0"; +$FAIL = 0; + +if ($opt_h) { + # print help here + print "imap_to_mbox.pl - with the following optional arguments: + -S Use an SSL connection (default $SSL) + -s Server specification (default $SERVER) + -u User login (default $USER) + -p

User password + -P

Server Port (default $PORT) + -i INBOX save path (default $INBOX_PATH) + -f Save path for other folders (default $FOLDERS_PATH) + -m Regexp for IMAP folders not to be saved: + $DONT_MOVE + -r Read delimiter (default \"$READ_DELIMITER\") + -w Write Delimiter (default \"$WRITE_DELIMITER\") + -b Banned chars (default \"$BANNED_CHARS\") + -c Strip CRs from saved files [for Unix] (default \"$CR\") + -D Delete files downloaded on server + -d Debug mode (default $DEBUG)\n"; + exit 1; +} + +## do our magic tricks ###################################### +connect_imap; +find_folders; + + +sub connect_imap() +{ +# Open an SSL session to the IMAP server +# Handles the SSL setup, and gives us back a socket + my $ssl; + if ($opt_S) { + $ssl=IO::Socket::SSL->new( + PeerHost => "$SERVER:imaps" +# , SSL_version => 'SSLv2' + ); + + defined $ssl + or die "Error connecting to $SERVER:imaps - $@"; + + $ssl->autoflush(1); + } + + $imap = Mail::IMAPClient->new( + Socket => ($opt_S ? $ssl : 0), + Server => $SERVER, + User => $USER, + Password => $PASSWORD, + Port => $PORT, + Debug => $DEBUG, + Uid => 0, + Clear => 1, + ) + or die ("Could not connect to $SERVER:$PORT with $USER: $! $?\n"); +} + +sub find_folders() +{ + my @folders = $imap->folders; +# push(@folders, "INBOX"); + + foreach my $folder (@folders) + { my $message_count = $imap->message_count($folder); + if(! $message_count) { + print("* $folder is empty, skipping.\n"); + next; + } + if($folder =~ /$DONT_MOVE/) { + print("! $folder matches DONT_MOVE ruleset, skipping\n"); + next; + } + + my $new_folder = $folder; + $new_folder =~ s/\./_/g; + $new_folder =~ s/\Q$READ_DELIMITER/$WRITE_DELIMITER/g; + my $path + = $new_folder eq "INBOX" ? "$INBOX_PATH" + : "$FOLDERS_PATH/$new_folder"; + + printf "x %4i %-45.45s => %s", $message_count, $folder, $path; + write_folder $folder, $path, 1, $message_count; + } +} + +sub write_folder($$$$) +{ my($folder, $newpath, $first_message, $last_message) = @_; + + $imap->select($folder) + or warn "Could not examine $folder: $!"; + + my $new_dir = dirname $newpath; + my $new_file = basename $newpath; + + -d $new_dir + or mkpath($new_dir, 0700) + or die "Cannot create $new_dir:$!\n"; + + open mbox, $WRITE_MODE, $newpath + or die "Cannot create file $newpath: $!\n"; + + for (my $i=$first_message; $i<$last_message+1; ++$i) + { my $date = UnixDate(ParseDate($imap->internaldate($i)), + "%a %b %e %T %Y"); + my $user = $imap->get_envelope($i)->from_addresses; + $user =~ s/^.*\<([^>]*)\>/$1/; + $user = '-' unless $user; + print '.' if $i%25 == 0; + + my $msg_header = $imap->fetch($i, "FAST") + or warn "Could not fetch header $i from $folder\n"; + + my $msg_rfc822 = $imap->fetch($i, "RFC822"); + unless($msg_rfc822) + { warn "Could not fetch RFC822 $i from $folder\n"; + $FAIL=1 + } + + undef $start; + foreach (@$msg_rfc822) + { if($_ =~ /\: / && !$message) + { ++$message; + print mbox "From $user $date\n"; + } + + if(/^\)\r/) + { undef $message; + print mbox "\n\n"; + } + next unless $message; + $_ =~ s/\r$//; + $_ = $imap->Strip_cr($_) if $CR; + print mbox "$_"; + + } + if($DELETE && ! $FAIL) + { $imap->delete_message($i) + or warn "Could not delete_message: $@\n"; + $FAIL = 0; + } + } + + close mbox + or die "Write errors to $newpath: $!\n"; + + if($DELETE) + { $imap->expunge($folder) + or warn "Could not expunge: $@\n"; + } + + print "\n"; +} + +# 2008/08/07 - Added SSL support, fixed From header printing, and CR +# elimination (sobek) +# +# Revision 19991216.7 2002/08/23 13:29:48 dkernen +# +# Revision 19991216.6 2000/12/11 21:58:52 dkernen +# +# Revision 19991216.5 1999/12/16 17:19:12 dkernen +# Bring up to same level +# +# Revision 19991124.3 1999/12/16 17:14:25 dkernen +# Incorporate changes for exists method performance enhancement +# +# Revision 19991124.02 1999/11/24 17:46:19 dkernen +# More fixes to t/basic.t +# +# Revision 19991124.01 1999/11/24 16:51:49 dkernen +# Changed t/basic.t to test for UIDPLUS before trying UID cmds +# +# Revision 1.3 1999/11/23 17:51:06 dkernen +# Committing version 1.06 distribution copy diff --git a/Mail-IMAPClient-3.10/examples/imtestExample.pl b/Mail-IMAPClient-3.10/examples/imtestExample.pl new file mode 100755 index 0000000..27938e9 --- /dev/null +++ b/Mail-IMAPClient-3.10/examples/imtestExample.pl @@ -0,0 +1,226 @@ +#!/usr/local/bin/perl + +use Sys::Hostname; +use Mail::IMAPClient; +use IPC::Open3; +use IO::Socket::UNIX; +use IO::Socket; +use Socket; +use Getopt::Std; +&getopts('ha:df:i:o:p:r:m:u:x:w:p:s:'); + +if ($opt_h) { + print <<" HELP"; + $0 -- uses imtest to connect and authenticate to imap server + + Options: + -h print this help message + + -a auth authenticate as user 'auth'. This value is passed as the '-a' value + to imtest and defaults to whatever you supplied for -u. + -d turn on Mail::IMAPClient debugging + -f file write Mail::IMAPClient debugging info to file 'file' + -m mech use authentication mechanism "mech"; default is to not supply -m to + imtest + -i path path to imtest executable; default is to let your shell find it via the + PATH environmental variable. + -p port port on mail server to connect to (default is 143) + -r rlm Use realm 'rlm' (default is name of mail server) + -s srvr Name of IMAP mail server (default is the localhost's hostname) + -u usr Use 'usr' as the user id (required) + -w pswd Use 'pswd' as the password for 'usr' (required) + -x path Path to Unix socket (fifo). Default is '/tmp/$0.sock'. + -o 'ops' Pass the string 'ops' directy to imtest as additional options. + This is how you get "other" imtest options passed to imtest. (I only + included switches for options that are either really common or useful + to the IMAPClient object as well as to imtest.) + + Many of these switches have the same function here as with imtest. I added a + few extras though! + + Example: + $0 -o '-k 128 -l 128' -s imapmail -u test -w testpswd \ + -i /usr/local/src/cyrus/cyrus-imapd-2.1.11/imtest/ \ + -m DIGEST-MD5 + + It's a good idea to test your options by running imtest from the command line + (but without the -x switch) first. Once you have it working by hand you should + be able to get it to work from this script (or one remarkably like it) without + too much bloodshed. + + HELP + exit; +} + +$opt_u and $opt_w or die "No userid/password credentials supplied. I hate that.\n"; +$opt_a ||= $opt_u; + +if ($opt_i ) { + $opt_i =~ m#^[/\.]# or $opt_i = "./$opt_i"; + $opt_i =~ m#imtest$# or ( -x $opt_i and -f $opt_i ) + or $opt_i .= ( $opt_i =~ m#/$# ? "imtest" : "/imtest") ; + -x $opt_i and -f $opt_i or die "Cannot find executable $opt_i\n"; +} + + +$opt_p ||= 143; +$opt_s ||= hostname; +$opt_r ||= $opt_s; +$opt_x ||= "/tmp/$0.sock"; + + +my($rfh,$wfh,$efh) ; + + +my($imt) = ($opt_i ? "$opt_i " : "imtest ") . + ($opt_m ? "-m $opt_m ":"" ) . + qq(-r $opt_r -a $opt_a -u $opt_u ). + qq(-x $opt_x -w $opt_w -p $opt_p $opt_s); + +open3($wfh,$rfh,$efh,$imt); + +my $line; + +until ($line =~ /^Security strength factor:/i ) { + $line = <$rfh> or die "EOF\n"; + print STDERR "Prolog: $line" if $opt_d; +} +sleep 5; +my $sock = IO::Socket::UNIX->new("$opt_x") + or warn "No socket: $!\n" and exit; + +print STDERR "<<>>\n" if $opt_d; +my $imap = Mail::IMAPClient->new; +$imap->Prewritemethod(\&Mail::IMAPClient::Strip_cr); +$imap->User("$opt_u"); +$imap->Server("$opt_s"); +$imap->Port("$opt_p"); +$imap->Debug($opt_d); +$imap->Debug_fh($opt_f||\*STDERR); +$imap->State($imap->Connected); +$imap->Socket($sock); + +# Your code goes here: + +$imap->Select("INBOX"); +for my $m (@{$imap->search("TEXT SUBJECT")} ) { + print "Message $m:\t",$imap->subject($m),"\n"; +} +# You should have finished your code by about here +$imap->logout; + +print STDERR "<<>>\n" if $opt_d; + +exit; + +=head1 NAME + +imtestExample.pl -- uses imtest to connect and authenticate to imap server + + +=head1 DESCRIPTION + + +=head2 Options + +=over 4 + +=item -h + +print this help message + +=item -a auth + +authenticate as user 'auth'. This value is passed as the '-a' value +to imtest and defaults to whatever you supplied for -u. + +=item -d + +turn on Mail::IMAPClient debugging + +=item -f file + +write Mail::IMAPClient debugging info to file 'file' + +=item -m mech + +use authentication mechanism "mech"; default is to not supply -m to + imtest + +=item -i path + +path to imtest executable; default is to let your shell find it via the +PATH environmental variable. + +=item -p port + +port on mail server to connect to (default is 143) + +=item -r rlm + +Use realm 'rlm' (default is name of mail server) + +=item -s srvr + +Name of IMAP mail server (default is the localhost's hostname) + +=item -u usr + +Use 'usr' as the user id (required) + +=item -w pswd + +Use 'pswd' as the password for 'usr' (required) + +=item -x path + +Path to Unix socket (fifo). Default is '/tmp/$0.sock'. + +=item -o 'ops' + +Pass the string 'ops' directy to imtest as additional options. +This is how you get "other" imtest options passed to imtest. (I only +included switches for options that are either really common or useful +to the IMAPClient object as well as to imtest.) + +Many of these switches have the same function here as with imtest. I added a +few extras though! + +=back + +Example: + + imtestExample.pl -o '-k 128 -l 128' -s imapmail -u test -w testpswd \ + -i /usr/local/src/cyrus/cyrus-imapd-2.1.11/imtest/ \ + -m DIGEST-MD5 + +It's a good idea to test your options by running imtest from the command line +(but without the -x switch) first. Once you have it working by hand you should +be able to get it to work from this script (or one remarkably like it) without +too much bloodshed. + + +=head1 AUTHOR + +David J. Kernen + +The Kernen Group, Inc. + +imap@kernengroup.com + +Based on a suggestion by Tara L. Andrews. + +=head1 COPYRIGHT + +This example and Mail::IMAPClient are Copyright (c) 2003 +by The Kernen Group, Inc. All rights reserved. + +This example is distributed with Mail::IMAPClient and +subject to the same licensing requirements as Mail::IMAPClient. + +imtest is a utility distributed with Cyrus IMAP server, +Copyright (c) 1994-2000 Carnegie Mellon University. +All rights reserved. + +=cut + diff --git a/Mail-IMAPClient-3.10/examples/migrate_mail2.pl b/Mail-IMAPClient-3.10/examples/migrate_mail2.pl new file mode 100755 index 0000000..841f67b --- /dev/null +++ b/Mail-IMAPClient-3.10/examples/migrate_mail2.pl @@ -0,0 +1,326 @@ +#!/usr/local/bin/perl +#$Id: migrate_mail2.pl,v 19991216.4 2003/06/12 21:38:33 dkernen Exp $ +# +# An example of how to migrate from a Netscape server +# (which uses a slash as a separator and which does +# not allow subfolders under the INBOX, only next to it) +# to a Cyrus server (which uses a dot (.) as a separator +# and which requires subfolders to be under "INBOX"). +# There are also some allowed-character differences taken +# into account but this is by no means complete AFAIK. +# +# This is an example. If you are doing mail migrations +# then this may in fact be a very helpful example but +# it is unlikely to work 100% correctly as-is. +# A good place to start is by testing a rather large-volume +# transfer of actual mail from the source server with the +# -v option turned on and redirect output to a file for +# perusal. Examine the output carefully for unexpected +# results, such as a number of messages being skipped because +# they're already in the target folder when you know darn +# well this is the first time you ran the script. This +# would indicate an incompatibility with the logic for +# detecting duplicates, unless for some reason the source +# mailbox contains a lot of duplicate messages to begin with. +# (The latter case is an example of why you should use an +# actual mailbox stuffed with actual mail for test; if you +# generate test messages and then test migrating those you +# will only prove that your test messages are migratable. +# +# Also, you may need to play with the rules +# for translating folder names based on what kind of +# names your target server and source server support. +# +# You may also need to play with the logic that determines +# whether or not a message has already been migrated, +# especially if your source server has messages that +# did not come from an SMTP gateway or something like that. +# +# Some servers allow folders to contain mail and subfolders, +# some allow folders to only contain either mail or subfolders. +# If you are migrating from a "mixed use" type to a "single use" +# type server then you'll have to figure out how to deal +# with this. (This script deals with this by creating folders like +# "/blah_mail", "/blah/blah_mail", and "/blah/blah/blah_mail" +# to hold mail if the source folder contains mail and subfolders +# and the target server supports only single-use folders. +# You may not choose a different strategy.) +# +# Finally, it's possible that in some server-to-server +# copies, the source server supports messages that the +# target server considers unacceptable. For example, some +# but not all IMAP servers flat out refuse to accept +# messages with "base newlines", which is to say messages +# whose lines are match the pattern /[^\r]\n$/. There is +# no logic in this script that deals with the situation; +# you will have to identify it if it exists and figure +# out how you want to handle it. +# +# This is probably not an exhaustive list of issues you'll +# face in a migration, but it's a start. +# +# If you're just migrating from an old version to a newer +# version of the same server then you'll probably have +# a much easier time of it. +# +# + +use Mail::IMAPClient; +use Data::Dumper; +use IO::File; +use File::Basename ; +use Getopt::Std; +use strict; +use vars qw/ $opt_B $opt_D $opt_T $opt_U + $opt_W $opt_b $opt_d $opt_h + $opt_t $opt_u $opt_w $opt_v + $opt_s $opt_S $opt_W $opt_p + $opt_P $opt_f $opt_F $opt_m + $opt_M +/; + +getopts('vs:S:u:U:dDb:B:f:F:w:W:p:P:t:T:hm:M:'); + +if ( $opt_h ) { + print STDERR <<"HELP"; + +$0 - an example script demonstrating the use of the Mail::IMAPClient's + migrate method. + +Syntax: + $0 -s source_server -u source_user -w source_password -p source_port \ + -d debug_source -f source_debugging_file -b source_buffsize \ + -t source_timeout -m source_auth_mechanism \ + -S target_server -U target_user -W target_password -P target_port \ + -D debug_target -F target_debugging_file -B target_buffsize \ + -T target_timeout -M target_auth_mechanism \ + -v + +where "source" refers to the "copied from" mailbox, target is the +"copied to" mailbox, and -v turns on verbose output. +Authentication mechanisms default to "PLAIN". + +HELP + exit; +} +$opt_v and ++$|; +print "$0: Started at ",scalar(localtime),"\n" if $opt_v; + +$opt_p||=143; +$opt_P||=143; + +# Make a connection to the source mailbox: +my $imap = Mail::IMAPClient->new( + Server => $opt_s, + User => $opt_u, + Password=> $opt_w, + Uid => 1, + Port => $opt_p, + Debug => $opt_d||0, + Buffer => $opt_b||4096, + Fast_io => 1, + ( $opt_m ? ( Authmechanism => $opt_m) : () ), + Timeout => $opt_t, + ($opt_f ? ( Debug_fh=>IO::File->new(">$opt_f" )) : ()), +) or die "$@"; + +# Make a connection to the target mailbox: +my $imap2 = Mail::IMAPClient->new( + Server => $opt_S, + User => $opt_U, + Password=> $opt_W, + Port => $opt_P, + Uid => 1, + Debug => $opt_D||0, + ( $opt_M ? ( Authmechanism => $opt_M) : () ), + ($opt_F ? ( Debug_fh=>IO::File->new(">$opt_F")) : ()), + Buffer => $opt_B||4096, + Fast_io => 1, + Timeout => $opt_T, # True value +) or die "$@"; + +# Turn off buffering on debug files: +$imap->Debug_fh->autoflush; +$imap2->Debug_fh->autoflush; + +# Get folder hierarchy separator characters from source and target: +my $sep1 = $imap->separator; +my $sep2 = $imap2->separator; + +# Find out if source and target support subfolders inside INBOX: +my $inferiorFlag1 = $imap->is_parent("INBOX"); +my $inferiorFlag2 = $imap2->is_parent("INBOX"); + +# Set up a test folders to see if the source and target support mixed-use +# folders (i.e. folders with both subfolders and mail messages): +my $testFolder1 = "Migrate_Test_$$" ; # Ex: Migrate_Test_1234 +$testFolder1 = $inferiorFlag2 ? + "INBOX" . $sep2 . $testFolder1 : + $testFolder1 ; + +# The following folder will be a subfolder of $testFolder1: +my $testFolder2 = "Migrate_Test_$$" . $sep2 . "Migrate_test_subfolder_$$" ; +$testFolder2 = $inferiorFlag2 ? "INBOX" . $sep2 . $testFolder2 : $testFolder2 ; + +$imap2->create($testFolder2) ; # Create the subfolder first; RFC2060 dictates that + # the parent folder should be created at the same time + + +# The following line inspired the selectable method. It was also made obsolete by it, +# but I'm leaving it as is to demonstrate use of lower-level method calls: +my $mixedUse2 = grep(/NoSelect/i,$imap2->list("",$testFolder1))? 0 : 1; + +# Repeat the above with the source mailbox: +$testFolder2 = "Migrate_Test_$$" . $sep1 . "Migrate_test_subfolder_$$" ; +$testFolder2 = $inferiorFlag1 ? "INBOX" . $sep1 . $testFolder1 : $testFolder1 ; + +$imap->create($testFolder2) ; + +my $mixedUse1 = grep(/NoSelect/i,$imap->list("",$testFolder1))? 0 : 1; + +print "Imap host $opt_s:$opt_p uses a '$sep1' as a separator and ", + ( defined($inferiorFlag1) ? "allows " : "does not allow "), + "children in the INBOX. It supports ", + ($mixedUse1?"mixed use ":"single use "), "folders.\n" if $opt_v; + +print "Imap host $opt_S:$opt_P uses a '$sep2' as a separator and ", + ( defined($inferiorFlag2) ? "allows " : "does not allow "), + "children in the INBOX. It supports ", + ($mixedUse2?"mixed use ":"single use "), "folders.\n" if $opt_v; + +for ($testFolder1,$testFolder2) {$imap->delete($_); $imap2->delete($_);} + +my($totalMsgs, $totalBytes) = (0,0); + +# Now we will migrate the folder. Here we are doing one message at a time +# so that we can do more granular status reporting and error checking. +# A lazier way would be to do all the messages in one migrate method call +# (specifying "ALL" as the message number) but then we wouldn't be able +# to print out which message we were migrating and it would be a little +# bit tougher to control checking for duplicates and stuff like that. +# We could also check the size of the message on the target right after +# the migrate as an extra safety check if we wanted to but I didn't bother +# here. (I saved as an exercise for the reader. Yeah! That's it! An exercise!) + +# Iterate over all the folders in the source mailbox: +for my $f ($imap->folders) { + # Select the folder on the source side: + $imap->select($f) ; + + # Massage the foldername into an acceptable target-side foldername: + my $targF = ""; + my $srcF = $f; + $srcF =~ s/^INBOX$sep1//i; + if ( $inferiorFlag2 ) { + $targF = $srcF eq "INBOX" ? "INBOX" : "INBOX.$f" ; + } else { + $targF = $srcF ; + } + + $targF =~ s/$sep1/$sep2/go unless $sep1 eq $sep2; + $targF =~ tr/#\$\& '"/\@\@+_/; + if ( $imap->is_parent($f) and !$mixedUse2 ) { + $targF .= "_mail" ; + } + print "Migrating folder $f to $targF\n" if $opt_v; + + # Create the (massaged) folder on the target side: + unless ( $imap2->exists($targF) ) { + $imap2->create($imap2->Massage($targF)) + or warn "Cannot create $targF on " . $imap2->Server . ": $@\n" and next; + } + + # ... and select it + $imap2->select($imap2->Massage($targF)) + or warn "Cannot select $targF on " . $imap2->Server . ": $@\n" and next; + + # now that we know the target folder is selectable, we can close it again: + $imap2->close; + my $count = 0; + my $expectedTotal = $imap->message_count($f) ; + + # Now start iterating over all the messages on the source side... + for my $msg ($imap->messages) { + ++$count; + my $h = ""; + # Get some basic info about the message: + eval { $h = ($imap->parse_headers($msg,"Message-id")||{})->{'Message-id'}[0]}; + my $tsize = $imap->size($msg); + my $ret = 0 ; my $h2 = []; + + # Make sure we didn't already migrate the message in a previous pass: + $imap2->select($targF); + if ( $tsize and $h and $h2 = $imap2->search( + HEADER => 'Message-id' => $imap2->Quote($h), + NOT => SMALLER => $tsize, + NOT => LARGER => $tsize + ) + ) { + print + "Skipping $f/$msg to $targF. ", + "One or more messages (" ,join(", ",@$h2), + ") with the same size and message id ($h) ", + "is already on the server. ", + "\n" + if $opt_v; + $imap2->close; + + } else { + + print + "Migrating $f/$msg to $targF. ", + "Message #$count of $expectedTotal has ", + $tsize , " bytes.", + "\n" if $opt_v; + $imap2->close; + + # Migrate the message: + my $ret = $imap->migrate($imap2,$msg,"$targF") ; + $ret and ( $totalMsgs++ , $totalBytes += $tsize); + $ret or warn "Cannot migrate $f/$msg to $targF on " . $imap2->Server . ": $@\n" ; + } + } +} + +print "$0: Finished migrating $totalMsgs messages and $totalBytes bytes at ",scalar(localtime),"\n" + if $opt_v; +exit; + + +=head1 AUTHOR + +David J. Kernen + +The Kernen Group, Inc. + +imap@kernengroup.com + +=head1 COPYRIGHT + +This example and Mail::IMAPClient are Copyright (c) 2003 +by The Kernen Group, Inc. All rights reserved. + +This example is distributed with Mail::IMAPClient and +subject to the same licensing requirements as Mail::IMAPClient. + +imtest is a utility distributed with Cyrus IMAP server, +Copyright (c) 1994-2000 Carnegie Mellon University. +All rights reserved. + +=cut + +#$Log: migrate_mail2.pl,v $ +#Revision 19991216.4 2003/06/12 21:38:33 dkernen +# +#Preparing 2.2.8 +#Added Files: COPYRIGHT +#Modified Files: Parse.grammar +#Added Files: Makefile.old +# Makefile.PL Todo sample.perldb +# BodyStructure.pm +# Parse.grammar Parse.pod +# range.t +# Thread.grammar +# draft-crispin-imapv-17.txt rfc1731.txt rfc2060.txt rfc2062.txt +# rfc2221.txt rfc2359.txt rfc2683.txt +# diff --git a/Mail-IMAPClient-3.10/examples/migrate_mbox.pl b/Mail-IMAPClient-3.10/examples/migrate_mbox.pl new file mode 100755 index 0000000..dab3689 --- /dev/null +++ b/Mail-IMAPClient-3.10/examples/migrate_mbox.pl @@ -0,0 +1,131 @@ +#!/usr/local/bin/perl +# +# This is an example demonstrating the use of the migrate method. +# Note that the migrate method is considered experimental and should +# be used with caution. +# +#$Id: migrate_mbox.pl,v 19991216.2 2003/06/12 21:38:33 dkernen Exp $ +# + +use Mail::IMAPClient; +use IO::File; +use File::Basename ; +use Getopt::Std; +use warnings; +use vars qw/$opt_h $opt_H + $opt_s $opt_u $opt_p $opt_d $opt_b $opt_o + $opt_S $opt_U $opt_P $opt_D $opt_B $opt_O +/; + +getopts('Hhs:S:u:U:p:P:d:D:b:B:o:O:'); +if ($opt_h or $opt_H ) { +print << "HELP"; + + +Usage: + +$0 -[h|H] -- prints this message + +Lower-case options are for source server; upper-case options are for the target server. + +$0 -s server -S server -u uid -U uid -p passwd -P passwd \ + -b buffersize -B buffersize -o debugFile -O debugFile > error_file + +All uppercase options except -O default to the lowercase option that was specified. +If you don't specify any uppercase options at all then God help you, I don't know +what will happen. + +Always capture STDERR so that you'll be able to resolve any problems that come up. + + +HELP + +exit; +} + +my $imap = Mail::IMAPClient->new( + Server => $opt_s, + User => $opt_u, + Password=> $opt_p, + Uid => 1, + Debug => $opt_d, + Buffer => $opt_b||4096, + Fast_io => 1, + Timeout => 160, # True value + Debug_fh=> ( + $opt_o ? IO::File->new(">$opt_o")||die "can't open $opt_o: $!\n" : undef ) +) or die "Error opening source connection: $@\n"; + +my $imap2 = Mail::IMAPClient->new( + Server => $opt_S||$opt_s, + User => $opt_U||$opt_u, + Password=> $opt_P||$opt_p, + Uid => 1, + Debug => $opt_D||$opt_d, + Buffer => $opt_B||$opt_b||4096, + Fast_io => 1, + Timeout => 160, + Debug_fh=> ( + $opt_O ? IO::File->new(">$opt_O")||die "can't open $opt_O: $!\n" : undef ) +) or die "Error opening target connection: $@\n"; + + +$imap->Debug_fh->autoflush; +$imap2->Debug_fh->autoflush; + +for my $f ($imap->folders) { $imap->select($f) ; $imap->migrate($imap2,"ALL") ;} + + +=head1 AUTHOR + +David J. Kernen + +The Kernen Group, Inc. + +imap@kernengroup.com + +=head1 COPYRIGHT + +This example and Mail::IMAPClient are Copyright (c) 2003 +by The Kernen Group, Inc. All rights reserved. + +This example is distributed with Mail::IMAPClient and +subject to the same licensing requirements as Mail::IMAPClient. + +imtest is a utility distributed with Cyrus IMAP server, +Copyright (c) 1994-2000 Carnegie Mellon University. +All rights reserved. + +=cut + +# +#$Log: migrate_mbox.pl,v $ +#Revision 19991216.2 2003/06/12 21:38:33 dkernen +# +#Preparing 2.2.8 +#Added Files: COPYRIGHT +#Modified Files: Parse.grammar +#Added Files: Makefile.old +# Makefile.PL Todo sample.perldb +# BodyStructure.pm +# Parse.grammar Parse.pod +# range.t +# Thread.grammar +# draft-crispin-imapv-17.txt rfc1731.txt rfc2060.txt rfc2062.txt +# rfc2221.txt rfc2359.txt rfc2683.txt +# +#Revision 1.1 2003/06/12 21:38:15 dkernen +# +#Preparing 2.2.8 +#Added Files: COPYRIGHT +#Modified Files: Parse.grammar +#Added Files: Makefile.old +# Makefile.PL Todo sample.perldb +# BodyStructure.pm +# Parse.grammar Parse.pod +# range.t +# Thread.grammar +# draft-crispin-imapv-17.txt rfc1731.txt rfc2060.txt rfc2062.txt +# rfc2221.txt rfc2359.txt rfc2683.txt +# +# diff --git a/Mail-IMAPClient-3.10/examples/populate_mailbox.pl b/Mail-IMAPClient-3.10/examples/populate_mailbox.pl new file mode 100755 index 0000000..f88fb5d --- /dev/null +++ b/Mail-IMAPClient-3.10/examples/populate_mailbox.pl @@ -0,0 +1,319 @@ +#!/usr/local/bin/perl +#$Id: populate_mailbox.pl,v 19991216.8 2003/06/12 21:38:34 dkernen Exp $ # +use Time::Local ; +use FileHandle ; +use File::Copy ; +use Mail::IMAPClient; +use Sys::Hostname ; + # +my $default_user = 'default' ; +my $default_pswd = 'default' ; + # +######################################################################### +# ARGS: DATE = YYYYMMDDHHMM (defaults to current system date) # +# UID = IMAP account id (defaults to $default_user) # +# PSWD = uid's password (defaults to $default_pswd) # +# HOST = Target host (defaults to localhost) # +# CLEAN = 1 (defaults to 0; used to clean out mailbox 1st) # +# CLEANONLY= 1 (defaults to 0; if 1 then only CLEAN is done) # +# DOMAIN = x.com (no default) the mail domain for UID's address # +# # +# EG: populate_mailbox.pl DATE=200001010100 UID=testuser # +# # +######################################################################### + # +(my($x)= join(" ",@ARGV)) ; +$x=~s~=~ ~g ; +chomp($x) ; + # +my %hash = split(/\s+/, $x) if $x ; + # +while (my ($k,$v) = each %hash ) { + $hash{uc $k} = $v ; + } + +while (my ($k,$v) = each %hash ) { + delete $hash{$k} if $k =~ tr/[a-z]// ; + } + ; +$hash{UID} ||= "$default_user" ; +$hash{PSWD} ||= "$default_pswd" ; +$hash{HOST} ||= hostname ; + # +while (my ($k,$v) = each %hash ) { + print "Running with $k set to $v\n" ; + } + # +my $domain = $hash{DOMAIN} or die "No mail domain provided.\n" ; +my $now = seconds($hash{DATE}) || time ; + # +my $six = $now - ( 6 * 24 * 60 * 60 ) ; +my $seven = $now - ( 7 * 24 * 60 * 60 ) ; +my $notthirty = $now - ( 29 * 24 * 60 * 60 ) ; +my $thirty = $now - ( 30 * 24 * 60 * 60 ) ; +my $notsixty = $now - ( 59 * 24 * 60 * 60 ) ; +my $sixty = $now - ( 60 * 24 * 60 * 60 ) ; +my $notd365 = $now - ( 364 * 24 * 60 * 60 ) ; +my $d365 = $now - ( 365 * 24 * 60 * 60 ) ; + # +$hash{SUBJECTS} = [ "Sixty days old", "Less than sixty days old" , + "365 days old", "Less than 365 days old" , + "Trash/Incinerator -- 7 days old" , + "Sent -- 29 days old" , + "Sent -- 30 days old" , + "Trash -- 6 days old" , + ] ; +$hash{FOLDERS} = [ "Sent", "INBOX", "Trash" , + "365_folder", "Trash/Incinerator" , + "not_365_folder" , + ] ; + # +&clean_mailbox if $hash{CLEANONLY} || $hash{CLEAN} ; +exit if $hash{CLEANONLY} ; + # +#send to: date: subject: # +#-------- --- ----- --------- # +sendmail( $hash{UID}, $sixty, "Sixty days old" ) ; +sendmail( $hash{UID}, $notsixty, "Less than sixty days old") ; +sendmail( $hash{UID}, $d365, "365 days old" ) ; +sendmail( $hash{UID}, $notd365, "Less than 365 days old" ) ; + # +populate_trash("Trash/Incinerator",$hash{UID}, $seven, 7 ) ; +populate_trash( "Trash" , $hash{UID}, $six, 6 ) ; +populate_trash( "Sent" , $hash{UID}, $thirty, 30 ) ; +populate_trash( "Sent" , $hash{UID}, $notthirty, 29 ) ; + # +movemail( "365 days old" , + "365_folder" ) ; + # +movemail( "Less than 365 days old" , + "not_365_folder" ) ; + # +exit ; + # + # +sub seconds { + my $d = shift or return undef ; + my($yy,$moy,$dom,$hr,$min) = + # + $d =~ m! ^ # anchor at start # + (\d\d\d\d) # year # + (\d\d) # month # + (\d\d) # day # + (\d\d) # hour # + (\d\d) # minute # + !x ; + # + return timegm(0,$min,$hr,$dom,$moy-1,($yy>99?$yy-1900:$yy)) ; + } + # +sub sendmail { + # + my($to,$date,$subject) = @_ ; + my $text = <new ( + Server => $hash{HOST} , + User => $hash{UID} , + Password=> $hash{PSWD} ) + or die "can't connect: $!\n" ; + # + $imap->append("INBOX",$text) ; + $imap->logout ; + } + } + # +sub populate_trash { + my $where = shift ; + my $to = shift ; + my $date = shift ; + my $d = shift ; + # + my($ss,$min,$hr,$day,$mon,$year)=gmtime($date) ; + $mon++ ; + $year += 1900 ; + my $fn =sprintf("%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d" , + $year,$mon,$day,$hr,$min,$ss ) ; + my $x = 0 ; + my $subject = "$where -- $d days old" ; + while ($x++ < 10) { + my $fh ; + $fh .= "Date: @{[&rfc822_date($date)]}\n" ; + $fh .= <new ( + Server => $hash{HOST} , + User => $hash{UID} , + Password=> $hash{PSWD} ) + or die "can't connect: $!\n" ; + $imap->append($where, $fh) ; + # + } + # + } + # +sub movemail { + # + my ($subj,$fold) = @_ ; + my $fh = Mail::IMAPClient->new ( + Debug => 0 , + Server => $hash{HOST} , + User => $hash{UID} , + Password => $hash{PSWD} , + ) + ; + # + $fh->select("inbox") or die "cannot open inbox: $!\n" ; + # + foreach my $f ($fh->search(qq(SUBJECT "$subj")) ) { + # + $fh->move($fold,$f) ; + # + } + # + } + # +sub clean_mailbox { + # + my $fh =Mail::IMAPClient->new ( + Debug => 0 , + Server => $hash{HOST} , + User => $hash{UID} , + Password => $hash{PSWD} , + ) + ; + for my $x (@{$hash{FOLDERS}}) { + my @msgs ; + $fh->create($x) unless $fh->exists($x) ; + $fh->select($x) ; + for my $s (@{$hash{SUBJECTS}}) { + push @msgs, $fh->search(qq(SUBJECT "$s")) ; + } + $fh->delete_message(@msgs) if scalar(@msgs) ; + $fh->expunge ; + } + } + # +sub rfc822_date { +#Date: Fri, 09 Jul 1999 13:10:55 -0400 # +my $date = shift ; +my @date = localtime($date) ; +my @dow = qw{ Sun Mon Tue Wed Thu Fri Sat } ; +my @mnt = qw{ Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec} ; + # +return sprintf ( + "%s, %2.2d %s %4.4s %2.2d:%2.2d:%2.2d -0400" , + $dow[$date[6]] , + $date[3] , + $mnt[$date[4]] , + $date[5]+=1900 , + $date[2] , + $date[1] , + $date[0] ) + ; + } + + +=head1 AUTHOR + +David J. Kernen + +The Kernen Group, Inc. + +imap@kernengroup.com + +=head1 COPYRIGHT + +This example and Mail::IMAPClient are Copyright (c) 2003 +by The Kernen Group, Inc. All rights reserved. + +This example is distributed with Mail::IMAPClient and +subject to the same licensing requirements as Mail::IMAPClient. + +imtest is a utility distributed with Cyrus IMAP server, +Copyright (c) 1994-2000 Carnegie Mellon University. +All rights reserved. + +=cut + +# $Id: populate_mailbox.pl,v 19991216.8 2003/06/12 21:38:34 dkernen Exp $ +# $Log: populate_mailbox.pl,v $ +# Revision 19991216.8 2003/06/12 21:38:34 dkernen +# +# Preparing 2.2.8 +# Added Files: COPYRIGHT +# Modified Files: Parse.grammar +# Added Files: Makefile.old +# Makefile.PL Todo sample.perldb +# BodyStructure.pm +# Parse.grammar Parse.pod +# range.t +# Thread.grammar +# draft-crispin-imapv-17.txt rfc1731.txt rfc2060.txt rfc2062.txt +# rfc2221.txt rfc2359.txt rfc2683.txt +# +# Revision 1.1 2003/06/12 21:38:16 dkernen +# +# Preparing 2.2.8 +# Added Files: COPYRIGHT +# Modified Files: Parse.grammar +# Added Files: Makefile.old +# Makefile.PL Todo sample.perldb +# BodyStructure.pm +# Parse.grammar Parse.pod +# range.t +# Thread.grammar +# draft-crispin-imapv-17.txt rfc1731.txt rfc2060.txt rfc2062.txt +# rfc2221.txt rfc2359.txt rfc2683.txt +# +# Revision 19991216.7 2002/08/23 13:29:49 dkernen +# +# Modified Files: Changes IMAPClient.pm INSTALL MANIFEST Makefile Makefile.PL README Todo test.txt +# Made changes to create version 2.1.6. +# Modified Files: +# imap_to_mbox.pl populate_mailbox.pl +# Added Files: +# cleanTest.pl migrate_mbox.pl +# +# Revision 19991216.6 2000/12/11 21:58:53 dkernen +# +# Modified Files: +# build_dist.pl build_ldif.pl copy_folder.pl find_dup_msgs.pl +# imap_to_mbox.pl populate_mailbox.pl +# to add CVS data +# +# Revision 19991216.5 1999/12/16 17:19:15 dkernen +# Bring up to same level +# +# Revision 19991124.3 1999/12/16 17:14:26 dkernen +# Incorporate changes for exists method performance enhancement +# +# Revision 19991124.02 1999/11/24 17:46:21 dkernen +# More fixes to t/basic.t +# +# Revision 19991124.01 1999/11/24 16:51:51 dkernen +# Changed t/basic.t to test for UIDPLUS before trying UID cmds +# +# Revision 1.4 1999/11/23 17:51:06 dkernen +# Committing version 1.06 distribution copy +# diff --git a/Mail-IMAPClient-3.10/examples/sharedFolder.pl b/Mail-IMAPClient-3.10/examples/sharedFolder.pl new file mode 100755 index 0000000..3432c8f --- /dev/null +++ b/Mail-IMAPClient-3.10/examples/sharedFolder.pl @@ -0,0 +1,88 @@ +#!/usr/local/bin/perl +#$Id: sharedFolder.pl,v 19991216.1 2003/06/12 21:38:35 dkernen Exp $ + +use Mail::IMAPClient; +use Getopt::Std; +use File::Basename; +getopts('s:u:p:f:dh'); + +if ($opt_h) { + + print STDERR "$0 -- example of how to select shared folder\n", + "\n\nUsage:\n", + "\t-s server -- specify name or ip address of mail server\n", + "\t-u userid -- specify login name of authenticating user\n", + "\t-p passwd -- specify login password of authenticating user\n", + "\t-f folder -- specify shared folder to access (i.e. '-f frank/INBOX')\n", + "\t-h display this help message\n\n"; + "\t-d turn on debugging output\n\n"; + exit; +} + +my $server = $opt_s or die "No server name specified\n"; +my $user = $opt_u or die "No user name specified\n"; +my $pass = $opt_p or die "No password specified\n"; +my $folder = $opt_f or die "No shared folder specified\n"; + +chomp $pass; +my $imap = Mail::IMAPClient->new(Server=>$server,User=>$user,Password=>$pass,Debug=>$opt_d) + or die "Can't connect to $user\@$server: $@ $!\n"; + +my($prefix,$prefSep) = @{$imap->namespace->[1][0]} + or die "Can't get shared folder namespace or separator: $@\n"; + + +my $target = $prefix . + ( $prefix =~ /\Q$prefSep\E$/ || $opt_f =~ /^\Q$prefSep/ ? "" : $prefSep ) . + $opt_f ; + +print "Selecting $target\n"; + +$imap->select($target) + or die "Cannot select $target: $@\n"; + +print "Ok: $target has ", $imap->message_count($target)," messages.\n"; + +$imap->logout; +exit; + + +=head1 AUTHOR + +David J. Kernen + +The Kernen Group, Inc. + +imap@kernengroup.com + +=head1 COPYRIGHT + +This example and Mail::IMAPClient are Copyright (c) 2003 +by The Kernen Group, Inc. All rights reserved. + +This example is distributed with Mail::IMAPClient and +subject to the same licensing requirements as Mail::IMAPClient. + +imtest is a utility distributed with Cyrus IMAP server, +Copyright (c) 1994-2000 Carnegie Mellon University. +All rights reserved. + +=cut + +# +#$Log: sharedFolder.pl,v $ +#Revision 19991216.1 2003/06/12 21:38:35 dkernen +# +#Preparing 2.2.8 +#Added Files: COPYRIGHT +#Modified Files: Parse.grammar +#Added Files: Makefile.old +# Makefile.PL Todo sample.perldb +# BodyStructure.pm +# Parse.grammar Parse.pod +# range.t +# Thread.grammar +# draft-crispin-imapv-17.txt rfc1731.txt rfc2060.txt rfc2062.txt +# rfc2221.txt rfc2359.txt rfc2683.txt +# +# diff --git a/Mail-IMAPClient-3.10/lib/Mail/IMAPClient.pm b/Mail-IMAPClient-3.10/lib/Mail/IMAPClient.pm new file mode 100644 index 0000000..0af0bd2 --- /dev/null +++ b/Mail-IMAPClient-3.10/lib/Mail/IMAPClient.pm @@ -0,0 +1,2827 @@ +use warnings; +use strict; + +package Mail::IMAPClient; +our $VERSION = '3.10'; + +use Mail::IMAPClient::MessageSet; + +use Carp; # to be removed +use Data::Dumper; # to be removed + +use Socket(); +use IO::Socket(); +use IO::Select(); +use IO::File(); +use Carp qw(carp); + +use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK); +use Errno qw/EAGAIN/; +use List::Util qw/first min max sum/; +use MIME::Base64; +use File::Spec (); + +use constant Unconnected => 0; +use constant Connected => 1; # connected; not logged in +use constant Authenticated => 2; # logged in; no mailbox selected +use constant Selected => 3; # mailbox selected + +use constant INDEX => 0; # Array index for output line number +use constant TYPE => 1; # Array index for line type + # (either OUTPUT, INPUT, or LITERAL) +use constant DATA => 2; # Array index for output line data + +use constant NonFolderArg => 1; # Value to pass to Massage to + # indicate non-folder argument + +my %SEARCH_KEYS = map { ( $_ => 1 ) } qw/ + ALL ANSWERED BCC BEFORE BODY CC DELETED DRAFT FLAGGED + FROM HEADER KEYWORD LARGER NEW NOT OLD ON OR RECENT + SEEN SENTBEFORE SENTON SENTSINCE SINCE SMALLER SUBJECT + TEXT TO UID UNANSWERED UNDELETED UNDRAFT UNFLAGGED + UNKEYWORD UNSEEN/; + +sub _debug +{ my $self = shift; + return unless $self->Debug; + + my $text = join '', @_; + $text =~ s/\r\n/\n /g; + $text =~ s/\s*$/\n/; + + my $fh = $self->{Debug_fh} || \*STDERR; + print $fh $text; +} + +BEGIN { + # set-up accessors + foreach my $datum ( + qw(State Port Server Folder Peek User Password Timeout Buffer + Debug Count Uid Debug_fh Maxtemperrors Authuser Authmechanism Authcallback + Ranges Readmethod Showcredentials Prewritemethod Ignoresizeerrors + Supportedflags Proxy)) + { no strict 'refs'; + *$datum = sub { @_ > 1 ? ($_[0]->{$datum} = $_[1]) : $_[0]->{$datum} + }; + } +} + +sub LastError +{ my $self = shift; + @_ or return $self->{LastError}; + + $self->_debug("ERROR: ", $@ = $self->{LastError} = shift); + $@; +} + +sub Fast_io(;$) +{ my ($self, $use) = @_; + defined $use + or return $self->{File_io}; + + my $socket = $self->{Socket} + or return; + + unless($use) + { eval { fcntl($socket, F_SETFL, delete $self->{_fcntl}) } + if exists $self->{_fcntl}; + $@ = ''; + $self->{Fast_io} = 0; + return; + } + + my $fcntl = eval { fcntl($socket, F_GETFL, 0) }; + if($@) + { $self->{Fast_io} = 0; + $self->_debug("not using Fast_IO; not available on this platform") + unless $self->{_fastio_warning_}++; + $@ = ''; + return; + } + + $self->{Fast_io} = 1; + my $newflags = $self->{_fcntl} = $fcntl; + $newflags |= O_NONBLOCK; + fcntl($socket, F_SETFL, $newflags); +} + +# removed +sub EnableServerResponseInLiteral {undef} + +sub Wrap { shift->Clear(@_) } + +# The following class method is for creating valid dates in appended msgs: + +my @dow = qw/Sun Mon Tue Wed Thu Fri Sat/; +my @mnt = qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/; + +sub Rfc822_date +{ my $class = shift; #Date: Fri, 09 Jul 1999 13:10:55 -0000# + my $date = $class =~ /^\d+$/ ? $class : shift; # method or function? + my @date = gmtime $date; + + sprintf "%s, %02d %s %04d %02d:%02d:%02d -%04d" + , $dow[$date[6]], $date[3], $mnt[$date[4]], $date[5]+1900 + , $date[2], $date[1], $date[0], $date[8]; +} + +# The following class method is for creating valid dates for use +# in IMAP search strings: + +sub Rfc2060_date +{ my $class = shift; # 11-Jan-2000 + my $stamp = $class =~ /^\d+$/ ? $class : shift; # method or function + my @date = gmtime $stamp; + + sprintf "%02d-%s-%04d", $date[3], $mnt[$date[4]], $date[5]+1900; +} + +sub Rfc2060_datetime($;$) +{ my ($class, $stamp, $zone) = @_; # 11-Jan-2000 04:04:04 +0000 + $zone ||= '+0000'; + my @date = gmtime $stamp; + + sprintf "%02d-%s-%04d %02d:%02d:%02d %s", $date[3], $mnt[$date[4]] + , $date[5]+1900, $date[2], $date[1], $date[0], $zone; +} + +# Change CRLF into \n +# Change CRLF into \n + +sub Strip_cr +{ my $class = shift; + if( !ref $_[0] && @_==1 ) + { (my $string = $_[0]) =~ s/\r\n/\n/g; + return $string; + } + + wantarray + ? map { s/\r\n/\n/gm; $_ } (ref $_[0] ? @{$_[0]} : @_) + : [ map { s/\r\n/\n/gm; $_ } (ref $_[0] ? @{$_[0]} : @_) ]; +} + +# The following defines a special method to deal with the Clear parameter: + +sub Clear +{ my ($self, $clear) = @_; + defined $clear or return $self->{Clear}; + + my $oldclear = $self->{Clear}; + $self->{Clear} = $clear; + + my @keys = reverse $self->_trans_index; + + for(my $i = $clear; $i < @keys ; $i++ ) + { delete $self->{History}{$keys[$i]}; + } + + $oldclear; +} + +# read-only access to the transaction number +sub Transaction { shift->Count }; + +# remove doubles from list +sub _remove_doubles(@) { my %seen; grep { ! $seen{$_}++ } @_ } + +# the constructor: +sub new +{ my $class = shift; + my $self = + { LastError => "", + , Uid => 1 + , Count => 0 + , Fast_io => 1 + , Clear => 5 + , Maxtemperrors => 'unlimited' + , State => Unconnected + , Authmechanism => 'LOGIN' + , Port => 143 + , Timeout => 30 + , History => {} + }; + while(@_) + { my $k = ucfirst lc shift; + my $v = shift; + $self->{$k} = $v if defined $v; + } + bless $self, ref($class)||$class; + + if(my $sup = $self->{Supportedflags}) # unpack into case-less HASH + { my %sup = map { m/^\\?(\S+)/ ? lc $1 : () } @$sup; + $self->{Supportedflags} = \%sup; + } + + $self->{Debug_fh} ||= \*STDERR; + CORE::select((select($self->{Debug_fh}),$|++)[0]); + + if($self->Debug) + { $self->_debug("Started at ".localtime()); + $self->_debug("Using Mail::IMAPClient version $VERSION on perl $]"); + } + + $self->Socket($self->{Socket}) + if $self->{Socket}; + + if($self->{Rawsocket}) + { + my $sock = delete $self->{Rawsocket}; + # Ignore Rawsocket if Socket has already been set -- TODO carp/croak? + $self->RawSocket($sock) unless $self->{Socket}; + } + + !$self->{Socket} && $self->{Server} ? $self->connect : $self; +} + +sub connect(@) +{ my $self = shift; + %$self = (%$self, @_); + + my $server = $self->Server; + my $port = $self->Port; + my @timeout = $self->Timeout ? (Timeout => $self->Timeout) : (); + + my $sock; + + if(File::Spec->file_name_is_absolute($server)) + { $self->_debug("Connecting to unix socket $server"); + $sock = IO::Socket::UNIX->new + ( Peer => $server + , Debug => $self->Debug + , @timeout + ); + } + else + { $self->_debug("Connecting to $server port $port"); + $sock = IO::Socket::INET->new + ( PeerAddr => $server + , PeerPort => $port + , Proto => 'tcp' + , Debug => $self->Debug + , @timeout + ); + } + + unless($sock) + { $self->LastError("Unable to connect to $server: $@"); + return undef; + } + + $self->_debug("Connected to $server"); + $self->Socket($sock); +} + +sub RawSocket(;$) +{ my ($self, $sock) = @_; + defined $sock + or return $self->{Socket}; + + $self->{Socket} = $sock; + $self->{_select} = IO::Select->new($sock); + + delete $self->{_fcntl}; + $self->Fast_io($self->Fast_io); + + $sock; +} + +sub Socket($) +{ my ($self, $sock) = @_; + defined $sock + or return $self->{Socket}; + + $self->RawSocket($sock); + $self->State(Connected); + + my $code; + LINE: + while(my $output = $self->_read_line) + { foreach my $o (@$output) + { $self->_record($self->Count, $o); + next unless $o->[TYPE] eq "OUTPUT"; + + $code = $o->[DATA] =~ /^\*\s+(OK|BAD|NO|PREAUTH)/i ? uc($1) : undef; + last LINE; + } + } + $code or return undef; + + if($code eq 'BYE' || $code eq 'NO') + { $self->State(Unconnected); + return undef; + } + + if($code eq 'PREAUTH') + { $self->State(Authenticated); + return $self; + } + + $self->User && $self->Password ? $self->login : $self; +} + +sub login +{ my $self = shift; + my $auth = $self->Authmechanism; + return $self->authenticate($auth, $self->Authcallback) + if $auth ne 'LOGIN'; + + my $passwd = $self->Password; + if($passwd =~ m/\W/) # need to quote + { $passwd =~ s/(["\\])/\\$1/g; + $passwd = qq{"$passwd"}; + } + + my $id = $self->User; + $id = qq{"$id"} if $id !~ /^".*"$/; + + $self->_imap_command("LOGIN $id $passwd") + or return undef; + + $self->State(Authenticated); + $self; +} + +sub proxyauth +{ my ($self, $user) = @_; + $self->_imap_command("PROXYAUTH $user") ? $self->Results : undef; +} + +sub separator +{ my ($self, $target) = @_; + unless(defined $target) + { # separator is namespace's 1st thing's 1st thing's 2nd thing: + my $sep = eval { $self->namespace->[0][0][1] }; + return $sep if $sep; + $target = ''; + } + + return $self->{separators}{$target} + if $self->{separators}{$target}; + + my $list = $self->list(undef, $target); + foreach my $line (@$list) + { if($line =~ /^\*\s+LIST\s+\([^)]*\)\s+(\S+)/) + { my $s = $1; + $s =~ s/^\"(.*?)\"$/$1/; + return $self->{separators}{$target} = $s; + } + } + $self->{separators}{$target} = '/'; + +} + +sub sort +{ my ($self, $crit, @a) = @_; + + $crit =~ /^\(.*\)$/ # wrap criteria in parens + or $crit = "($crit)"; + + $self->_imap_uid_command(SORT => $crit, @a) + or return wantarray ? () : []; + + my @results = $self->History; + my @hits; + foreach (@results) + { chomp; + s/\r$//; + s/^\*\s+SORT\s+// or next; + push @hits, grep /\d/, split; + } + wantarray ? @hits : \@hits; +} + +sub list +{ my ($self, $reference, $target) = @_; + defined $reference or $reference = ""; + defined $target or $target = '*'; + length $target or $target = '""'; + + $target eq '*' || $target eq '""' + or $target = $self->Massage($target); +; + $self->_imap_command( qq[LIST "$reference" $target] ) + or return undef; + + wantarray ? $self->History : $self->Results; +} + +sub lsub +{ my ($self, $reference, $target) = @_; + defined $reference or $reference = ""; + defined $target or $target = '*'; + $target = $self->Massage($target); + + $self->_imap_command( qq[LSUB "$reference" $target] ) + or return undef; + + wantarray ? $self->History : $self->Results; +} + +sub subscribed +{ my ($self, $what) = @_; + my $known = $what ? $what.$self->separator($what)."*" : undef; + + my @list = $self->lsub(undef, $known); + push @list, $self->lsub(undef, $what) if $what && $self->exists($what); + + my @folders; + for(my $m = 0; $m < @list; $m++ ) + { $list[$m] or next; + + if($list[$m] !~ /\r\n$/) + { $list[$m] .= $list[$m+1]; + $list[$m+1] = ""; + } + + # $self->_debug("Subscribed: examining $list[$m]"); + + push @folders, $1||$2 + if $list[$m] =~ + m/ ^ \* \s+ LSUB # * LSUB + \s+ \( [^\)]* \) \s+ # (Flags) + (?:"[^"]*"|NIL)\s+ # "delimiter" or NIL + (?:"([^"]*)"|(.*))\r\n$ # Name or "Folder name" + /ix; + } + + my @clean = _remove_doubles @folders; + wantarray ? @clean : \@clean; +} + +sub deleteacl +{ my ($self, $target, $user) = @_; + $target = $self->Massage($target); + $user =~ s/^"(.*)"$/$1/; + $user =~ s/"/\\"/g; + + $self->_imap_command( qq[DELETEACL $target "$user"] ) + or return undef; + + wantarray ? $self->History : $self->Results; +} + +sub setacl +{ my ($self, $target, $user, $acl) = @_; + $target ||= $self->Folder; + $target = $self->Massage($target); + + $user ||= $self->User; + $user =~ s/^"(.*)"$/$1/; + $user =~ s/"/\\"/g; + + $acl =~ s/^"(.*)"$/$1/; + $acl =~ s/"/\\"/g; + + $self->_imap_command( qq[SETACL $target "$user" "$acl"] ) + or return undef; + + wantarray ? $self->History : $self->Results; +} + + +sub getacl +{ my ($self, $target) = @_; + defined $target or $target = $self->Folder; + my $mtarget = $self->Massage($target); + $self->_imap_command( qq[GETACL $mtarget] ) + or return undef; + + my @history = $self->History; + my $hash; + for(my $x = 0; $x < @history; $x++ ) + { + next if $history[$x] !~ /^\* ACL/; + + my $perm = $history[$x]=~ /^\* ACL $/ + ? $history[++$x].$history[++$x] + : $history[$x]; + + $perm =~ s/\s?\r\n$//; + until( $perm =~ /\Q$target\E"?$/ || !$perm) + { $perm =~ s/\s([^\s]+)\s?$// or last; + my $p = $1; + $perm =~ s/\s([^\s]+)\s?$// or last; + my $u = $1; + $hash->{$u} = $p; + $self->_debug("Permissions: $u => $p"); + } + } + $hash; +} + +sub listrights +{ my ($self, $target, $user) = @_; + $target ||= $self->Folder; + $target = $self->Massage($target); + + $user ||= $self->User; + $user =~ s/^"(.*)"$/$1/; + $user =~ s/"/\\"/g; + + $self->_imap_command( qq[LISTRIGHTS $target "$user"] ) + or return undef; + + my $resp = first { /^\* LISTRIGHTS/ } $self->History; + my @rights = split /\s/, $resp; + my $rights = join '', @rights[4..$#rights]; + $rights =~ s/"//g; + wantarray ? split(//, $rights) : $rights; +} + +sub select +{ my ($self, $target) = @_; + defined $target or return undef; + + my $qqtarget = $self->Massage($target); + my $old = $self->Folder; + + $self->_imap_command("SELECT $qqtarget") + or return undef; + + $self->State(Selected); + $self->Folder($target); + $old || $self; # ??$self?? +} + +sub message_string +{ my ($self, $msg) = @_; + my $expected_size = $self->size($msg); + defined $expected_size or return undef; # unable to get size + + my $peek = $self->Peek ? '.PEEK' : ''; + my $cmd = $self->imap4rev1 ? "BODY$peek\[]" : "RFC822$peek"; + + $self->fetch($msg, $cmd) + or return undef; + + my $string = $self->_transaction_literals; + + unless($self->Ignoresizeerrors) + { # Should this return undef if length != expected? + # now, attempts are made to salvage parts of the message. + if( length($string) != $expected_size ) + { $self->LastError("message_string() " + . "expected $expected_size bytes but received ".length($string)); + } + + $string = substr $string, 0, $expected_size + if length($string) > $expected_size; + + if( length($string) < $expected_size ) + { $self->LastError("message_string() expected ". + "$expected_size bytes but received ".length($string)); + return undef; + } + } + + $string; +} + +sub bodypart_string +{ my($self, $msg, $partno, $bytes, $offset) = @_; + + unless($self->imap4rev1) + { $self->LastError("Unable to get body part; server ".$self->Server + . " does not support IMAP4REV1"); + return undef; + } + + $offset ||= 0; + my $cmd = "BODY" . ($self->Peek ? '.PEEK' : '') . "[$partno]" + . ($bytes ? "<$offset.$bytes>" : ''); + + $self->fetch($msg, $cmd) + or return undef; + + $self->_transaction_literals; +} + +sub message_to_file +{ my $self = shift; + my $fh = shift; + my $msgs = join ',', @_; + + my $handle; + if(ref $fh) { $handle = $fh } + else + { $handle = IO::File->new(">>$fh"); + unless(defined($handle)) + { $self->LastError("Unable to open $fh: $!"); + return undef; + } + binmode $handle; # For those of you who need something like this... + } + + my $clear = $self->Clear; + $self->Clear($clear) + if $self->Count >= $clear && $clear > 0; + + my $peek = $self->Peek ? '.PEEK' : ''; + my $cmd = $self->imap4rev1 ? "BODY$peek\[]" : "RFC822$peek"; + my $uid = $self->Uid ? "UID " : ""; + my $trans = $self->Count($self->Count+1); + my $string = "$trans ${uid}FETCH $msgs $cmd"; + + $self->_record($trans, [0, "INPUT", $string] ); + + my $feedback = $self->_send_line($string); + unless($feedback) + { $self->LastError("Error sending '$string' to IMAP: $!"); + return undef; + } + + my $code; + + READ: + until($code) + { my $output = $self->_read_line($handle) + or return undef; + + foreach my $o (@$output) + { $self->_record($trans,$o); + next unless $self->_is_output($o); + + $code = $o->[DATA] =~ /^$trans\s+(OK|BAD|NO)/mi ? $1 : undef; + if($o->[DATA] =~ /^\*\s+BYE/im) + { $self->State(Unconnected); + return undef; + } + } + } + ref $fh or close $handle; + $code =~ /^OK/im ? $self : undef; +} + +sub message_uid +{ my ($self, $msg) = @_; + + foreach ($self->fetch($msg, "UID")) + { return $1 if m/\(UID\s+(\d+)\s*\)\r?$/; + } + undef; +} + +#???? this code is very clumpsy, and currently probably broken. +# Why not use a pipe??? +# Is a quadratic slowdown not much simpler and better??? +# Shouldn't the slowdowns extend over multiple messages? +# --> create clean read and write methods + +sub migrate +{ my ($self, $peer, $msgs, $folder) = @_; + my $toSock = $peer->Socket, + my $fromSock = $self->Socket; + my $bufferSize = $self->Buffer || 4096; + + unless(eval {$peer->IsConnected} ) + { $self->LastError("Invalid or unconnected " . ref($self) + . " object used as target for migrate. $@"); + return undef; + } + + unless($folder) + { unless($folder = $self->Folder) + { $self->LastError( "No folder selected on source mailbox."); + return undef; + } + + unless($peer->exists($folder) || $peer->create($folder)) + { $self->LastError("Unable to create folder $folder on target " + . "mailbox: ". $peer->LastError); + return undef + }; + } + + defined $msgs or $msgs = "ALL"; + $msgs = $self->search("ALL") + if uc $msgs eq 'ALL'; + + my $range = $self->Range($msgs); + my $clear = $self->Clear; + + $self->_debug("Migrating the following msgs from $folder: $range"); + MSG: + foreach my $mid ($range->unfold) + { + $self->_debug("Migrating message $mid in folder $folder"); + + my $leftSoFar = my $size = $self->size($mid); + + # fetch internaldate and flags of original message: + my $intDate = $self->internaldate($mid); + my @flags = grep !/\\Recent/i, $self->flags($mid); + my $flags = join ' ', $peer->supported_flags(@flags); + + # set up transaction numbers for from and to connections: + my $trans = $self->Count($self->Count+1); + my $ptrans = $peer->Count($peer->Count+1); + + # If msg size is less than buffersize then do whole msg in one + # transaction: + if($size <= $bufferSize) + { my $new_mid = $peer->append_string + ($folder, $self->message_string($mid), $flags, $intDate); + + unless(defined $new_mid) + { $self->LastError("Unable to append to $folder " + . "on target mailbox. ". $peer->LastError); + return undef; + } + + $self->_debug("Copied message $mid in folder $folder to " + . $peer->User . '@' . $peer->Server + . ". New message UID is $new_mid") + if $self->Debug; + + $peer->_debug("Copied message $mid in folder $folder from " + . $self->User . '@' . $self->Server + . ". New message UID is $new_mid") + if $peer->Debug; + + next MSG; + } + + # otherwise break it up into digestible pieces: + my ($cmd, $extract_size); + if($self->imap4rev1) + { $cmd = $self->Peek ? 'BODY.PEEK[]' : 'BODY[]'; + $extract_size = sub { $_[0] =~ /\(.*BODY\[\]<\d+> \{(\d+)\}/i; $1 }; + } + else + { $cmd = $self->Peek ? 'RFC822.PEEK' : 'RFC822'; + $extract_size = sub { $_[0] =~ /\(RFC822\[\]<\d+> \{(\d+)\}/i; $1 }; + } + + # Now let's warn the peer that there's a message coming: + + my $pstring = "$ptrans APPEND " . $self->Massage($folder) + . (length $flags ? " ($flags)" : '') . qq[ "$intDate" {$size}]; + + $self->_debug("About to issue APPEND command to peer for msg $mid"); + + $peer->_record($ptrans, [0, "INPUT", $pstring] ); + unless($peer->_send_line($pstring)) + { $self->LastError("Error sending '$pstring' to target IMAP: $!"); + return undef; + } + + # Get the "+ Go ahead" response: + my $code = 0; + until($code eq '+' || $code =~ /NO|BAD|OK/) + { + my $readSoFar = 0; + my $fromBuffer = '';; + $readSoFar += sysread($toSock, $fromBuffer, 1, $readSoFar) || 0 + until $fromBuffer =~ /\r\n/; + + $code = $fromBuffer =~ /^\+/ ? $1 + : $fromBuffer =~ / ^(?:\d+\s(BAD|NO))/ ? $1 : 0; + + $peer->_debug("$folder: received $fromBuffer from server"); + + # ... and log it in the history buffers + $self->_record($trans, [0, "OUTPUT", + "Mail::IMAPClient migrating message $mid to $peer->User\@$peer->Server"] ); + $peer->_record($ptrans, [0, "OUTPUT", $fromBuffer] ); + } + + if($code ne '+') + { $self->_debug("Error writing to target host: $@"); + next MIGMSG; + } + + # Here is where we start sticking in UID if that parameter + # is turned on: + my $string = ($self->Uid ? "UID " : "") . "FETCH $mid $cmd"; + + # Clean up history buffer if necessary: + $self->Clear($clear) + if $self->Count >= $clear && $clear > 0; + + # position will tell us how far from beginning of msg the + # next IMAP FETCH should start (1st time start at offet zero): + my $position = 0; + my $chunkCount = 0; + my $readSoFar = 0; + while($leftSoFar > 0) + { my $take = min $leftSoFar, $bufferSize; + my $newstring = "$trans $string<$position.$take>"; + + $self->_record($trans, [0, "INPUT", $newstring] ); + $self->_debug("Issuing migration command: $newstring"); + + unless($self->_send_line($newstring)) + { $self->LastError("Error sending '$newstring' to source IMAP: $!"); + return undef; + } + + my $chunk; + my $fromBuffer = ""; + until($chunk = $extract_size->($fromBuffer)) + { $fromBuffer = ''; + sysread($fromSock, $fromBuffer, 1, length $fromBuffer) + until $fromBuffer =~ /\r\n$/; + + $self->_record($trans, [0, "OUTPUT", $fromBuffer]); + + if($fromBuffer =~ /^$trans (?:NO|BAD)/ ) + { $self->LastError($fromBuffer); + next MIGMSG; + } + + if($fromBuffer =~ /^$trans (?:OK)/ ) + { $self->LastError("Unexpected good return code " . + "from source host: $fromBuffer"); + next MIGMSG; + } + } + + $fromBuffer = ""; + while($readSoFar < $chunk) + { $readSoFar += sysread($fromSock, $fromBuffer + , $chunk-$readSoFar,$readSoFar) ||0; + } + + my $wroteSoFar = 0; + my $temperrs = 0; + my $waittime = .02; + my $maxwrite = 0; + my $maxagain = $self->Maxtemperrors || 10; + undef $maxagain if $maxagain eq 'unlimited'; + my @previous_writes; + + while($wroteSoFar < $chunk) + { while($wroteSoFar < $readSoFar) + { my $ret = syswrite($toSock, $fromBuffer + , $chunk - $wroteSoFar, $wroteSoFar); + + if(defined $ret) + { $wroteSoFar += $ret; + $maxwrite = max $maxwrite, $ret; + $temperrs = 0; + } + + if($! == EAGAIN || $ret==0) + { if(defined $maxagain && $temperrs++ > $maxagain) + { $self->LastError("Persistent '$!' errors"); + return undef; + } + + $waittime = $self->_optimal_sleep($maxwrite, + $waittime, \@previous_writes); + next; + } + + return; # no luck + } + + $peer->_debug("Chunk $chunkCount: wrote $wroteSoFar (of $chunk)"); + } + } + + $position += $readSoFar; + $leftSoFar -= $readSoFar; + my $fromBuffer = ""; + + # Finish up reading the server response from the fetch cmd + # on the source system: + + undef $code; + until($code) + { $self->_debug("Reading from source server; expecting ') OK' type response"); + my $output = $self->_read_line or return undef; + foreach my $o (@$output) + { $self->_record($trans, $o); + $self->_is_output($o) or next; + + $code = $o->[DATA] =~ /^$trans (OK|BAD|NO)/mi ? $1 : undef; + if($o->[DATA] =~ /^\*\s+BYE/im) + { $self->State(Unconnected); + return undef; + } + } + } + + # Now let's send a to the peer to signal end of APPEND cmd: + { my $wroteSoFar = 0; + my $fromBuffer = "\r\n"; + $wroteSoFar += syswrite($toSock,$fromBuffer,2-$wroteSoFar,$wroteSoFar)||0 + until $wroteSoFar >= 2; + + } + + # Finally, let's get the new message's UID from the peer: + my $new_mid; + undef $code; + until($code) + { $peer->_debug("Reading from target: expect new uid in response"); + + my $output = $peer->_read_line or last; + foreach my $o (@$output) + { $peer->_record($ptrans,$o); + next unless $peer->_is_output($o); + + $code = $o->[DATA] =~ /^$ptrans (OK|BAD|NO)/mi ? $1 : undef; + $new_mid = $o->[DATA] =~ /APPENDUID \d+ (\d+)/ ? $1 : undef + if $code; + + if($o->[DATA] =~ /^\*\s+BYE/im) + { $peer->State(Unconnected); + return undef; + } + } + + $new_mid ||= "unknown"; + } + if($self->Debug) + { $self->_debug("Copied message $mid in folder $folder to " + . $peer->User.'@'.$peer->Server. ". New Message UID is $new_mid"); + + $peer->_debug("Copied message $mid in folder $folder from " + . $self->User.'@'.$self->Server . ". New Message UID is $new_mid"); + } + } + + $self; +} + +# Optimization of wait time between syswrite calls only runs if syscalls +# run too fast and fill the buffer causing "EAGAIN: Resource Temp. Unavail" +# errors. The premise is that $maxwrite will be approx. the same as the +# smallest buffer between the sending and receiving side. Waiting time +# between syscalls should ideally be exactly as long as it takes the +# receiving side to empty that buffer, minus a little bit to prevent it +# from emptying completely and wasting time in the select call. + +sub _optimal_sleep($$$) +{ my ($self, $maxwrite, $waittime, $last5writes) = @_; + + push @$last5writes, $waittime; + shift @$last5writes if @$last5writes > 5; + + my $bufferavail = (sum @$last5writes) / @$last5writes; + + if($bufferavail < .4 * $maxwrite) + { # Buffer is staying pretty full; we should increase the wait + # period to reduce transmission overhead/number of packets sent + $waittime *= 1.3; + } + elsif($bufferavail > .9 * $maxwrite) + { # Buffer is nearly or totally empty; we're wasting time in select + # call that could be used to send data, so reduce the wait period + $waittime *= .5; + } + + CORE::select(undef, undef, undef, $waittime); + $waittime; +} + +sub body_string +{ my ($self, $msg) = @_; + my $ref = $self->fetch($msg, "BODY" .($self->Peek ? ".PEEK" : "")."[TEXT]"); + + my $string = join '', map {$_->[DATA]} + grep {$self->_is_literal($_)} @$ref; + + return $string + if $string; + + my $head; + while($head = shift @$ref) + { $self->_debug("body_string: head = '$head'"); + + last if $head =~ + /(?:.*FETCH .*\(.*BODY\[TEXT\])|(?:^\d+ BAD )|(?:^\d NO )/i; + } + + unless(@$ref) + { $self->LastError("Unable to parse server response from ".$self->LastIMAPCommand); + return undef; + } + + my $popped; + $popped = pop @$ref # (-: vi + until ($popped && $popped =~ /\)\r\n$/) # (-: vi + || ! grep /\)\r\n$/, @$ref; + + if($head =~ /BODY\[TEXT\]\s*$/i ) + { # Next line is a literal + $string .= shift @$ref while @$ref; + $self->_debug("String is now $string") + if $self->Debug; + } + + $string; +} + +sub examine +{ my ($self, $target) = @_; + defined $target or return undef; + + $self->_imap_command('EXAMINE ' . $self->Massage($target)) + or return undef; + + my $old = $self->Folder; + $self->Folder($target); + $self->State(Selected); + $old || $self; +} + +sub idle +{ my $self = shift; + my $good = '+'; + my $count = $self->Count +1; + $self->_imap_command("IDLE", $good) ? $count : undef; +} + +sub done +{ my $self = shift; + my $count = shift || $self->Count; + + my $clear = $self->Clear; + $self->Clear($clear) + if $self->Count >= $clear && $clear > 0; + + my $string = "DONE\r\n"; + $self->_record($count, [$self->_next_index($count), "INPUT", $string] ); + + unless($self->_send_line($string, 1)) + { $self->LastError("Error sending '$string' to IMAP: $!"); + return undef; + } + + my $code; + until($code && $code =~ /OK|BAD|NO/m) + { my $output = $self->_read_line or return undef; + for my $o (@$output) + { $self->_record($count,$o); + next unless $self->_is_output($o); + $code = $o->[DATA] =~ /^(?:$count) (OK|BAD|NO)/m ? $1 : undef; + $self->State(Unconnected) if $o->[DATA] =~ /^\*\s+BYE/; + } + } + + $code eq 'OK' ? @{$self->Results} : undef; #?? enforce list context? +} + +sub tag_and_run +{ my ($self, $string, $good) = @_; + $self->_imap_command($string, $good); + @{$self->Results}; #??? enforce list context +} + +# _{name} methods are undocumented and meant to be private. + +# _imap_command runs a command, inserting the correct tag +# and and whatnot. +# When updating _imap_command, remember to examine the run method, +# too, since it is very similar. + +sub _imap_command +{ my $self = shift; + my $string = shift or return undef; + my $good = shift || 'GOOD'; + my $qgood = quotemeta $good; + + my $clear = $self->Clear; + $self->Clear($clear) + if $self->Count >= $clear and $clear > 0; + + my $count = $self->Count($self->Count+1); + $string = "$count $string"; + + $self->_record($count, [0, "INPUT", $string] ); + + unless($self->_send_line($string)) + { $self->LastError("Error sending '$string' to IMAP: $!"); + return undef; + } + + my $code; + + READ: + until($code) + { my $output = $self->_read_line or return undef; + foreach my $o (@$output) + { $self->_record($count, $o); + $self->_is_output($o) or next; + + if($good eq '+') + { $o->[DATA] =~ /^$count\s+(OK|BAD|NO|$qgood)|^($qgood)/mi; + $code = $1||$2; + } + else + { ($code) = $o->[DATA] =~ /^$count\s+(OK|BAD|NO|$qgood)/mi; + } + if ($o->[DATA] =~ /^\*\s+BYE/im) + { $self->State(Unconnected); + return undef; + } + } + } + + $code =~ /^OK|$qgood/im ? $self : undef; + +} + +sub _imap_uid_command +{ my ($self, $cmd) = (shift, shift); + my $args = @_ ? join(" ", '', @_) : ''; + my $uid = $self->Uid ? 'UID ' : ''; + $self->_imap_command("$uid$cmd$args"); +} + +sub run +{ my $self = shift; + my $string = shift or return undef; + my $good = shift || 'GOOD'; + my $count = $self->Count($self->Count+1); + my $tag = $string =~ /^(\S+) / ? $1 : undef; + + $tag or $self->LastError("Invalid string passed to run method; no tag found."); + + my $qgood = quotemeta($good); + my $clear = $self->Clear; + $self->Clear($clear) + if $self->Count >= $clear && $clear > 0; + + $self->_record($count, [$self->_next_index($count), "INPUT", $string] ); + + unless($self->_send_line("$string",1)) + { $self->LastError("Error sending '$string' to IMAP: $!"); + return undef; + } + + my ($code, $output); + $output = ""; + + until($code =~ /(OK|BAD|NO|$qgood)/m ) + { $output = $self->_read_line or return undef; + foreach my $o (@$output) + { $self->_record($count,$o); + $self->_is_output($o) or next; + + if($good eq '+') + { $o->[DATA] =~ /^(?:$tag|\*) (OK|BAD|NO|$qgood)|(^$qgood)/m; + $code = $1 || $2; + } + else + { ($code) = $o->[DATA] =~ /^(?:$tag|\*) (OK|BAD|NO|$qgood)/m; + } + + $o->[DATA] =~ /^\*\s+BYE/ + and $self->State(Unconnected); + } + } + + $tag eq $count + or $self->{History}{$tag} = $self->{History}{$count}; + + $code =~ /^OK|$qgood/ ? @{$self->Results} : undef; +} + +# _record saves the conversation into the History structure: +sub _record +{ my ($self, $count, $array) = @_; + if ($array->[DATA] =~ /^\d+ LOGIN/i && !$self->Showcredentials) + { $array->[DATA] =~ s/LOGIN.*/LOGIN XXXXXXXX XXXXXXXX/i; + } + + push @{$self->{History}{$count}}, $array; + + if($array->[DATA] =~ /^\d+\s+(BAD|NO)\s/im ) + { $self->LastError($array->[DATA]); + } + $self; +} + +#_send_line writes to the socket: +sub _send_line +{ my ($self, $string, $suppress) = (shift, shift, shift); + + $string =~ s/\r?\n?$/\r\n/ + unless $suppress; + + if($string =~ s/^([^\n{]*\{(\d+)\}\r\n)(.)/$3/) # ;-} vi + { # Line starts with literal + my ($first, $len) = ($1, $2); + $self->_debug("Sending literal: $first\tthen: $string"); + + $self->_send_line($first) or return undef; + my $output = $self->_read_line or return undef; + + my $code; + foreach my $o (@$output) + { $self->_record($self->Count, $o); + $code = $o->[DATA] =~ /(^\+|NO|BAD)/i ? $1 : undef; + + if($o->[DATA] =~ /^\*\s+BYE/) + { $self->State(Unconnected); + $self->close; + return undef; + } + elsif($o->[DATA]=~ /^\d+\s+(NO|BAD)/i ) + { return undef; + } + } + + $code eq '+' or return undef; + + # the second part follows the non-literal output, as below. + } + + unless($self->IsConnected) + { $self->LastError("NO Not connected."); + return undef; + } + + if(my $prew = $self->Prewritemethod) + { $string = $prew->($self, $string); + } + + $self->_debug("Sending: $string"); + + my $total = 0; + my $temperrs = 0; + my $maxwrite = 0; + my $waittime = .02; + my @previous_writes; + + my $maxagain = $self->Maxtemperrors || 10; + undef $maxagain if $maxagain eq 'unlimited'; + + while($total < length $string) + { my $written = syswrite($self->Socket, $string, length($string)-$total, + $total); + + if(defined $written) + { $temperrs = 0; + $total += $written; + next; + } + + if($! == EAGAIN) + { if(defined $maxagain && $temperrs++ > $maxagain) + { $self->LastError("Persistent error $!"); + return undef; + } + + $waittime = $self->_optimal_sleep($maxwrite, $waittime, \@previous_writes); + next; + } + + return undef; # no luck + } + + $self->_debug("Sent $total bytes"); + $total; +} + +# _read_line: read one line from the socket + +# It is also re-implemented in: message_to_file +# +# $output = $self->_read_line($literal_callback, $output_callback) +# Both input argument are optional, but if supplied must either +# be a filehandle, coderef, or undef. +# +# Returned argument is a reference to an array of arrays, ie: +# $output = [ +# [ $index, 'OUTPUT'|'LITERAL', $output_line ] , +# [ $index, 'OUTPUT'|'LITERAL', $output_line ] , +# ... # etc, +# ]; + +sub _read_line +{ my ($self, $literal_callback, $output_callback) = @_; + + my $socket = $self->Socket; + unless($self->IsConnected && $socket) + { $self->LastError("NO Not connected"); + return undef; + } + + my $iBuffer = ""; + my $oBuffer = []; + my $index = $self->_next_index; + my $timeout = $self->Timeout; + my $readlen = $self->{Buffer} || 4096; + my $fast_io = $self->Fast_io; + + until(@$oBuffer # there's stuff in output buffer: + && $oBuffer->[-1][TYPE] eq 'OUTPUT' # that thing is an output line: + && $oBuffer->[-1][DATA] =~ /\r?\n$/ # the last thing there has cr-lf: + && !length $iBuffer # and the input buffer has been MT'ed: + ) + { my $transno = $self->Transaction; + + if($timeout) + { my $rvec = 0; + vec($rvec, fileno($self->Socket), 1) = 1; + unless(CORE::select($rvec, undef, $rvec, $timeout)) + { $self->LastError("Tag $transno: Timeout after $timeout seconds" + . " waiting for data from server"); + + $self->_record($transno, + [ $self->_next_index($transno), "ERROR" + , "$transno * NO Timeout after $timeout seconds " . + "during read from server"]); + + $self->LastError("Timeout after $timeout seconds during " + . "read from server"); + + return undef; + } + } + + my $ret = $self->_sysread($socket, \$iBuffer, $readlen,length $iBuffer); + if($timeout && !defined $ret) + { # Blocking read error... + my $msg = "Error while reading data from server: $!\r\n"; + $self->LastError($msg); + $self->_record($transno, + [ $self->_next_index($transno), "ERROR", "$transno * NO $msg"]); + return undef; + } + + if(defined $ret && $ret == 0) # Caught EOF... + { my $msg = "Socket closed while reading data from server.\r\n"; + $self->LastError($msg); + $self->_record($transno, + [ $self->_next_index($transno), "ERROR","$transno * NO $msg"]); + return undef; + } + + while($iBuffer =~ s/^(.*?\r?\n)//) # consume line + { my $current_line = $1; + if($current_line !~ s/\s*\{(\d+)\}\r?\n$//) + { push @$oBuffer, [$index++, 'OUTPUT' , $current_line]; + next; + } + + push @$oBuffer, [$index++, 'OUTPUT', $current_line]; + + ## handle LITERAL + # BLAH BLAH {nnn}\r\n + # [nnn bytes of literally transmitted stuff] + # [part of line that follows literal data]\r\n + + my $expected_size = $1; + + $self->_debug("LITERAL: received literal in line ". + "$current_line of length $expected_size; attempting to ". + "retrieve from the " . length($iBuffer) . + " bytes in: $iBuffer"); + + my $litstring; + if(length $iBuffer >= $expected_size) + { # already received all data + $litstring = substr $iBuffer, 0, $expected_size, ''; + } + else + { # literal data still to arrive + $litstring = $iBuffer; + $iBuffer = ''; + + while($expected_size > length $litstring) + { if($timeout) + { # wait for data from the the IMAP socket. + my $rvec = 0; + vec($rvec, fileno($self->Socket), 1) = 1; + unless(CORE::select($rvec, undef, $rvec, $timeout)) + { $self->LastError("Tag $transno: Timeout waiting for " + . "literal data from server"); + return undef; + } + } + else # 1 ms before retry + { CORE::select(undef, undef, undef, 0.001); + } + + fcntl($socket, F_SETFL, $self->{_fcntl}) #???why + if $fast_io && defined $self->{_fcntl}; + + my $ret = $self->_sysread($socket, \$litstring + , $expected_size - length $litstring, length $litstring); + + $self->_debug("Received ret=$ret and buffer = " . + "\n$litstring\nwhile processing LITERAL"); + + if($timeout && !defined $ret) + { $self->_record($transno, + [ $self->_next_index($transno), "ERROR", + "$transno * NO Error reading data from server: $!"]); + return undef; + } + + if($ret==0 && $socket->eof) + { $self->_record($transno, + [ $self->_next_index($transno), "ERROR", + "$transno * BYE Server unexpectedly closed connection: $!"]); + $self->State(Unconnected); + return undef; + } + } + } + + if(!$literal_callback) { ; } + elsif(UNIVERSAL::isa($literal_callback, 'GLOB')) + { print $literal_callback $litstring; + $litstring = ""; + } + elsif(UNIVERSAL::isa($literal_callback, 'CODE')) + { $literal_callback->($litstring) + if defined $litstring + } + else + { $self->LastError("'$literal_callback' is an " + . "invalid callback; must be a filehandle or CODE"); + } + + $self->Fast_io($fast_io) if $fast_io; # ??? + push @$oBuffer, [$index++, 'LITERAL', $litstring]; + } + } + + $self->_debug("Read: ". join "\n ", map {$_->[DATA]} @$oBuffer); + @$oBuffer ? $oBuffer : undef; +} + +sub _sysread($$$$) +{ my ($self, $fh, $buf, $len, $off) = @_; + my $rm = $self->Readmethod; + $rm ? $rm->($self, @_) : sysread($fh, $$buf, $len, $off); +} + +sub _trans_index() { sort {$a <=> $b} keys %{$_[0]->{History}} } + +# all default to last transaction +sub _transaction(;$) { @{$_[0]->{History}{$_[1] || $_[0]->Transaction} || []} } +sub _trans_data(;$) { map { $_->[DATA] } $_[0]->_transaction($_[1]) } + +sub Report { + my $self = shift; + map { $self->_trans_data($_) } $self->_trans_index; +} + +sub LastIMAPCommand(;$) +{ my ($self, $trans) = @_; + my $msg = ($self->_transaction($trans))[0]; + $msg ? $msg->[DATA] : undef; +} + +sub History(;$) +{ my ($self, $trans) = @_; + my ($cmd, @a) = $self->_trans_data($trans); + wantarray ? @a : \@a; +} + +sub Results(;$) +{ my ($self, $trans) = @_; + my @a = $self->_trans_data($trans); + wantarray ? @a : \@a; +} + +# Don't know what it does, but used a few times. +sub _transaction_literals() +{ my $self = shift; + join '', map { $_->[DATA] } + grep { $self->_is_literal($_) } + $self->_transaction; +} + +sub Escaped_results +{ my ($self, $trans) = @_; + my @a; + foreach my $line (grep defined, $self->Results($trans)) + { if($self->_is_literal($line)) + { $line->[DATA] =~ s/([\\\(\)"\r\n])/\\$1/g; + push @a, qq("$line->[DATA]"); + } + else { push @a, $line->[DATA] } + } + + shift @a; # remove cmd + wantarray ? @a : \@a; +} + +sub Unescape +{ my $whatever = $_[1]; + $whatever =~ s/\\([\\\(\)"\r\n])/$1/g; + $whatever; +} + +sub logout +{ my $self = shift; + $self->_imap_command("LOGOUT"); + + delete $self->{Folders}; + delete $self->{_IMAP4REV1}; + $self->State(Unconnected); + if(my $sock = delete $self->{Socket}) { eval {$sock->close} } + $self; +} + +sub folders($) +{ my ($self, $what) = @_; + + return wantarray ? @{$self->{Folders}} : $self->{Folders} + if !$what && $self->{Folders}; + + my @list; + if($what) + { my $sep = $self->separator($what); + my $whatsub = $what =~ m/\Q${sep}\E$/ ? "$what*" : "$what$sep*"; + push @list, $self->list(undef, $whatsub); + push @list, $self->list(undef, $what) if $self->exists($what); + } + else + { push @list, $self->list(undef, undef); + } + + my @folders; + for(my $m = 0; $m < @list; $m++ ) + { if($list[$m] && $list[$m] !~ /\r\n$/ ) + { $self->_debug("folders: concatenating $list[$m] and $list[$m+1]"); + $list[$m] .= $list[$m+1]; + splice @list, $m+1, 1; + } + + $list[$m] =~ / ^\* \s+ LIST \s+ \([^\)]*\) \s+ # * LIST (Flags) + (?:\" [^"]* \" | NIL ) \s+ # "delimiter" or NIL + (?:\"([^"]*)\" | (\S+)) \s*$ # "name" or name + /ix + or next; + + push @folders, $1 || $2; + } + + my @clean = _remove_doubles @folders; + $self->{Folders} = \@clean unless $what; + + wantarray ? @clean : \@clean; +} + +sub exists +{ my ($self, $folder) = @_; + $self->status($folder) ? $self : undef; +} + +# Updated to handle embedded literal strings +sub get_bodystructure +{ my($self, $msg) = @_; + unless(eval {require Mail::IMAPClient::BodyStructure; 1} ) + { $self->LastError("Unable to use get_bodystructure: $@"); + return undef; + } + + my @out = $self->fetch($msg,"BODYSTRUCTURE"); + my $bs = ""; + my $output = first { /BODYSTRUCTURE\s+\(/i } @out; # Wee! ;-) + if($output =~ /\r\n$/) + { $bs = eval { Mail::IMAPClient::BodyStructure->new($output) }; + } + else + { $self->_debug("get_bodystructure: reassembling original response"); + my $started = 0; + my $output = ''; + foreach my $o ($self->_transaction) + { next unless $self->_is_output_or_literal($o); + $started++ if $o->[DATA] =~ /BODYSTRUCTURE \(/i; ; # Hi, vi! ;-) + $started or next; + + if(length $output && $self->_is_literal($o) ) + { my $data = $o->[DATA]; + $data =~ s/"/\\"/g; + $data =~ s/\(/\\\(/g; + $data =~ s/\)/\\\)/g; + $output .= qq("$data"); + } + else { $output .= $o->[DATA] } + + $self->_debug("get_bodystructure: reassembled output=$output"); + } + eval { $bs = Mail::IMAPClient::BodyStructure->new( $output )}; + } + + $self->_debug("get_bodystructure: msg $msg returns: ". $bs||"UNDEF"); + $bs; +} + +# Updated to handle embedded literal strings +sub get_envelope +{ my ($self,$msg) = @_; + unless( eval {require Mail::IMAPClient::BodyStructure ; 1 } ) + { $self->LastError("Unable to use get_envelope: $@"); + return undef; + } + + my @out = $self->fetch($msg, 'ENVELOPE'); + my $bs = ""; + my $output = first { /ENVELOPE \(/i } @out; # vi ;-) + + unless($output) + { $self->LastError("Unable to use get_envelope: $@"); + return undef; + } + + if($output =~ /\r\n$/ ) + { eval { $bs = Mail::IMAPClient::BodyStructure::Envelope->new($output) }; + } + else + { $self->_debug("get_envelope: reassembling original response"); + my $started = 0; + $output = ''; + foreach my $o ($self->_transaction) + { next unless $self->_is_output_or_literal($o); + $self->_debug("o->[DATA] is $o->[DATA]"); + + $started++ if $o->[DATA] =~ /ENVELOPE \(/i; # Hi, vi! ;-) + $started or next; + + if(length($output) && $self->_is_literal($o) ) { + my $data = $o->[DATA]; + $data =~ s/"/\\"/g; + $data =~ s/\(/\\\(/g; + $data =~ s/\)/\\\)/g; + $output .= '"'.$data.'"'; + } else { + $output .= $o->[DATA]; + } + $self->_debug("get_envelope: reassembled output=$output"); + } + + eval { $bs=Mail::IMAPClient::BodyStructure::Envelope->new($output) }; + } + + $self->_debug("get_envelope: msg $msg returns ref: ". $bs||"UNDEF"); + $bs; +} + +sub fetch +{ my $self = shift; + my $what = shift || "ALL"; + + my $take + = $what eq 'ALL' ? $self->Range($self->messages) + : ref $what || $what =~ /^[,:\d]+\w*$/ ? $self->Range($what) + : $what; + + $self->_imap_uid_command(FETCH => $take, @_) + or return; + + wantarray ? $self->History : $self->Results; +} + +sub fetch_hash +{ my $self = shift; + my $uids = ref $_[-1] ? pop @_ : {}; + my @words = @_; + my $what = join ' ', @_; + + for(@words) + { s/([\( ])FAST([\) ])/${1}FLAGS INTERNALDATE RFC822\.SIZE$2/i; + s/([\( ])FULL([\) ])/${1}FLAGS INTERNALDATE RFC822\.SIZE ENVELOPE BODY$2/i; + } + + my $msgref = scalar $self->messages; + my $output = scalar $self->fetch($msgref, "($what)"); + + for(my $x = 0; $x <= $#$output ; $x++) + { my $entry = {}; + my $l = $output->[$x]; + + if($self->Uid) + { my $uid = $l =~ /\bUID\s+(\d+)/i ? $1 : undef; + $uid or next; + + if($uids->{$uid}) { $entry = $uids->{$uid} } + else { $uids->{$uid} ||= $entry } + + } + else + { my $mid = $l =~ /^\* (\d+) FETCH/i ? $1 : undef; + $mid or next; + + if($uids->{$mid}) { $entry = $uids->{$mid} } + else { $uids->{$mid} ||= $entry } + } + + foreach my $w (@words) + { if($l =~ /\Q$w\E\s*$/i ) + { $entry->{$w} = $output->[$x+1]; + $entry->{$w} =~ s/(?:\n?\r)+$//g; + chomp $entry->{$w}; + } + else + { + $l =~ /\( # open paren followed by ... + (?:.*\s)? # ...optional stuff and a space + \Q$w\E\s # escaped fetch field + (?:" # then: a dbl-quote + (\\.| # then bslashed anychar(s) or ... + [^"]+) # ... nonquote char(s) + "| # then closing quote; or ... + \( # ...an open paren + (\\.| # then bslashed anychar or ... + [^\)]+) # ... non-close-paren char + \)| # then closing paren; or ... + (\S+)) # unquoted string + (?:\s.*)? # possibly followed by space-stuff + \) # close paren + /xi; + $entry->{$w} = defined $1 ? $1 : defined $2 ? $2 : $3; + } + } + } + wantarray ? %$uids : $uids; +} + +sub store +{ my ($self, @a) = @_; + delete $self->{Folders}; + $self->_imap_uid_command(STORE => @a) + or return undef; + wantarray ? $self->History : $self->Results; +} + +sub _imap_folder_command($$@) +{ my ($self, $command) = (shift, shift); + delete $self->{Folders}; + my $folder = $self->Massage(shift); + + $self->_imap_command(join ' ', $command, $folder, @_) + or return; + + wantarray ? $self->History : $self->Results; +} + +sub subscribe($) { $_[0]->_imap_folder_command(SUBSCRIBE => $_[1]) } +sub unsubscribe($) { $_[0]->_imap_folder_command(UNSUBSCRIBE => $_[1]) } +sub delete($) { $_[0]->_imap_folder_command(DELETE => $_[1]) } +sub create($) { my $self=shift; $self->_imap_folder_command(CREATE => @_)} + +# rfc2086 +sub myrights($) { $_[0]->_imap_folder_command(MYRIGHTS => $_[1]) } + +sub close +{ my $self = shift; + delete $self->{Folders}; + $self->_imap_command('CLOSE') + or return undef; + wantarray ? $self->History : $self->Results; +} + +sub expunge +{ my ($self, $folder) = @_; + + my $old = $self->Folder || ''; + if(defined $folder && $folder eq $old) + { $self->_imap_command('EXPUNGE') + or return undef; + } + else + { $self->select($folder); + my $succ = $self->_imap_command('EXPUNGE'); + $self->select($old); + $succ or return undef; + } + + wantarray ? $self->History : $self->Results; +} + +sub rename +{ my ($self, $from, $to) = @_; + + if($from =~ /^"(.*)"$/) + { $from = $1 unless $self->exists($from); + $from =~ s/"/\\"/g; + } + + if($to =~ /^"(.*)"$/) + { $to = $1 unless $self->exists($from) && $from =~ /^".*"$/; + $to =~ s/"/\\"/g; + } + + $self->_imap_command( qq[RENAME "$from" "$to"] ) ? $self : undef; +} + +sub status +{ my ($self, $folder) = (shift, shift); + defined $folder or return; + + my $which = @_ ? join(" ", @_) : 'MESSAGES'; + + my $box = $self->Massage($folder); + $self->_imap_command("STATUS $box ($which)") + or return undef; + + wantarray ? $self->History : $self->Results; +} + +sub flags +{ my ($self, $msgspec) = (shift, shift); + my $msg + = UNIVERSAL::isa($msgspec, 'Mail::IMAPClient::MessageSet') + ? $msgspec + : $self->Range($msgspec); + + $msg->cat(@_) if @_; + + # Send command + $self->fetch($msg, "FLAGS") + or return; + + my $u_f = $self->Uid; + my $flagset = {}; + + # Parse results, setting entry in result hash for each line + foreach my $line ($self->Results) + { $self->_debug("flags: line = '$line'"); + if ( $line =~ + /\* \s+ (\d+) \s+ FETCH \s+ # * nnn FETCH + \( + (?:\s* UID \s+ (\d+) \s* )? # optional: UID nnn + FLAGS \s* \( (.*?) \) \s* # FLAGS (\Flag1 \Flag2) + (?:\s* UID \s+ (\d+) \s* )? # optional: UID nnn + \) + /x + ) + { my $mailid = $u_f ? ($2||$4) : $1; + $flagset->{$mailid} = [ split " ", $3 ]; + } + } + + # Or did he want a hash from msgid to flag array? + return $flagset + if ref $msgspec; + + # or did the guy want just one response? Return it if so + my $flagsref = $flagset->{$msgspec}; + wantarray ? @$flagsref : $flagsref; +} + +# reduce a list, stripping undeclared flags. Flags with or without +# leading backslash. +sub supported_flags(@) +{ my $self = shift; + my $sup = $self->Supportedflags + or return @_; + + return map { $sup->($_) } @_ + if ref $sup eq 'CODE'; + + grep { $sup->{ /^\\(\S+)/ ? lc $1 : ()} } @_; +} + +sub parse_headers +{ my ($self, $msgspec, @fields) = @_; + my $fields = join ' ', @fields; + my $msg = ref $msgspec eq 'ARRAY' ? $self->Range($msgspec) : $msgspec; + my $peek = !defined $self->Peek || $self->Peek ? '.PEEK' : ''; + + my $string = "$msg BODY$peek" + . ($fields eq 'ALL' ? '[HEADER]' : "[HEADER.FIELDS ($fields)]"); + + my @raw = $self->fetch($string) + or return undef; + + my %headers; # message ids to headers + my $h; # fields for current msgid + my $field; # previous field name, for unfolding + my %fieldmap = map { ( lc($_) => $_ ) } @fields; + my $msgid; + + foreach my $header (map {split /\r?\n/} @raw) + { # little problem: Windows2003 has UID as body, not in header + if($header =~ s/^\* \s+ (\d+) \s+ FETCH \s+ + \( (.*?) BODY\[HEADER (?:\.FIELDS)? .*? \]\s*//ix) + { # start new message header + ($msgid, my $msgattrs) = ($1, $2); + $h = {}; + if($self->Uid) # undef when win2003 + { $msgid = $msgattrs =~ m/\b UID \s+ (\d+)/x ? $1 : undef } + + $headers{$msgid} = $h if $msgid; + } + $header =~ /\S/ or next; # skip empty lines. + + # ( for vi + if($header =~ /^\)/) # end of this message + { undef $h; # inbetween headers + next; + } + elsif(!$msgid && $header =~ /^\s*UID\s+(\d+)\s*\)/) + { $headers{$1} = $h; # finally found msgid, win2003 + undef $h; + next; + } + + unless(defined $h) + { last if $header =~ / OK /i; + $self->_debug("found data between fetch headers: $header"); + next; + } + + if($header =~ s/^(\S+)\:\s*//) + { $field = $fieldmap{lc $1} || $1; + push @{$h->{$field}}, $header; + } + elsif(ref $h->{$field} eq 'ARRAY') # folded header + { $h->{$field}[-1] .= $header; + } + } + + # if we asked for one message, just return its hash, + # otherwise, return hash of numbers => header hash + ref $msgspec eq 'ARRAY' ? \%headers : $headers{$msgspec}; +} + +sub subject { $_[0]->get_header($_[1], "Subject") } +sub date { $_[0]->get_header($_[1], "Date") } +sub rfc822_header { shift->get_header(@_) } + +sub get_header +{ my ($self, $msg, $field) = @_; + my $headers = $self->parse_headers($msg, $field); + $headers ? $headers->{$field}[0] : undef; +} + +sub recent_count +{ my ($self, $folder) = (shift, shift); + + $self->status($folder, 'RECENT') + or return undef; + + my $r = first {s/\*\s+STATUS\s+.*\(RECENT\s+(\d+)\s*\)/$1/} $self->History; + chomp $r; + $r; +} + +sub message_count +{ my $self = shift; + my $folder = shift || $self->Folder; + + $self->status($folder, 'MESSAGES') + or return undef; + + foreach my $result ($self->Results) + { return $1 if $result =~ /\(MESSAGES\s+(\d+)\s*\)/i; + } + + undef; +} + +sub recent() { shift->search('recent') } +sub seen() { shift->search('seen') } +sub unseen() { shift->search('unseen') } +sub messages() { shift->search('ALL') } + +sub sentbefore($$) { shift->_search_date(sentbefore => @_) } +sub sentsince($$) { shift->_search_date(sentsince => @_) } +sub senton($$) { shift->_search_date(senton => @_) } +sub since($$) { shift->_search_date(since => @_) } +sub before($$) { shift->_search_date(before => @_) } +sub on($$) { shift->_search_date(on => @_) } + +sub _search_date($$$) +{ my($self, $how, $time) = @_; + my $imapdate; + + if($time =~ /\d\d-\D\D\D-\d\d\d\d/ ) + { $imapdate = $time; + } + elsif($time =~ /^\d+$/ ) + { my @ltime = localtime $time; + $imapdate = sprintf "%2.2d-%s-%4.4d" + , $ltime[3], $mnt[$ltime[4]], $ltime[5] + 1900; + } + else + { $self->LastError("Invalid date format supplied for '$how': $time"); + return undef; + } + + $self->_imap_uid_command(SEARCH => $how, $imapdate) + or return undef; + + my @hits; + foreach ($self->History) + { chomp; + s/\r$//; + s/^\*\s+SEARCH\s+//i or next; + push @hits, grep /\d/, split; + } + $self->_debug("Hits are: @hits"); + wantarray ? @hits : \@hits; +} + +sub or +{ my ($self, @what) = @_; + if(@what < 2) + { $self->LastError("Invalid number of arguments passed to or()"); + return undef; + } + + my $or = "OR ".$self->Massage(shift @what)." ".$self->Massage(shift @what); + + $or = "OR $or " . $self->Massage($_) + for @what; + + $self->_imap_uid_command(SEARCH => $or) + or return undef; + + my @hits; + foreach ($self->History) + { chomp; + s/\r$//; + s/^\*\s+SEARCH\s+//i or next; + push @hits, grep /\d/, split; + } + $self->_debug("Hits are now: @hits"); + + wantarray ? @hits : \@hits; +} + +sub disconnect { shift->logout } + +sub search +{ my ($self, @a) = @_; + + $@ = ""; + # massage? + $a[-1] = $self->Massage($a[-1], 1) + if @a > 1 && !exists $SEARCH_KEYS{uc $a[-1]}; + + $self->_imap_uid_command(SEARCH => @a) + or return undef; + + my @hits; + foreach ($self->History) + { chomp; + s/\r\n?/ /g; + s/^\*\s+SEARCH\s+(?=.*?\d)// or next; + push @hits, grep /^\d+$/, split; + } + + @hits + or $self->_debug("Search successfull but found no matching messages"); + + wantarray ? @hits + : !@hits ? undef + : $self->Ranges ? $self->Range(\@hits) + : \@hits; +} + +# returns a Thread data structure +my $thread_parser; +sub thread +{ my $self = shift; + my $algorythm = shift || + ( $self->has_capability("THREAD=REFERENCES") + ? 'REFERENCES' + : 'ORDEREDSUBJECT' + ); + my $charset = shift || 'UTF-8'; + my @a = @_ ? @_ : 'ALL'; + + $a[-1] = $self->Massage($a[-1], 1) + if @a > 1 && ! exists $SEARCH_KEYS{uc $a[-1]}; + + $self->_imap_uid_command(THREAD => $algorythm, $charset, @a) + or return undef; + + unless($thread_parser) + { return if $thread_parser == 0; + + eval "require Mail::IMAPClient::Thread"; + if($@) + { $self->LastError($@); + $thread_parser = 0; + return undef; + } + $thread_parser = Mail::IMAPClient::Thread->new; + } + + my $thread; + foreach ($self->History) + { /^\*\s+THREAD\s+/ or next; + s/\r\n*|\n+/ /g; + $thread = $thread_parser->start($_); + } + + unless($thread) + { $self->LastError("Thread search completed successfully but found no matching messages"); + return undef; + } + + $thread; +} + +sub delete_message +{ my $self = shift; + my @msgs = map {ref $_ eq 'ARRAY' ? @$_ : split /\,/} @_; + + $self->store(join(',', @msgs), '+FLAGS.SILENT','(\Deleted)') + ? scalar @msgs + : 0 +} + +sub restore_message +{ my $self = shift; + my $msgs = join ',', map {ref $_ eq 'ARRAY' ? @$_ : split /\,/} @_; + + $self->store($msgs,'-FLAGS','(\Deleted)'); + scalar grep /^\*\s\d+\sFETCH\s\(.*FLAGS.*(?!\\Deleted)/, $self->Results; +} + +#??? compare to uidnext. Why is Massage missing? +sub uidvalidity +{ my ($self, $folder) = @_; + my $vline = first { /UIDVALIDITY/i } $self->status($folder, "UIDVALIDITY"); + defined $vline && $vline =~ /\(UIDVALIDITY\s+([^\)]+)/ ? $1 : undef; +} + +sub uidnext +{ my $self = shift; + my $folder = $self->Massage(shift); + my $line = first { /UIDNEXT/i } $self->status($folder, "UIDNEXT"); + defined $line && $line =~ /\(UIDNEXT\s+([^\)]+)/ ? $1 : undef; +} + +sub capability +{ my $self = shift; + + if($self->{CAPABILITY}) + { my @caps = keys %{$self->{CAPABILITY}}; + return wantarray ? @caps : \@caps; + } + + $self->_imap_command('CAPABILITY') + or return undef; + + my @caps = map { split } grep s/^\*\s+CAPABILITY\s+//, $self->History; + foreach (@caps) + { $self->{CAPABILITY}{uc $_}++; + $self->{uc $1} = uc $2 if /(.*?)\=(.*)/; + } + + wantarray ? @caps : \@caps; +} + +sub has_capability +{ my ($self, $which) = @_; + $self->capability; + $which ? $self->{CAPABILITY}{uc $which} : undef; +} + +sub imap4rev1 +{ my $self = shift; + return $self->{_IMAP4REV1} if exists $self->{_IMAP4REV1}; + $self->{_IMAP4REV1} = $self->has_capability('IMAP4REV1'); +} + +#??? what a horror! +sub namespace { + # Returns a nested list as follows: + # [ + # [ + # [ $user_prefix, $user_delim ] (,[$user_prefix2 ,$user_delim ],...), + # ], + # [ + # [ $shared_prefix,$shared_delim] (,[$shared_prefix2,$shared_delim],... ), + # ], + # [ + # [$public_prefix, $public_delim] (,[$public_prefix2,$public_delim],...), + # ], + # ]; + + my $self = shift; + unless($self->has_capability("NAMESPACE")) + { $self->LastError("NO NAMESPACE not supported by ".$self->Server); + return undef; + } + + my $got = $self->_imap_command("NAMESPACE") or return; + my @namespaces = map { /^\* NAMESPACE (.*)/ ? $1 : () } + $got->Results; + + my $namespace = shift @namespaces; + $namespace =~ s/\r?\n$//; + + my($personal, $shared, $public) = $namespace =~ m# + (NIL|\((?:\([^\)]+\)\s*)+\))\s + (NIL|\((?:\([^\)]+\)\s*)+\))\s + (NIL|\((?:\([^\)]+\)\s*)+\)) + #xi; + + my @ns; + $self->_debug("NAMESPACE: pers=$personal, shared=$shared, pub=$public"); + foreach ($personal, $shared, $public) + { uc $_ ne 'NIL' or next; + s/^\((.*)\)$/$1/; + + my @pieces = m#\(([^\)]*)\)#g; + $self->_debug("NAMESPACE pieces: @pieces"); + + push @ns, [ map { [ m#"([^"]*)"\s*#g ] } @pieces ]; + } + + wantarray ? @ns : \@ns; +} + +sub internaldate +{ my ($self, $msg) = @_; + $self->_imap_uid_command(FETCH => $msg, 'INTERNALDATE') + or return undef; + my $internalDate = join '', $self->History; + $internalDate =~ s/^.*INTERNALDATE "//si; + $internalDate =~ s/\".*$//s; + $internalDate; +} + +sub is_parent +{ my ($self, $folder) = (shift, shift); + my $list = $self->list(undef, $folder) || "NO NO BAD BAD"; + my $line; + + for(my $m = 0; $m < @$list; $m++) + { return undef + if $list->[$m] =~ /\bNoInferior\b/i; + + if($list->[$m] =~ s/(\{\d+\})\r\n$// ) + { $list->[$m] .= $list->[$m+1]; + splice @$list, $m+1, 1; + } + + $line = $list->[$m] + if $list->[$m] =~ + /^ \* \s+ LIST \s+ # * LIST + \([^\)]*\) \s+ # (Flags) + \"[^"]*\" \s+ # "delimiter" + (?:\"[^"]*\"|\S+) \s*$ # Name or "Folder name" + /x; + } + + unless(length $line) + { $self->_debug("Warning: separator method found no correct o/p in:\n\t". + join "\n\t", @$list); + return 0; + } + + $line =~ /^\*\s+LIST\s+ \( ([^\)]*) \s*\)/x + or return 0; + + my $flags = $1; + + return 1 if $flags =~ /HasChildren/i; + return 0 if $flags =~ /HasNoChildren/i; + return 0 if $flags =~ /\\/; # other flags found + + # flag not supported, try via folders() + my $sep = $self->separator($folder) || $self->separator(undef); + my $lead = $folder . $sep; + my $len = length $lead; + scalar grep {$lead eq substr($_, 0, $len)} $self->folders; +} + +sub selectable +{ my ($self, $f) = @_; + not( grep /NoSelect/i, $self->list("", $f) ); +} + +sub append +{ my $self = shift; + my $folder = shift; + my $text = @_ > 1 ? join("\r\n", @_) : shift; + $text =~ s/\r?\n/\r\n/g; + + $self->append_string($folder, $text); +} + +sub append_string($$$;$$) +{ my $self = shift; + my $folder = $self->Massage(shift); + my ($text, $flags, $date) = @_; + + if(defined $flags) + { $flags =~ s/^\s+//g; + $flags =~ s/\s+$//g; + $flags = "($flags)" if $flags !~ /^\(.*\)$/; + } + + if(defined $date) + { $date =~ s/^\s+//g; + $date =~ s/\s+$//g; + $date = qq/"$date"/ if $date !~ /^"/; + } + + my $clear = $self->Clear; + $self->Clear($clear) + if $self->Count >= $clear and $clear > 0; + + my $count = $self->Count($self->Count+1); + $text =~ s/\r?\n/\r\n/g; + + my $command = "$count APPEND $folder " . ($flags ? "$flags " : "") . + ($date ? "$date " : "") . "{" . length($text) . "}\r\n"; + + $self->_record($count,[$self->_next_index, "INPUT", $command]); + + my $bytes = $self->_send_line($command.$text."\r\n"); + unless(defined $bytes) + { $self->LastError("Error sending command '$command': $@"); + return undef; + } + + $self->_record($count,[$self->_next_index, "INPUT", $text]); + + my $code; + my $output; + until($code) + { $output = $self->_read_line or return undef; + foreach my $o (@$output) + { $self->_record($count, $o); + $code = $o->[DATA] =~ /^(?:$count|\*)\s+(OK|NO|BAD)\s/i ? $1 :undef; + + if($o->[DATA] =~ /^\*\s+BYE/im) + { $self->State(Unconnected); + $self->LastError("Error trying to append: $o->[DATA]"); + return undef; + } + + if($code && $code !~ /^OK/im) + { $self->LastError("Error trying to append: $o->[DATA]"); + return undef; + } + } + } + + my $data = join '' + , map {$_->[TYPE] eq "OUTPUT" ? $_->[DATA] : ()} + @$output; + + $data =~ m#\s+(\d+)\]# ? $1 : $self; #???? +} + +sub append_file +{ my ($self, $folder, $file, $control, $flags, $use_filetime) = @_; + my $count = $self->Count($self->Count+1); #???? too early? + my $mfolder = $self->Massage($folder); + + $flags ||= ''; + my $fflags = $flags =~ m/^\(.*\)$/ ? $flags : "($flags)"; + + unless(-f $file) + { $self->LastError("File $file not found."); + return undef; + } + + my $fh = IO::File->new($file); + unless($fh) + { $self->LastError("Unable to open $file: $!"); + return undef; + } + + my $date = ''; + if($use_filetime) + { my $f = $self->Rfc2060_datetime(($fh->stat)[9]); + $date = qq{"$f" }; + } + + my $bare_nl_count = grep m/^\n$|[^\r]\n$/, <$fh>; + + seek($fh,0,0); + + my $clear = $self->Clear; + $self->Clear($clear) + if $self->Count >= $clear and $clear > 0; + + my $length = $bare_nl_count + -s $file; + my $string = "$count APPEND $mfolder $fflags $date\{$length}\r\n"; + + $self->_record($count, [$self->_next_index($count), "INPUT", $string] ); + + unless($self->_send_line($string)) + { $self->LastError("Error sending '$string' to IMAP: $!"); + $fh->close; + return undef; + } + + my $code; + + until($code) + { my $output = $self->_read_line; + unless($output) + { $fh->close; + return undef; + } + + foreach my $o (@$output) + { $self->_record($count,$o); + $code = $o->[DATA] =~ /(^\+|^\d+\sNO\s|^\d+\sBAD)\s/i ? $1 : undef; + + if($o->[DATA] =~ /^\*\s+BYE/ ) + { $self->State(Unconnected); + $fh->close; + return undef; + } + elsif($o->[DATA]=~ /^\d+\s+(NO|BAD)/i ) + { $fh->close; + return undef; + } + } + } + + # Slurp up headers: later we'll make this more efficient I guess + + local $/ = "\r\n\r\n"; + my $text = <$fh>; + + $text =~ s/\r?\n/\r\n/g; + $self->_record($count, [$self->_next_index($count),"INPUT","{From $file}"]); + + unless($self->_send_line($text)) + { $self->LastError("Error sending append msg text to IMAP: $!"); + $fh->close; + return undef; + } + $self->_debug("control points to $$control\n") if ref $control; + + $/ = ref $control ? "\n" : $control ? $control : "\n"; + while(defined($text = <$fh>)) + { $text =~ s/\r?\n/\r\n/g; + $self->_record($count, + [ $self->_next_index($count), "INPUT", "{from $file}"]); + + unless($self->_send_line($text,1)) + { $self->LastError("Error sending append msg text to IMAP: $!"); + $fh->close; + return undef; + } + } + + unless($self->_send_line("\r\n")) + { $self->LastError("Error sending append msg text to IMAP: $!"); + $fh->close; + return undef; + } + + # Now for the crucial test: Did the append work or not? + my $uid; + undef $code; + until($code) + { my $output = $self->_read_line or return undef; + foreach my $o (@$output) + { $self->_record($count, $o); + $self->_debug("append_file: Does $o->[DATA] have the code"); + $code = $o->[DATA] =~ m/^\d+\s(NO|BAD|OK)/i ? $1 : undef; + $uid = $o->[DATA] =~ m/UID\s+\d+\s+(\d+)\]/ ? $1 : undef; + + if($o->[DATA] =~ /^\*\s+BYE/) + { $self->State(Unconnected); + $fh->close; + return undef; + } + elsif($o->[DATA]=~ /^\d+\s+(NO|BAD)/i ) + { $fh->close; + return undef; + } + } + } + $fh->close; + + $code ne 'OK' ? undef + : defined $uid ? $uid + : $self; +} + +sub authenticate +{ my ($self, $scheme, $response) = @_; + $scheme ||= $self->Authmechanism; + $response ||= $self->Authcallback; + my $clear = $self->Clear; + $self->Clear($clear) + if $self->Count >= $clear && $clear > 0; + + my $count = $self->Count($self->Count+1); + my $string = "$count AUTHENTICATE $scheme"; + + $self->_record($count, [ $self->_next_index, "INPUT", $string] ); + + unless($self->_send_line($string)) + { $self->LastError("Error sending '$string' to IMAP: $!"); + return undef; + } + + my $code; + until($code) + { my $output = $self->_read_line or return undef; + foreach my $o (@$output) + { $self->_record($count, $o); + $code = $o->[DATA] =~ /^\+\s+(\S+)\s*$/ ? $1 + : $o->[DATA] =~ /^\+\s*$/ ? 'OK' + : undef; + + if($o->[DATA] =~ /^\*\s+BYE/) + { $self->State(Unconnected); + return undef; + } + } + } + + return undef + if $code =~ /^BAD|^NO/; + + if($scheme eq 'CRAM-MD5') + { $response ||= sub + { my ($code, $client) = @_; + use Digest::HMAC_MD5; + my $hmac = Digest::HMAC_MD5::hmac_md5_hex(decode_base64($code), $client->Password); + encode_base64($client->User." ".$hmac, ''); + }; + } + elsif($scheme eq 'DIGEST-MD5') + { $response ||= sub + { my ($code, $client) = @_; + require Authen::SASL; + require Digest::MD5; + + my $authname = $client->Authuser; + defined $authname or $authname = $client->User; + + my $sasl = Authen::SASL->new + ( mechanism => 'DIGEST-MD5' + , callback => + { user => $client->User + , pass => $client->Password + , authname => $authname + } + ); + + # client_new is an empty function for DIGEST-MD5 + my $conn = $sasl->client_new('imap', 'localhost', ''); + my $answer = $conn->client_step(decode_base64 $code); + + encode_base64($answer, '') + if defined $answer; + }; + } + elsif($scheme eq 'PLAIN') # PLAIN SASL + { $response ||= sub + { my ($code, $client) = @_; + encode_base64($client->User . chr(0) . $client->Proxy + . chr(0) . $client->Password, ''); + }; + } + elsif($scheme eq 'NTLM') + { $response ||= sub + { my ($code, $client) = @_; + require Authen::NTLM; + Authen::NTLM::ntlm_user($self->User); + Authen::NTLM::ntlm_password($self->Password); + Authen::NTLM::ntlm(); + }; + } + + unless($self->_send_line($response->($code, $self))) + { $self->LastError("Error sending append msg text to IMAP: $!"); + return undef; + } + + undef $code; + until($code) + { my $output = $self->_read_line or return undef; + foreach my $o (@$output) + { $self->_record($count, $o); + $code = $o->[DATA] =~ /^\+\s+(.*?)\s*$/ ? $1 : undef; + + if($code) + { unless($self->_send_line($response->($code, $self))) + { $self->LastError("Error sending append msg text to IMAP: $!"); + return undef; + } + undef $code; # Clear code; we're still not finished + } + + $code = $1 if $o->[DATA] =~ /^$count\s+(OK|NO|BAD)\b/; + if($o->[DATA] =~ /^\*\s+BYE/) + { $self->State(Unconnected); + return undef; + } + } + } + + + $code eq 'OK' + or return undef; + + Authen::NTLM::ntlm_reset() + if $scheme eq 'NTLM'; + + $self->State(Authenticated); + $self; + +} + +# UIDPLUS response from a copy: [COPYUID (uidvalidity) (origuid) (newuid)] +sub copy +{ my ($self, $target, @msgs) = @_; + + $target = $self->Massage($target); + @msgs = $self->Ranges ? $self->Range(@msgs) + : sort { $a <=> $b } map { ref $_ ? @$_ : split(',',$_) } @msgs; + + my $msgs = $self->Ranges ? $self->Range(@msgs) + : join ',', map {ref $_ ? @$_ : $_} @msgs; + + $self->_imap_uid_command(COPY => $msgs, $target) + or return undef; + + my @results = $self->History; + + my @uids; + foreach (@results) + { chomp; + s/\r$//; + s/^.*\[COPYUID\s+\d+\s+[\d:,]+\s+([\d:,]+)\].*/$1/ or next; + push @uids, /(\d+):(\d+)/ ? ($1 ... $2) : (split /\,/); + + } + @uids ? join(",",@uids) : $self; +} + +sub move +{ my ($self, $target, @msgs) = @_; + + $self->exists($target) + or $self->create($target) && $self->subscribe($target); + + my $uids = $self->copy($target, map {ref $_ eq 'ARRAY' ? @$_ : $_} @msgs) + or return undef; + + $self->delete_message(@msgs) + or carp $self->LastError; + + $uids; +} + +sub set_flag +{ my ($self, $flag, @msgs) = @_; + @msgs = @{$msgs[0]} if ref $msgs[0] eq 'ARRAY'; + $flag = "\\$flag" + if $flag =~ /^(?:Answered|Flagged|Deleted|Seen|Draft)$/i; + + my $which = $self->Ranges ? $self->Range(@msgs) : join(',',@msgs); + $self->store($which, '+FLAGS.SILENT', "($flag)"); +} + +sub see +{ my($self, @msgs) = @_; + @msgs = @{$msgs[0]} if ref $msgs[0] eq 'ARRAY'; + $self->set_flag('\\Seen', @msgs); +} + +sub mark +{ my($self, @msgs) = @_; + @msgs = @{$msgs[0]} if ref $msgs[0] eq 'ARRAY'; + $self->set_flag('\\Flagged', @msgs); +} + +sub unmark +{ my($self, @msgs) = @_; + @msgs = @{$msgs[0]} if ref $msgs[0] eq 'ARRAY'; + $self->unset_flag('\\Flagged', @msgs); +} + +sub unset_flag { + my ($self, $flag, @msgs) = @_; + @msgs = @{$msgs[0]} if ref $msgs[0] eq 'ARRAY'; + + $flag = "\\$flag" + if $flag =~ /^(?:Answered|Flagged|Deleted|Seen|Draft)$/i; + + $self->store( join(",",@msgs), "-FLAGS.SILENT ($flag)" ); +} + +sub deny_seeing +{ my ($self, @msgs) = @_; + @msgs = @{$msgs[0]} if ref $msgs[0] eq 'ARRAY'; + $self->unset_flag('\\Seen', @msgs); +} + +sub size +{ my ($self, $msg) = @_; + my @data = $self->fetch($msg, "(RFC822.SIZE)"); + defined $data[0] or return undef; + + my $size = first { /RFC822\.SIZE/ } @data; + $size =~ /RFC822\.SIZE\s+(\d+)/; + $1; +} + +sub getquotaroot +{ my ($self, $what) = @_; + my $who = $what ? $self->Massage($what) : "INBOX"; + $self->_imap_command("GETQUOTAROOT $who") ? $self->Results : undef; +} + +sub getquota +{ my ($self, $what) = @_; + my $who = $what ? $self->Massage($what) : "user/$self->{User}"; + $self->_imap_command("GETQUOTA $who") ? $self->Results : undef; +} + +# usage: $imap->setquota($folder, storage => 512) +sub setquota(@) +{ my ($self, $what) = (shift, shift); + my $who = $what ? $self->Massage($what) : "user/$self->{User}"; + my @limits; + while(@_) + { my $key = uc shift @_; + push @limits, $key => shift @_; + } + local $" = ' '; + $self->_imap_command("SETQUOTA $who (@limits)") ? $self->Results : undef; +} + +sub quota +{ my $self = shift; + my $what = shift || "INBOX"; + $self->_imap_command("GETQUOTA $what") or $self->getquotaroot($what); + (map { /.*STORAGE\s+\d+\s+(\d+).*\n$/ ? $1 : () } $self->Results)[0]; +} + +sub quota_usage +{ my $self = shift; + my $what = shift || "INBOX"; + $self->_imap_command("GETQUOTA $what") || $self->getquotaroot($what); + ( map { /.*STORAGE\s+(\d+)\s+\d+.*\n$/ ? $1 : () } $self->Results)[0]; +} + +sub Quote($) { $_[0]->Massage($_[1], NonFolderArg) } + +sub Massage($;$) +{ my ($self, $name, $notFolder) = @_; + $name =~ s/^\"(.*)\"$/$1/ unless $notFolder; + + $name =~ /["\\]/ ? "{".length($name)."}\r\n$name" + : $name =~ /[\s{}()]/ ? qq["$name"] + : $name; +} + +sub unseen_count +{ my ($self, $folder) = (shift, shift); + $folder ||= $self->Folder; + $self->status($folder, 'UNSEEN') or return undef; + + my $r = first { s/\*\s+STATUS\s+.*\(UNSEEN\s+(\d+)\s*\)/$1/ } + $self->History; + + $r =~ s/\D//g; + $r; +} + +sub Status { shift->State } +sub IsUnconnected { shift->State == Unconnected } +sub IsConnected { shift->State >= Connected } +sub IsAuthenticated { shift->State >= Authenticated } +sub IsSelected { shift->State == Selected } + +# The following private methods all work on an output line array. +# _data returns the data portion of an output array: +sub _data { ref $_[1] && defined $_[1]->[TYPE] ? $_[1]->[DATA] : undef } + +# _index returns the index portion of an output array: +sub _index { ref $_[1] && defined $_[1]->[TYPE] ? $_[1]->[INDEX] : undef } + +# _type returns the type portion of an output array: +sub _type { ref $_[1] && $_[1]->[TYPE] } + +# _is_literal returns true if this is a literal: +sub _is_literal { ref $_[1] && $_[1]->[TYPE] && $_[1]->[TYPE] eq 'LITERAL' }; + +# _is_output_or_literal returns true if this is an +# output line (or the literal part of one): + +sub _is_output_or_literal { ref $_[1] && defined $_[1]->[TYPE] + && ($_[1]->[TYPE] eq "OUTPUT" || $_[1]->[TYPE] eq "LITERAL") }; + +# _is_output returns true if this is an output line: +sub _is_output { ref $_[1] && $_[1]->[TYPE] && $_[1]->[TYPE] eq "OUTPUT" }; + +# _is_input returns true if this is an input line: +sub _is_input { ref $_[1] && $_[1]->[TYPE] && $_[1]->[TYPE] eq "INPUT" }; + +# _next_index returns next_index for a transaction; may legitimately +# return 0 when successful. +sub _next_index { my $r = $_[0]->_transaction($_[1]); $r } + +sub Range +{ my ($self, $targ) = (shift, shift); + + UNIVERSAL::isa($targ, 'Mail::IMAPClient::MessageSet') + ? $targ->cat(@_) + : Mail::IMAPClient::MessageSet->new($targ, @_); +} + +1; diff --git a/Mail-IMAPClient-3.10/lib/Mail/IMAPClient.pod b/Mail-IMAPClient-3.10/lib/Mail/IMAPClient.pod new file mode 100644 index 0000000..705e7a6 --- /dev/null +++ b/Mail-IMAPClient-3.10/lib/Mail/IMAPClient.pod @@ -0,0 +1,3723 @@ +=head1 NAME + +Mail::IMAPClient - An IMAP Client API + +=head1 DESCRIPTION + +This module provides methods implementing the IMAP protocol. It allows +perl scripts to interact with IMAP message stores. + +The module is used by constructing or instantiating a new IMAPClient +object via the L constructor method. Once the object has been +instantiated, the L method is either implicitly or explicitly +called. At that point methods are available that implement the IMAP +client commands as specified in I. When processing is +complete, the I object method should be called. + +This documentation is not meant to be a replacement for RFC2060, and +the wily programmer will have a copy of that document handy when coding +IMAP clients. + +Note that this documentation uses the term I in place of +RFC2060's use of I. This documentation reserves the use of the +term I to refer to the set of folders owned by a specific IMAP +id. + +RFC2060 defines four possible states for an IMAP connection: not +authenticated, authenticated, selected, and logged out. These +correspond to the B constants C, +C, C, and C, respectively. These +constants are implemented as class methods, and can be used in +conjunction with the L method to determine the status of an +B object and its underlying IMAP session. Note that an +B object can be in the C state both before a +server connection is made and after it has ended. This differs slightly +from RFC2060, which does not define a pre-connection status. For a +discussion of the methods available for examining the B +object's status, see the section labeled L<"Status Methods">, below. + +=head2 Advanced Authentication Mechanisms + +RFC2060 defines two commands for authenticating to an IMAP server: +LOGIN for plain text authentication and AUTHENTICATE for more secure +authentication mechanisms. Currently Mail::IMAPClient supports +DIGEST-MD5, CRAM-MD5, LOGIN, PLAIN (SASL), and NTLM authentication. + +There are also a number of methods and parameters that you can use to +build your own authentication mechanism. Since this topic is a source of +many questions, I will provide a quick overview here. All of the methods +and parameters discussed here are described in more detail elsewhere in +this document; this section is meant to help you get started. + +First of all, if you just want to do plain text authentication and +your server is okay with that idea then you don't even need to read +this section. + +Second of all, the intent of this section is to help you implement the +authentication mechanism of your choice, but you will have to understand +how that mechanism works. There are I of authentication mechanisms +and most of them are not available to me to test with for one reason or +another. Even if this section does not answer all of your authentication +questions it I contain all the answers that I have, which I admit +are scant. + +Third of all, if you manage to get any advanced authentication mechanisms +to work then please consider donating them to this module. I don't quite +have a framework visualized for how different authentication mechanisms +could "plug in" to this module but I would like to eventually see this +module distributed with a number of helper modules to implement various +authentication schemes. + +The B's support for add-on authentication mechanisms is +pretty straight forward and is built upon several assumptions. Basically +you create a callback to be used to provide the response to the server's +challenge. The I parameter contains a reference to the +callback, which can be an anonymous subroutine or a named subroutine. +Then, you identify your authentication mechanism, either via the +I parameter or as an argument to L. + +You may also need to provide a subroutine to encrypt (or whatever) data +before it is sent to the server. The I parameter must +contain a reference to this subroutine. And, you will need to decrypt +data from the server; a reference to the subroutine that does this must +be stored in the I parameter. + +This framework is based on the assumptions that a) the mechanism you are +using requires a challenge-response exchange, and b) the mechanism does +not fundamentally alter the exchange between client and server but merely +wraps the exchange in a layer of encryption. It particularly assumes +that the line-oriented nature of the IMAP conversation is preserved; +authentication mechanisms that break up messages into blocks of a +predetermined size may still be possible but will certainly be more +difficult to implement. + +Alternatively, if you have access to B, a utility included in +the Cyrus IMAP distribution, you can use that utility to broker your +communications with the IMAP server. This is quite easy to implement. An +example, L, can be found in the C +subdirectory of the source distribution. + +The following list summarizes the methods and parameters that you may +find useful in implementing advanced autentication: + +=over 4 + +=item authenticate method + +This method implements the AUTHENTICATE IMAP client command as documented +in RFC2060. If you have set the I parameter then the +L method will call L instead of doing a clear text +login, which is its normal behavior. If you don't want B to call +B on your behalf then you can call it yourself. Instead +of setting an I you can just pass the authmechanism as +the first argument to AUTHENTICATE. + +=item Socket and RawSocket Parameters + +Both parameters hold a reference to the socket connection. Normally this +is set for you by the L method, but if you are implementing +an advanced authentication technique you may choose to set up your own +socket connection and then set this parameter manually, bypassing the +B method completely. This is also useful if you want to use +L alternatives, like L. + +The I parameter simply records the socket to use for future +operations, without attempting any interaction on it. In this case, you +have to be sure to handle all the preliminar operations and to manually +set the B object in sync with its actual status with +respect to this socket (see below for additional parameters regarding +this, especially the I parameter). + +The I parameter, instead, also attempts to carry on preliminar +phases if the conditions apply. If both parameters are present, this +takes the precedence over I. It is primarily used to +provide an alternative socket for communications, e.g. to use +L instead of L used by L +by default. + +B +As of version 2.99_04 of this module, the I parameter has +changed semantics to make it more "DWIM". The I parameter was +introduced as a replacement for the I parameter in older version. + +=item State, Server, Proxy, Password, and User Parameters + +If you need to make your own connection to the server and perform your +authentication manually, then you can set these parameters to keep your +B object in sync with its actual status. Of these, +only the I parameter is always necessary. The others need to be +set only if you think your program will need them later. + +I is required for PLAIN (SASL) authentication. + +=item Authmechanism + +Set this to the value that AUTHENTICATE should send to the server as the +authentication mechanism. If you are brokering your own authentication +then this parameter may be less useful. It is also not needed by the +L method. It exists solely so that you can set it when +you call L to instantiate your object. The B method will +call L, who will call L. If B sees that you've +set an I then it will call B, using your +I and I parameters as arguments. + +=item Authuser + +Normally you authenticate and log in with the username specified in +the User parameter. When you are using DIGEST-MD5 as I, +you can optionally specify a different username for the final log in. +This can be useful to mark messages as seen for the I +if you don't know the password of the user as the seen state +is often a per-user state. + +=item Authcallback + +The I parameter, if set, should contain a pointer +to a subroutine. The L method will use this as the callback +argument to the B method if the I and +I parameters are both set. If you set I +but not I then the default callback for your mechanism will +be used. All supported authentication mechanisms have a default callback; +in every other case not supplying the callback results in an error. + +Most advanced authentication mechanisms require a challenge-response +exchange. After the L method sends " AUTHENTICATE +\r\n" to the IMAP server, the server replies with +a challenge. The B method then invokes the code whose +reference is stored in the I parameter as follows: + + $Authcallback->($challenge,$imap) + +where C<$Authcallback> is the code reference stored in the I +parameter, C<$challenge> is the challenge received from the IMAP server, +and C<$imap> is a pointer to the B object. The return +value from the I routine should be the response to the +challenge, and that return value will be sent by the L +method to the server. + +=item Readmethod + +The I parameter points to a routine that will read data from +the socket connection. This read method will replace the B that +would otherwise be performed by B. The replacement +method is called with five arguments. The first is a pointer to the +B object; the rest are the four arguments required by +the B function. Note the third argument (which corresponds to +the second argument to B) is a buffer to read into; this will +be a pointer to a scalar. So for example if your I were +just going to replace B without any intervening processing +(which would be silly but this is just an example after all) then you +would set your I like this: + + $imap->Readmethod( + sub { + my($self) = shift; + my($handle,$buffer,$count,$offset) = @_; + return sysread( $handle, $$buffer, $count, $offset); + } + ); + +Note particularly the double dollar signs in C<$$buffer> in the B +call; this is not a typo! + +=item Prewritemethod + +The I, if defined, should contain a pointer to a +subroutine. It is called immediately prior to writing to the socket +connection. It is called by B with two arguments: +a reference to the B object and the ASCII text +string to be written. It should return another string that will be +the actual string sent to the IMAP server. The idea here is that your +I will do whatever encryption is necessary and then +return the result to the caller so it in turn can be sent to the server. + +=item Ignoresizeerrors + +Certain (caching) servers, like Exchange 2007, often report the wrong +message size. Instead of chopping the message into a size that it +fits the specified size, the reported size will be simply ignored +when this parameter is set to C<1>. + +=item Supportedflags + +Especially when C is used, the receiving peer may need to +be configured explicitly with the list of supported flags; that may +be different from the source IMAP server. + +The names are to be specified as an ARRAY. Black-slashes and casing +will be ignored. + +You may also specify a CODE reference, which will be called for each of +the flags seperately. In this case, the flags are not (yet) normalized. +The returned lists of the CODE calls are shape the resulting flag list. + +=back + +=head2 Errors + +If you attempt an operation that results in an error, then you can +retrieve the text of the error message by using the L +method. However, since the L method is an object method (and +not a class method) you will only be able to use this method if you've +successfully created your object. Errors in the L method can +prevent your object from ever being created. Additionally, if you +supply the I, I, and I parameters to L, it +will attempt to call B and B, either of which could +fail and cause your L method call to return C (in which case +your object will have been created but its reference will have been +discarded before ever having been returned to you). + +If this happens to you, you can always check C<$@>. B +will populate that variable with something useful if either of the +L, L, or L methods fail. In fact, as of version 2, +the C<$@> variable will always contain error info from the last error, +so you can print that instead of calling L if you wish. + +If you run your script with warnings turned on (which I'm sure you'll +do at some point because it's such a good idea) then any error message +that gets placed into the L slot (and/or in C<$@>) will +automatically generate a warning. + +=head2 Transactions + +RFC2060 requires that each line in an IMAP conversation be prefixed +with a tag. A typical conversation consists of the client issuing a +tag-prefixed command string, and the server replying with one of more +lines of output. Those lines of output will include a command +completion status code prefixed by the same tag as the original command +string. + +The B module uses a simple counter to ensure that each +client command is issued with a unique tag value. This tag value is +referred to by the B module as the transaction number. A +history is maintained by the B object documenting each +transaction. The L method returns the number of the last +transaction, and can be used to retrieve lines of text from the +object's history. + +The L parameter is used to control the size of the session +history so that long-running sessions do not eat up unreasonable +amounts of memory. See the discussion of L under L<"Parameters"> +for more information. + +The L transaction returns the history of the entire IMAP +session since the initial connection or for the last I +transactions. This provides a record of the entire conversation, +including client command strings and server responses, and is a +wonderful debugging tool as well as a useful source of raw data for +custom parsing. + +=head1 CLASS METHODS + +There are a couple of methods that can be invoked as class methods. +Generally they can be invoked as an object method as well, as a +convenience to the programmer. (That is, as a convenience to the +programmer who wrote this module, as well as the programmers using it. +It's easier I to enforce a class method's classiness.) Note that +if the L method is called as an object method, the object returned +is identical to what have would been returned if L had been called +as a class method. It doesn't give you a copy of the original object or +anything like that. + +=head2 new + +Example: + + Mail::IMAPClient->new(%args) or die "Could not new: $@\n"; + +The L method creates a new instance of an B object. If +the I parameter is passed as an argument to B, then B +will implicitly call the L method, placing the new object in +the I state. If I and I values are also +provided, then L will in turn call L, and the resulting +object will be returned from B in the I state. + +If the I parameter is not supplied then the B +object is created in the I state. + +If the B method is passed arguments then those arguments will be +treated as a list of key=>value pairs. The key should be one of the +parameters as documented under L<"Parameters">, below. + +Here are some examples: + + use Mail::IMAPClient; + + # returns an unconnected Mail::IMAPClient object: + my $imap = Mail::IMAPClient->new; + # ... + # intervening code using the 1st object, then: + # (returns a new, authenticated Mail::IMAPClient object) + $imap = Mail::IMAPClient->new( + Server => $host, + User => $id, + Password=> $pass, + Clear => 5, # Unnecessary since '5' is the default + # ... # Other key=>value pairs go here + ) or die "Cannot connect to $host as $id: $@"; + +See also L<"Parameters">, below, and L<"connect"> and L<"login"> for +information on how to manually connect and login after B. + + +=head2 Authenticated + +Example: + + $Authenticated = $imap->Authenticated(); + # or: + $imap->Authenticated($new_value); # But you'll probably never need to do this + +returns a value equal to the numerical value associated with an object +in the B state. This value is normally maintained by the +B module, so you typically will only query it and +won't need to set it. + +B For a more programmer-friendly idiom, see the L, +L, L, and L object methods. You +will usually want to use those methods instead of one of the above. + +=head2 Connected + +Example: + + $Connected = $imap->Connected(); + # or: + $imap->Connected($new_value); # But you'll probably never need to do this + +returns a value equal to the numerical value associated with an object +in the B state. This value is normally maintained by the +B module, so you typically will only query it and +won't need to set it. + +B For a more programmer-friendly idiom, see the L, +L, L, and L object methods. You +will usually want to use those methods instead of one of the above. + +=head2 Quote + +Example: + + $imap->search(HEADER => 'Message-id' => $imap->Quote($msg_id)); + +The B method accepts a value as an argument. It returns its +argument as a correctly quoted string or a literal string. + +Note that you should not use this on folder names, since methods that accept +folder names as an argument will quote the folder name arguments appropriately +for you. (Exceptions to this rule are methods that come with IMAP extensions +that are not explicitly supported by B.) + +If you are getting unexpected results when running methods with values that +have (or might have) embedded spaces, double quotes, braces, or parentheses, +then you may wish to call B to quote these values. You should B +use this method with foldernames or with arguments that are wrapped in quotes +or parens if those quotes or parens are there because the RFC2060 spec requires +them. So, for example, if RFC requires an argument in this format: + + ( argument ) + +and your argument is (or might be) "pennies (from heaven)", then you could just +use: + + $argument = "(" . $imap->Quote($argument) . ")" + +and be done with it. + +Of course, the fact that sometimes these characters are sometimes required +delimiters is precisely the reason you must quote them when they are I +delimiting. For example: + + + $imap->Search('SUBJECT',"(no subject)"); + # WRONG! Sends this to imap server: + # Search SUBJECT (no subject)\r\n + + $imap->Search('SUBJECT',$imap->Quote("(no subject)")); + # Correct! Sends this to imap server: + # Search SUBJECT "(no subject)"\r\n + + +On the other hand: + + $imap->store('+FLAGS',$imap->Quote("(\Deleted)")); + # WRONG! Sends this to imap server: + # [UID] STORE +FLAGS "(\Deleted)"\r\n + + + $imap->store($imap->Quota('+FLAGS'),"(\Deleted)"); + # CORRECT! Sends this to imap server: + # [UID] STORE +FLAGS (\Deleted)\r\n + +In the above, I had to abandon the many methods available to +B programmers (such as L and all-lowercase +L) for the sake of coming up with an example. However, there are +times when unexpected values in certain places will force you to B. +An example is RFC822 Message-id's, which I don't contain quotes or +parens. So you don't worry about it, until suddenly searches for certain +message-id's fail for no apparent reason. (A failed search is not simply a +search that returns no hits; it's a search that flat out didn't happen.) +This normally happens to me at about 5:00 pm on the one day when I was hoping +to leave on time. (By the way, my experience is that any character that can +possibly find its way into a Message-Id eventually will, so when dealing +with these values take proactive, defensive measures from the very start. +In fact, as I was typing the above, a buddy of mine came in to ask advice about +a logfile parsing routine he was writing in which the fields were delimited +by colons. One of the fields was a Message Id, and, you guessed it, some of the +message id's in the log had (unescaped!) colons embedded in them and were +screwing up his C. So there you have it, it's not just me. This is +everyone's problem.) + +=head2 Range + +Example: + + my %parsed = $imap->parse_headers( + $imap->Range($imap->messages), + "Date", + "Subject" + ); + +The B method will condense a list of message sequence numbers or +message UID's into the most compact format supported by RFC2060. It accepts +one or more arguments, each of which can be: + +=over 8 + +=item a) a message number, + +=item b) a comma-separated list of message numbers, + +=item c) a colon-separated range of message numbers (i.e. "$begin:$end") + +=item d) a combination of messages and message ranges, separated by commas +(i.e. 1,3,5:8,10), or + +=item e) a reference to an array whose elements are like I through I. + +=back + +The B method returns a reference to a B +object. The object has all kinds of magic properties, one of which being that +if you treat it as if it were just a string it will act like it's just a +string. This means you can ignore its objectivity and just treat it like a +string whose value is your message set expressed in compact format. + +You may want to use this method if you find that fetch operations on large +message sets seem to take a really long time, or if your server rejects +these requests with the claim that the input line is too long. You may also +want to use this if you need to add or remove messages to your message set +and want an easy way to manage this. + +For more information on the capabilities of the returned object reference, +see L. + +=head2 Rfc2060_date + +Example: + + $Rfc2060_date = $imap->Rfc2060_date($seconds); + # or: + $Rfc2060_date = Mail::IMAPClient->Rfc2060_date($seconds); + +The B method accepts one input argument, a number of +seconds since the epoch date. It returns an RFC2060 compliant date +string for that date (as required in date-related arguments to SEARCH, +such as "since", "before", etc.). + +=head2 Rfc2060_datetime + +Example: + + $date = $imap->Rfc2060_datetime($seconds); + # or: + $date = Mail::IMAPClient->Rfc2060_datetime($seconds); + +The B method accepts one or two arguments: a obligatory +timestamp and an optional zone. The zone shall be formatted as +C<< [+-]\d{4} >>, and defaults to C<< +0000 >>. The timestamp follows the +definition of the output of the platforms specific C