From: Thomas Roessler Date: Mon, 8 Jun 1998 09:16:03 +0000 (+0000) Subject: Initial revision X-Git-Tag: muttintl-0-92-8i~1 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=1a5381e07e97fe482c2b3a7c75f99938f0b105d4;p=mutt Initial revision --- 1a5381e07e97fe482c2b3a7c75f99938f0b105d4 diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..31425363 --- /dev/null +++ b/COPYING @@ -0,0 +1,410 @@ +Most of this package's source code is distributed under +the GNU Public license and copyright (c) 1996, 1997 by +Michael Elkins. pgppubring.c is copyright(c) 1997 by +Thomas Roessler and also distributed under the GPL. The +SHA1 implementation has been taken from Eric A. Young's +SSLeay package; a different copyright notice applies; see +below. + + + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU 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. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), 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 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 show them these terms so they know 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. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + 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 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 derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 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 License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +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. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary 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 + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 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 Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing 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 for copying, distributing or modifying +the Program or works based on it. + + 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. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. 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 this 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 +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. 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 + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +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. + + 12. 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 the public, 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision 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 is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + + +The following copyright notice applies to the SHA1 +implementation: + + + Copyright (C) 1995-1997 Eric Young (eay@cryptsoft.com) + All rights reserved. + + This package is an SSL implementation written + by Eric Young (eay@cryptsoft.com). + The implementation was written so as to conform with Netscapes SSL. + + This library is free for commercial and non-commercial use as long as + the following conditions are aheared to. The following conditions + apply to all code found in this distribution, be it the RC4, RSA, + lhash, DES, etc., code; not just the SSL code. The SSL documentation + included with this distribution is covered by the same copyright terms + except that the holder is Tim Hudson (tjh@cryptsoft.com). + + Copyright remains Eric Young's, and as such any Copyright notices in + the code are not to be removed. + If this package is used in a product, Eric Young should be given attribution + as the author of the parts of the library used. + This can be in the form of a textual message at program startup or + in documentation (online or textual) provided with the package. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + "This product includes cryptographic software written by + Eric Young (eay@cryptsoft.com)" + The word 'cryptographic' can be left out if the rouines from the library + being used are not cryptographic related :-). + 4. If you include any Windows specific code (or a derivative thereof) from + the apps directory (application code) you must include an acknowledgement: + "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + + THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + The licence and distribution terms for any publically available version or + derivative of this code cannot be changed. i.e. this code cannot simply be + copied and put under another distribution licence + [including the GNU Public Licence.] diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 00000000..d45c12fc --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,16 @@ +Copyright (C) 1996-8 Michael R. Elkins + + 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 00000000..822fafc7 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,1709 @@ +Changes since 0.88 +------------------ + +- [patch-0.88.me.buffy_zero.1] 0 length mbox and MMDF mailboxes should never + be marked as having new mail in them + +- [patch-0.88.me.enter_string.1] rewrote ci_enter_string() to remove + duplicated redraw code, and renamed to mutt_enter_string() + +- [patch-0.88.ld.ctx_changed.1] fixed problem with setting the changed flag + for the mailbox when recovering from an externally modified mailbox + +- [patch-0.88.pk.signed.1] fixed comparison between short and unsigned short + in the pager + +- [patch-0.88.me.set_no.1] makes "unset no" equivalent to "set " + +- [patch-0.88.me.state_flag] removes the OPTVERIFYSIG pseudo-option and + replaces it with a "flags" member in the STATE structure + +- [patch-0.88.me.short] use 'int' instead of 'unsigned short' for message + numbers to allow mutt to read mailboxes larger than 65535 messages + +- [patch-0.88.me.hold.1] removed the $hold variable. the same functionality + is provided by "set nomove" + +- [patch-0.88.me.buffy_notify.1] move the notification of new mail out of + mutt_buffy_check() into a separate routine + +- [patch-0.88.me.empty_fields.1] skip processing of empty header fields in + _mutt_read_rfc822_header() + +- [patch-0.88.me.var_table.1] use the .data field instead of .bit for + storing the option for DT_BOOL and DT_QUAD vars + +- [patch-0.88.me.rx_not.1] regexps in the *-hook commands can now optionally + be prefixed with a "!" (exclamation point) to indicate that the hook + should be executed when the target does NOT match the following pattern + +- [patch-0.88.me.sig.2] removes the $local_site, $remote_sig and $local_sig + variables. the same functionality is provided by using a send-hook to set + the $signature variable: + send-hook . set signature=~/.sig-local + send-hook !hmc\\.edu set signature=~/.sig-remote + +- [patch-0.88.me.from.1] honor $reverse_name even when $use_from is unset + +- [patch-0.88.bj.status_color.1] removed hack needed to reset the background + color before the buffy_notify patch + +- [patch-0.88.me.parse_param.2] whitespace after a MIME content-type + parameter was not stripped (eg. "charset =us-ascii" would result in the + parameter "charset " in the structure) + +Changes since 0.88.1 +-------------------- + +- split up mutt.h into several smaller files with groups of related data + in order to reduce the dependencies on mutt.h + +- removed the $point_new variable + +- the .not member of the REGEXP struct was not initialized for the *-hook + commands, thus causing them not to work properly + +- removed bogus ! handling code in the DT_RX variables + +- [patch-0.88.ld.magic.1] fix to return type MBOX for the default magic + for zero length files if $mbox_type is something other than "mbox" or + "mmdf" + +Changes since 0.88.2 +-------------------- + +- [patch-0.88.2.kd.buffy_pop.2] support for --enable-buffy-size and + --enable-pop were broken after the reorganization of mutt.h + +- [patch-0.88.2.me.ignore_rx.1] removes the ignore and unignore commands and + replaces them with the $ignore and $unignore variables, which are regular + expressions used for matching lines to weed/show + +- [patch-0.88.2.me.followup.1] adds support for the Mail-Reply-To: and + Mail-Followup-To: header fields + +- [patch-0.88.me.hdrline.1] fixed segfault with %t in $hdr_format if neither + the to: or cc: field exists + +- [patch-0.88.me.abort_subject.1] print error message if + $abort_nosubject=yes and the user deletes the default subject and tries to + send instead of just silently ignoring the send + +- [patch-0.88.me.long_re.1] increased the max length for regexp's in + send-hook's to be 256 chars + +- [patch-0.88.bj.isprint.1] treat chars >= 0xa0 as printable + +- [patch-0.88.me.half_page.1] adds the half-up and half-down functions to + the index and generic key maps. scrolls the page 1/2 screen up or down + +- [patch-0.88.me.useraddr.1] $alternates would not be consulted when the + address to be matches was not fully qualified + +- [patch-0.88.me.from_quote.1] add support for messages separators in mbox + mailboxes where the mailbox part of the return-path is a quoted-string + eg: From "michael elkins"@cs.hmc.edu Sat Nov 22 15:55:07 PST 1997 + +- [patch-0.88.me.mx.1] moved code for adding the From_ line to new messages + in mbox/mmdf mailboxes to mx_open_new_message(), and removes the + postponed_mail() func from send.c in favor of mutt_num_postponed() + +- [patch-0.88.2.me.post_err.1] abort exiting the compose menu if there was a + problem writing the message to the $postponed mailbox + +- [patch-0.88.2.me.find_lists.1] if the to: list was missing, the cc: list + was not searched for mailing lists when doing a list-reply + +- [patch-0.88.2.me.buffy_notify.1] makes mutt keep track of the number of + mailboxes for which the user has not been notified of new mail so that + mutt_buffy_notify() can exit immediately without traversing the mailbox + list if there are no pending notifications + +- [patch-0.88.2.bugfix.mtsirkin.buffy_size.1] fixed some bugs with the + buffy_size pacth + +Changes since 0.88.3 +-------------------- + +- undid previous patch-0.88.2.me.ignore_rx.1 which changed `ignore' and + `unignore' to regexp vars instead of commands + +- [patch-0.88.3.me.attach_file.2] with $edit_hdrs set, files can be attached + to the message by placing `Attach: ' in the message header while + editing the message + +- [patch-0.88.3.me.find_lists.1] fixed bug in the list-reply function + +- [patch-0.88.3.me.followup.1] the mail-followup-to: field should be set for + all types of messages, not just group-reply + +- [patch-0.88.3.pk.fccdate.1] the date in fcc:'s to mbox/mmdf mailboxes was + incorrect + +- [patch-0.88.3.bugfix.mtsirkin.buffy_size.1] fixed some more bugs in the + buffy_size handling + +- [patch-0.88.3.me.menu.1] adds the $menu_scroll which causes mutt not to do + an implicit next-page when doing a next-message on the last message + displayed on the screen, and adds the current-top, current-middle, + current-bottom functions to reposition the current message at the top, + middle and bottom of the screen, respectively + +- [patch-0.88.3.me.buffy.1] avoid the double-stat() in mutt_buffy_check() by + saving the magic in the BUFFY struct + +- [patch-0.88.3.me.buffy_config.1] the interval that mutt will check for new + mail in the `mailboxes' list is now configurable via the $mail_check + variable (default: 3 seconds) + +- [patch-0.88.3.me.hook.1] fixed the !-handling in the *-hook commands to + allow specification of ! (shortcut for $spoolfile) in folder-hook by + placing quotes around it (eg., folder-hook '!' set signature=~/.signature) + +Changes since 0.88.4 +-------------------- + +- changed default value of $move to `no' + +- [patch-0.88.4.me.from.1] fixed problem with duplicate From_ lines when + saving from mbox/mmdf mailboxes + +- [patch-0.88.4.thread.1] modified threading algorithm such that the entire + mailbox does not need to be rethreaded when new mail arrives or when + switching among the different sorting methods + +- [patch-0.88.4.de.buffy_stuff.1] removed code for testing newly created + mailboxes based upon the magic type + +- [patch-0.884.de.buffy_new_folder.1] adds support for detecting created + buffy mailboxes + +- [patch-0.88.4.speedup.mtsirkin.buffy_size.1] speeds up startup time with + the --enable-buffy-size configure option + +Changes since 0.88.5 +-------------------- + +- [patch-0.88.5.bugfix.mtsirkin.buffy_size.1] fix bug in buffy_size + +- [patch-0.88.5.de.pop_var.1] remove unneeded variable + +- [patch-0.88.5.me.thread.2] fix segfault on sync + +- [patch-0.88.5.me.rx.1] a path may now be specified with --with-rx + +- [patch-0.88.5.de.aliasnull.1] fixed segfault when creating an alias while + in limited view + +- [patch-0.88.5.me.hook.1] fixed a bug which caused *-hook's with the same + regexp to be ignored + +- [patch-0.88.5.me.followup.1] the Mail-Followup-To: field was not rfc2047 + encoded + +- [patch-0.88.5.me.menu.2] fixed some miscellaneous redraw problems related + to the menu movement commands + +- [patch-0.88.4.me.str_sub.2] moves handling of backquotes (``) in muttrc + commands to mutt_extract_token() so that they are only evaluated in + appropriate places + +- [patch-0.88.5.me.generic_menu.2] created the `generic' keymap which the + other menus (except for pager and editor) default to if a key is not found + in the menu specific keymap + +- [patch-0.88.5.nit.mtsirkin.buffy_time.1] disable the utime() call when + BUFFY_SIZE is enabled + +- [patch-0.88.5.me.mx_cleanup.1] separates append code out of + mx_open_mailbox() into mx_open_mailbox_append() for better code + readability. + +- [patch-0.88.5.me.pattern.2] rewrite of searching/limiting code to + pre-compile the search pattern + +Changes since 0.88.6 +-------------------- + +- [patch-0.88.6.bigfix.init.mtsirkin.1] fixed variable type problem + +- [patch-0.88.6.jf.followup.1] fix off by 1 error in parsing of the + mail-followup-to: field + +- [patch-0.88.6.me.backtic.2] backtics were not handled in the alias and + my_hdr commands + +- [patch-0.88.6.me.bindings.1] added missing J and K bindings for the index + menu + +- [patch-0.88.6.me.pattern.2] fixed bugs in the search pattern compiler + +- [patch-0.88.6.de.sortbug.1] fixed coredump when resorting when new mail + arrives in an empty box + +- [patch-0.88.6.de.in_reply_to.1] increased length of buffer used to store + the message-id from the in-reply-to: header field + +- [patch-0.88.6.bl.doc_nits.1] updated docs on the alias expansion + +- [patch-0.88.6.de.remove_search_body.1] removes the search-body function + from the index menu now that the same feature is supported with the + rewritten search functions + +- [patch-0.88.6.de.search-body-doc.1] removes documentation references for + the search-body command + +- [patch-0.88.6.nit.pattern.mtsirkin.1] fixed compiler warning about const + being ignored + +- [patch-0.88.6.me.help_unbound.1] unbound functions are now displayed in + the help menus + +- [patch-0.88.6.me.received.1] if no From_ line is found, parse the first + Received: header field to get the local delivery date instead of using the + file modification time + +- [patch-0.88.6.kd.half_up.1] fixed bug in half-up function + +- fixed the eat_regexp() function to use _mutt_extract_token() so that + things like \n and \t get expanded + +- extended _mutt_extract_token() to be able to be used in eat_regexp() + by adding the option not to reap quotes, and to recognize the logic + operators for the pattern matching + +Changes since 0.88.7 +-------------------- + +- changed handling of \ to not be interpreted inside of single quotes + +- runs of quotes in muttrc commands are now supported, so that + the following are equivalent: + michael\ elkins + michael' 'elkins + "michael"' 'elkins + +- fixed backtic handling so that the output is considered as separate tokens + unless, so that you may do: mailboxes `echo ~/Mail/Lists/*` + +- "ignore from_" is no longer supported, you should use + ignore "From " + +- created new menu function menu_check_recenter() in menu.c which is + responsible for checking if the current message has moved off the current + displayed range and recenters if needed + +- removed support for old-style Mutt postponed mailboxes which used the + X-Mutt-Attachment: header field + +- [patch-0.88.7.me.alias.1] fix segfault on recursive aliases + +- [patch-0.88.7.me.attach_desc.1] allow optional specification of the + attachment description after the filename in the 'Attach:' header field + with $edit_hdrs + +- [patch-0.88.7.me.help.1] fixed display of unbound functions in help to + crossref between the generic and menu specific keymaps so that functions + which are bound do not show up as unbound + +- [patch-0.88.7.me.pager_search.1] regexps should be compiled with + REG_NEWLINE + +- [patch-0.88.7.me.rx.1] use macro REGCOMP() instead of directly calling + regcomp() + +- [patch-0.88.7.me.parse.1] received: parsing did not work, and copying a + message to =postponed and recalling it resulted in some duplicate fields + in the header + +- [patch-0.88.6.kd.help_length.1] shortened descriptions of several help + strings + +- [patch-0.88.7.de.pattern.1] fixed off by one errors in selecting ranges of + messages + +- [patch-0.88.7.kd.half_down.1] fixed half-down op to mimic the next-page op + where if there is no next page, the pointer is moved to the last entry in + the menu instead of printing an error + +- added a sleep() call after the info about the state of the mailbox after a + sync operation so that the user has time to read it before the message + about sorting pops up + +Changes since 0.88.8 +-------------------- + +- [patch-0.88.8.me.no_url.1, patch-0.88.8.no_url.1-2] the url menu and all + referecnes to it has been removed. equivalent functionality can be + obtained by using the external program + ftp://ftp.cs.hmc.edu/pub/me/urlview-0.3.tar.gz and the following macro: + macro index \cb |urlview\n + +- [patch-0.88.8.me.refresh.1] patch to avoid calls to refresh() in the + middle of executing macros + +- [patch-0.88.8.me.nested_quote.1] fixed problem with nested quotes in + muttrc commands + +- [patch-0.88.8.me.tag_thread.1] adds the tag-thread (ESC-t) function + +- [patch-0.88.me.reply_self.1] change handling of $metoo so that it will + leave the user's address on the to: line if it is the only address in the + list + +- [patch-0.88.me.decode_copy.1] removes the $forw_decode, $decode_format + variables, the decode-save and decode-copy functions, and rewrite of the + attachment handling functions to be much simpler + +- [patch-0.88.8.me.thread.1] fixed segfault on thread resort when new mail + arrives is a message in the mailbox has no message-id: field + +- mutt would dump core if given a bad date for the ~d search pattern + +- [patch-0.88.8i.de.fast_forward.1] skip prompt for subject when forwarding + if $fast_reply is set + +- fixed compilation error on systems without support for color related to + the color quoting support (SunOS 4.1.x) + +- [patch-0.88.8-bugfix.buffy_size.mtsirkin.2] fixed a bug in buffy_size + support + +- [patch-0.88.8.kd.help_length.1] fix to truncate macro descriptions in the + help menu at the screen edge, and updated docs with new short function + descriptions + +- increased size of buffer used to store regepxs for the *-hook commands + from SHORT_STRING to STRING + +Changes since 0.88.9 +-------------------- + +- [patch-0.88.8-bugfix.buffy_size.mtsirkin.1] missed patch from previous + buffy_size fix + +- [patch-0.89.9.bl.fcc_fix.1] body of message was not written out for fcc + and postponement + +- [patch-0.88.9i.me.copy.1] created unified functions to copying/appending + messages. adds $forw_decode, $forw_quote, decode-save and decode-copy + +- [patch-0.88.9.me.refresh.1] use mutt_refresh() instead of refresh() in + mutt_enter_string() + +- with $edit_hdrs, if you delete the in-reply-to: field Mutt will + automatically remove the References: field as well + +- mutt_query_exit() should call timeout(-1) so the prompt doesn't abort. + +- [patch-0.88.9.me.fcc.1] removed the fcc-hook command + +- the return value of mutt_send_message() was not checked so if sending mail + failed for some reason (like a full $tmpdir or error invoking $sendmail), + Mutt would incorrectly report that the mail had been sent + +- [patch-0.88.9.me.pseudo_thread.1] fixed problem with rethreading messages + with no real reference when new mail arrives + +- [patch-0.88.9i.kd.first_entry.1] fixed segfault on first-entry function if + there are no entries in the menu + +- added a section to the doc describing the features of $edit_hdrs (fcc:, + attach: and references-magic) + +- [patch-0.88.bj.browser.1] fixed some problems with the file browser + +Changes since 0.88.10 +--------------------- + +- return is now bound to `tag-entry' for the alias menu + +- configure --with-homespool is now equivalent to --with-homespool=mailbox + +- [patch-0.88.10.me.invoke_sendmail.1] remove check for return value of + invoke_sendmail() since mutt_system() always returns -1 on some systems + +- [patch-0.88.10.me.push_refresh.1] fixed bug with mailbox parsing status + not being displayed if the user has a `push' command in their muttrc or in + a folder-hook + +- [patch-0.88.10.de.thread-commands.1] cleans up the subthread operations + +- [patch-0.88.10i.de.sort_case.1] sorting by subject/author should be case + insensitive + +- [patch-0.88.10.me.pipe_attach.2] piping attachments did not work + +- [patch-0.88.10.kd.doc_nit.1] fixed doc bug wrt $forw_quote + +- manual.html readded to the `all' target in doc/makefile + +Changes since 0.88.11 +--------------------- + +- changed default binding of pipe-entry to `|' in the compose menu + +- changed default binding of filter-entry to `F' in the compose menu + +- auto_view was not used when decoding messages with $pipe_decode set + +- piping messages did not use the new mutt_copy_message() function + +- use AC_REPLACE_FUNCS for check of strcasecmp() instead of AC_CHECK_FUNCS + +- [patch-0.88.11.me.copy_header.1] fixed problem when message header is + terminated by CRLF instead of just LF + +- [patch-0.88.11.de.reverse_name.1] $reverse_name should override a default + `my_hdr' command + +Changes since 0.88.12 +--------------------- + +- [patch-0.88.12.me.broswer.1] fixed bug in status line for generic menu + code, and added entry numbers so that `jump' can be used effectively in + the file browser + +- [patch-0.88.12.me.attach_reply.1] fixed segfault when replying to a single + message/rfc822 attachment + +- [patch-0.88.12.kd.alias_redraw.1] fixed redraw bug when exiting the alias + menu + +- [patch-0.88.12.kd.help_wrap.2] fixed display of long macros in the help + display + +- [patch-0.88.12.kd.resort.1] cause a resort on ':set sort=xxx' + +- fixed bug where it was impossible to search for strings with any of + !, ~ or | in them + +- removed prototype for mutt_new_address() in rfc822.h since it is a macro + +- added doc blurb for $forw_decode + +- optimized loop for marking messages as `old' by checking if OPTMARKOLD is + set before entering the loop + +- $timeout should only affect the first keystroke of a multi-key command in + the `index' menu + +Changes since 0.88.13 +--------------------- + +- [patch-0.88.13.me.date.1] fixed problems with the date (~d) pattern + matching code + +- [patch-0.88.13.me.send.2] fixed some deficiencies in replying to tagged + messages + +- fixed segfault when downloading POP3 mail + +- [patch-0.88.13.me.cache.1] fixed problem with search cache when status + flags on messages change + +- [patch-0.88.13.me.fcc_hook.1] restored the fcc-hook command + + +PGP related changes since 0.88.13i +---------------------------------- + +- [patch-0.88.13i.tlr.pgp3_fingerprint.1] Code clean-up + with respect to PGP version 3 fingerprints. + +- [patch-0.88.13i.tlr.pgp_invoke.1] New PGP invocation + code which enables PGP 5 to check signatures and will + make the integration of other PGP implementations with + mutt easier. + +Changes since 0.88.15 +--------------------- + +- [patch-0.88.14i.ds.flagdoc.1] fixed the description of the flags in + $hdr_format in the manual + +- messages were not included in replies in the non-PGP version of Mutt + +- [patch-0.88.15.me.reply_attach.1] fixed segfault when using replying to an + attachment from the `attach' menu + +- [patch-0.88.15.me.followup.1] fixed bug in generation of the + mail-followup-to: field + +- [patch-0.88.15.me.ret_val.1] fixed some unchecked return values from + mx_open_new_message() and mx_append() + + +PGP related changes since 0.88.14i +---------------------------------- + +- [patch-0.88.14i.tlr.pgp_cleanup.1] Random clean-up + +- [patch-0.88.14i.tlr.pgp_flags.2] Display key + capabilities as ls(1) style flags on the key selection + menu. + +- [patch-0.88.14i.tlr.pgp_invoke.1] Fix a typo in + pgpinvoke.c. + +- [patch-0.88.14i.tlr.pgp_long_ids.1] Introduces a + pgp_long_ids option. If set, 64 Bit Key IDs will be + passed to PGP. + +- [patch-0.88.14i.tlr.pgp_sendkey.1] Fixes a segmentation + fault when sending PGP keys. + +- [patch-0.88.14i.tlr.pgp_tempfiles.2] Cleans up a + temporary file leak in the PGP code. + +Changes since 0.89 +------------------ + +- [patch-0.89i.de.no_subj.1] fixed possible coredump on NULL subject + +- [patch-0.89i.tlr.snprintf.1] added unsigned hexadecimal support to the + supplied snprintf() routine + +- [patch-0.89.bl.rfc1524_ws.1] fixed problem with whitespace in mailcap + entries + +- [patch-0.89.bj.slang10config.1] fixed configure script to work with + slang-1.0 beta + +- [patch-0.89.bugfix.buffycheck.mtsirkin.1] fixed bug with buffy + notification not being updated when entering a mailbox + +- when compiling with slang, set ESCDELAY to 300 because the default is too + fast for slow terminals (this is what ncurses uses by default) + +- [patch-0.88.13i.de.buffy_notify.1] fixed problem with new mail + notification in the current mailbox being clobbered by notifications in + other mailboxes + +- truncated the ChangeLog to entries after version 0.88 + +- [patch-0.88.14.tlr.msg_7bit.1] fixed problem with $mime_fwd + +- [patch-0.89.bj.set_followup_to.1] the user's address was added to the + mail-followup-to field if $metoo is set + +PGP related changes since 0.89i +------------------------------- + +- [patch-0.89i.tlr.pgp_sigvrfy.1] When verifying PGP/MIME + signatures, the LF -> CRLF could fail under certain + circumstances. + +- [tlr] Some long -> unsigned long changes in the pgp code + which are more of an aesthetical nature. + +- [tlr] The default of the $pgp_strict_enc variable has + been changed to "set". + +- [tlr] pgp-Notes.txt has been somewhat extended. + +Changes since 0.89.1 +-------------------- + +- [patch-0.89i.me.pointer.1] fix to use `unsigned long' instead of `void *' + for arguments where the data may be either an integer or a pointer. this + was needed for 64bit systems where `sizeof(void*)!=sizeof(int)' + +- [patch-0.89i.me.pattern_hook.4] the pattern matching language can now be + used in send-hook, save-hook and fcc-hook instead of a single regular + expression + +- [patch-0.89.bl.alter_weed.1] improves the mutlipart/alternative handler to + use auto_view, and adds viewing of MIME headers for attachments with + 'display-headers' + +- [patch-0.89.1.sec.expires_hdr.1] added support for the Expires: field. + auto-deletion of expiried messages is controlled with $expires + +- [patch-0.89.1i.co.mailx.1] added feedback for the `~r' command in mailx + mode + +- [patch-0.89.r.buf.1] buffer for backtic expansion of `mailboxes' was not + long enough + +- [patch-0.89.1i.de.mail_followup_to-leak.1] fixed memory leak in freeing + the mail-followup-to: field from the ENVELOPE struct + +- [patch-0.89.1i.de.list-reply-leak.1] fixed memory leak in the list-reply + function + +- [patch-0.89.1i.de.send-menu-leak.1] fixed memory leak when attaching files + to messages + +- [patch-0.89.1i.de.remove_user.1] fixed bug in removing the user's address + from the list if it is the only recipient + +- [patch-0.89.1i.de.pattern-regexp-leak.1] fixed memory leak with regular + expression compiled by mutt_pattern_comp() were never free()d + +- [patch-0.89.1i.de.beep_new.1] adds the $beep_new variable to control + whether or not mutt will beep when new mail arrives + +- [patch-0.89i.de.fcc-save-hook.1] adds new command `fcc-save-hook' which + creates a hook for use with either fcc-hook or save-hook + +- [patch-0.89.1i.de.buffy_sanity.1] fixes a problem with the buffy code + +- restored blank line between the "----- Forwarded message from..." line and + the text of the message + +- [patch-0.89.1i.de.tag-subthread.1] adds the `tag-subthread' command to the + pager + +PGP related changes since 0.89.1i +--------------------------------- + +- [patch-0.89i.dme.attach-key.1], [patch-0.89i.dme.extract-key.1] Fixes to + the PGP 5 invocation code. + +- [patch-0.89.1i.tlr.pgp_extract.2] Fixes various problems with the PGP + code and attachment menu, in particular with respect to extracting keys. + +Changes since 0.90 +-------------------- + +- [patch-0.90.me.sort.2] $sort_aux is now used for all primary sorting + methods + +- [patch-0.90i.me.pattern.1] extended date matching language (~d) to accept + relative dates offset from the current date + +- [patch-0.90.me.thread_hash.3] modified threading algorithm to use hash + tables instead of a linear search + +- [patch-0.90i.de.difftime.1] remove use of difftime() since it is not + portable + +- [patch-0.89.1i.de-bj.buffy_sanity.1-2] cleanup of the buffy check routine + +- [patch-0.90.me.addr_pattern.1] fixed some problems with the address + matching routines which caused problems for the send-hook and fcc-hook + commands + +- the size of the temp buffer used to expand regexps into patterns in + mutt_check_simple() was too short (SHORT_STRING) to handle the + patterns for send-hook, save-hook and fcc-hook, so it was increased to + LONG_STRING (1024) bytes + +- [patch-0.90.me.safe_path.1] fixed segfault when trying to save a message + with no recipients + +- [patch-0.89i.de.index-percentage.1] adds `%P' to $status_format to display + the percentage of the menu already seen (similar to the way the pager + works) + +- [patch-0.89.1i.bj.pager-redraw-clenaup.1] fixed some redraw problems in + the pager with $pager_index_lines set + +- [patch-0.89.1i.fp.filter.1] screen was not updated when filtering an + attachment an the the cte changed + +PGP related changes since 0.90i +------------------------------- + +- [patch-0.90i.tlr.pgp_attach_keys.1] Fixes a core dump on + the compose menu when attaching PGP public keys. + +- [patch-0.90i.tlr.pgp_encrflags.1] There were several + points in the code where a message's pgp flag was + evaluated the wrong way (namely, ...->pgp == PGPENCRYPT + instead of ... & PGPENCR). + +Changes since 0.90.1 +-------------------- + +- [patch-0.90.1i.tlr.hdrignore.1] fixed bug with weeding headers with the + CH_MIME bit set in mutt_copy_hdr() + +- [patch-0.90.1i.de.buffy-cleanup.1] cleaned up buffy to remove some + duplicated code and share some common global information + +- [patch-0.90.1i.arl.fcc-save.1] fixed some typos in the manual wrt to the + fcc-save-hook command + +- expansion of environment variables in mutt_extract_token() did not accept + underscore (_) as a valid value in variable names + +- fixed possible segfault in mutt_extract_token() if the result of a backtic + expansion was longer than the buffer used to store the expanded version of + the string + +- expansion of backtics is now option and controlled by whether or not the + `expn' argument in non-NULL. this is needed by eat_regexp() where we + don't want backtic expansion + +- the ->personal member of the address was not matched in match_adrlist() + +- [patch-0.90.1.me.hash.1] fixed bug in hash_delete_hash() where the wrong + entry could be deleted when the key matched, but had a different data + pointer even if the data pointer was specified in the call + +- changed default expansion for save-hook to '~f %s' and fcc/send-hook to + '~t %s | ~c %s' + +- the current search pattern was not invalidated if $simple_search changed + while in the middle of a search + +- [patch-0.90.1i.dme.nit-to_chars.1] fixed bug in docs wrt to $to_chars + +- added documentation for the `pattern_hook' patch + +- [patch-0.90.1.me.sort_to.1] adds support for sorting by the to: field with + `set sort=to' + +Changes since 0.90.2 +-------------------- + +- [patch-0.90.2i.de.toggle-quoted-redraw.1] fixed redraw problem with the + toggle-quoted function + +- [patch-0.90.2.de.buffy_check.1] fixed problem with -Z option + +- [patch-0.90.2.de.buffy_times.1] fixed problem of buffy reporting new mail + when saving messags from within mutt + +- [patch-0.90.1i.bj.in-reply-to-decode.1] fix to rfc2047 decode the + in-reply-to field before extracting the message-id + +- [patch-0.90.2.me.resort.1] fix to resort mailbox with $sort_aux or + $strict_threads changes + +- [patch-0.90.2.de.pretty_size.1] fixed rounding error which could cause the + display to be more than 4 chars + +- [patch-0.90.2.me.score.3] adds scoring support + +- [patch-0.90.2i.jg.pipe_from.1] the `From ' line was missing when piping a + message + +- [patch-0.89.1i.bj.skip_quoted.1] adds a `skip-quoted' (default: S) + function to the pager + +- tag-prefix was broken in the generic menu code when $auto_tag was unset + +- fixed bug in mutt_extract_token() wrt expanding backtics (``) + +- [patch-90.2i.tlr.addrbook.1] fixes address book so that select-entry + returns the current or tagged messages and exits + +- ESCDELAY default for slang is used again instead of setting it to 300ms + +- added `~R' to match references in the mutt_pattern_comp() + +PGP related changes since 0.90.2i +--------------------------------- + +- [patch-0.90.2i.tlr.pgp-disptrust.1] The trust status of + a key-user id relation is always displayed on the key + selection menu. + +- [patch-0.90.2i.tlr.pgp-subkeys.1] Subkeys are always + referred to using the principal key's numerical user ID. + This seems to be necessary to make PGP 5.0 extract them + properly. + +- The bit count for DSA keys is now displayed correctly. + +- A new option pgp_language (default: "en") is introduced. + This can be used to achieve an effect similar to the + alt_pgp patch. See doc/pgp-Notes.txt for details. A + big Thank You to Roland Rosenfeld for this idea! + +Changes since 0.90.3 +-------------------- + +- [patch-0.90.3.me.sort.1] fixed several bug relating to sorting by to or + score + +- %s in $status_format no longer includes $sort_aux, use %S to get the + $sort_aux value if desired (default value for $status_format now uses + (%s/%S) to mimic the old behavior) + +- [patch-0.90.3i.tlr.addrbook.1] fixed bug selecting the first alias in the + address book display + +- fixed bug in create-alias where mutt would dump core on a message with a + malformed address + +- parse_date() now always returns -1 on error + +- mailbox was not rethreaded when $sort_re changes + +- [patch-0.90.3.de.I_DECL.1] changes a few remaining variables in init.h to + the new I_DECL format + +- since most of the pattern matching commands were `envelope' types, changed + the semantics so that header and body matching are only done if the + M_FULL_MSG flag is set with mutt_pattern_comp() + +- backslash quoting did not work for the folder-hook and send-hook commands + +- use mutt_mktime() instead of mktime() in is_from() + +- [patch-0.90.3i.bj.skip-quoted.2] fixed bug in the skip-quoted function + +- [patch-0.90.3i.me.attach_pager.1] allows use of save/pipe/next/prev while + viewing attachments from the attach or compose menus + +- mutt_gen_msgid() changed to use getpid() so that unique id's are created + when mutt is invoked quickly in batch mode + +- Mutt now reports an error if the file specified by the `-F' command line + option does not exist + +- [jkj@sco.com] adds the $meta_key boolean option to case 8bit input to be + delivered as ESC + the char with the hi bit stripped + +- [patch-0.90.3i.me.editor_ext.1] improved line editor to be able to handle + unprintable control chars and to be able to bind the completion character + +- [patch-0.90.2i.bj.copy_hdr.2] rewrite of mutt_copy_hdr() to do reordering + in a single pass and to correctly unwrap fields for rfc2047 decoding + +- moved mutt_copy_hdr() to copy.c, and mutt_print_message() to commands.c + and eliminated print.c + +- `mutt -v' now displays the release date along with the version number. + the release date is also displayed with the `display-version' function + + +PGP related changes since 0.90.3i +--------------------------------- + +- [patch-0.90.3i.tlr.pgpkeyssort.1] If two keys have the + same User ID (i.e., mail address), use the numerical key + ID as the second sorting criteria for the key selection + menu. + +- [patch-0.90.3i.doc_pgp.1] Documentation for the PGP + support. (By Roland Rosenfeld + ). + +Changes since 0.90.4 +-------------------- + +- [patch-0.90.4i.bj.show_version.1] reduce code size required to print + compile time options + +- fix so that specifying more than two -v's on the command line always + prints the copyright statement instead of ignoring them + +- [patch-0.90.4.me.veryclean.1] adds a `veryclean' target to the Makefile to + remove files created by `configure' + +- the `inv' prefix now works for quadoptions + +- [patch-0.90.4.me.editor.1] adds `quote-char' function, and displays + control chars with the `markers' color, fixes bug in `buffy-cycle' + +- [patch-0.90.4i.me.unscore.1] adds the `unscore' command + +- [patch-0.89.1i.bj.tag-save.1] fixed bug in tag-save command + +- [patch-0.90.4i.me.help.1] added mutt_compile_help() to generate help + strings from `struct mapping_t' + +- [patch-0.90.4i.bj.hdr_fmt_recv.1] adds the %(fmt) to $hdr_format to + display the received time of a message + +- [patch-0.90.4i.bj.compose-disp-hdrs.3] fixes bug wrt to header weeding + with display-headers in the compose menu + +- [patch-0.90.4i.bjdeme.time.1] fixed calculation of local timezone and + added speedup when parsing mbox/mmdf mailbox and calculating the received + time from the `From ' line + +- added `flag' argument to mutt_pattern_exec() so that the + M_MATCH_FULL_ADDRESS option could be passed so that searching will match + the real name while the *-hook commands will not + + +PGP related changes since 0.90.3i +--------------------------------- + +- [patch-0.90.3i.tlr.pgpkeyssort.1] If two keys have the + same User ID (i.e., mail address), use the numerical key + ID as the second sorting criteria for the key selection + menu. + +- [patch-0.90.3i.doc_pgp.1] Documentation for the PGP + support. (By Roland Rosenfeld + ). +Changes since 0.90.5 +-------------------- + +- [patch-0.90.5i.me.new_addr.1] replaces rfc822 address parser and related + data structures + +- [patch-0.90.5.bj.parse-speedup.new_addr-2] rewrite of + mutt_read_rfc822_line() and mutt_read_rfc822_header() to be more efficient + +- [patch-0.90.5i.me.hash_ext.1] added support for unique keys in the hash + table, and optional argument to the destroy functions to free the + associated data + +- [patch-0.90.5i.me.alias_sort.1] the sorting method and alias menu format + are now configurable via $alias_sort and $alias_format, respectively + +- [tlr] mx_open_mailbox() did not correctly free the created CONTEXT struct + upon failure + +- created function mutt_version() to display Mutt's version number + +- [patch-0.90.5i.rc.exactcompile.1] fixed warning about unused function when + compiling with `#undef EXACT_ADDRESS' in rfc822.c + +- [patch-0.90.5.sec.expires_new.1] adds ~S and ~E to match superceded and + expired messages + +- [patch-0.90.5.me.decode_followup.1] addresses in mail-followup-to: were + not rfc2047 decoded + +- [patch-0.90.5.bj.manual-nits.1] adds missing docs relating $status_format + +Changes since 0.90.6 +-------------------- + +- [patch-0.90.6.me.unwrap_line.1] fixed bug in read_rfc822_line() wrt to + unwrapping folded lines + +- [patch-0.90.6.co.exactadd.1] fixed botched #ifdef in rfc822.c when + compiling with EXACT_ADDRESS + +- [patch-0.90.6i.jg.supersedes.2] changed `supercedes' to `supersedes' as + described in the current mailext I-D + +- added blurb on `--enable-exact-address' to the INSTALL file + +- fixed bug where messages with invalid dates showed up as being sent on Dec + 31, 1969 + +- added '-e' command line option to specify a config command to be run + _after_ parsing of initialization files + (eg. mutt -e 'set edit_hdrs' mutt-dev) + +- added `edit-from' function to the `compose' menu (the From: field is now + also displayed) + +- added real bounds checking to rfc2047_encode_string() and friends + +- [tlr] fixed several bugs in the rfc2047 `Q' encoding (tabs were not + encoded, chars with hex value less than 0x10 were not padded with 0, =? in + a string was not encoded) + +- [patch-0.90.6.bugfix.locktimeout.1] added optional timeout to the locking + code so that the check for new mail will not to acquire a lock wait while + large mail is being delivered + +- [fvl] mutt_get_name() would segfault if a NULL address was passed in + +- [patch-0.90.6i.de.old-unread.1] adds %o and %u to $status_format to allow + display the number of `old' and `unread' messages + +- [patch-0.90.6.co.fwd.1] adds a blank line before the `---End of forwarded + message' line + +- the confirmation prompt for bounce-message will now only display the + address without the personal name to conserve space + +- [vl] arg to isspace() was not cast to `unsigned char' in rfc822.c + +- updated the pattern matching functions to add the following operators: + ~p matches personal mail (finds $alternates in to: or cc:) + ~l matches mail addressed to `lists' + ~L finds in to:, cc:, from: or sender: + ~C finds in to: or cc: + + +PGP related changes since 0.90.6 +-------------------------------- + +- [patch-0.90.5i.rs.pgp_hdr.1] Preserve a message's PGP + settings when postponing + +Changes since 0.90.7 +-------------------- + +- included state.h, list.h, send.h in mutt.h and removed the others from the + distribution + +- [patch-0.90.7.me.default_from.1] + +- [patch-0.90.7i.de.limit.1] full name in addresses was not matched for `limit' + +- [patch-0.90.6.bugfix.locktimeout.1-2] changed `timeout' to `retry' in the + locking code since the former was misleading + +- [patch-0.90.7i.jg.help_line.1] offset of attachment was not calculated + correctly in the compose menu, cause the help line to be overwritten + +- fixed memory leak in rfc822_parse_address() when a parse error occurs (the + previously parsed address list was not returned and not freed) + +- [patch-0.90.7.me.vsnprintf.1] fixed bug where existence of snprintf() + assumed the existence of vsnprintf() + +- create-alias suggested the full email address as the default alias name + instead of just the mailbox portion + +- documented the `score' and `unscore' commands, and added %N to the + $hdr_format description + +- [patch-0.90.7i.bj.fgets_nit.1] size of buffer passed to fgets() was + incorrect + +- [patch-0.90.7i.bj.read_line.1] create common function mutt_read_line() for + reading lines which may contain a backslash on the end to wrap to the next + line + +- [patch-0.90.7i.bj.source_rc.1] makes source_rc() use the new + mutt_read_line() function + +- [patch-0.90.7.bugfix.buffycheck.mtsirkin.1] fixed another problem with + buffy + +- [patch-0.90.7.me.group_addr.1] fixed segfault when $use_domain is set and + you try to send to a group mailbox address + +- you can now use `mono normal' to set to normal attribute + +- [patch-0.90.7i.de.hook.2] adds the $default_hook variable to control the + expansion of `simple' patterns in the send-hook, fcc-hook and save-hook + commands + +- it was not possible to use the $hdr_format sequences in $indent_str when + the switch to the new mutt_copy_message() and mutt_copy_hdr() + +- new common function km_get_table() which retrives the mapping of function + names based upon the MENU_* value + +- [patch-0.90.7i.bj.mailcap_parse.1] rewrote the mailcap parsing routines to + fix some bugs and be more efficient + +- [mac@eecg.utoronto.ca] added the `search-opposite' to search for the + current pattern in the opposite direction from the last search + + +Changes since 0.90.8 +-------------------- + +- [patch-0.90.8.bj.display_loop.1] toggle of header weeding in the attach + and compose menus did not work + +- [patch-0.90.8.bj.manual_nit.1] documentation for displaying recieve date + in $hdr_format was missing + +- [patch-0.90.8.bj.pager_compile_help.1] pager did not use new function + mutt_compile_help() + +- [patch-0.90.8.bj.rename_file.1] changes mutt_rename_file() return codes + to be consistent with rename() + +- [patch-0.90.8.me.search.1] was not possible to bind the search functions + in the pager + +- [patch-0.90.8.me.time.1] fixed bug with the `~d >N' pattern + +- [patch-0.90.8.bj.reading_attachments.1] switching between viewing + attachments without returing to the attach menu was broken + +- [patch-0.90.8.bj.SKIPWS.1] removed unnecessary check for \0 in the + SKIPWS() macro + +- dest->fp was used directly in mx_open_new_message() where it should have + used msg->fp + +- `shell-escape' was moved to the generic keymap so that all menus can make + use of the function + +- added documentation on the `search-opposite' function + +- [patch-0.90.8i.tlr.rfc1524_fclose.1] fixed bug where fclose() could be + called with a NULL argument + + +PGP related changes since 0.90.7 +-------------------------------- + +- [patch-0.90.7i.bj.pgp_hdr.1] Fixes some nits in + conection with the pgp_hdr patch. + +- [patch-0.90.7i.rs.pgp_hdr_doc.1] Adds documentation for + the pgp_hdr patche's effects. + +- [patch-0.90.7i.tlr.pgp_keysel.1] Fixes uniqueness + problems with the pgp key selection code. + +- [patch-0.90.8i.rr.pgp_get_table.1] The help function + wouldn't work on the PGP key selection menu. + +- [patch-0.90.8i.tlr.pgp_asymm.1] This patch replaces the + pgp_version variable by three variables: + pgp_receive_version, pgp_send_version, pgp_key_version. + +- [patch-0.90.8i.tlr.pgp_micalg.1] This patch enables the + user to generate correct "micalg" parameters when + composing PGP/MIME signed messages. + +Changes since 0.90.9 +-------------------- + +- [patch-0.90.9.me.personal_hook.1] fixed bug where ~P in send-hook patterns + did not work + +- [patch-0.90.9i.tlr.mutt_add_list.1] mutt_add_list() could create memory + which was unitialized to 0 + +- [patch-0.90.9.me.group_alias.1] fixed bug where group address names were + expanded if a matching alias was found + +- definition of SKIPWS() in rfc822.c changed to match that in protos.h + +- [patch-0.90.9.josh.write_percent.1] the %-done is now shown when writing + out mbox/mmdf mailboxes just as is done for reading + +- body of message was not quoted with $indent_str if $forw_quote was set, + but $forw_decode is unset + +- changed initial value of $default_hook to "~f %s !~P | (~P ~C %s)" + + +PGP related changes since 0.90.9i +--------------------------------- + +- [patch-0.90.9i.tlr.pgppath.1] Properly check whether + PGPV2PATH and PGPV3PATH are defined in init.c. + +- [patch-0.90.9i.rr.pgp_key_version.1] Fixes a typo in the + pgp_key_version entry in init.h. + +- configure.in has been fixed so that _PGPPATH is not + defined unless at least one version of PGP exists on the + system. + +- A +nobatchinvalidkeys=off parameter has been added to + the pgpe invocation in pgpinvoke.c. +Changes since 0.90.10 +--------------------- + +- added check for empty string arguments to the bind and macro commands + +- moved bit fields in CONTEXT and BODY structs to the end of the struct def + since there were not enough bits to fit into a 32-bit word + +- split include_message() into include_forward() and include_reply() since + little code was shared for the two cases + +- $forw_decode no longer controls weeding of the header when replying and + $headers is set + +- added doc blurb for $mime_fwd on the effects of decoding when this + variable is unset + +- [patch-0.90.10.me.unread.1] fixed error in calculation of unread messages + when the `new' bit is cleared from a message + +- fixed y2k problem in ~d pattern + +- an error was reported in the ~d pattern if using the open-ended form (eg. + 1/1/98-) and any more text followed the pattern + + +PGP related changes since 0.90.10i +---------------------------------- + +- mutt -v now correctly displays the various PGPPATH + variables. +Changes since 0.90.11 +--------------------- + +- [patch-0.89.1i.bj.enriched.1] fixed buffer overflow in the text/enriched + handler + +- [patch-0.90.10.bl.sort_alias.1] adds the `unsorted' method for $sort_alias + +- [patch-0.90.10i.co.reply_regexp.1] changed initial value for $reply_regexp + to catch the `Re[2]:' style + +- changed check for new mail in MH-style mailboxes to consider messages new + if mtime >= atime + +- forwarded messages with $mime_fwd set are no longer forced to 7-bits when + not using PGP + +- updated the NEWS file with comments from the dev list + +- [patch-0.90.11i.de.stdin.2] fixed bugs with using stdin for -i or -H + command line arguments + +- [patch-0.90.11i.de.safe_fopen.1] changed many instances of fopen() to + safe_fopen() to avoid tempfile race conditions + +- the generic menu code did not print "Tag-" when using tag-prefix + +- [0.90.11-vikas.doc_nit] fixed bug with use of > in the description of + $status_format + +- [patch-0.90.11i.ds.man_tilde_nit.1] fixed use of ~ where ˜ should + have been used in manual + +- [patch-0.90.11i.rr.date_received.1] the parser table for ~r in the pattern + language used eat_regexp() instead of eat_date() + +- [patch-0.90.11i.me.attach_reply] fixes reply/forward of (tagged) + attachments from the attach menu + +- [patch-0.90.11.bj.make_string.1] fixes display of control chars and other + non-printable chars in the $*_format variables + +- [patch-0.90.11i.tlr.cdisp.2] fixes message_to_7bit() to not destroy the + existing content-disposition `filename' information + +- fixed bug in rfc822 parser where quoted-pair was not accepted in the + `mailbox' or `domain' tokens + + +PGP related changes since 0.90.11i +---------------------------------- + +- [patch-0.90.11i.roro.doc.1.gz] Added some more PGP + documentation. Spelling and grammar corrections from + native speakers are welcome! +Changes since 0.90.12 +--------------------- + +- [patch-0.90.11i.me.boundary.1] mutt_write_mime_body() did not use + mutt_get_parameter() to fetch the MIME boundary + +- [patch-0.90.12i.tlr.recall.1] fixed segfault when attempting to recall + postponed messages + +- [patch-0.90.12i.rr.hdrline.1] fixed misapplied patch for the make_string + support + +- fixed error message on nonexistant file specified with -i or -H + +- [patch-0.90.12.me.safe_fopen.1] safe_fopen should not be used when it is + desirable for the specified file to be overwritten + + +PGP related changes since 0.90.12i +---------------------------------- + +- [patch-0.90.12i.maj.pgp_extract_keys_nit.1] Correct the + behaviour when typing C-K in an empty folder. +Changes since 0.90.13 +--------------------- + +- [patch-0.90.13i.de.enter_fname.1] buffy-cycle should act like complete + when in the save/copy message prompts + +- [patch-0.90.13i.rr.normalize_time.1] mutt_normalize_time() didn't handle + the case where a date rolled over to the previous year when selecting by + number of months in the past + +Changes since 0.91 +------------------ + +- fixed bug in rfc822_parse_adrlist() where it would segfault on a bad address + which contained only a comment + +- [patch-0.90.12i.tlr.recall.1] fixed segfault when attempting to recall a + postponed message which was a reply to another message + +- [patch-0.91.me.exit_main.1] fixes collision in function name for "exit" in + the "generic" and "index" keymaps + +- corrected documentation on $hdrs which erroneously stated that user defined + headers are never included in batch mode sending + +Changes since 0.91.1 +-------------------- + +- [patch-0.91i.me.dyn_str.2] changes global string variables to `char *' + instead of fixed size arrays, and moves defaults for variables to the + MuttVars[] array + +- [patch-0.91.me.format_string.1] creates common function + mutt_FormatString() for handling %-substituted strings in a generic manner + +- [patch-0.91.vikas.limit_size.4] adds %L to $hdr_format to display the + total size of all visible messages when limited + +- [patch-0.91.me.followup.1] Adds boolean $followup_to to control + generatation of the Mail-Followup-To field. Also allows user to edit this + field and not have it overwritten. + +- [patch-0.91i.me.sendmail.1] Removes $sendmail_bounce. Mutt now passes all + recipients on the command line for normal sending. + +- [patch-0.90.10i.bj.current_time.1] adds sequence % to $hdr_format to + display the current time using strftime(fmt) + +- [patch-0.90.10.bl.alternative_order.1] allows user specification of the + order in which to choose types from multipart/alternative messages + +- [my_hdr-subject-patch2] allows specification of a default subject (if none is + given using the `my_hdr' command + +- [patch-0.91i.as.revname.1] makes $reverse_name look at from: if the message + you are replying to is from you + +- added new operator ~B to pattern matching language which matches a regexp + anywhere in a message (more efficient than ~h %s | ~b %s). + +- changed the `source' command to use mutt_open_read() instead of fopen() so + that you can read from the output of a script (eg. "source + ~/bin/mutt_commands|"). + +- fixed typos in the description of `forget-passphrase' in the manual + +- the -e option was not document in either the man page nor the manual + +- [patch-0.91.bl.composetyped.1] adds support for creating non-text body + parts from within the compose menu + +- $reverse_name is now handled before send-hook so that user's can match on + the from field with ~f + +- [patch-0.91i.de.threadlimit.1] allows drawing of thread trees while in + limited display + +- [mutt-0.89i-quadopt_mimefwd.patch] turns $mime_fwd into a quad-option + +- [patch-0.91i.jmy.folder_format.1] the display of the `browser' menu is now + configurable via the $folder_format variable + +- [patch-0.91i.jmy.pattern-size.1] adds ~z operator to the pattern matching + function to match by size + +- rfc822.c now uses some functions from mutt proper (safe_strdup and + mutt_substrdup) instead of attempting to be a self-contained library + + +PGP related changes since 0.91.1 +-------------------------------- + +- [patch-0.91.1i.tlr.pgp_obsolete_vars.1] Removes the + obsolete pgp_v3_* variables. +Changes since 0.92 +------------------ + +- [patch-0.91.me.reuse_hook.1] the result portion of those hooks which can + have only a single matching pattern (ie. send-hook) were not updated if the + same pattern was specified with a different result. + +- [patch-0.92.me.empty_pop_str.1] fixes core when fetching POP3 mail if any + of the $pop_* string variables were empty + +- [patch-0.92.me.empty_route.1] + +- [patch-0.92.me.regexp_case.1] REG_ICASE was not set for regexp variable + defaults in mutt_restore_default() + +- [patch-0.92.de.threadlimitfix.1] fixes small bug in the threadlimit code + which could cause extra work to be done when not needed + +- [patch-0.92.me.format_pad.1] fixed segfault when the value of + $status_format is wider than the screen + +- [patch-0.92.me.draft_path.1] the paths for neither -i nor -H were being + expanded before use + +- [patch-0.92.vikas.nits] + 1. %M in $status_format was getting truncated after 3 digits because of a + tiny mistake in mu + 2. The ~z pattern matching has a small nit at the boundaries where + ~z<1024 matches Content-length: 1024 as well. I just changed the inclusive + ranges ( <= and >= ) to exclusive ranges ( < and > ) in pattern.c + 3. Added a line about the new %L format specifier for $status_format + + +PGP related changes since 0.92 +------------------------------ + +- No more (char *) deferences of (char **) in pgp_invoke.c. +Changes since 0.92.1 +-------------------- + +- [0.92.1.me.format_pad.1] fixed another problem with the result of + subtracting two size_t's and the result being negative + +- [0.92.1.me.batch_copy.1] $copy was ignored when sending mail in batch mode + +- [patch-0.92.1.josh.sort_re-tilde.1] reset default values of $sort_re and + $tilde to their 0.91.1 values. + +- [patch-0.92.me.func_macro.1] changes function key names to use <> around + then (eg. ) and allows them to be used in macros as well + (eg. macro index Y *) + +- [patch-0.92.1i.rr.reset.2] adds the `reset' command and the `&' prefix to + `set' to allow resetting variables to the default value + +- changed the %g (group) and %u (userid) sequences of $folder_format to + print the gid and uid, respectively, if they can't be resolved to names, + instead of diplaying "(null)" + +- [patch-0.92.1i.me.sendmail_wait.2] adds $sendmail_wait which specifies the + number of seconds to wait for sendmail to finish before background it + +- [patch-0.92.1.co.bounce.2] fixes problem with the bounce-message + confirmation prompt + +- [patch-0.92.1.bl.attach_del.2] adds the ability to delete attachments + +- [patch-0.92.1i.jg.default_opts.2] changed var defaults to match what is in + the manual + +- [patch-0.92.1i.rr.no_signature.1] fixed segfault with + set signature='' + when composing mail + +- [patch-0.92.1i.rr.manual_nits.1] fixed some spelling errors in the manual + +- [mutt-0.91.1a.patch (partial)] if KEY_RESIZE is defined, mutt_getch() will + ignore it (generated by ncurses 4.2 on SIGWINCH) + +- [patch-0.92.1.bl.query.1] adds support for querying an external database + for email addresses + + +PGP related changes since 0.92.1i +--------------------------------- + +- [patch-0.92.1i.wn.verify_sig-pgp_timeout.1] Make the + verify_sig and pgp_timeout defaults consistent between + init.h and the manual. + +- [patch-0.92.1i.tlr.pgp_keysel.1] Fix a segmentation + fault in the key selection code. + +- [patch-0.92.1i.tlr.pgpsign.1] The PGP signing code works + again. + +- Changed $verify_sig to $pgp_verify_sig. +Changes since 0.92.2 +-------------------- + +- [patch-0.92.2i.tlr.sendlib_args.1] fixes segfault when sending mail + +- [patch-0.92.2.me.reverse_name.1] fixed problem with $reverse_name altering + the HEADER struct for the message being replied to. + +- [patch-0.92.2.sort_flag.1] fixed problem with `score' command not updating + the score when $sort or $sort_aux was not `score' + +- [patch-0.92.2i.me.sendmail.1] adds debugging info to the send_msg() + command + +- [patch-0.92.2i.dme.generic-op_jump.1] fixed missing binding for 3-9 to + `jump' in the generic keymap + +- [patch-0.92.2.me.sort_alias.1] fixed problems with $sort_alias not working + +- [patch-0.92.2i.me.msg_search.1] fixed bugs in the ~h, ~B and ~b search + patterns + +- [patch-0.92.2i.me.postpone.1] x-mutt-references was not removed when + recalling a postponed reply when no mailbox is open + +- included for def of SEEK_SET in copy.c (SunOS 4.1.4) + +- [patch.sendlib.2] fixes signal handling in send_msg(), and fixes leftover + temporary files after sending the message + +- [patch-0.91.1i.aw.purgeprompt.1] the prompt for $delete now shows how many + messages will be purged + +- [patch-0.92.1i.jg.empty_to.1] makes the string used for the group mailbox + name when there are no to: or cc: recipients configurable via the + $empty_to variable + +- moved mutt_signal_init() before initscr() in start_curses() so that + ncurses 4.2 will not attempt to install its on SIGWINCH handler + + +PGP related changes since 0.92.2i +--------------------------------- + +- White space problems around "#ifdef _PGPPATH" should be + fixed now. + +- The .spec file for RedHat is gone. +Changes since 0.92.3 +-------------------- + +- alarm() was not actually set to put sendmail in the background when + $sendmail_wait > 0 + +- [patch-0.92.3.de.threadlimitfix.1] + +- [patch-0.92.3.josh.delete_format.1] changes default for $delete_format + +- [patch-0.92.3.me.var_syn.1] renames some variables and creats a synonym + data type + +- [patch-0.92.3.vikas.sort_alias] fixed bug with `set sort_alias=unsorted' + +- [patch-0.92.3i.de.hashtable-leak.1] fixed memory leak when destroying + hash tables + +- [de] fixed memory leak in init.c where the value of $alias_file was not + free()d before assigning the default from the default muttrc to use + +- [patch-0.92.3i.tlr.keymaps.1] fixed missing keybindings for the `jump' + command + +- fixed typo "Purpge" in mx_close_mailbox() + +- [patch-0.91.1.ldm.ignore_list_reply_to] fixed bug with handling of + $ignore_list_reply_to + +- fixed signal initialization to call before initscr() for ncurses and after + initscr() for slang + +- [patch-0.92.3.vikas.alias_search] makes searching in the alias menu + WYSIWYG + +- fixed memory leak in parse_route_addr() when the route-addr portion of an + address was unable to be parsed + +- [patch-0.92.3i.tlr.query_real.1] + +- [patch-0.92.3.color_index.7] adds the ability to color lines in the index + using the searching language + + +PGP related changes since 0.92.3i +--------------------------------- + +- [patch-0.92.3i.tlr.keymaps.1] Add some lost keymap + bindings for the generic menu. + +- [patch-0.92.3i.tlr.pgp_default_version.1] The default + value of $pgp_default_version is now set correctly. + +- [patch-0.92.3.bl.pgp_attach_del.1] Handle PGP/MIME + messages correctly when deleting attachments. + +- [patch-0.92.3i.rr.postpone_pgp.1] Corrects some bugs + with respect to postponing PGP signed messages. +Changes since 0.92.4 +-------------------- + +- [patch-0.92.4.josh.empty_querycmd.2] fixed segfault on $query_command + being set to '' + +- [recv.diff] the warning from filter-attachment was not cleared when you + answer no or abort + +- [patch-0.92.4i.rr.mime_forward_decode.1] splits $forward_decode into + $forward_decode and $mime_forward_decode + +- [patch-0.92.1.optional_list.2] makes the %L in $header_format an optional + string using the %?L?...? syntax + +- [patch-0.92.4.me.savehook.1] makes it possible to use ~b and ~h in + save-hook commands + +- [patch-0.92.4i.me.token.2] upgrades the muttrc parser to use dynamic + buffer allocation, and allows multiple commands on a single line separated + by semicolons + +- the bcc: field is again output to the $sendmail program, and is assumed + that the MTA will take care of removing it + +- fixed memory leak in parse_route_addr() + + +PGP related changes since 0.92.3i +--------------------------------- + +- [patch-0.92.3i.tlr.keymaps.1] Add some lost keymap + bindings for the generic menu. + +- [patch-0.92.3i.tlr.pgp_default_version.1] The default + value of $pgp_default_version is now set correctly. + +- [patch-0.92.3.bl.pgp_attach_del.1] Handle PGP/MIME + messages correctly when deleting attachments. + +- [patch-0.92.3i.rr.postpone_pgp.1] Corrects some bugs + with respect to postponing PGP signed messages. +Changes since 0.92.5 +-------------------- + +- [patch-0.92.4i.de.parse_date.1] better recovery from badly formatted + timezones + +- [patch-0.92.5.de.Muttrc-memleak.1] fixed memory leak when using the -F + command line option + +- [patch-0.92.5i.bj.pgp_bitfield.1] + +- [patch-0.92.5i.jg.empty_to_doc.1] adds documentation for the $empty_to + variable + +- [patch-0.92.5i.rr.ask_quit.1] new variable $quit which controls the prompt + for exiting mutt + +- [patch-0.92.5.vikas.color_index.7-10] fixed bugs in the `uncolor' command + +- fixed coredump if $to_chars is set to '' + +- [patch-0.92.5.vikas.wrap_search] new variable $wrap_search which controls + whether or not searches wrap around the first or last message + +- [patch-0.92.4i.dj.addr_hook_patterns.3] the mailbox specified in a + save-hook is now %-expanded using $header_format + +- the default save mailbox no longer looks at defined `lists'. to get the + old behavior, use + save-hook ~l %B + +- optional strings in format strings have been extended to allow for an + `else' clause. the new syntax is: + %??&? + or if no `else' clause is desired (backwards compatibility): + %??? + +- [patch-0.92.5.bj.interrupt_search.1] allows interruption of `search' using + the shell's interupt (SIGINT) character + +- [patch-0.92.5i.jg.attach_split_sep.1] removed useless code and docs + relating to $attach_split and $attach_sep + +- [patch-0.92.5.vikas.toggle_write] if the mailbox is read-only and the + user attempts to sync, a more informative error message is now displayed + + +Changes since 0.92.6 +-------------------- + +- [patch-0.92.6.vikas.limit_new.3] adds support for new mail delivery while + a limit is in effect + +- [patch-0.92.6.vikas.wrap_search.2] adds docs for $wrap_search + +- [patch-0.92.6i.de.quit.1] changes the default for $quit to `yes' + +- [patch-0.92.6.me.rfc822.1] fixed buffer overrun error in + rfc822_write_address_real() + +- [mutt-0.92.6i-mhsupport.patch] + +- [patch-0.92.6i.de.reopen_unread.1] + +- consolidated common code for editing address lists in edit_envelope() + +- changed parse_alias() to use mutt_parse_adrlist() instead of + rfc822_parse_adrlist() so that you can have simple aliases like: + alias group bob joe + with no commas (mailx compatibility) + diff --git a/Changes b/Changes new file mode 100644 index 00000000..7b012d81 --- /dev/null +++ b/Changes @@ -0,0 +1,38 @@ +Changes since 0.92.7 +-------------------- + +- [patch-0.92.7.handler.empty_cc_bcc.1] fixed bug in edit_envelope() which + caused the message to be aborted if the cc or bcc field was empty + +- buffer used to store the error message in mutt_pattern_comp() called + sizeof() on the wrong variable + +- renamed $header_format to $index_format ($hdr_format is still accepted) + +- [patch-0.92.7.vikas.postpone.1] fixed typo for binding the `postpone' + menu, and changes the menu to use $index_format for display of postponed + messages + +- mutt_error() is now a function pointer which either has the value of + mutt_curses_error() or mutt_nocurses_error() which simplifies the muttrc + parsing code + +- "source '~/bin/myscript|'" was fixed so that it works correctly + +- [patch-0.92.7.me.mh_sync.1] fixed bug where syncing mh mailboxes silently + failed + +- [patch-0.92.7.me.pattern.1] fixes bug with mismatched backtics in the + pattern language + +- mutt_default_from() no longer sets the "real name" portion of the return + address so that it can be delayed until after execution of send-hook + commands + + +PGP-related changes since 0.92.6i +--------------------------------- + +- [patch-0.92.6i.tlr.pgp_longids.1] Correct the + calculation of 64 bit "v4" key IDs. + diff --git a/INSTALL b/INSTALL new file mode 100644 index 00000000..edef9e84 --- /dev/null +++ b/INSTALL @@ -0,0 +1,144 @@ +Supported platforms +=================== + +Mutt has been reported to compile and run under the following Unix operating +systems: + + AIX + BSDI + Convex + Data General Unix (DG/UX) + Digital Unix (OSF/1) + DYNIX/ptx + FreeBSD + HP-UX + IRIX + Linux + Atari MiNT + MkLinux + NetBSD + QNX + Solaris + SunOS + Ultrix + UnixWare + +- An ANSI C compiler (such as gcc) is required. + +- You must also have a SysV compatible curses library, or you must + install either + + GNU ncurses, ftp://prep.ai.mit.edu/pub/gnu/ + + or + + S-Lang, ftp://space.mit.edu/pub/davis/slang/ + +Installation +============ + +Installing Mutt is rather painless through the use of the GNU autoconf +package. Simply untar the Mutt distribution, and run the ``configure'' +script. In most cases, it will automatically determine everything it needs +to know in order to compile. However, there are a few options to +``configure'' to help it out, or change the default behavior: + +--prefix=DIR + install Mutt in DIR instead of /usr/local + +--with-sharedir=DIR + specify where to put architecture independent data files + +--with-curses=DIR + use the curses lib in DIR/lib. If you have ncurses, ``configure'' + will automatically look in /usr/include/ncurses for the include + files. + +--with-slang[=DIR] + use the S-Lang library instead of ncurses. This library seems to + work better for some people because it is less picky about proper + termcap entries than ncurses. It is recommended that you use at + *least* version 0.99-38 with Mutt. + +--with-mailpath=DIR + specify where the spool mailboxes are located on your system + +--with-homespool[=FILE] + treat file in the user's home directory as the spool mailbox. Note + that this is *not* the full pathname, but relative to the user's + home directory. Defaults to "mailbox" if FILE is not specified. + +--enable-pop + enable POP3 support + +--enable-hidden-host + local hostname is not part of the FQDN. + +--with-rx + use GNU rx instead of local regexp routines. Many systems don't + have the POSIX compliant regcomp/regexec/regfree routines, so this + provides a way to support them. + +--enable-flock + use flock() to lock files + +--disable-fcntl + by default, Mutt uses fcntl() to lock files. Over NFS this can + result in poor performance on read/write. Note that using this + option could be dangerous if dotlocking is also disabled + +--enable-nfs-fix + some implementations of NFS do not always write the + atime/mtime of small files. This means that Mutt's ``mailboxes'' + feature does not always work properly, as it uses these + attributes to work out whether the file has new mail. This + option enables a workaround to this bug. + +--enable-locales-fix + on some systems, the result of isprint() can't be used reliably + to decide which characters are printable, even if you set the + LANG environment variable. If you set this option, Mutt will + assume all characters in the ISO-8859-* range are printable. If + you leave it unset, Mutt will attempt to use isprint() if either + of the environment variables LANG, LC_ALL or LC_CTYPE is set, + and will revert to the ISO-8859-* range if they aren't. + +--with-exec-shell=SHELL + on some versions of unix, /bin/sh has a bug that makes using emacs + with mutt very difficult. If you have the problem that whenever + you press control-G in emacs, mutt and emacs become very confused, + you may want to try using a Bourne-derived shell other than + /bin/sh here. Some shells that may work are bash, zsh, and ksh. + C shells such as csh and tcsh will amost certainly not work right. + Note that this option is unrelated to what shell mutt gives you + when you press '!'. Only use this option to solve the above problem, + and only specify one of the above shells as its argument. + +--enable-exact-address + By default, Mutt will rewrite all addresses in the form + Personal Name + regardless of the input. By enabling this option, Mutt will write + addresses in the same form they are parsed. NOTE: this requires + significantly more memory + +Once ``configure'' has completed, simply type ``make install.'' + +Mutt should compile cleanly (without errors) and you should end up with a +binary called ``mutt.'' If you get errors about undefined symbols like +A_NORMAL or KEY_MIN, then you probably don't have a SysV compliant curses +library. You should install either ncurses or S-Lang (see above), and then +run the ``configure'' script again. + +Platform Notes +============== + +Solaris 2.4 + + The system regcomp() and regexec() routines are very badly broken. + So much so that using them will cause Mutt to be totally unusable. + The --with-rx option to `configure' should always be used. (Note: + the problems have apparently been fixed in Solaris 2.5) + + Color does not work right with Solaris curses. You will have to + compile with either ncurses or slang to get working color support. + diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 00000000..0fe9f8a5 --- /dev/null +++ b/Makefile.in @@ -0,0 +1,143 @@ +# +# Copyright (C) 1996,1997 Michael R. Elkins +# +# 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +SHELL=/bin/sh +VERSION=@VERSION@ + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +bindir=@bindir@ +libdir=@libdir@ +mandir=@mandir@ +sharedir=@sharedir@ +srcdir=@srcdir@ +VPATH=@srcdir@ +@SET_MAKE@ + +INSTALL=@INSTALL@ +CC=@CC@ +XCPPFLAGS=-I. @CPPFLAGS@ +CFLAGS=@CFLAGS@ -DSHAREDIR=\"$(sharedir)\" $(XCPPFLAGS) +LDFLAGS=@LDFLAGS@ +LIBS=@LIBS@ +OPS=@OPS@ +OBJS= addrbook.o alias.o attach.o browser.o buffy.o color.o \ + commands.o complete.o compose.o copy.o curs_lib.o curs_main.o date.o \ + edit.o enter.o flags.o init.o filter.o from.o getdomain.o \ + handler.o hash.o hdrline.o headers.o help.o hook.o keymap.o lib.o \ + main.o mbox.o menu.o mh.o mx.o pager.o parse.o pattern.o \ + postpone.o query.o recvattach.o rfc822.o \ + rfc1524.o rfc2047.o score.o send.o sendlib.o signal.o sort.o \ + status.o system.o thread.o @LIBOBJS@ + +CLEANFILES=mutt *.o core +VERYCLEANFILES=$(CLEANFILES) Makefile config.cache config.log \ + config.status config.h +DISTCLEANFILES=$(VERYCLEANFILES) tags keymap_defs.h *.rej *.orig *~ Makefile.bak + +# kill these files when making new export distributions +NONEXPORT=pgp.c pgp.h pgpinvoke.c pgpkey.c pgppubring.c sha.h sha1dgst.c \ + sha_locl.h OPS.PGP doc/pgp-Notes.txt doc/language.txt \ + doc/language50.txt + +all: mutt + +mutt: keymap_defs.h $(OBJS) $(REGEX) + $(CC) -o mutt $(OBJS) $(REGEX) $(LDFLAGS) $(LIBS) + +keymap_defs.h: Makefile $(OPS) + rm -f keymap_defs.h + $(srcdir)/gen_defs $(OPS) > keymap_defs.h + +install: mutt + $(srcdir)/mkinstalldirs $(bindir) + -mv -f $(bindir)/mutt $(bindir)/mutt.old + $(INSTALL) @MUTT_GROUP@ -m @MUTT_PERMISSION@ mutt $(bindir) + $(srcdir)/mkinstalldirs $(mandir)/man1 + $(INSTALL) -m 644 $(srcdir)/doc/mutt.man $(mandir)/man1/mutt.1 + -if [ ! -f $(sharedir)/Muttrc ]; then \ + $(srcdir)/mkinstalldirs $(sharedir); \ + $(INSTALL) -m 644 $(srcdir)/Muttrc $(sharedir); \ + fi + -if [ ! -f $(sharedir)/mime.types ]; then \ + $(INSTALL) -m 644 $(srcdir)/mime.types $(sharedir); \ + fi + +uninstall: + rm -f $(bindir)/mutt $(sharedir)/Muttrc $(mandir)/man1/mutt.1 + +$(srcdir)/configure: $(srcdir)/configure.in + autoconf + +Makefile: $(srcdir)/Makefile.in + ./config.status + +config.h.in: $(srcdir)/acconfig.h + autoheader + +config.h: $(srcdir)/config.h.in + ./config.status + +tags: + (cd $(srcdir) && ctags *.[ch]) + +dep: Makefile + mv Makefile Makefile.bak + awk -f $(srcdir)/depend.awk < Makefile.bak > Makefile + echo '# DO NOT REMOVE THIS LINE' >> Makefile + $(CC) -MM $(XCPPFLAGS) $(srcdir)/*.c >> Makefile + +clean-real: + (cd $(srcdir) && rm -f $(CLEANFILES)) + (cd $(srcdir)/doc && $(MAKE) $@) + +clean: + rm -f $(CLEANFILES) + +veryclean: + rm -f $(VERYCLEANFILES) + +distclean: + (cd $(srcdir) && rm -f $(DISTCLEANFILES)) + (cd $(srcdir)/doc && $(MAKE) $@) + +reldate: + rm -f $(srcdir)/reldate.h + echo 'const char *ReleaseDate = "'`date +%Y-%m-%d`'";' > $(srcdir)/reldate.h + +# make export version +usdist: distclean reldate + rm -rf /tmp/mutt-$(VERSION) + (cd .. && cp -r mutt-$(VERSION) /tmp) + for i in `grep _PGPPATH $(srcdir)/*.[ch] | sed 's/^\([^:]*\).*/\1/' | grep -v '/main\.c' | grep -v '/pgp'` ; do \ + target=`basename $$i` ; \ + $(srcdir)/reap.pl _PGPPATH < $(srcdir)/$$target > /tmp/mutt-$(VERSION)/$$target; \ + done + for i in $(NONEXPORT); do \ + rm -f /tmp/mutt-$(VERSION)/$$i; \ + done + (cd /tmp && tar cvf mutt-$(VERSION).tar mutt-$(VERSION) && gzip mutt-$(VERSION).tar) + rm -rf /tmp/mutt-$(VERSION) + +# make international distribution +dist: distclean reldate + (cd $(srcdir)/.. && tar cfz mutt-$(VERSION)i.tar.gz mutt-$(VERSION)i) + +rx/librx.a: + (cd rx && $(MAKE) CC="$(CC)" CFLAGS="$(CFLAGS)") + diff --git a/Mush.rc b/Mush.rc new file mode 100644 index 00000000..41008926 --- /dev/null +++ b/Mush.rc @@ -0,0 +1,18 @@ +# +# Key bindings similar to those of MUSH +# + +bind index . display-message +bind index t display-message +macro index n j\n +bind index + next-message +bind index j next-message +bind index J next-message +bind index - previous-message +bind index k previous-message +bind index K previous-message +bind index { top-page +bind index } bottom-page +bind index f change-folder +bind index \cu sync-mailbox +bind index * flag-message diff --git a/Muttrc b/Muttrc new file mode 100644 index 00000000..9eed4f12 --- /dev/null +++ b/Muttrc @@ -0,0 +1,24 @@ +# +# System configuration file for Mutt +# + +# default list of header fields to weed when displaying +# +ignore "from " received content- mime-version status x-status message-id +ignore sender references return-path lines + +# imitate the old search-body function +macro index \eb '/~b ' + +# simluate the old url menu +macro index \cb |urlview\n +macro pager \cb |urlview\n + +# If Mutt is unable to determine your sites domain name correctly, you can +# set the default here. +# +# set hostname=cs.hmc.edu + +# If your sendmail supports the -B8BITMIME flag, enable the following +# +# set use_8bitmime diff --git a/NEWS b/NEWS new file mode 100644 index 00000000..bab7ea60 --- /dev/null +++ b/NEWS @@ -0,0 +1,79 @@ +Announcing Mutt 0.91 +==================== + +NOTE: PLEASE read the following list of changes carefully, especially the +first couple entries related to changes in variables/commands and see the +manual before posting questions. Taking a little time to read this notice +will significantly reduce the amount of confusion as to why some things +might seem broken at first. + +Major changes since 0.89.1: + +- The matching for the send-hook, save-hook and fcc-hook commands has been + extended so that it understands Mutt's matching language (used for + searching and limits). You may still use a "simple" regular expression, + but Mutt will expand that with the value of $default_hook (similar to + $simple_search). Additionally, all hooks are now executed in the order + they appear in your .muttrc instead of first doing `to', then `cc'. + +- new config command `score' which allows you to assign a numerical value to + a message based upon header information (similar to scoring in slrn). the + score can be displed in $hdr_format with %N, you can sort by `score' and + you can match messages with a certain score (or range) with the `~n' + operator + +- new variable $beep_new which causes Mutt to beep when new mail is + delivered to a mailbox specified by the `mailboxes' command + +- new sort method `to' for sorting by who messages are addressed to + +- new variable $meta_key which causes Mutt to interpet keystrokes with the + hi bit set as ESC plus the key with the hi bit stripped (eg. ALT-x will + get interpetted as "\ex") + +- new variable $sort_alias which controls the order the aliases appear in + the menu (may be either "alias" or "address") + +- new variable $alias_format to control the way the aliases are displayed in + the alias menu + +- additional operators to the pattern matching lanauge: + ~S superseded messages + ~E expired messages + ~p personal mail (finds $alternates in to: or cc:) + ~P mail from you (finds $alternates in from: or sender:) + ~l list mail (finds `lists' in to: or cc:) + ~L finds in to:, cc:, from: or sender: + ~C finds in to: or cc: + +- additional formats for $hdr_format + %N displays the message score + %(fmt) displayes the message's received time using strftime() + +- additional formats for $status_format + %P percentage of menu seen + %S current value of $sort_aux (%s no longer displays $sort_aux) + %o number of old messages + %u number of unread messages + +- new configure option `--enable-exact-address' which causes Mutt to rewrite + addresses in the same format they were parsed instead of rewriting them in + the form `Name ' + +- new command line argument `-e' which allows you to specify configuration + commands to be executed *after* your .muttrc is parsed + (eg. mutt -e 'set edit_hdrs' mutt-dev) + +- you can now use `mono normal' to set to normal attribute + +- new function `search-opposite' which searches for the current search + pattern in the opposition direction from the previous search + +Mutt's primary distribution point is ftp://ftp.cs.hmc.edu/pub/me/mutt. +See the Mutt Home Page (http://www.cs.hmc.edu/~me/mutt/) for mirror sites. + +Bug reports should be sent to the Mutt User's Mailing List +. + +Michael Elkins +April 10, 1998 diff --git a/OPS b/OPS new file mode 100644 index 00000000..049e8922 --- /dev/null +++ b/OPS @@ -0,0 +1,142 @@ +OP_NULL "null operation" +OP_ATTACH_VIEW_MAILCAP "force viewing of attachment using mailcap" +OP_ATTACH_VIEW_TEXT "view attachment as text" +OP_BOTTOM_PAGE "move to the bottom of the page" +OP_BOUNCE_MESSAGE "remail a message to another user" +OP_BROWSER_NEW_FILE "select a new file in this directory" +OP_CHANGE_DIRECTORY "change directories" +OP_CHECK_NEW "check mailboxes for new mail" +OP_COMPOSE_ATTACH_FILE "attach a file(s) to this message" +OP_COMPOSE_EDIT_BCC "edit the BCC list" +OP_COMPOSE_EDIT_CC "edit the CC list" +OP_COMPOSE_EDIT_DESCRIPTION "edit attachment description" +OP_COMPOSE_EDIT_ENCODING "edit attachment transfer-encoding" +OP_COMPOSE_EDIT_FCC "enter a file to save a copy of this message in" +OP_COMPOSE_EDIT_FILE "edit the file to be attached" +OP_COMPOSE_EDIT_FROM "edit the from field" +OP_COMPOSE_EDIT_HEADERS "edit the message with headers" +OP_COMPOSE_EDIT_MESSAGE "edit the message" +OP_COMPOSE_EDIT_MIME "edit attachment using mailcap entry" +OP_COMPOSE_EDIT_REPLY_TO "edit the Reply-To field" +OP_COMPOSE_EDIT_SUBJECT "edit the subject of this message" +OP_COMPOSE_EDIT_TO "edit the TO list" +OP_COMPOSE_EDIT_TYPE "edit attachment type" +OP_COMPOSE_ISPELL "run ispell on the message" +OP_COMPOSE_NEW_MIME "compose new attachment using mailcap entry" +OP_COMPOSE_POSTPONE_MESSAGE "save this message to send later" +OP_COMPOSE_RENAME_FILE "rename/move an attached file" +OP_COMPOSE_SEND_MESSAGE "send the message" +OP_COMPOSE_TOGGLE_UNLINK "toggle whether to delete file after sending it" +OP_COPY_MESSAGE "copy a message to a file/mailbox" +OP_CREATE_ALIAS "create an alias from a message sender" +OP_CURRENT_BOTTOM "move entry to bottom of screen" +OP_CURRENT_MIDDLE "move entry to middle of screen" +OP_CURRENT_TOP "move entry to top of screen" +OP_DECODE_COPY "make decoded (text/plain) copy" +OP_DECODE_SAVE "make decoded copy (text/plain) and delete" +OP_DELETE "delete the current entry" +OP_DELETE_SUBTHREAD "delete all messages in subthread" +OP_DELETE_THREAD "delete all messages in thread" +OP_DISPLAY_ADDRESS "display full address of sender" +OP_DISPLAY_HEADERS "display message with full headers" +OP_DISPLAY_MESSAGE "display a message" +OP_EDITOR_BACKSPACE "delete the char in front of the cursor" +OP_EDITOR_BACKWARD_CHAR "move the cursor one character to the left" +OP_EDITOR_BOL "jump to the beginning of the line" +OP_EDITOR_BUFFY_CYCLE "cycle among incoming mailboxes" +OP_EDITOR_COMPLETE "complete filename or alias" +OP_EDITOR_COMPLETE_QUERY "complete address with query" +OP_EDITOR_DELETE_CHAR "delete the char under the cursor" +OP_EDITOR_EOL "jump to the end of the line" +OP_EDITOR_FORWARD_CHAR "move the cursor one character to the right" +OP_EDITOR_HISTORY_DOWN "scroll up through the history list" +OP_EDITOR_HISTORY_UP "scroll up through the history list" +OP_EDITOR_KILL_EOL "delete chars from cursor to end of line" +OP_EDITOR_KILL_LINE "delete all chars on the line" +OP_EDITOR_KILL_WORD "delete the word in front of the cursor" +OP_EDITOR_QUOTE_CHAR "quote the next typed key" +OP_ENTER_COMMAND "enter a muttrc command" +OP_ENTER_MASK "enter a file mask" +OP_EXIT "exit this menu" +OP_FILTER "filter attachment through a shell command" +OP_FIRST_ENTRY "move to the first entry" +OP_FLAG_MESSAGE "toggle a message's 'important' flag" +OP_FORWARD_MESSAGE "forward a message with comments" +OP_GENERIC_SELECT_ENTRY "select the current entry" +OP_GROUP_REPLY "reply to all recipients" +OP_HALF_DOWN "scroll down 1/2 page" +OP_HALF_UP "scroll up 1/2 page" +OP_HELP "this screen" +OP_JUMP "jump to an index number" +OP_LAST_ENTRY "move to the last entry" +OP_LIST_REPLY "reply to specified mailing list" +OP_MACRO "execute a macro" +OP_MAIL "compose a new mail message" +OP_MAIN_CHANGE_FOLDER "open a different folder" +OP_MAIN_CHANGE_FOLDER_READONLY "open a different folder in read only mode" +OP_MAIN_CLEAR_FLAG "clear a status flag from a message" +OP_MAIN_DELETE_PATTERN "delete messages matching a pattern" +OP_MAIN_FETCH_MAIL "retrieve mail from POP server" +OP_MAIN_FIRST_MESSAGE "move to the first message" +OP_MAIN_LAST_MESSAGE "move to the last message" +OP_MAIN_LIMIT "show only messages matching a pattern" +OP_MAIN_NEXT_NEW "jump to the next new message" +OP_MAIN_NEXT_SUBTHREAD "jump to the next subthread" +OP_MAIN_NEXT_THREAD "jump to the next thread" +OP_MAIN_NEXT_UNDELETED "move to the next undeleted message" +OP_MAIN_NEXT_UNREAD "jump to the next unread message" +OP_MAIN_PREV_THREAD "jump to previous thread" +OP_MAIN_PREV_SUBTHREAD "jump to previous subthread" +OP_MAIN_PREV_UNDELETED "move to the last undelete message" +OP_MAIN_PREV_NEW "jump to the previous new message" +OP_MAIN_PREV_UNREAD "jump to the previous unread message" +OP_MAIN_READ_THREAD "mark the current thread as read" +OP_MAIN_READ_SUBTHREAD "mark the current subthread as read" +OP_MAIN_SET_FLAG "set a status flag on a message" +OP_MAIN_SYNC_FOLDER "save changes to mailbox" +OP_MAIN_TAG_PATTERN "tag messages matching a pattern" +OP_MAIN_UNDELETE_PATTERN "undelete messages matching a pattern" +OP_MAIN_UNTAG_PATTERN "untag messages matching a pattern" +OP_MIDDLE_PAGE "move to the middle of the page" +OP_NEXT_ENTRY "move to the next entry" +OP_NEXT_LINE "scroll down one line" +OP_NEXT_PAGE "move to the next page" +OP_PAGER_BOTTOM "jump to the bottom of the message" +OP_PAGER_EXIT "return to the main-menu" +OP_PAGER_HIDE_QUOTED "toggle display of quoted text" +OP_PAGER_SKIP_QUOTED "skip beyond quoted text" +OP_PAGER_TOP "jump to the top of the message" +OP_PIPE "pipe message/attachment to a shell command" +OP_PREV_ENTRY "move to the previous entry" +OP_PREV_LINE "scroll up one line" +OP_PREV_PAGE "move to the previous page" +OP_PRINT "print the current entry" +OP_QUERY "query external program for addresses" +OP_QUERY_APPEND "append new query results to current results" +OP_QUIT "save changes to mailbox and quit" +OP_RECALL_MESSAGE "recall a postponed message" +OP_REDRAW "clear and redraw the screen" +OP_REPLY "reply to a message" +OP_SAVE "save message/attachment to a file" +OP_SEARCH "search for a regular expression" +OP_SEARCH_REVERSE "search backwards for a regular expression" +OP_SEARCH_NEXT "search for next match" +OP_SEARCH_OPPOSITE "search for next match in opposite direction" +OP_SEARCH_TOGGLE "toggle search pattern coloring" +OP_SHELL_ESCAPE "invoke a command in a subshell" +OP_SORT "sort messages" +OP_SORT_REVERSE "sort messages in reverse order" +OP_TAG "tag the current entry" +OP_TAG_PREFIX "apply next function to tagged messages" +OP_TAG_SUBTHREAD "tag the current subthread" +OP_TAG_THREAD "tag the current thread" +OP_TOGGLE_NEW "toggle a message's 'new' flag" +OP_TOGGLE_WRITE "toggle whether the mailbox will be rewritten" +OP_TOP_PAGE "move to the top of the page" +OP_UNDELETE "undelete the current entry" +OP_UNDELETE_THREAD "undelete all messages in thread" +OP_UNDELETE_SUBTHREAD "undelete all messages in subthread" +OP_VERSION "show the Mutt version number and date" +OP_VIEW_ATTACH "view attachment using mailcap entry if necessary" +OP_VIEW_ATTACHMENTS "show MIME attachments" +OP_MAIN_SHOW_LIMIT "show currently active limit pattern" diff --git a/OPS.PGP b/OPS.PGP new file mode 100644 index 00000000..ff1a7a22 --- /dev/null +++ b/OPS.PGP @@ -0,0 +1,7 @@ +OP_COMPOSE_ATTACH_KEY "attach a PGP public key" +OP_COMPOSE_PGP_MENU "show PGP options" +OP_EXTRACT_KEYS "extract PGP public keys" +OP_FORGET_PASSPHRASE "wipe PGP passphrase from memory" +OP_MAIL_KEY "mail a PGP public key" +OP_VERIFY_KEY "verify a PGP public key" +OP_VIEW_ID "view the key's user id" diff --git a/Pine.rc b/Pine.rc new file mode 100644 index 00000000..5eb0f86c --- /dev/null +++ b/Pine.rc @@ -0,0 +1,40 @@ +# +# This file contains commands to change the keybindings in Mutt to be +# similar to those of PINE 3.95. +# + +bind index v display-message +bind index p previous-undeleted +bind index n next-undeleted +bind index ' ' next-page +bind index c mail +bind index g change-folder +bind index w search +bind index y print-message +bind index x sync-mailbox +bind index $ sort-mailbox +bind index a tag-prefix +bind index ; tag-message + +# Not possible to simulate zoom-out... +macro index z ltagged\r + +bind pager p previous-undeleted +bind pager n next-undeleted +bind pager ' ' next-page +bind pager g change-folder +bind pager c mail +bind pager w search +bind pager y print-message +bind pager \n noop # PINE prints "No default action for this menu." +bind pager up previous-line +bind pager down next-line + +bind compose \cx send-message + +# PINE has different defaults for this variables +set folder=~/mail +set record=+sent-mail +set nosave_name +set postponed=~/postponed-msgs +set hdr_format="%Z %3C %{%b %d} %-19.19L (%5c) %s" diff --git a/README b/README new file mode 100644 index 00000000..0b566b07 --- /dev/null +++ b/README @@ -0,0 +1,17 @@ +README for mutt-0.90i +======================= + +Installation instructructions are detailed in ``INSTALL''. + +The user manual is in doc/manual.txt. + +PGP users please read doc/pgp-Notes.txt before proceeding. + +For more information, see the Mutt home page, +http://www.cs.hmc.edu/~me/mutt/index.html. + +The primary distribution point for Mutt is +ftp://ftp.cs.hmc.edu/pub/me/mutt. See the home page for mirror sites. + +Michael Elkins , January 22, 1998 +Thomas Roessler , February 3, 1998 diff --git a/TODO b/TODO new file mode 100644 index 00000000..8b4c410d --- /dev/null +++ b/TODO @@ -0,0 +1,49 @@ +- Other than multipart/mixed and PGP/MIME, Mutt should allow the user to + specify what to do with other types of multipart messages (i.e., so a user + can deal with S/MIME messages reasonably) + +- option to not include attachments in replies + +- handle message/external-body in some fashion + +- handle message/partial reconstruction + +- not possible to view the header of a single part message which contains + something that requires a mailcap entry to view + +- need to clean up the error recovery when running out of space when syncing + a mbox/mmdf mailbox + +- BODY struct should probably have a pointer to its corresponding HEADER + struct. this is needed for mh/maildir mailboxes so the correct pathname + can be found. Or perhaps all we need is a .hdr member of the STATE struct + so that all of the MIME handlers can look up the corresponding HEADERs if + need be? + +- mailbox resync code for mh and maildir mailboxes + +- fold long user-defined header fields + +- history classes + +- document honored environment variables + +- add -v flag to pass to sendmail... + +- ordered tag + +- rfc822 parser needs to do something with the @host1@host2: portion of the + route-addr + +- command completion for `enter-command' + +- new forward command to include first attachment in the editor and attach + other attachments to the message + +- adjust the names of *adr() and *adrlist() and calling routines for + consistency + +- mbox-hook entries should override $move? + +- add a preserved flag to messages + diff --git a/acconfig.h b/acconfig.h new file mode 100644 index 00000000..acd02bb0 --- /dev/null +++ b/acconfig.h @@ -0,0 +1,104 @@ + +/* Enable debugging info */ +#define DEBUG + +/* Does your version of PGP support the PGPPASSFD environment variable? */ +#define HAVE_PGPPASSFD + +/* Disable the X-Mailer header? */ +#undef NO_XMAILER + +/* What is your domain name? */ +#undef DOMAIN +@TOP@ + +/* Mutt version info */ +#undef VERSION + +/* use dotlocking to lock mailboxes? */ +#undef USE_DOTLOCK + +/* use flock() to lock mailboxes? */ +#undef USE_FLOCK + +/* Use fcntl() to lock folders? */ +#undef USE_FCNTL + +/* + * Define if you have problems with mutt not detecting new/old mailboxes + * over NFS. Some NFS implementations incorrectly cache the attributes + * of small files. + */ +#undef NFS_ATTRIBUTE_HACK + +/* Do you want support for the POP3 protocol? (--enable-pop) */ +#undef USE_POP + +/* + * Is mail spooled to the user's home directory? If defined, MAILPATH should + * be set to the filename of the spool mailbox relative the the home + * directory. + * use: configure --with-homespool=FILE + */ +#undef HOMESPOOL + +/* Where new mail is spooled */ +#undef MAILPATH + +/* Should I just use the domain name? (--enable-hidden-host) */ +#undef HIDDEN_HOST + +/* Does your system have the srand48() suite? */ +#undef HAVE_SRAND48 + +/* Where to find sendmail on your system */ +#undef SENDMAIL + +/* Where is PGP located on your system? */ +#undef _PGPPATH + +/* Where is PGP 2.* located on your system? */ +#undef _PGPV2PATH + +/* Where is PGP 5 located on your system? */ +#undef _PGPV3PATH + +/* Do we have PGP 2.*? */ +#undef HAVE_PGP2 + +/* Do we have PGP 5.0 or up? */ +#undef HAVE_PGP5 + +/* Where to find ispell on your system? */ +#undef ISPELL + +/* Should Mutt run setgid "mail" ? */ +#undef USE_SETGID + +/* Does your curses library support color? */ +#undef HAVE_COLOR + +/* Are we using GNU rx? */ +#undef USE_GNU_RX + +/* Compiling with SLang instead of curses/ncurses? */ +#undef USE_SLANG_CURSES + +/* program to use for shell commands */ +#define EXECSHELL "/bin/sh" + +/* The "buffy_size" feature */ +#undef BUFFY_SIZE + +/* The result of isprint() is unreliable? */ +#undef LOCALES_HACK + +/* Enable exact regeneration of email addresses as parsed? NOTE: this requires + significant more memory when defined. */ +#undef EXACT_ADDRESS + +/* Does your system have the snprintf() call? */ +#undef HAVE_SNPRINTF + +/* Does your system have the vsnprintf() call? */ +#undef HAVE_VSNPRINTF diff --git a/addrbook.c b/addrbook.c new file mode 100644 index 00000000..36038e09 --- /dev/null +++ b/addrbook.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_menu.h" +#include "sort.h" + +#include +#include +#include + +#define RSORT(x) (SortAlias & SORT_REVERSE) ? -x : x + +static struct mapping_t AliasHelp[] = { + { "Exit", OP_EXIT }, + { "Select", OP_GENERIC_SELECT_ENTRY }, + { "Help", OP_HELP }, + { NULL } +}; + +static const char * +alias_format_str (char *dest, size_t destlen, char op, const char *src, + const char *fmt, const char *ifstring, const char *elsestring, + unsigned long data, format_flag flags) +{ + char tmp[SHORT_STRING], adr[SHORT_STRING]; + ALIAS *alias = (ALIAS *) data; + + switch (op) + { + case 'a': + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, alias->name); + break; + case 'r': + adr[0] = 0; + rfc822_write_address (adr, sizeof (adr), alias->addr); + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, adr); + break; + case 'n': + snprintf (tmp, sizeof (tmp), "%%%sd", fmt); + snprintf (dest, destlen, tmp, alias->num + 1); + break; + case 't': + dest[0] = alias->tagged ? '*' : ' '; + dest[1] = 0; + break; + } + + return (src); +} + +int alias_search (MUTTMENU *m, regex_t *re, int n) +{ + int i, match = 0; + char s[LONG_STRING]; + int slen = sizeof(s); + + for (i=0; i < m->max; i++) + { + mutt_FormatString (s, slen, NONULL (AliasFmt), alias_format_str, + (unsigned long) ((ALIAS **) m->data)[n], 0); + if ((match = regexec (re, s, 0, NULL, 0))) return match; + } + return (0); +} + + +void alias_entry (char *s, size_t slen, MUTTMENU *m, int num) +{ + mutt_FormatString (s, slen, NONULL (AliasFmt), alias_format_str, (unsigned long) ((ALIAS **) m->data)[num], 0); +} + +int alias_tag (MUTTMENU *menu, int n) +{ + return (((ALIAS **) menu->data)[n]->tagged = !((ALIAS **) menu->data)[n]->tagged); +} + +static int alias_SortAlias (const void *a, const void *b) +{ + ALIAS *pa = *(ALIAS **) a; + ALIAS *pb = *(ALIAS **) b; + int r = strcasecmp (pa->name, pb->name); + + return (RSORT (r)); +} + +static int alias_SortAddress (const void *a, const void *b) +{ + ADDRESS *pa = (*(ALIAS **) a)->addr; + ADDRESS *pb = (*(ALIAS **) b)->addr; + int r; + + if (pa->personal) + { + if (pb->personal) + r = strcasecmp (pa->personal, pb->personal); + else + r = 1; + } + else if (pb->personal) + r = -1; + else + r = strcasecmp (pa->mailbox, pb->mailbox); + return (RSORT (r)); +} + +void mutt_alias_menu (char *buf, size_t buflen, ALIAS *aliases) +{ + ALIAS *aliasp; + MUTTMENU *menu; + ALIAS **AliasTable = NULL; + int t = -1; + int i, done = 0; + char helpstr[SHORT_STRING]; + + if (!aliases) + { + mutt_error ("You have no aliases!"); + return; + } + + /* tell whoever called me to redraw the screen when I return */ + set_option (OPTNEEDREDRAW); + + menu = mutt_new_menu (); + menu->make_entry = alias_entry; + menu->search = alias_search; + menu->tag = alias_tag; + menu->menu = MENU_ALIAS; + menu->title = "Aliases"; + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_ALIAS, AliasHelp); + + /* count the number of aliases */ + for (aliasp = aliases; aliasp; aliasp = aliasp->next) + menu->max++; + + menu->data = AliasTable = (ALIAS **) safe_calloc (menu->max, sizeof (ALIAS *)); + + for (i = 0, aliasp = aliases; aliasp; aliasp = aliasp->next, i++) + AliasTable[i] = aliasp; + + if ((SortAlias & SORT_MASK) != SORT_ORDER) + { + qsort (AliasTable, i, sizeof (ALIAS *), + (SortAlias & SORT_MASK) == SORT_ADDRESS ? alias_SortAddress : alias_SortAlias); + } + + for (i=0; imax; i++) AliasTable[i]->num = i; + + while (!done) + { + switch (mutt_menuLoop (menu)) + { + case OP_GENERIC_SELECT_ENTRY: + t = menu->current; + case OP_EXIT: + done = 1; + break; + } + } + + for (i = 0; i < menu->max; i++) + { + if (AliasTable[i]->tagged) + { + rfc822_write_address (buf, buflen, AliasTable[i]->addr); + t = -1; + } + } + + if(t != -1) + rfc822_write_address (buf, buflen, AliasTable[t]->addr); + + mutt_menuDestroy (&menu); + safe_free ((void **) &AliasTable); +} diff --git a/alias.c b/alias.c new file mode 100644 index 00000000..b4cd7c07 --- /dev/null +++ b/alias.c @@ -0,0 +1,401 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_regex.h" + +#include +#include + +static ADDRESS *lookup_alias (const char *s) +{ + ALIAS *t = Aliases; + + for (; t; t = t->next) + if (!strcasecmp (s, t->name)) + return (t->addr); + return (NULL); /* no such alias */ +} + +static ADDRESS *mutt_expand_aliases_r (ADDRESS *a, LIST **expn) +{ + ADDRESS *head = NULL, *last = NULL, *t, *w; + LIST *u; + char i; + + while (a) + { + if (!a->group && !a->personal && a->mailbox && strchr (a->mailbox, '@') == NULL) + { + t = lookup_alias (a->mailbox); + + if (t) + { + i = 0; + for (u = *expn; u; u = u->next) + { + if (strcmp (a->mailbox, u->data) == 0) /* alias already found */ + { + dprint (1, (debugfile, "mutt_expand_aliases_r(): loop in alias found for '%s'\n", a->mailbox)); + i = 1; + break; + } + } + + if (!i) + { + u = safe_malloc (sizeof (LIST)); + u->data = safe_strdup (a->mailbox); + u->next = *expn; + *expn = u; + w = rfc822_cpy_adr (t); + w = mutt_expand_aliases_r (w, expn); + if (head) + last->next = w; + else + head = last = w; + while (last && last->next) + last = last->next; + } + t = a; + a = a->next; + t->next = NULL; + rfc822_free_address (&t); + continue; + } + else + { + struct passwd *pw = getpwnam (a->mailbox); + char buffer[256], *p; + + if (pw) + { + strfcpy (buffer, pw->pw_gecos, sizeof (buffer)); + if ((p = strchr (buffer, ','))) + *p = 0; + a->personal = safe_strdup (buffer); +#ifdef EXACT_ADDRESS + free (a->val); + a->val = NULL; +#endif + } + } + } + + if (head) + { + last->next = a; + last = last->next; + } + else + head = last = a; + a = a->next; + last->next = NULL; + } + + if (option (OPTUSEDOMAIN) && Fqdn[0] != '@') + { + /* now qualify all local addresses */ + rfc822_qualify (head, Fqdn); + } + + return (head); +} + +ADDRESS *mutt_expand_aliases (ADDRESS *a) +{ + ADDRESS *t; + LIST *expn = NULL; /* previously expanded aliases to avoid loops */ + + t = mutt_expand_aliases_r (a, &expn); + mutt_free_list (&expn); + return (t); +} + +/* if someone has an address like + * From: Michael `/bin/rm -f ~` Elkins + * and the user creates an alias for this, Mutt could wind up executing + * the backtics because it writes aliases like + * alias me Michael `/bin/rm -f ~` Elkins + * To avoid this problem, use a backslash (\) to quote any backtics. We also + * need to quote backslashes as well, since you could defeat the above by + * doing + * From: Michael \`/bin/rm -f ~\` Elkins + * since that would get aliased as + * alias me Michael \\`/bin/rm -f ~\\` Elkins + * which still gets evaluated because the double backslash is not a quote. + */ +static void write_safe_address (FILE *fp, char *s) +{ + while (*s) + { + if (*s == '\\' || *s == '`') + fputc ('\\', fp); + fputc (*s, fp); + s++; + } +} + +void mutt_create_alias (ENVELOPE *cur, ADDRESS *iadr) +{ + ALIAS *new, *t; + char buf[LONG_STRING], prompt[SHORT_STRING], *pc; + FILE *rc; + ADDRESS *adr = NULL; + + if (cur) + { + if (mutt_addr_is_user (cur->from)) + { + if (cur->to && !mutt_is_mail_list (cur->to)) + adr = cur->to; + else + adr = cur->cc; + } + else if (cur->reply_to && !mutt_is_mail_list (cur->reply_to)) + adr = cur->reply_to; + else + adr = cur->from; + + } + else if (iadr) + { + adr = iadr; + } + + if (adr && adr->mailbox) + { + strfcpy (buf, adr->mailbox, sizeof (buf)); + if ((pc = strchr (buf, '@'))) + *pc = 0; + } + else + buf[0] = '\0'; + + if (mutt_get_field ("Alias as: ", buf, sizeof (buf), 0) != 0 || !buf[0]) + return; + + /* check to see if the user already has an alias defined */ + if (lookup_alias (buf)) + { + mutt_error ("You already have an alias defined with that name!"); + return; + } + + new = safe_calloc (1, sizeof (ALIAS)); + new->name = safe_strdup (buf); + + if (adr) + strfcpy (buf, adr->mailbox, sizeof (buf)); + else + buf[0] = 0; + + if (mutt_get_field ("Address: ", buf, sizeof (buf), 0) != 0 || !buf[0]) + { + mutt_free_alias (&new); + return; + } + new->addr = rfc822_parse_adrlist (new->addr, buf); + + if (adr && adr->personal && !mutt_is_mail_list (adr)) + strfcpy (buf, adr->personal, sizeof (buf)); + else + buf[0] = 0; + + if (mutt_get_field ("Personal name: ", buf, sizeof (buf), 0) != 0) + { + mutt_free_alias (&new); + return; + } + new->addr->personal = safe_strdup (buf); + + buf[0] = 0; + rfc822_write_address (buf, sizeof (buf), new->addr); + snprintf (prompt, sizeof (prompt), "[%s = %s] Accept?", new->name, buf); + if (mutt_yesorno (prompt, 1) != 1) + { + mutt_free_alias (&new); + return; + } + + if ((t = Aliases)) + { + while (t->next) + t = t->next; + t->next = new; + } + else + Aliases = new; + + strfcpy (buf, NONULL (AliasFile), sizeof (buf)); + if (mutt_get_field ("Save to file: ", buf, sizeof (buf), M_FILE) != 0) + return; + mutt_expand_path (buf, sizeof (buf)); + if ((rc = fopen (buf, "a"))) + { + buf[0] = 0; + rfc822_write_address (buf, sizeof (buf), new->addr); + fprintf (rc, "alias %s ", new->name); + write_safe_address (rc, buf); + fputc ('\n', rc); + fclose (rc); + mutt_message ("Alias added."); + } + else + mutt_perror (buf); +} + +/* + * This routine looks to see if the user has an alias defined for the given + * address. + */ +ADDRESS *alias_reverse_lookup (ADDRESS *a) +{ + ALIAS *t = Aliases; + ADDRESS *ap; + + if (!a || !a->mailbox) + return NULL; + + for (; t; t = t->next) + { + /* cycle through all addresses if this is a group alias */ + for (ap = t->addr; ap; ap = ap->next) + { + if (!ap->group && ap->mailbox && + strcasecmp (ap->mailbox, a->mailbox) == 0) + return ap; + } + } + return 0; +} + +/* alias_complete() -- alias completion routine + * + * given a partial alias, this routine attempts to fill in the alias + * from the alias list as much as possible + */ +int mutt_alias_complete (char *s, size_t buflen) +{ + ALIAS *a = Aliases; + ALIAS *a_list = NULL, *a_cur = NULL; + char bestname[STRING]; + int i; + + memset (bestname, 0, sizeof (bestname)); + +#define min(a,b) ((aname && strstr (a->name, s) == a->name) + { + if (!bestname[0]) /* init */ + strfcpy (bestname, a->name, min (strlen (a->name) + 1, sizeof (bestname))); + else + { + for (i = 0 ; a->name[i] && a->name[i] == bestname[i] ; i++) + ; + bestname[i] = 0; + } + } + a = a->next; + } + + if ((bestname[0] == 0) || /* if we didn't find anything */ + (s[0] == 0)) /* or we weren't given anything */ + { + mutt_alias_menu (s, buflen, Aliases); + return 0; + } + else + { + if (strcmp (bestname, s) == 0) /* add anything to the completion? */ + { + /* build alias list and show it */ + a = Aliases; + while (a) + { + if (a->name && (strstr (a->name, s) == a->name)) + { + if (!a_list) /* init */ + a_cur = a_list = (ALIAS *) safe_malloc (sizeof (ALIAS)); + else + { + a_cur->next = (ALIAS *) safe_malloc (sizeof (ALIAS)); + a_cur = a_cur->next; + } + memcpy (a_cur, a, sizeof (ALIAS)); + a_cur->next = NULL; + } + a = a->next; + } + + s[0] = 0; /* reset string before passing to alias_menu */ + mutt_alias_menu (s, buflen, a_list); + + /* free the alias list */ + while (a_list) + { + a_cur = a_list; + a_list = a_list->next; + safe_free ((void **) &a_cur); + } + + return 0; + } + else /* we are adding something to the completion */ + strfcpy (s, bestname, strlen (bestname) + 1); + } + + return 1; +} + +/* returns TRUE if the given address belongs to the user. */ +int mutt_addr_is_user (ADDRESS *addr) +{ + char buf[LONG_STRING]; + + /* NULL address is assumed to be the user. */ + if (!addr) + return 1; + if (!addr->mailbox) + return 0; + if (strcasecmp (addr->mailbox, Username) == 0) + return 1; + snprintf (buf, sizeof (buf), "%s@%s", Username, Hostname); + if (strcasecmp (addr->mailbox, buf) == 0) + return 1; + snprintf (buf, sizeof (buf), "%s@%s", Username, Fqdn); + if (strcasecmp (addr->mailbox, buf) == 0) + return 1; + + if (Alternates.pattern && + regexec (Alternates.rx, addr->mailbox, 0, NULL, 0) == 0) + return 1; + return 0; +} + +/* returns 1 if the list of address contains a known mailing list */ +int mutt_is_list_recipient (ADDRESS *a) +{ + for (; a; a = a->next) + if (mutt_is_mail_list (a)) + return 1; + return 0; +} diff --git a/attach.c b/attach.c new file mode 100644 index 00000000..6ee4631e --- /dev/null +++ b/attach.c @@ -0,0 +1,769 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_menu.h" +#include "mutt_curses.h" +#include "keymap.h" +#include "rfc1524.h" +#include "mime.h" +#include "pager.h" + +#include +#include +#include +#include +#include +#include +#include + +/* return 1 if require full screen redraw, 0 otherwise */ +int mutt_compose_attachment (BODY *a) +{ + char type[STRING]; + char command[STRING]; + rfc1524_entry *entry = rfc1524_new_entry (); + + snprintf (type, sizeof (type), "%s/%s", TYPE (a->type), a->subtype); + if (rfc1524_mailcap_lookup (a, type, entry, M_COMPOSE)) + { + if (entry->composecommand || entry->composetypecommand) + { + char newfile[_POSIX_PATH_MAX] = ""; + + if (entry->composetypecommand) + strfcpy (command, entry->composetypecommand, sizeof (command)); + else + strfcpy (command, entry->composecommand, sizeof (command)); + if (rfc1524_expand_filename (entry->nametemplate, + a->filename, newfile, sizeof (newfile))) + { + dprint(1, (debugfile, "oldfile: %s\t newfile: %s\n", + a->filename, newfile)); + if (!mutt_rename_file (a->filename, newfile)) + { + if (!mutt_yesorno ("Can't match nametemplate, continue?", 1)) + return 0; + } + else + { + safe_free ((void **) &a->filename); + a->filename = safe_strdup (newfile); + } + } + + if (rfc1524_expand_command (a, a->filename, type, + command, sizeof (command))) + { + /* For now, editing requires a file, no piping */ + mutt_error ("Mailcap compose entry requires %%s"); + } + else + { + endwin (); + mutt_system (command); + if (entry->composetypecommand) + { + BODY *b; + FILE *fp, *tfp; + char tempfile[_POSIX_PATH_MAX]; + + if ((fp = safe_fopen (a->filename, "r")) == NULL) + { + mutt_perror ("Failure to open file to parse headers."); + return 0; + } + + b = mutt_read_mime_header (fp, 0); + if (b) + { + if (b->parameter) + { + mutt_free_parameter (&a->parameter); + a->parameter = b->parameter; + b->parameter = NULL; + } + if (b->description) { + safe_free ((void **) &a->description); + a->description = b->description; + b->description = NULL; + } + if (b->form_name) + { + safe_free ((void **) &a->form_name); + a->form_name = b->form_name; + b->form_name = NULL; + } + + /* Remove headers by copying out data to another file, then + * copying the file back */ + fseek (fp, b->offset, 0); + mutt_mktemp (tempfile); + if ((tfp = safe_fopen (tempfile, "w")) == NULL) + { + mutt_perror ("Failure to open file to strip headers."); + return 0; + } + mutt_copy_stream (fp, tfp); + fclose (fp); + fclose (tfp); + mutt_unlink (a->filename); + mutt_rename_file (tempfile, a->filename); + + mutt_free_body (&b); + } + } + } + } + } + else + { + rfc1524_free_entry (&entry); + mutt_message ("No mailcap compose entry for %s, creating empty file.",type); + return 1; + } + + rfc1524_free_entry (&entry); + return 1; +} + +/* + * Currently, this only works for send mode, as it assumes that the + * BODY->filename actually contains the information. I'm not sure + * we want to deal with editing attachments we've already received, + * so this should be ok. + * + * Returns 1 if editor found, 0 if not (useful to tell calling menu to + * redraw) + */ +int mutt_edit_attachment (BODY *a, int opt) +{ + char type[STRING]; + char command[STRING]; + rfc1524_entry *entry = rfc1524_new_entry (); + + snprintf (type, sizeof (type), "%s/%s", TYPE (a->type), a->subtype); + if (rfc1524_mailcap_lookup (a, type, entry, M_EDIT)) + { + if (entry->editcommand) + { + char newfile[_POSIX_PATH_MAX] = ""; + + strfcpy (command, entry->editcommand, sizeof (command)); + if (rfc1524_expand_filename (entry->nametemplate, + a->filename, newfile, sizeof (newfile))) + { + dprint(1, (debugfile, "oldfile: %s\t newfile: %s\n", + a->filename, newfile)); + if (mutt_rename_file (a->filename, newfile)) + { + if (!mutt_yesorno ("Can't match nametemplate, continue?", 1)) + return 0; + } + else + { + safe_free ((void **) &a->filename); + a->filename = safe_strdup (newfile); + } + } + + if (rfc1524_expand_command (a, a->filename, type, + command, sizeof (command))) + { + /* For now, editing requires a file, no piping */ + mutt_error ("Mailcap Edit entry requires %%s"); + } + else + { + endwin (); + mutt_system (command); + } + } + } + else if (a->type == TYPETEXT) + { + /* On text, default to editor */ + mutt_edit_file (strcmp ("builtin", Editor) == 0 ? Visual : Editor, + a->filename); + } + else + { + rfc1524_free_entry (&entry); + mutt_error ("No mailcap edit entry for %s",type); + return 0; + } + + rfc1524_free_entry (&entry); + return 1; +} + +int mutt_is_autoview (char *type) +{ + LIST *t = AutoViewList; + int i; + + while (t) + { + i = strlen (t->data) - 1; + if ((i > 0 && t->data[i-1] == '/' && t->data[i] == '*' && + strncasecmp (type, t->data, i) == 0) || + strcasecmp (type, t->data) == 0) + return 1; + t = t->next; + } + + return 0; +} + +/* returns -1 on error, 0 or the return code from mutt_do_pager() on success */ +int mutt_view_attachment (FILE *fp, BODY *a, int flag) +{ + char tempfile[_POSIX_PATH_MAX] = ""; + char pagerfile[_POSIX_PATH_MAX] = ""; + int is_message; + int use_mailcap; + int use_pipe = 0; + int use_pager = 1; + char type[STRING]; + char command[STRING]; + char descrip[STRING]; + rfc1524_entry *entry = NULL; + int rc = -1; + + is_message = (a->type == TYPEMESSAGE && a->subtype && + (!strcasecmp (a->subtype,"rfc822") || + !strcasecmp (a->subtype, "news"))); + use_mailcap = (flag == M_MAILCAP || + (flag == M_REGULAR && mutt_needs_mailcap (a))); + snprintf (type, sizeof (type), "%s/%s", TYPE (a->type), a->subtype); + + if (use_mailcap) + { + entry = rfc1524_new_entry (); + if (!rfc1524_mailcap_lookup (a, type, entry, 0)) + { + if (flag == M_REGULAR) + { + /* fallback to view as text */ + rfc1524_free_entry (&entry); + mutt_error ("No matching mailcap entry found. Viewing as text."); + flag = M_AS_TEXT; + use_mailcap = 0; + } + else + goto return_error; + } + } + + if (use_mailcap) + { + if (!entry->command) + { + mutt_error ("MIME type not defined. Cannot view attachment."); + goto return_error; + } + strfcpy (command, entry->command, sizeof (command)); + + if (rfc1524_expand_filename (entry->nametemplate, a->filename, + tempfile, sizeof (tempfile))) + { + if (fp == NULL) + { + /* send case: the file is already there */ + if (mutt_rename_file (a->filename, tempfile)) + { + if (mutt_yesorno ("Can't match nametemplate, continue?", 1) == M_YES) + strfcpy (tempfile, a->filename, sizeof (tempfile)); + else + goto return_error; + } + else + { + safe_free ((void **) &a->filename); + a->filename = safe_strdup (tempfile); + } + } + } + else if (fp == NULL) /* send case */ + strfcpy (tempfile, a->filename, sizeof (tempfile)); + + if (fp) + { + /* recv case: we need to save the attachment to a file */ + if (mutt_save_attachment (fp, a, tempfile, 0) == -1) + goto return_error; + } + + use_pipe = rfc1524_expand_command (a, tempfile, type, + command, sizeof (command)); + use_pager = entry->copiousoutput; + } + + if (use_pager) + { + if (fp && !use_mailcap && a->filename) + { + /* recv case */ + strfcpy (pagerfile, a->filename, sizeof (pagerfile)); + mutt_adv_mktemp (pagerfile); + } + else + mutt_mktemp (pagerfile); + } + + if (use_mailcap) + { + pid_t thepid = 0; + FILE *pagerfp = NULL; + FILE *tempfp = NULL; + FILE *filter_in; + FILE *filter_out; + + if (!use_pager) + endwin (); + + if (use_pager || use_pipe) + { + if (use_pager && ((pagerfp = safe_fopen (pagerfile, "w")) == NULL)) + { + mutt_perror ("fopen"); + goto return_error; + } + if (use_pipe && ((tempfp = fopen (tempfile, "r")) == NULL)) + { + if (pagerfp) + fclose (pagerfp); + mutt_perror ("fopen"); + goto return_error; + } + + if ((thepid = mutt_create_filter (command, use_pipe ? &filter_in : NULL, + use_pager ? &filter_out : NULL, NULL)) == -1) + { + if (pagerfp) + fclose (pagerfp); + if (tempfp) + fclose (tempfp); + mutt_error ("Cannot create filter"); + goto return_error; + } + + if (use_pipe) + { + mutt_copy_stream (tempfp, filter_in); + fclose (tempfp); + fclose (filter_in); + } + if (use_pager) + { + mutt_copy_stream (filter_out, pagerfp); + fclose (filter_out); + fclose (pagerfp); + if (a->description) + snprintf (descrip, sizeof (descrip), + "---Command: %-20.20s Description: %s", + command, a->description); + else + snprintf (descrip, sizeof (descrip), + "---Command: %-30.30s Attachment: %s", command, type); + } + + if ((mutt_wait_filter (thepid) || (entry->needsterminal && + option (OPTWAITKEY))) && !use_pager) + mutt_any_key_to_continue (NULL); + } + else + { + /* interactive command */ + if (mutt_system (command) || + (entry->needsterminal && option (OPTWAITKEY))) + mutt_any_key_to_continue (NULL); + } + } + else + { + /* Don't use mailcap; the attachment is viewed in the pager */ + + if (flag == M_AS_TEXT) + { + /* just let me see the raw data */ + if (mutt_save_attachment (fp, a, pagerfile, 0)) + goto return_error; + } + else + { + /* Use built-in handler */ + set_option (OPTVIEWATTACH); /* disable the "use 'v' to view this part" + * message in case of error */ + if (mutt_decode_save_attachment (fp, a, pagerfile, 1, 0)) + { + unset_option (OPTVIEWATTACH); + goto return_error; + } + unset_option (OPTVIEWATTACH); + } + + if (a->description) + strfcpy (descrip, a->description, sizeof (descrip)); + else + snprintf (descrip, sizeof (descrip), "---Attachment: %s", type); + } + + /* We only reach this point if there have been no errors */ + + if (use_pager) + { + pager_t info; + + memset (&info, 0, sizeof (info)); + info.fp = fp; + info.bdy = a; + info.ctx = Context; + rc = mutt_do_pager (descrip, pagerfile, is_message, &info); + } + else + rc = 0; + + return_error: + + if (entry) + rfc1524_free_entry (&entry); + if (fp && tempfile[0]) + mutt_unlink (tempfile); + if (pagerfile[0]) + mutt_unlink (pagerfile); + + return rc; +} + +/* returns 1 on success, 0 on error */ +int mutt_pipe_attachment (FILE *fp, BODY *b, const char *path, char *outfile) +{ + STATE o; + pid_t thepid; + + memset (&o, 0, sizeof (STATE)); + + if (outfile && *outfile) + if ((o.fpout = safe_fopen (outfile, "w")) == NULL) + { + mutt_perror ("fopen"); + return 0; + } + + endwin (); + + if (fp) + { + /* recv case */ + + STATE s; + + memset (&s, 0, sizeof (STATE)); + + if (outfile && *outfile) + thepid = mutt_create_filter (path, &s.fpout, &o.fpin, NULL); + else + thepid = mutt_create_filter (path, &s.fpout, NULL, NULL); + + s.fpin = fp; + mutt_decode_attachment (b, &s); + fclose (s.fpout); + } + else + { + /* send case */ + + FILE *ifp, *ofp; + + if ((ifp = fopen (b->filename, "r")) == NULL) + { + mutt_perror ("fopen"); + fclose (o.fpout); + return 0; + } + + if (outfile && *outfile) + thepid = mutt_create_filter (path, &ofp, &o.fpin, NULL); + else + thepid = mutt_create_filter (path, &ofp, NULL, NULL); + + mutt_copy_stream (ifp, ofp); + fclose (ofp); + fclose (ifp); + } + + if (outfile && *outfile) + { + mutt_copy_stream (o.fpin, o.fpout); + fclose (o.fpin); + fclose (o.fpout); + } + + if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY)) + mutt_any_key_to_continue (NULL); + return 1; +} + +/* returns 0 on success, -1 on error */ +int mutt_save_attachment (FILE *fp, BODY *m, char *path, int flags) +{ + if (fp) + { + /* In recv mode, extract from folder and decode */ + + STATE s; + + memset (&s, 0, sizeof (s)); + if (flags == M_SAVE_APPEND) + s.fpout = safe_fopen (path, "a"); + else + s.fpout = fopen (path, "w"); + if (s.fpout == NULL) + { + mutt_perror ("fopen"); + return (-1); + } + fseek ((s.fpin = fp), m->offset, 0); + mutt_decode_attachment (m, &s); + + if (fclose (s.fpout) != 0) + { + mutt_perror ("fclose"); + return (-1); + } + } + else + { + /* In send mode, just copy file */ + + FILE *ofp, *nfp; + + if ((ofp = fopen (m->filename, "r")) == NULL) + { + mutt_perror ("fopen"); + return (-1); + } + + if ((nfp = safe_fopen (path, "w")) == NULL) + { + mutt_perror ("fopen"); + fclose (ofp); + return (-1); + } + + if (mutt_copy_stream (ofp, nfp) == -1) + { + mutt_error ("Write fault!"); + fclose (ofp); + fclose (nfp); + return (-1); + } + fclose (ofp); + fclose (nfp); + } + + return 0; +} + +/* returns 0 on success, -1 on error */ +int mutt_decode_save_attachment (FILE *fp, BODY *m, char *path, + int displaying, int flags) +{ + STATE s; + unsigned int saved_encoding = 0; + + memset (&s, 0, sizeof (s)); + s.flags = displaying ? M_DISPLAY : 0; + + if (flags == M_SAVE_APPEND) + s.fpout = safe_fopen (path, "a"); + else + s.fpout = fopen (path, "w"); + if (s.fpout == NULL) + { + perror ("fopen"); + return (-1); + } + + if (fp == NULL) + { + /* When called from the compose menu, the attachment isn't parsed, + * so we need to do it here. */ + struct stat st; + + if (stat (m->filename, &st) == -1) + { + perror ("stat"); + fclose (s.fpout); + return (-1); + } + + if ((s.fpin = fopen (m->filename, "r")) == NULL) + { + perror ("fopen"); + return (-1); + } + + saved_encoding = m->encoding; + m->length = st.st_size; + m->encoding = ENC8BIT; + m->offset = 0; + if (m->type == TYPEMESSAGE && m->subtype && + (!strcasecmp (m->subtype,"rfc822") || + !strcasecmp (m->subtype, "news"))) + m->parts = mutt_parse_messageRFC822 (s.fpin, m); + } + else + s.fpin = fp; + + mutt_body_handler (m, &s); + + fclose (s.fpout); + if (fp == NULL) + { + m->length = 0; + m->encoding = saved_encoding; + if (m->parts) + mutt_free_body (&m->parts); + fclose (s.fpin); + } + + return (0); +} + +/* Ok, the difference between send and receive: + * recv: BODY->filename is a suggested name, and Context|HEADER points + * to the attachment in mailbox which is encooded + * send: BODY->filename points to the un-encoded file which contains the + * attachment + */ + +int mutt_print_attachment (FILE *fp, BODY *a) +{ + char newfile[_POSIX_PATH_MAX] = ""; + char type[STRING]; + pid_t thepid; + FILE *ifp, *fpout; + + snprintf (type, sizeof (type), "%s/%s", TYPE (a->type), a->subtype); + + if (rfc1524_mailcap_lookup (a, type, NULL, M_PRINT)) + { + char command[_POSIX_PATH_MAX+STRING]; + rfc1524_entry *entry; + int piped = FALSE; + + entry = rfc1524_new_entry (); + rfc1524_mailcap_lookup (a, type, entry, M_PRINT); + if (rfc1524_expand_filename (entry->nametemplate, a->filename, + newfile, sizeof (newfile))) + { + if (!fp) + { + /* only attempt file move in send mode */ + + if (mutt_rename_file (a->filename, newfile)) + { + if (mutt_yesorno ("Can't match nametemplate, continue?", 1) != M_YES) + { + rfc1524_free_entry (&entry); + return 0; + } + strfcpy (newfile, a->filename, sizeof (newfile)); + } + else + { + safe_free ((void **)&a->filename); + a->filename = safe_strdup (newfile); + } + } + } + + /* in recv mode, save file to newfile first */ + if (fp) + mutt_save_attachment (fp, a, newfile, 0); + + strfcpy (command, entry->printcommand, sizeof (command)); + piped = rfc1524_expand_command (a, newfile, type, command, sizeof (command)); + + endwin (); + + /* interactive program */ + if (piped) + { + if ((ifp = fopen (newfile, "r")) == NULL) + { + mutt_perror ("fopen"); + rfc1524_free_entry (&entry); + return (0); + } + + thepid = mutt_create_filter (command, &fpout, NULL, NULL); + mutt_copy_stream (ifp, fpout); + fclose (fpout); + fclose (ifp); + if (mutt_wait_filter (thepid) || option (OPTWAITKEY)) + mutt_any_key_to_continue (NULL); + } + else + { + if (mutt_system (command) || option (OPTWAITKEY)) + mutt_any_key_to_continue (NULL); + } + + if (fp) + mutt_unlink (newfile); + + rfc1524_free_entry (&entry); + return (1); + } + + if (!strcasecmp ("text/plain", a->subtype) || + !strcasecmp ("application/postscript", a->subtype)) + return (mutt_pipe_attachment (fp, a, PrintCmd, NULL)); + else if (mutt_can_decode (a)) + { + /* decode and print */ + + int rc = 0; + + mutt_mktemp (newfile); + if (mutt_decode_save_attachment (fp, a, newfile, 0, 0) == 0) + { + if ((ifp = fopen (newfile, "r")) != NULL) + { + endwin (); + thepid = mutt_create_filter (PrintCmd, &fpout, NULL, NULL); + mutt_copy_stream (ifp, fpout); + fclose (ifp); + fclose (fpout); + if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY)) + mutt_any_key_to_continue (NULL); + rc = 1; + } + } + mutt_unlink (newfile); + return rc; + } + else + { + mutt_error ("I don't know how to print that!"); + return 0; + } +} diff --git a/attach.h b/attach.h new file mode 100644 index 00000000..46e374dc --- /dev/null +++ b/attach.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* common protos for compose / attach menus */ + +int mutt_tag_attach (MUTTMENU *menu, int n); + +void mutt_save_attachment_list (FILE *fp, int tag, BODY *top); +void mutt_pipe_attachment_list (FILE *fp, int tag, BODY *top, int filter); +void mutt_print_attachment_list (FILE *fp, int tag, BODY *top); +void mutt_attach_display_loop (MUTTMENU *menu, int op, FILE *fp, ATTACHPTR **idx); diff --git a/bind.c b/bind.c new file mode 100644 index 00000000..a327bf70 --- /dev/null +++ b/bind.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "keymap.h" +#include "mapping.h" + +#include + +static struct mapping_t Menus[] = { + { "alias", MENU_ALIAS }, + { "attach", MENU_ATTACH }, + { "browser", MENU_FOLDER }, + { "compose", MENU_COMPOSE }, + { "editor", MENU_EDITOR }, + { "generic", MENU_GENERIC }, + { "index", MENU_MAIN }, + { "pager", MENU_PAGER }, + + + +#ifdef _PGPPATH + { "pgp", MENU_PGP }, +#endif /* _PGPPATH */ + + + + { NULL, 0 } +}; + +#define mutt_check_menu(s) mutt_getvaluebyname(s, Menus) + +/* expects to see: */ +static const char *parse_keymap (int *menu, + char *key, + size_t keylen, + const char *s, + char *err, + size_t errlen) +{ + char buf[SHORT_STRING]; + char expn[SHORT_STRING]; + + /* menu name */ + s = mutt_extract_token (buf, sizeof (buf), s, expn, sizeof (expn), 0); + + if (s) + { + if ((*menu = mutt_check_menu (buf)) == -1) + { + snprintf (err, errlen, "%s: no such menu", s); + return (NULL); + } + + /* key sequence */ + s = mutt_extract_token (key, keylen, s, expn, sizeof (expn), 0); + + if (s) + return s; + } + + strfcpy (err, "too few arguments", errlen); + return (NULL); +} + +static int +try_bind (char *key, int menu, char *func, struct binding_t *bindings) +{ + int i; + + for (i = 0; bindings[i].name; i++) + if (strcmp (func, bindings[i].name) == 0) + { + km_bindkey (key, menu, bindings[i].op, NULL); + return (0); + } + return (-1); +} + +/* bind menu-name '' function-name */ +int mutt_parse_bind (const char *s, unsigned long data, char *err, size_t errlen) +{ + struct binding_t *bindings = NULL; + char buf[SHORT_STRING]; + char key[SHORT_STRING]; + char expn[SHORT_STRING]; + int menu; + + if ((s = parse_keymap (&menu, key, sizeof (key), s, err, errlen)) == NULL) + return (-1); + + switch (menu) + { + case MENU_MAIN: + bindings = OpMain; + break; + case MENU_GENERIC: + bindings = OpGeneric; + break; + case MENU_COMPOSE: + bindings = OpCompose; + break; + case MENU_PAGER: + bindings = OpPager; + break; + case MENU_POST: + bindings = OpPost; + break; + case MENU_FOLDER: + bindings = OpBrowser; + break; + case MENU_ATTACH: + bindings = OpAttach; + break; + case MENU_EDITOR: + bindings = OpEditor; + break; + case MENU_ALIAS: + bindings = OpAlias; + break; + + + +#ifdef _PGPPATH + case MENU_PGP: + bindings = OpPgp; + break; +#endif /* _PGPPATH */ + + + + } + + /* function to execute */ + s = mutt_extract_token (buf, sizeof (buf), s, expn, sizeof (expn), 0); + if (s) + { + strfcpy (err, "too many arguments", errlen); + return (-1); + } + + if (strcasecmp ("noop", buf) == 0) + { + km_bindkey (key, menu, OP_NULL, NULL); + return 0; + } + + if (menu != MENU_PAGER && menu != MENU_EDITOR && menu != MENU_GENERIC) + { + /* First check the "generic" list of commands. */ + if (try_bind (key, menu, buf, OpGeneric) == 0) + return 0; + } + + /* Now check the menu-specific list of commands (if they exist). */ + if (bindings && try_bind (key, menu, buf, bindings) == 0) + return 0; + + snprintf (err, errlen, "%s: no such function in map", buf); + return (-1); +} + +/* macro */ +int mutt_parse_macro (const char *s, unsigned long data, char *err, size_t errlen) +{ + int menu; + char key[SHORT_STRING]; + char buf[SHORT_STRING]; + char expn[SHORT_STRING]; + + if ((s = parse_keymap (&menu, key, sizeof (key), s, err, errlen)) == NULL) + return (-1); + + s = mutt_extract_token (buf, sizeof (buf), s, expn, sizeof (expn), M_CONDENSE); + if (s) + { + strfcpy (err, "too many arguments", errlen); + return (-1); + } + + km_bindkey (key, menu, OP_MACRO, buf); + + return 0; +} diff --git a/browser.c b/browser.c new file mode 100644 index 00000000..b66982cf --- /dev/null +++ b/browser.c @@ -0,0 +1,714 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "mutt_menu.h" +#include "buffy.h" +#include "sort.h" +#include "mailbox.h" + +#include +#include +#include +#include +#include +#include +#include + +/* HP-UX and ConvexOS don't have this macro */ +#ifndef S_ISLNK +#define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK ? 1 : 0) +#endif + +struct folder_file +{ + mode_t mode; + time_t mtime; + off_t size; + char *name; + char *desc; +}; + +struct browser_state +{ + struct folder_file *entry; + short entrylen; /* number of real entries */ + short entrymax; /* max entry */ +}; + +static struct mapping_t FolderHelp[] = { + { "Exit", OP_EXIT }, + { "Chdir", OP_CHANGE_DIRECTORY }, + { "Mask", OP_ENTER_MASK }, + { "Help", OP_HELP }, + { NULL } +}; + +typedef struct folder_t +{ + const char *name; + const struct stat *f; + int new; +} FOLDER; + +static char LastDir[_POSIX_PATH_MAX] = ""; + +/* Frees up the memory allocated for the local-global variables. */ +static void destroy_state (struct browser_state *state) +{ + int c; + + for (c = 0; c < state->entrylen; c++) + { + safe_free ((void **) &((state->entry)[c].name)); + safe_free ((void **) &((state->entry)[c].desc)); + } + safe_free ((void **) &state->entry); +} + +static int browser_compare_subject (const void *a, const void *b) +{ + struct folder_file *pa = (struct folder_file *) a; + struct folder_file *pb = (struct folder_file *) b; + + int r = strcmp (pa->name, pb->name); + + return ((BrowserSort & SORT_REVERSE) ? -r : r); +} + +static int browser_compare_date (const void *a, const void *b) +{ + struct folder_file *pa = (struct folder_file *) a; + struct folder_file *pb = (struct folder_file *) b; + + int r = pa->mtime - pb->mtime; + + return ((BrowserSort & SORT_REVERSE) ? -r : r); +} + +static int browser_compare_size (const void *a, const void *b) +{ + struct folder_file *pa = (struct folder_file *) a; + struct folder_file *pb = (struct folder_file *) b; + + int r = pa->size - pb->size; + + return ((BrowserSort & SORT_REVERSE) ? -r : r); +} + +static void browser_sort (struct browser_state *state) +{ + int (*f) (const void *, const void *); + + switch (BrowserSort & SORT_MASK) + { + case SORT_ORDER: + return; + case SORT_DATE: + f = browser_compare_date; + break; + case SORT_SIZE: + f = browser_compare_size; + break; + case SORT_SUBJECT: + default: + f = browser_compare_subject; + break; + } + qsort (state->entry, state->entrylen, sizeof (struct folder_file), f); +} + +static int link_is_dir (const char *path) +{ + struct stat st; + + if (stat (path, &st) == 0) + return (S_ISDIR (st.st_mode)); + else + return (-1); +} + +static const char * +folder_format_str (char *dest, size_t destlen, char op, const char *src, + const char *fmt, const char *ifstring, const char *elsestring, + unsigned long data, format_flag flags) +{ + char fn[SHORT_STRING], tmp[SHORT_STRING], permission[10]; + char date[16], *t_fmt; + time_t tnow; + FOLDER *folder = (FOLDER *) data; + struct passwd *pw; + struct group *gr; + + switch (op) + { + case 'd': + tnow = time (NULL); + t_fmt = tnow - folder->f->st_mtime < 31536000 ? "%b %d %H:%M" : "%b %d %Y"; + strftime (date, sizeof (date), t_fmt, localtime (&folder->f->st_mtime)); + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, date); + break; + case 'f': + strncpy (fn, folder->name, sizeof(fn) - 1); + strcat (fn, S_ISLNK (folder->f->st_mode) ? "@" : + (S_ISDIR (folder->f->st_mode) ? "/" : + ((folder->f->st_mode & S_IXUSR) != 0 ? "*" : ""))); + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, fn); + break; + case 'F': + sprintf (permission, "%c%c%c%c%c%c%c%c%c%c", + S_ISDIR(folder->f->st_mode) ? 'd' : (S_ISLNK(folder->f->st_mode) ? 'l' : '-'), + (folder->f->st_mode & S_IRUSR) != 0 ? 'r': '-', + (folder->f->st_mode & S_IWUSR) != 0 ? 'w' : '-', + (folder->f->st_mode & S_ISUID) != 0 ? 's' : (folder->f->st_mode & S_IXUSR) != 0 ? 'x': '-', + (folder->f->st_mode & S_IRGRP) != 0 ? 'r' : '-', + (folder->f->st_mode & S_IWGRP) != 0 ? 'w' : '-', + (folder->f->st_mode & S_ISGID) != 0 ? 's' : (folder->f->st_mode & S_IXGRP) != 0 ? 'x': '-', + (folder->f->st_mode & S_IROTH) != 0 ? 'r' : '-', + (folder->f->st_mode & S_IWOTH) != 0 ? 'w' : '-', + (folder->f->st_mode & S_ISVTX) != 0 ? 't' : (folder->f->st_mode & S_IXOTH) != 0 ? 'x': '-'); + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, permission); + break; + case 'g': + if ((gr = getgrgid (folder->f->st_gid))) + { + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, gr->gr_name); + } + else + { + snprintf (tmp, sizeof (tmp), "%%%sld", fmt); + snprintf (dest, destlen, tmp, folder->f->st_gid); + } + break; + case 'l': + snprintf (tmp, sizeof (tmp), "%%%sd", fmt); + snprintf (dest, destlen, tmp, folder->f->st_nlink); + break; + case 'N': + snprintf (tmp, sizeof (tmp), "%%%sc", fmt); + snprintf (dest, destlen, tmp, folder->new ? 'N' : ' '); + break; + case 's': + snprintf (tmp, sizeof (tmp), "%%%sld", fmt); + snprintf (dest, destlen, tmp, (long) folder->f->st_size); + break; + case 'u': + if ((pw = getpwuid (folder->f->st_uid))) + { + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, pw->pw_name); + } + else + { + snprintf (tmp, sizeof (tmp), "%%%sld", fmt); + snprintf (dest, destlen, tmp, folder->f->st_uid); + } + break; + } + return (src); +} + +static void add_folder (MUTTMENU *m, struct browser_state *state, + const char *name, const struct stat *s, int new) +{ + char buffer[_POSIX_PATH_MAX + SHORT_STRING]; + FOLDER folder; + + folder.name = name; + folder.f = s; + folder.new = new; + mutt_FormatString (buffer, sizeof (buffer), FolderFormat, folder_format_str, + (unsigned long) &folder, 0); + + if (state->entrylen == state->entrymax) + { + /* need to allocate more space */ + safe_realloc ((void **) &state->entry, + sizeof (struct folder_file) * (state->entrymax += 256)); + if (m) + m->data = state->entry; + } + + (state->entry)[state->entrylen].mode = s->st_mode; + (state->entry)[state->entrylen].mtime = s->st_mtime; + (state->entry)[state->entrylen].size = s->st_size; + (state->entry)[state->entrylen].name = safe_strdup (name); + (state->entry)[state->entrylen].desc = safe_strdup (buffer); + + (state->entrylen)++; +} + +static void init_state (struct browser_state *state, MUTTMENU *menu) +{ + state->entrylen = 0; + state->entrymax = 256; + state->entry = (struct folder_file *) safe_malloc (sizeof (struct folder_file) * state->entrymax); + if (menu) + menu->data = state->entry; +} + +static int examine_directory (MUTTMENU *menu, struct browser_state *state, + const char *d, const char *prefix) +{ + struct stat s; + DIR *dp; + struct dirent *de; + char buffer[_POSIX_PATH_MAX + SHORT_STRING]; + BUFFY *tmp; + + if (stat (d, &s) == -1) + { + mutt_perror (d); + return (-1); + } + + if (!S_ISDIR (s.st_mode)) + { + mutt_error ("%s is not a directory", d); + return (-1); + } + + mutt_buffy_check (0); + + if ((dp = opendir (d)) == NULL) + { + mutt_perror (d); + return (-1); + } + + init_state (state, menu); + + while ((de = readdir (dp)) != NULL) + { + if (strcmp (de->d_name, ".") == 0) + continue; /* we don't need . */ + + if (prefix && *prefix && strncmp (prefix, de->d_name, strlen (prefix)) != 0) + continue; + if (regexec (Mask.rx, de->d_name, 0, NULL, 0) != 0) + continue; + + snprintf (buffer, sizeof (buffer), "%s/%s", d, de->d_name); + if (lstat (buffer, &s) == -1) + continue; + + if ((! S_ISREG (s.st_mode)) && (! S_ISDIR (s.st_mode)) && + (! S_ISLNK (s.st_mode))) + continue; + + tmp = Incoming; + while (tmp && strcmp (buffer, tmp->path)) + tmp = tmp->next; + add_folder (menu, state, de->d_name, &s, (tmp) ? tmp->new : 0); + } + closedir (dp); + browser_sort (state); + return 0; +} + +static int examine_mailboxes (MUTTMENU *menu, struct browser_state *state) +{ + struct stat s; + char buffer[LONG_STRING]; + BUFFY *tmp = Incoming; + + if (!Incoming) + return (-1); + mutt_buffy_check (0); + + init_state (state, menu); + + do + { + if (lstat (tmp->path, &s) == -1) + continue; + + if ((! S_ISREG (s.st_mode)) && (! S_ISDIR (s.st_mode)) && + (! S_ISLNK (s.st_mode))) + continue; + + strfcpy (buffer, tmp->path, sizeof (buffer)); + mutt_pretty_mailbox (buffer); + + add_folder (menu, state, buffer, &s, tmp->new); + } + while ((tmp = tmp->next)); + browser_sort (state); + return 0; +} + +int select_file_search (MUTTMENU *menu, regex_t *re, int n) +{ + return (regexec (re, ((struct folder_file *) menu->data)[n].name, 0, NULL, 0)); +} + +void folder_entry (char *s, size_t slen, MUTTMENU *menu, int num) +{ + snprintf (s, slen, "%2d %s", num + 1, ((struct folder_file *) menu->data)[num].desc); +} + +static void init_menu (struct browser_state *state, MUTTMENU *menu, char *title, + size_t titlelen, int buffy) +{ + char path[_POSIX_PATH_MAX]; + + menu->current = 0; + menu->top = 0; + menu->max = state->entrylen; + if (buffy) + snprintf (title, titlelen, "Mailboxes [%d]", mutt_buffy_check (0)); + else + { + strfcpy (path, LastDir, sizeof (path)); + mutt_pretty_mailbox (path); + snprintf (title, titlelen, "Directory [%s], File mask: %s", + path, Mask.pattern); + } + menu->redraw = REDRAW_FULL; +} + +void mutt_select_file (char *f, size_t flen, int buffy) +{ + char buf[STRING]; + char prefix[_POSIX_PATH_MAX] = ""; + char helpstr[SHORT_STRING]; + char title[STRING]; + struct browser_state state; + MUTTMENU *menu; + struct stat st; + int i, killPrefix = 0; + + memset (&state, 0, sizeof (struct browser_state)); + + if (*f) + { + mutt_expand_path (f, flen); + for (i = strlen (f) - 1; i > 0 && f[i] != '/' ; i--); + if (i > 0) + { + if (f[0] == '/') + { + if (i > sizeof (LastDir) - 1) i = sizeof (LastDir) - 1; + strncpy (LastDir, f, i); + LastDir[i] = 0; + } + else + { + getcwd (LastDir, sizeof (LastDir)); + strcat (LastDir, "/"); + strncat (LastDir, f, i); + } + } + else + { + if (f[0] == '/') + strcpy (LastDir, "/"); + else + getcwd (LastDir, sizeof (LastDir)); + } + + if (i <= 0 && f[0] != '/') + strfcpy (prefix, f, sizeof (prefix)); + else + strfcpy (prefix, f + i + 1, sizeof (prefix)); + killPrefix = 1; + } + else if (!LastDir[0]) + strfcpy (LastDir, Maildir, sizeof (LastDir)); + + *f = 0; + + if (buffy) + { + if (examine_mailboxes (NULL, &state) == -1) + return; + } + else if (examine_directory (NULL, &state, LastDir, prefix) == -1) + return; + + menu = mutt_new_menu (); + menu->menu = MENU_FOLDER; + menu->make_entry = folder_entry; + menu->search = select_file_search; + menu->title = title; + menu->data = state.entry; + + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_FOLDER, FolderHelp); + + init_menu (&state, menu, title, sizeof (title), buffy); + + FOREVER + { + switch (i = mutt_menuLoop (menu)) + { + case OP_GENERIC_SELECT_ENTRY: + + if (!state.entrylen) + { + mutt_error ("No files match the file mask"); + break; + } + + if (S_ISDIR (state.entry[menu->current].mode) || + (S_ISLNK (state.entry[menu->current].mode) && + link_is_dir (state.entry[menu->current].name))) + { + /* make sure this isn't a MH or maildir mailbox */ + if (buffy) + { + strfcpy (buf, state.entry[menu->current].name, sizeof (buf)); + mutt_expand_path (buf, sizeof (buf)); + } + else + snprintf (buf, sizeof (buf), "%s/%s", LastDir, + state.entry[menu->current].name); + + if (mx_get_magic (buf) <= 0) + { + char OldLastDir[_POSIX_PATH_MAX]; + + /* save the old directory */ + strfcpy (OldLastDir, LastDir, sizeof (OldLastDir)); + + if (strcmp (state.entry[menu->current].name, "..") == 0) + { + if (strcmp ("..", LastDir + strlen (LastDir) - 2) == 0) + strcat (LastDir, "/.."); + else + { + char *p = strrchr (LastDir + 1, '/'); + + if (p) + *p = 0; + else + { + if (LastDir[0] == '/') + LastDir[1] = 0; + else + strcat (LastDir, "/.."); + } + } + } + else if (buffy) + { + sprintf (LastDir, "%s", state.entry[menu->current].name); + mutt_expand_path (LastDir, sizeof (LastDir)); + } + else + sprintf (LastDir + strlen (LastDir), "/%s", + state.entry[menu->current].name); + + destroy_state (&state); + if (killPrefix) + { + prefix[0] = 0; + killPrefix = 0; + } + buffy = 0; + if (examine_directory (menu, &state, LastDir, prefix) == -1) + { + /* try to restore the old values */ + strfcpy (LastDir, OldLastDir, sizeof (LastDir)); + if (examine_directory (menu, &state, LastDir, prefix) == -1) + { + strfcpy (LastDir, Homedir, sizeof (LastDir)); + return; + } + } + init_menu (&state, menu, title, sizeof (title), buffy); + break; + } + } + + if (buffy) + { + strfcpy (f, state.entry[menu->current].name, flen); + mutt_expand_path (f, flen); + } + else + snprintf (f, flen, "%s/%s", LastDir, state.entry[menu->current].name); + + /* Fall through to OP_EXIT */ + + case OP_EXIT: + + destroy_state (&state); + mutt_menuDestroy (&menu); + return; + + case OP_CHANGE_DIRECTORY: + + strfcpy (buf, LastDir, sizeof (buf)); + if (mutt_get_field ("Chdir to: ", buf, sizeof (buf), M_FILE) == 0 && + buf[0]) + { + buffy = 0; + mutt_expand_path (buf, sizeof (buf)); + if (stat (buf, &st) == 0) + { + if (S_ISDIR (st.st_mode)) + { + strfcpy (LastDir, buf, sizeof (LastDir)); + destroy_state (&state); + if (examine_directory (menu, &state, LastDir, prefix) == 0) + init_menu (&state, menu, title, sizeof (title), buffy); + else + { + mutt_error ("Error scanning directory."); + destroy_state (&state); + mutt_menuDestroy (&menu); + return; + } + } + else + mutt_error ("%s is not a directory.", buf); + } + else + mutt_perror (buf); + } + MAYBE_REDRAW (menu->redraw); + break; + + case OP_ENTER_MASK: + + strfcpy (buf, Mask.pattern, sizeof (buf)); + if (mutt_get_field ("File Mask: ", buf, sizeof (buf), 0) == 0) + { + regex_t *rx = (regex_t *) safe_malloc (sizeof (regex_t)); + int err; + + buffy = 0; + /* assume that the user wants to see everything */ + if (!buf[0]) + strfcpy (buf, ".", sizeof (buf)); + + if ((err = REGCOMP (rx, buf, REG_NOSUB | mutt_which_case (buf))) != 0) + { + regerror (err, rx, buf, sizeof (buf)); + regfree (rx); + safe_free ((void **) &rx); + mutt_error ("%s", buf); + } + else + { + safe_free ((void **) &Mask.pattern); + regfree (Mask.rx); + safe_free ((void **) &Mask.rx); + Mask.pattern = safe_strdup (buf); + Mask.rx = rx; + + destroy_state (&state); + if (examine_directory (menu, &state, LastDir, NULL) == 0) + init_menu (&state, menu, title, sizeof (title), buffy); + else + { + mutt_error ("Error scanning directory."); + mutt_menuDestroy (&menu); + return; + } + killPrefix = 0; + } + } + MAYBE_REDRAW (menu->redraw); + break; + + case OP_SORT: + case OP_SORT_REVERSE: + + { + int reverse = 0; + + move (LINES - 1, 0); + if (i == OP_SORT_REVERSE) + { + reverse = SORT_REVERSE; + addstr ("Reverse "); + } + addstr ("Sort by (d)ate, (a)lpha, si(z)e or do(n)'t sort? "); + clrtoeol (); + + while ((i = mutt_getch ()) != EOF && i != 'a' && i != 'd' && i != 'z' + && i != 'n') + { + if (i == ERR || CI_is_return (i)) + break; + else + BEEP (); + } + + if (i != EOF) + { + switch (i) + { + case 'a': + BrowserSort = reverse | SORT_SUBJECT; + break; + case 'd': + BrowserSort = reverse | SORT_DATE; + break; + case 'z': + BrowserSort = reverse | SORT_SIZE; + break; + case 'n': + BrowserSort = SORT_ORDER; + break; + } + browser_sort (&state); + menu->redraw = REDRAW_FULL; + } + } + + break; + + case OP_CHECK_NEW: + + buffy = 1 - buffy; + destroy_state (&state); + prefix[0] = 0; + killPrefix = 0; + if (buffy) + { + if (examine_mailboxes (menu, &state) == -1) + return; + } + else if (examine_directory (menu, &state, LastDir, prefix) == -1) + return; + init_menu (&state, menu, title, sizeof (title), buffy); + break; + + case OP_BROWSER_NEW_FILE: + + snprintf (buf, sizeof (buf), "%s/", LastDir); + if (mutt_get_field ("New file name: ", buf, sizeof (buf), M_FILE) == 0) + { + strfcpy (f, buf, flen); + destroy_state (&state); + mutt_menuDestroy (&menu); + return; + } + MAYBE_REDRAW (menu->redraw); + break; + } + } + /* not reached */ +} diff --git a/buffy.c b/buffy.c new file mode 100644 index 00000000..1fa1d7cf --- /dev/null +++ b/buffy.c @@ -0,0 +1,420 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "buffy.h" +#include "mx.h" +#include "mailbox.h" + +#include +#include +#include +#include +#include + +#include + +static time_t BuffyTime = 0; /* last time we started checking for mail */ +time_t BuffyDoneTime = 0; /* last time we knew for sure how much mail there was. */ +static short BuffyCount = 0; /* how many boxes with new mail */ +static short BuffyNotify = 0; /* # of unnotified new boxes */ + +#ifdef BUFFY_SIZE + +/* Find the last message in the file. * upon success return 0. * If no + * message found - return -1 */ +int fseek_last_message (FILE * f) +{ + long int pos; + char buffer[BUFSIZ + 7]; /* 7 for "\n\nFrom " */ + int bytes_read; + int i; /* Index into `buffer' for scanning. */ + memset (buffer, 0, BUFSIZ+7); + fseek (f, 0, SEEK_END); + pos = ftell (f); + + /* Set `bytes_read' to the size of the last, probably partial, buffer; 0 < + * `bytes_read' <= `BUFSIZ'. */ + bytes_read = pos % BUFSIZ; + if (bytes_read == 0) + bytes_read = BUFSIZ; + /* Make `pos' a multiple of `BUFSIZ' (0 if the file is short), so that all + * reads will be on block boundaries, which might increase efficiency. */ + while ((pos -= bytes_read) >= 0) + { + /* we save in the buffer at the end the first 7 chars from the last read */ + strncpy (buffer + BUFSIZ, buffer, 7); + fseek (f, pos, SEEK_SET); + bytes_read = fread (buffer, sizeof (char), bytes_read, f); + if (bytes_read == -1) + return -1; + for (i = bytes_read; --i >= 0;) + if (!strncmp (buffer + i, "\n\nFrom ", strlen ("\n\nFrom "))) + { /* found it - go to the beginning of the From */ + fseek (f, pos + i + 2, SEEK_SET); + return 0; + } + bytes_read = BUFSIZ; + } + + /* here we are at the beginning of the file */ + if (!strncmp ("From ", buffer, 5)) + { + fseek (f, 0, 0); + return (0); + } + + return (-1); +} + +/* Return 1 if the last message is new */ +int test_last_status_new (FILE * f) +{ + HEADER *hdr; + int result = 0; + + if (fseek_last_message (f) == -1) + return (0); + + hdr = mutt_new_header (); + mutt_read_rfc822_header (f, hdr); + if (!(hdr->read || hdr->old)) + result = 1; + mutt_free_header (&hdr); + + return result; +} + +int test_new_folder (const char *path) +{ + FILE *f; + int rc = 0; + int typ; + + typ = mx_get_magic (path); + + if (typ != M_MBOX && typ != M_MMDF) + return 0; + + f = fopen (path, "rb"); + rc = test_last_status_new (f); + fclose (f); + + return rc; +} + +BUFFY *mutt_find_mailbox (const char *path) +{ + BUFFY *tmp = NULL; + struct stat sb; + struct stat tmp_sb; + + if (stat (path,&sb) != 0) + return NULL; + + for (tmp = Incoming; tmp; tmp = tmp->next) + { + if (stat (tmp->path,&tmp_sb) ==0 && + sb.st_dev == tmp_sb.st_dev && sb.st_ino == tmp_sb.st_ino) + break; + } + return tmp; +} + +void mutt_update_mailbox (BUFFY * b) +{ + struct stat sb; + + if (!b) + return; + + if (stat (b->path, &sb) == 0) + b->size = (long) sb.st_size; + else + b->size = 0; + return; +} +#endif + +int mutt_parse_mailboxes (BUFFER *path, BUFFER *s, unsigned long data, BUFFER *err) +{ + BUFFY **tmp; + char buf[_POSIX_PATH_MAX]; +#ifdef BUFFY_SIZE + struct stat sb; +#endif /* BUFFY_SIZE */ + + while (MoreArgs (s)) + { + mutt_extract_token (path, s, 0); + strfcpy (buf, path->data, sizeof (buf)); + mutt_expand_path (buf, sizeof (buf)); + /* simple check to avoid duplicates */ + for (tmp = &Incoming; *tmp; tmp = &((*tmp)->next)) + { + if (strcmp (buf, (*tmp)->path) == 0) + break; + } + + if (!*tmp) + { + *tmp = (BUFFY *) safe_calloc (1, sizeof (BUFFY)); + (*tmp)->path = safe_strdup (buf); + (*tmp)->next = NULL; + } + + (*tmp)->new = 0; + (*tmp)->notified = 1; + (*tmp)->newly_created = 0; + +#ifdef BUFFY_SIZE + /* for buffy_size, it is important that if the folder is new (tested by + * reading it), the size is set to 0 so that later when we check we see + * that it increased . without buffy_size we probably don't care. + */ + if (stat ((*tmp)->path, &sb) == 0 && !test_new_folder ((*tmp)->path)) + { + /* some systems out there don't have an off_t type */ + (*tmp)->size = (long) sb.st_size; + } + else + (*tmp)->size = 0; +#endif /* BUFFY_SIZE */ + } + return 0; +} + +#ifdef BUFFY_SIZE +/* people use buffy_size on systems where modified time attributes are BADLY + * broken. Ignore them. + */ +#define STAT_CHECK (sb.st_size > tmp->size) +#else +#define STAT_CHECK (sb.st_mtime > sb.st_atime || (tmp->newly_created && sb.st_ctime == sb.st_mtime && sb.st_ctime == sb.st_atime)) +#endif /* BUFFY_SIZE */ + +int mutt_buffy_check (int force) +{ + BUFFY *tmp; + struct stat sb; + struct dirent *de; + DIR *dirp; + char path[_POSIX_PATH_MAX]; + struct stat contex_sb; + int res; + time_t t; + + /* fastest return if there are no mailboxes */ + if (!Incoming) + return 0; + t = time (NULL); + if (!force && t - BuffyTime < BuffyTimeout) + return BuffyCount; + + BuffyTime = t; + BuffyCount = 0; + BuffyNotify = 0; + + /* check device ID and serial number instead of comparing paths */ + if (!Context || !Context->path || stat (Context->path, &contex_sb) != 0) + { + contex_sb.st_dev=0; + contex_sb.st_ino=0; + } + + for (tmp = Incoming; tmp; tmp = tmp->next) + { + tmp->new = 0; + + if (stat (tmp->path, &sb) != 0 || + (!tmp->magic && (tmp->magic = mx_get_magic (tmp->path)) <= 0)) + { + /* if the mailbox still doesn't exist, set the newly created flag to + * be ready for when it does. + */ + tmp->newly_created = 1; + tmp->magic = 0; +#ifdef BUFFY_SIZE + tmp->size = 0; +#endif + continue; + } + + if (!Context || !Context->path || + sb.st_dev != contex_sb.st_dev || sb.st_ino != contex_sb.st_ino) + { + switch (tmp->magic) + { + case M_MBOX: + case M_MMDF: + + if (STAT_CHECK) + { + BuffyCount++; + tmp->new = 1; + } +#ifdef BUFFY_SIZE + else + { + /* some other program has deleted mail from the folder */ + tmp->size = (long) sb.st_size; + } +#endif + if (tmp->newly_created && + (sb.st_ctime != sb.st_mtime || sb.st_ctime != sb.st_atime)) + tmp->newly_created = 0; + + break; + + case M_MAILDIR: + + snprintf (path, sizeof (path), "%s/new", tmp->path); + if ((dirp = opendir (path)) == NULL) + { + tmp->magic = 0; + break; + } + while ((de = readdir (dirp)) != NULL) + { + if (*de->d_name != '.') + { + /* one new message is enough */ + BuffyCount++; + tmp->new = 1; + break; + } + } + closedir (dirp); + break; + + case M_MH: + + res = mh_parse_sequences (NULL, tmp->path); + if (res >= 0) + { + BuffyCount += res; + tmp->new = res; + } + else + { + tmp->magic = 0; + } + break; + } + } +#ifdef BUFFY_SIZE + else if (Context && Context->path) + tmp->size = (long) sb.st_size; /* update the size */ +#endif + + if (!tmp->new) + tmp->notified = 0; + else if (!tmp->notified) + BuffyNotify++; + } + BuffyDoneTime = BuffyTime; + return (BuffyCount); +} + +int mutt_buffy_notify (void) +{ + BUFFY *tmp; + char path[_POSIX_PATH_MAX]; + + if (mutt_buffy_check (0) && BuffyNotify) + { + for (tmp = Incoming; tmp; tmp = tmp->next) + { + if (tmp->new && !tmp->notified) + { + strfcpy (path, tmp->path, sizeof (path)); + mutt_pretty_mailbox (path); + mutt_message ("New mail in %s.", path); + tmp->notified = 1; + BuffyNotify--; + return (1); + } + } + /* there were no mailboxes needing to be notified, so clean up since + * BuffyNotify has somehow gottten out of sync + */ + BuffyNotify = 0; + } + return (0); +} + +/* + * mutt_buffy() -- incoming folders completion routine + * + * given a folder name, this routine gives the next incoming folder with new + * new mail. + */ +void mutt_buffy (char *s) +{ + int count; + BUFFY *tmp = Incoming; + + mutt_expand_path (s, _POSIX_PATH_MAX); + switch (mutt_buffy_check (0)) + { + case 0: + + s = '\0'; + break; + + case 1: + + while (tmp && !tmp->new) + tmp = tmp->next; + if (!tmp) + { + s = '\0'; + mutt_buffy_check (1); /* buffy was wrong - resync things */ + break; + } + strcpy (s, tmp->path); + mutt_pretty_mailbox (s); + break; + + default: + + count = 0; + while (count < 3) + { + if (strcmp (s, tmp->path) == 0) + count++; + else if (count && tmp->new) + break; + tmp = tmp->next; + if (!tmp) + { + tmp = Incoming; + count++; + } + } + if (count >= 3) + { + s = '\0'; + mutt_buffy_check (1); /* buffy was wrong - resync things */ + break; + } + strcpy (s, tmp->path); + mutt_pretty_mailbox (s); + break; + } +} diff --git a/buffy.h b/buffy.h new file mode 100644 index 00000000..c736a86a --- /dev/null +++ b/buffy.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +typedef struct buffy_t +{ + char *path; +#ifdef BUFFY_SIZE + long size; +#endif /* BUFFY_SIZE */ + struct buffy_t *next; + short new; /* mailbox has new mail */ + short notified; /* user has been notified */ + short magic; /* mailbox type */ + short newly_created; /* mbox or mmdf just popped into existence */ +} +BUFFY; + +WHERE BUFFY *Incoming INITVAL (0); +WHERE short BuffyTimeout INITVAL (3); + +extern time_t BuffyDoneTime; /* last time we knew for sure how much mail there was */ + +#ifdef BUFFY_SIZE +BUFFY *mutt_find_mailbox (const char *path); +void mutt_update_mailbox (BUFFY * b); +#endif diff --git a/color.c b/color.c new file mode 100644 index 00000000..6468b2e3 --- /dev/null +++ b/color.c @@ -0,0 +1,731 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "mapping.h" + +#include +#include +#include + +/* globals */ +int *ColorQuote; +int ColorQuoteUsed; +int ColorDefs[MT_COLOR_MAX]; +COLOR_LINE *ColorHdrList = NULL; +COLOR_LINE *ColorBodyList = NULL; +COLOR_LINE *ColorIndexList = NULL; + +/* local to this file */ +static int ColorQuoteSize; + +#ifdef HAVE_COLOR + +#define COLOR_DEFAULT (-2) + +typedef struct color_list +{ + short fg; + short bg; + short index; + short count; + struct color_list *next; +} COLOR_LIST; + +static COLOR_LIST *ColorList = NULL; +static int UserColors = 0; + +static struct mapping_t Colors[] = +{ + { "black", COLOR_BLACK }, + { "blue", COLOR_BLUE }, + { "cyan", COLOR_CYAN }, + { "green", COLOR_GREEN }, + { "magenta", COLOR_MAGENTA }, + { "red", COLOR_RED }, + { "white", COLOR_WHITE }, + { "yellow", COLOR_YELLOW }, +#if defined (USE_SLANG_CURSES) || defined (HAVE_USE_DEFAULT_COLORS) + { "default", COLOR_DEFAULT }, +#endif + { 0, 0 } +}; + +#endif /* HAVE_COLOR */ + +static struct mapping_t Fields[] = +{ + { "hdrdefault", MT_COLOR_HDEFAULT }, + { "quoted", MT_COLOR_QUOTED }, + { "signature", MT_COLOR_SIGNATURE }, + { "indicator", MT_COLOR_INDICATOR }, + { "status", MT_COLOR_STATUS }, + { "tree", MT_COLOR_TREE }, + { "error", MT_COLOR_ERROR }, + { "normal", MT_COLOR_NORMAL }, + { "tilde", MT_COLOR_TILDE }, + { "markers", MT_COLOR_MARKERS }, + { "header", MT_COLOR_HEADER }, + { "body", MT_COLOR_BODY }, + { "message", MT_COLOR_MESSAGE }, + { "attachment", MT_COLOR_ATTACHMENT }, + { "search", MT_COLOR_SEARCH }, + { "bold", MT_COLOR_BOLD }, + { "underline", MT_COLOR_UNDERLINE }, + { "index", MT_COLOR_INDEX }, + { NULL, 0 } +}; + +#define COLOR_QUOTE_INIT 8 + +void ci_start_color (void) +{ + memset (ColorDefs, A_NORMAL, sizeof (int) * MT_COLOR_MAX); + ColorQuote = (int *) safe_malloc (COLOR_QUOTE_INIT * sizeof (int)); + memset (ColorQuote, A_NORMAL, sizeof (int) * COLOR_QUOTE_INIT); + ColorQuoteSize = COLOR_QUOTE_INIT; + ColorQuoteUsed = 0; + + /* set some defaults */ + ColorDefs[MT_COLOR_STATUS] = A_REVERSE; + ColorDefs[MT_COLOR_INDICATOR] = A_REVERSE; + ColorDefs[MT_COLOR_SEARCH] = A_REVERSE; + ColorDefs[MT_COLOR_MARKERS] = A_REVERSE; + /* special meaning: toggle the relevant attribute */ + ColorDefs[MT_COLOR_BOLD] = 0; + ColorDefs[MT_COLOR_UNDERLINE] = 0; + +#ifdef HAVE_COLOR + start_color (); +#endif +} + +#ifdef HAVE_COLOR + +#ifdef USE_SLANG_CURSES +static char * get_color_name (int val) +{ + static char * missing[3] = {"brown", "lightgray", ""}; + int i; + + switch (val) + { + case COLOR_YELLOW: + return (missing[0]); + + case COLOR_WHITE: + return (missing[1]); + + case COLOR_DEFAULT: + return (missing[2]); + } + + for (i = 0; Colors[i].name; i++) + { + if (Colors[i].value == val) + return (Colors[i].name); + } + return (Colors[0].name); +} +#endif + +int mutt_alloc_color (int fg, int bg) +{ + COLOR_LIST *p = ColorList; + int i; + + /* check to see if this color is already allocated to save space */ + while (p) + { + if (p->fg == fg && p->bg == bg) + { + (p->count)++; + return (COLOR_PAIR (p->index)); + } + p = p->next; + } + + /* check to see if there are colors left */ + if (++UserColors > COLOR_PAIRS) return (A_NORMAL); + + /* find the smallest available index (object) */ + i = 1; + FOREVER + { + p = ColorList; + while (p) + { + if (p->index == i) break; + p = p->next; + } + if (p == NULL) break; + i++; + } + + p = (COLOR_LIST *) safe_malloc (sizeof (COLOR_LIST)); + p->next = ColorList; + ColorList = p; + + p->index = i; + p->count = 1; + p->bg = bg; + p->fg = fg; + +#if defined (USE_SLANG_CURSES) + if (fg == COLOR_DEFAULT || bg == COLOR_DEFAULT) + SLtt_set_color (i, NULL, get_color_name (fg), get_color_name (bg)); + else +#elif defined (HAVE_USE_DEFAULT_COLORS) + if (fg == COLOR_DEFAULT) + fg = -1; + if (bg == COLOR_DEFAULT) + bg = -1; +#endif + + init_pair (i, fg, bg); + + dprint(1,(debugfile,"mutt_alloc_color(): Color pairs used so far: %d\n", + UserColors)); + + return (COLOR_PAIR (p->index)); +} + +void mutt_free_color (int fg, int bg) +{ + COLOR_LIST *p, *q; + + p = ColorList; + while (p) + { + if (p->fg == fg && p->bg == bg) + { + (p->count)--; + if (p->count > 0) return; + + UserColors--; + dprint(1,(debugfile,"mutt_free_color(): Color pairs used so far: %d\n", + UserColors)); + + if (p == ColorList) + { + ColorList = ColorList->next; + safe_free ((void **) &p); + return; + } + q = ColorList; + while (q) + { + if (q->next == p) + { + q->next = p->next; + safe_free ((void **) &p); + return; + } + q = q->next; + } + /* can't get here */ + } + p = p->next; + } +} + +#endif /* HAVE_COLOR */ + +static COLOR_LINE *mutt_new_color_line (void) +{ + COLOR_LINE *p = safe_calloc (1, sizeof (COLOR_LINE)); + + return (p); +} + +static int add_pattern (COLOR_LINE **top, const char *s, int sensitive, + int fg, int bg, int attr, BUFFER *err, + /* is_index used to store compiled pattern + only for `index' color object + when called from mutt_parse_color() */ + int is_index) +{ + COLOR_LINE *tmp = *top; + + while (tmp) + { + if (sensitive) + { + if (strcmp (s, tmp->pattern) == 0) + break; + } + else + { + if (strcasecmp (s, tmp->pattern) == 0) + break; + } + tmp = tmp->next; + } + + if (tmp) + { +#ifdef HAVE_COLOR + if (fg != -1 && bg != -1) + { + if (tmp->fg != fg || tmp->bg != bg) + { + mutt_free_color (tmp->fg, tmp->bg); + tmp->fg = fg; + tmp->bg = bg; + attr |= mutt_alloc_color (fg, bg); + } + else + attr |= (tmp->pair & ~A_BOLD); + } +#endif /* HAVE_COLOR */ + tmp->pair = attr; + } + else + { + int r; + char buf[STRING]; + + tmp = mutt_new_color_line (); + if ((r = REGCOMP (&tmp->rx, s, (sensitive ? mutt_which_case (s) : REG_ICASE))) != 0) + { + regerror (r, &tmp->rx, err->data, err->dsize); + regfree (&tmp->rx); + safe_free ((void **) &tmp); + return (-1); + } + tmp->next = *top; + tmp->pattern = safe_strdup (s); +#ifdef HAVE_COLOR + tmp->fg = fg; + tmp->bg = bg; + attr |= mutt_alloc_color (fg, bg); + if (is_index) + { + strcpy(buf,tmp->pattern); + mutt_check_simple (buf, sizeof (buf), SimpleSearch); + tmp->color_pattern = mutt_pattern_comp (buf, M_FULL_MSG, err); + } +#endif + tmp->pair = attr; + *top = tmp; + } + + return 0; +} + +#ifdef HAVE_COLOR + +static int +parse_color_name (const char *s, int *col, int *attr, int brite, BUFFER *err) +{ + char *eptr; + + if (strncasecmp (s, "bright", 6) == 0) + { + *attr |= brite; + s += 6; + } + + /* allow aliases for xterm color resources */ + if (strncasecmp (s, "color", 5) == 0) + { + s += 5; + *col = strtol (s, &eptr, 10); + if (!*s || *eptr || *col < 0 || *col >= COLORS) + { + snprintf (err->data, err->dsize, "%s: color not supported by term", s); + return (-1); + } + } + else if ((*col = mutt_getvaluebyname (s, Colors)) == -1) + { + snprintf (err->data, err->dsize, "%s: no such color", s); + return (-1); + } + +#ifdef HAVE_USE_DEFAULT_COLORS + if (*col == COLOR_DEFAULT && use_default_colors () != OK) + { + strfcpy (err->data, "default colors not supported", err->dsize); + return (-1); + } +#endif + + return 0; +} + +/* usage: uncolor index pattern [pattern...] */ +int mutt_parse_uncolor (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + int object = 0, do_cache = 0; + COLOR_LINE *tmp, *last = NULL; + + mutt_extract_token (buf, s, 0); + + if ((object = mutt_getvaluebyname (buf->data, Fields)) == -1) + { + snprintf (err->data, err->dsize, "%s: no such object", buf->data); + return (-1); + } + + if (strncmp (buf->data, "index", 5) != 0) + { + strfcpy (err->data, "uncolor: command valid only for index object", err->dsize); + return (-1); + } + + if (!MoreArgs (s)) + { + strfcpy (err->data, "uncolor: too few arguments", err->dsize); + return (-1); + } + + while (MoreArgs (s)) + { + mutt_extract_token (buf, s, 0); + if (!strcmp ("*", buf->data)) + { + for (tmp = ColorIndexList; tmp; ) + { + if (!do_cache) + do_cache = 1; + last = tmp; + tmp = tmp->next; + mutt_pattern_free (&last->color_pattern); + mutt_free_color(last->fg,last->bg); + safe_free ((void **) &last); + } + ColorIndexList = NULL; + } + else + { + for (last = NULL, tmp = ColorIndexList; tmp; last = tmp, tmp = tmp->next) + { + if (!strcmp (buf->data, tmp->pattern)) + { + if (!do_cache) + do_cache = 1; + dprint(1,(debugfile,"Freeing pattern \"%s\" from ColorIndexList\n", + tmp->pattern)); + if (last) + last->next = tmp->next; + else + ColorIndexList = tmp->next; + mutt_pattern_free (&tmp->color_pattern); + mutt_free_color(tmp->fg,tmp->bg); + safe_free ((void **) &tmp); + break; + } + } + } + } + + if (do_cache && !option (OPTNOCURSES) && has_colors ()) + { + mutt_cache_index_colors (Context); + set_option (OPTFORCEREDRAWINDEX); + } + + return (0); +} + +/* usage: color [ ] */ +int mutt_parse_color (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + int object = 0, bold = 0, fg = 0, bg = 0, q_level = 0; + int r = 0; + char *eptr; + + mutt_extract_token (buf, s, 0); + if (!strncmp (buf->data, "quoted", 6)) + { + if (buf->data[6]) + { + q_level = strtol (buf->data + 6, &eptr, 10); + if (*eptr || q_level < 0) + { + snprintf (err->data, err->dsize, "%s: no such object", buf->data); + return (-1); + } + } + object = MT_COLOR_QUOTED; + } + else if ((object = mutt_getvaluebyname (buf->data, Fields)) == -1) + { + snprintf (err->data, err->dsize, "%s: no such object", buf->data); + return (-1); + } + + /* first color */ + if (! MoreArgs (s)) + { + strfcpy (err->data, "color: too few arguments", err->dsize); + return (-1); + } + mutt_extract_token (buf, s, 0); + + if (parse_color_name (buf->data, &fg, &bold, A_BOLD, err) != 0) + return (-1); + + /* second color */ + if (! MoreArgs (s)) + { + strfcpy (err->data, "color: too few arguments", err->dsize); + return (-1); + } + mutt_extract_token (buf, s, 0); + + /* A_BLINK turns the background color brite on some terms */ + if (parse_color_name (buf->data, &bg, &bold, A_BLINK, err) != 0) + return (-1); + + if (object == MT_COLOR_HEADER || object == MT_COLOR_BODY || object == MT_COLOR_INDEX) + { + if (! MoreArgs (s)) + { + strfcpy (err->data, "color: too few arguments", err->dsize); + return (-1); + } + + mutt_extract_token (buf, s, 0); + if (MoreArgs (s)) + { + strfcpy (err->data, "color: too many arguments", err->dsize); + return (-1); + } + + /* don't parse curses command if we're not using the screen */ + /* ignore color commands if we're running on a mono terminal */ + if (option (OPTNOCURSES) || !has_colors ()) + { + return 0; + } + + if (object == MT_COLOR_HEADER) + r = add_pattern (&ColorHdrList, buf->data, 0, fg, bg, bold, err,0); + else if (object == MT_COLOR_BODY) + r = add_pattern (&ColorBodyList, buf->data, 1, fg, bg, bold, err, 0); + else if (object == MT_COLOR_INDEX) + { + pattern_t *pat; + char tempbuf[LONG_STRING]; + + strfcpy (tempbuf, buf->data, sizeof (tempbuf)); + mutt_check_simple (tempbuf, sizeof (tempbuf), SimpleSearch); + if ((pat = mutt_pattern_comp (tempbuf, M_FULL_MSG, err)) == NULL) + { + mutt_pattern_free (&pat); + return (-1); + } + r = add_pattern (&ColorIndexList, buf->data, 1, fg, bg, bold, err, 1); + mutt_cache_index_colors(Context); + set_option (OPTFORCEREDRAWINDEX); + } + } + else + { + /* don't parse curses command if we're not using the screen */ + /* ignore color commands if we're running on a mono terminal */ + if (option (OPTNOCURSES) || !has_colors ()) + { + return 0; + } + + if (object == MT_COLOR_QUOTED) + { + if (q_level >= ColorQuoteSize) + { + safe_realloc ((void **) &ColorQuote, (ColorQuoteSize += 2) * sizeof (int)); + ColorQuote[ColorQuoteSize-2] = ColorDefs[MT_COLOR_QUOTED]; + ColorQuote[ColorQuoteSize-1] = ColorDefs[MT_COLOR_QUOTED]; + } + if (q_level >= ColorQuoteUsed) + ColorQuoteUsed = q_level + 1; + if (q_level == 0) + { + ColorDefs[MT_COLOR_QUOTED] = bold | mutt_alloc_color (fg, bg); + + ColorQuote[0] = ColorDefs[MT_COLOR_QUOTED]; + for (q_level = 1; q_level < ColorQuoteUsed; q_level++) + { + if (ColorQuote[q_level] == A_NORMAL) + ColorQuote[q_level] = ColorDefs[MT_COLOR_QUOTED]; + } + } + else + ColorQuote[q_level] = bold | mutt_alloc_color (fg, bg); + } + else + ColorDefs[object] = bold | mutt_alloc_color (fg, bg); + } + +#ifdef HAVE_BKGDSET + if (object == MT_COLOR_NORMAL) + BKGDSET (MT_COLOR_NORMAL); +#endif + + return (r); +} + +#endif /* HAVE_COLOR */ + +/* + * command: mono + * + * set attribute for an object when using a terminal with no color support + */ +int mutt_parse_mono (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + int r = 0, object, q_level = 0, attr = A_NORMAL; + char *eptr; + + mutt_extract_token (buf, s, 0); + if (strncmp (buf->data, "quoted", 6) == 0) + { + if (buf->data[6]) + { + q_level = strtol (buf->data + 6, &eptr, 10); + if (*eptr || q_level < 0) + { + snprintf (err->data, err->dsize, "%s: no such object", buf->data); + return (-1); + } + } + object = MT_COLOR_QUOTED; + } + else if ((object = mutt_getvaluebyname (buf->data, Fields)) == -1) + { + snprintf (err->data, err->dsize, "%s: no such object", buf->data); + return (-1); + } + + if (! MoreArgs (s)) + { + strfcpy (err->data, "mono: too few arguments", err->dsize); + return (-1); + } + + mutt_extract_token (buf, s, 0); + + if (strcasecmp ("bold", buf->data) == 0) + attr |= A_BOLD; + else if (strcasecmp ("underline", buf->data) == 0) + attr |= A_UNDERLINE; + else if (strcasecmp ("none", buf->data) == 0) + attr = A_NORMAL; + else if (strcasecmp ("reverse", buf->data) == 0) + attr |= A_REVERSE; + else if (strcasecmp ("standout", buf->data) == 0) + attr |= A_STANDOUT; + else if (strcasecmp ("normal", buf->data) == 0) + attr = A_NORMAL; /* needs use = instead of |= to clear other bits */ + else + { + snprintf (err->data, err->dsize, "%s: no such attribute", buf->data); + return (-1); + } + + if (object == MT_COLOR_HEADER || object == MT_COLOR_BODY || object == MT_COLOR_INDEX) + { + if (! MoreArgs (s)) + { + snprintf (err->data, err->dsize, "mono: missing regexp"); + return (-1); + } + + mutt_extract_token (buf, s, 0); + if (MoreArgs (s)) + { + strfcpy (err->data, "mono: too many arguments", err->dsize); + return (-1); + } + + /* if we have color, ignore the mono commands */ + if (option (OPTNOCURSES) +#ifdef HAVE_COLOR + || has_colors () +#endif + ) + { + return 0; + } + + if (object == MT_COLOR_HEADER) + r = add_pattern (&ColorHdrList, buf->data, 0, -1, -1, attr, err, 0); + else if (object == MT_COLOR_BODY) + r = add_pattern (&ColorBodyList, buf->data, 1, -1, -1, attr, err, 0); + else if (object == MT_COLOR_INDEX) + { + pattern_t *pat; + char tempbuf[LONG_STRING]; + + strfcpy (tempbuf, buf->data, sizeof (tempbuf)); + mutt_check_simple (tempbuf, sizeof (tempbuf), SimpleSearch); + if ((pat = mutt_pattern_comp (tempbuf, M_FULL_MSG, err)) == NULL) + { + mutt_pattern_free (&pat); + return (-1); + } + r = add_pattern (&ColorIndexList, buf->data, 1, -1, -1, attr, err, 1); + mutt_cache_index_colors (Context); + set_option (OPTFORCEREDRAWINDEX); + } + } + else + { + /* if we have color, ignore the mono commands */ + if (option (OPTNOCURSES) +#ifdef HAVE_COLOR + || has_colors () +#endif + ) + { + return 0; + } + + if (object == MT_COLOR_QUOTED) + { + if (q_level >= ColorQuoteSize) + { + safe_realloc ((void **) &ColorQuote, (ColorQuoteSize += 2) * sizeof (int)); + ColorQuote[ColorQuoteSize-2] = ColorDefs[MT_COLOR_QUOTED]; + ColorQuote[ColorQuoteSize-1] = ColorDefs[MT_COLOR_QUOTED]; + } + if (q_level >= ColorQuoteUsed) + ColorQuoteUsed = q_level + 1; + if (q_level == 0) + { + ColorDefs[MT_COLOR_QUOTED] = attr; + + ColorQuote[0] = ColorDefs[MT_COLOR_QUOTED]; + for (q_level = 1; q_level < ColorQuoteUsed; q_level++) + { + if (ColorQuote[q_level] == A_NORMAL) + ColorQuote[q_level] = ColorDefs[MT_COLOR_QUOTED]; + } + } + else + ColorQuote[q_level] = attr; + } + else + ColorDefs[object] = attr; + } + + return (r); +} diff --git a/commands.c b/commands.c new file mode 100644 index 00000000..4de052a3 --- /dev/null +++ b/commands.c @@ -0,0 +1,751 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "mutt_menu.h" +#include "mime.h" +#include "sort.h" +#include "mailbox.h" +#include "copy.h" +#include "mx.h" +#include "pager.h" + +#ifdef BUFFY_SIZE +#include "buffy.h" +#endif + + + +#ifdef _PGPPATH +#include "pgp.h" +#endif + + + + + + + + + + + +#include +#include +#include +#include +#include +#include +#include +#include + +extern char *ReleaseDate; + +/* The folder the user last saved to. Used by ci_save_message() */ +static char LastSaveFolder[_POSIX_PATH_MAX] = ""; + +/* for compatibility with metamail */ +static int is_mmnoask (const char *buf) +{ + char tmp[LONG_STRING], *p, *q; + int lng; + + if ((p = getenv ("MM_NOASK")) != NULL && *p) + { + if (strcmp (p, "1") == 0) + return (1); + + strfcpy (tmp, p, sizeof (tmp)); + p = tmp; + + while ((p = strtok (p, ",")) != NULL) + { + if ((q = strrchr (p, '/')) != NULL) + { + if (*(q+1) == '*') + { + if (strncasecmp (buf, p, q-p) == 0) + return (1); + } + else + { + if (strcasecmp (buf, p) == 0) + return (1); + } + } + else + { + lng = strlen (p); + if (buf[lng] == '/' && strncasecmp (buf, p, lng) == 0) + return (1); + } + + p = NULL; + } + } + + return (0); +} + +int mutt_display_message (HEADER *cur) +{ + char tempfile[_POSIX_PATH_MAX], buf[LONG_STRING]; + int rc = 0, builtin = 0; + int cmflags = M_CM_DECODE | M_CM_DISPLAY; + FILE *fpout; + + snprintf (buf, sizeof (buf), "%s/%s", TYPE (cur->content->type), + cur->content->subtype); + + if (cur->mailcap && !mutt_is_autoview (buf)) + { + if (is_mmnoask (buf)) + rc = M_YES; + else + rc = query_quadoption (OPT_USEMAILCAP, "Display message using mailcap?"); + if (rc < 0) + return 0; + else if (rc == M_YES) + { + MESSAGE *msg; + + if ((msg = mx_open_message (Context, cur->msgno)) != NULL) + { + mutt_view_attachment (msg->fp, cur->content, M_REGULAR); + mx_close_message (&msg); + mutt_set_flag (Context, cur, M_READ, 1); + } + return 0; + } + } + + mutt_parse_mime_message (Context, cur); + + + +#ifdef _PGPPATH + /* see if PGP is needed for this message. if so, we should exit curses */ + if (cur->pgp) + { + if (cur->pgp & PGPENCRYPT) + { + if (!pgp_valid_passphrase ()) + return 0; + + cmflags |= M_CM_VERIFY; + mutt_message ("Invoking PGP..."); + } + else if (cur->pgp & PGPSIGN) + { + /* find out whether or not the verify signature */ + if (query_quadoption (OPT_VERIFYSIG, "Verify PGP signature?") == M_YES) + { + cmflags |= M_CM_VERIFY; + mutt_message ("Invoking PGP..."); + } + } + } +#endif + + + + + + + + mutt_mktemp (tempfile); + if ((fpout = safe_fopen (tempfile, "w")) == NULL) + { + mutt_error ("Could not create temporary file!"); + return (0); + } + + if (strcmp (Pager, "builtin") == 0) + builtin = 1; + else + { + mutt_make_string (buf, sizeof (buf), PagerFmt, cur); + fputs (buf, fpout); + fputs ("\n\n", fpout); + } + + if (mutt_copy_message (fpout, Context, cur, cmflags, + (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) | CH_DECODE | CH_FROM) == -1) + { + fclose (fpout); + unlink (tempfile); + return 0; + } + + if (fclose (fpout) != 0 && errno != EPIPE) + { + mutt_perror ("fclose"); + mutt_unlink (tempfile); + return (0); + } + + if (builtin) + { + pager_t info; + + /* Invoke the builtin pager */ + memset (&info, 0, sizeof (pager_t)); + info.hdr = cur; + info.ctx = Context; + rc = mutt_pager (NULL, tempfile, 1, &info); + } + else + { + endwin (); + snprintf (buf, sizeof (buf), "%s %s", Pager, tempfile); + mutt_system (buf); + unlink (tempfile); + keypad (stdscr, TRUE); + mutt_set_flag (Context, cur, M_READ, 1); + if (option (OPTPROMPTAFTER)) + { + mutt_ungetch (mutt_any_key_to_continue ("Command: ")); + rc = km_dokey (MENU_PAGER); + } + else + rc = 0; + } + + return rc; +} + +void ci_bounce_message (HEADER *h, int *redraw) +{ + char prompt[SHORT_STRING]; + char buf[HUGE_STRING] = { 0 }; + ADDRESS *adr = NULL; + int rc; + + snprintf (prompt, sizeof(prompt), "Bounce %smessage%s to: ", + h ? "" : "tagged ", h ? "" : "s"); + rc = mutt_get_field (prompt, buf, sizeof (buf), M_ALIAS); + + if (option (OPTNEEDREDRAW)) + { + unset_option (OPTNEEDREDRAW); + *redraw = REDRAW_FULL; + } + + if (rc || !buf[0]) + return; + + if (!(adr = rfc822_parse_adrlist (adr, buf))) + { + mutt_error ("Error parsing address!"); + return; + } + + adr = mutt_expand_aliases (adr); + + buf[0] = 0; + rfc822_write_address (buf, sizeof (buf), adr); + + snprintf (prompt, (COLS > sizeof(prompt) ? sizeof(prompt) : COLS) - 13, + "Bounce message%s to %s", (h ? "" : "s"), buf); + strcat(prompt, "...?"); + if (mutt_yesorno (prompt, 1) != 1) + { + rfc822_free_address (&adr); + CLEARLINE (LINES-1); + return; + } + + mutt_bounce_message (h, adr); + rfc822_free_address (&adr); + mutt_message ("Message%s bounced.", h ? "" : "s"); +} + +void mutt_pipe_message_to_state (HEADER *h, STATE *s) +{ + if (option (OPTPIPEDECODE)) + mutt_parse_mime_message (Context, h); + mutt_copy_message (s->fpout, Context, h, + option (OPTPIPEDECODE) ? M_CM_DECODE : 0, + option (OPTPIPEDECODE) ? CH_FROM | CH_WEED | CH_DECODE : CH_FROM); +} + +int mutt_pipe_message (HEADER *h) +{ + STATE s; + char buffer[LONG_STRING]; + int i, rc = 0; + pid_t thepid; + + buffer[0] = 0; + if (mutt_get_field ("Pipe to command: ", buffer, sizeof (buffer), 0) != 0 || + !buffer[0]) + return 0; + mutt_expand_path (buffer, sizeof (buffer)); + + memset (&s, 0, sizeof (s)); + + endwin (); + if (h) + { + + + +#ifdef _PGPPATH + if (option (OPTPIPEDECODE)) + { + mutt_parse_mime_message (Context, h); + if(h->pgp & PGPENCRYPT && !pgp_valid_passphrase()) + return 1; + } + endwin (); +#endif + + + + thepid = mutt_create_filter (buffer, &s.fpout, NULL, NULL); + mutt_pipe_message_to_state (h, &s); + fclose (s.fpout); + rc = mutt_wait_filter (thepid); + } + else + { /* handle tagged messages */ + + + +#ifdef _PGPPATH + + if(option(OPTPIPEDECODE)) + { + for (i = 0; i < Context->vcount; i++) + if(Context->hdrs[Context->v2r[i]]->tagged) + { + mutt_parse_mime_message(Context, Context->hdrs[Context->v2r[i]]); + if (Context->hdrs[Context->v2r[i]]->pgp & PGPENCRYPT && + !pgp_valid_passphrase()) + return 1; + } + } +#endif + + + + if (option (OPTPIPESPLIT)) + { + for (i = 0; i < Context->vcount; i++) + { + if (Context->hdrs[Context->v2r[i]]->tagged) + { + endwin (); + thepid = mutt_create_filter (buffer, &(s.fpout), NULL, NULL); + mutt_pipe_message_to_state (Context->hdrs[Context->v2r[i]], &s); + /* add the message separator */ + if (PipeSep) + state_puts (PipeSep, &s); + fclose (s.fpout); + if (mutt_wait_filter (thepid) != 0) + rc = 1; + } + } + } + else + { + endwin (); + thepid = mutt_create_filter (buffer, &(s.fpout), NULL, NULL); + for (i = 0; i < Context->vcount; i++) + { + if (Context->hdrs[Context->v2r[i]]->tagged) + { + mutt_pipe_message_to_state (Context->hdrs[Context->v2r[i]], &s); + /* add the message separator */ + if (PipeSep) + state_puts (PipeSep, &s); + } + } + fclose (s.fpout); + if (mutt_wait_filter (thepid) != 0) + rc = 1; + } + } + + if (rc || option (OPTWAITKEY)) + mutt_any_key_to_continue (NULL); + return 1; +} + +int mutt_select_sort (int reverse) +{ + int method = Sort; /* save the current method in case of abort */ + int ch; + + Sort = 0; + while (!Sort) + { + mvprintw (LINES - 1, 0, +"%sSort (d)ate/(f)rm/(r)ecv/(s)ubj/t(o)/(t)hread/(u)nsort/si(z)e/s(c)ore?: ", + reverse ? "Rev-" : ""); + ch = mutt_getch (); + if (ch == ERR || CI_is_return (ch)) + { + Sort = method; + CLEARLINE (LINES-1); + return (-1); + } + switch (ch) + { + case 'c': + Sort = SORT_SCORE; + break; + case 'd': + Sort = SORT_DATE; + break; + case 'f': + Sort = SORT_FROM; + break; + case 'o': + Sort = SORT_TO; + break; + case 'r': + Sort = SORT_RECEIVED; + break; + case 's': + Sort = SORT_SUBJECT; + break; + case 't': + Sort = SORT_THREADS; + break; + case 'u': + Sort = SORT_ORDER; + break; + case 'z': + Sort = SORT_SIZE; + break; + default: + BEEP (); + break; + } + } + CLEARLINE (LINES-1); + if (reverse) + Sort |= SORT_REVERSE; + + return (Sort != method ? 0 : -1); /* no need to resort if it's the same */ +} + +/* invoke a command in a subshell */ +void mutt_shell_escape (void) +{ + char buf[LONG_STRING]; + + buf[0] = 0; + if (mutt_get_field ("Shell command: ", buf, sizeof (buf), M_CMD) == 0) + { + if (!buf[0]) + strfcpy (buf, Shell, sizeof (buf)); + CLEARLINE (LINES-1); + endwin (); + fflush (stdout); + if (mutt_system (buf) != 0 || option (OPTWAITKEY)) + mutt_any_key_to_continue (NULL); + } +} + +/* enter a mutt command */ +void mutt_enter_command (void) +{ + BUFFER err, token; + char buffer[LONG_STRING], errbuf[SHORT_STRING]; + int r; + int old_strictthreads = option (OPTSTRICTTHREADS); + int old_sortre = option (OPTSORTRE); + + buffer[0] = 0; + if (mutt_get_field (":", buffer, sizeof (buffer), 0) != 0 || !buffer[0]) + return; + err.data = errbuf; + err.dsize = sizeof (errbuf); + memset (&token, 0, sizeof (token)); + r = mutt_parse_rc_line (buffer, &token, &err); + FREE (&token.data); + if (errbuf[0]) + { + /* since errbuf could potentially contain printf() sequences in it, + we must call mutt_error() in this fashion so that vsprintf() + doesn't expect more arguments that we passed */ + if (r == 0) + mutt_message ("%s", errbuf); + else + mutt_error ("%s", errbuf); + } + if (option (OPTSTRICTTHREADS) != old_strictthreads || + option (OPTSORTRE) != old_sortre) + set_option (OPTNEEDRESORT); +} + +void mutt_display_address (ADDRESS *adr) +{ + char buf[SHORT_STRING]; + + buf[0] = 0; + rfc822_write_address (buf, sizeof (buf), adr); + mutt_message ("%s", buf); +} + +/* returns 1 if OK to proceed, 0 to abort */ +static int save_confirm_func (const char *s, struct stat *st) +{ + char tmp[_POSIX_PATH_MAX]; + int ret = 1; + + if (stat (s, st) != -1) + { + if (mx_get_magic (s) == -1) + { + mutt_error ("%s is not a mailbox!", s); + return 0; + } + + if (option (OPTCONFIRMAPPEND)) + { + snprintf (tmp, sizeof (tmp), "Append messages to %s?", s); + if (mutt_yesorno (tmp, 1) < 1) + ret = 0; + } + } + else + { + st->st_mtime = 0; + st->st_atime = 0; + + if (errno == ENOENT) + { + if (option (OPTCONFIRMCREATE)) + { + snprintf (tmp, sizeof (tmp), "Create %s?", s); + if (mutt_yesorno (tmp, 1) < 1) + ret = 0; + } + } + else + { + mutt_perror (s); + return 0; + } + } + + CLEARLINE (LINES-1); + return (ret); +} + +/* returns 0 if the copy/save was successful, or -1 on error/abort */ +int mutt_save_message (HEADER *h, int delete, int decode, int *redraw) +{ + int i, need_buffy_cleanup; + int cmflags = decode ? M_CM_DECODE : 0; + int chflags = decode ? CH_XMIT | CH_MIME : CH_UPDATE_LEN; + char prompt[SHORT_STRING], buf[_POSIX_PATH_MAX]; + CONTEXT ctx; + struct stat st; +#ifdef BUFFY_SIZE + BUFFY *tmp = NULL; +#else + struct utimbuf ut; +#endif + + *redraw = 0; + + snprintf (prompt, sizeof (prompt), "%s%s to mailbox", + decode ? (delete ? "Decode-save" : "Decode-copy") : + (delete ? "Save" : "Copy"), h ? "" : " tagged"); + + if (h) + mutt_default_save (buf, sizeof (buf), h); + else + { + /* look for the first tagged message */ + + for (i = 0; i < Context->vcount; i++) + { + if (Context->hdrs[Context->v2r[i]]->tagged) + { + h = Context->hdrs[Context->v2r[i]]; + break; + } + } + + if (h) + { + mutt_default_save (buf, sizeof (buf), h); + h = NULL; + } + } + + mutt_pretty_mailbox (buf); + if (mutt_enter_fname (prompt, buf, sizeof (buf), redraw, 0) == -1) + return (-1); + + if (*redraw != REDRAW_FULL) + { + if (!h) + *redraw = REDRAW_INDEX | REDRAW_STATUS; + else + *redraw = REDRAW_STATUS; + } + + if (!buf[0]) + return (-1); + + /* This is an undocumented feature of ELM pointed out to me by Felix von + * Leitner + */ + if (strcmp (buf, ".") == 0) + strfcpy (buf, LastSaveFolder, sizeof (buf)); + else + strfcpy (LastSaveFolder, buf, sizeof (LastSaveFolder)); + + mutt_expand_path (buf, sizeof (buf)); + + /* check to make sure that this file is really the one the user wants */ + if (!save_confirm_func (buf, &st)) + { + CLEARLINE (LINES-1); + return (-1); + } + + mutt_message ("Copying to %s...", buf); + + if (mx_open_mailbox (buf, M_APPEND, &ctx) != NULL) + { + if (h) + { + if (decode) + mutt_parse_mime_message (Context, h); + if (mutt_append_message (&ctx, Context, h, cmflags, chflags) == 0 && delete) + { + mutt_set_flag (Context, h, M_DELETE, 1); + mutt_set_flag (Context, h, M_TAG, 0); + } + } + else + { + for (i = 0; i < Context->vcount; i++) + { + if (Context->hdrs[Context->v2r[i]]->tagged) + { + h = Context->hdrs[Context->v2r[i]]; + if (decode) + mutt_parse_mime_message (Context, h); + mutt_append_message (&ctx, Context, h, cmflags, chflags); + if (delete) + { + mutt_set_flag (Context, h, M_DELETE, 1); + mutt_set_flag (Context, h, M_TAG, 0); + } + } + } + } + + need_buffy_cleanup = (ctx.magic == M_MBOX || ctx.magic == M_MMDF); + + mx_close_mailbox (&ctx); + + if (need_buffy_cleanup) + { +#ifdef BUFFY_SIZE + tmp = mutt_find_mailbox (buf); + if (tmp && !tmp->new) + mutt_update_mailbox (tmp); +#else + /* fix up the times so buffy won't get confused */ + if (st.st_mtime > st.st_atime) + { + ut.actime = st.st_atime; + ut.modtime = time (NULL); + utime (buf, &ut); + } + else + utime (buf, NULL); +#endif + } + + mutt_clear_error (); + } + + return (0); +} + +static void print_msg (FILE *fp, CONTEXT *ctx, HEADER *h) +{ + + + +#ifdef _PGPPATH + if (h->pgp & PGPENCRYPT) + { + if (!pgp_valid_passphrase ()) + return; + endwin (); + } +#endif + + + + mutt_parse_mime_message (ctx, h); + mutt_copy_message (fp, ctx, h, M_CM_DECODE, CH_WEED | CH_DECODE); +} + +void mutt_print_message (HEADER *h) +{ + int i, count = 0; + pid_t thepid; + FILE *fp; + + if (query_quadoption (OPT_PRINT, + h ? "Print message?" : "Print tagged messages?") != M_YES) + return; + endwin (); + if ((thepid = mutt_create_filter (PrintCmd, &fp, NULL, NULL)) == -1) + return; + if (h) + { + print_msg (fp, Context, h); + count++; + } + else + { + for (i = 0 ; i < Context->vcount ; i++) + { + if (Context->hdrs[Context->v2r[i]]->tagged) + { + print_msg (fp, Context, Context->hdrs[Context->v2r[i]]); + /* add a formfeed */ + fputc ('\f', fp); + count++; + } + } + } + fclose (fp); + if (mutt_wait_filter (thepid) || option (OPTWAITKEY)) + mutt_any_key_to_continue (NULL); + mutt_message ("Message%s printed", (count > 1) ? "s" : ""); +} + +void mutt_version (void) +{ + mutt_message ("Mutt %s (%s)", VERSION, ReleaseDate); +} diff --git a/complete.c b/complete.c new file mode 100644 index 00000000..360eebf3 --- /dev/null +++ b/complete.c @@ -0,0 +1,166 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" + +#include +#include +#include +#include +#include + +/* given a partial pathname, this routine fills in as much of the rest of the + * path as is unique. + * + * return 0 if ok, -1 if no matches + */ +int mutt_complete (char *s) +{ + char *p; + DIR *dirp; + struct dirent *de; + int i ,init=0; + size_t len; + char dirpart[_POSIX_PATH_MAX], exp_dirpart[_POSIX_PATH_MAX]; + char filepart[_POSIX_PATH_MAX]; + + if (*s == '=' || *s == '+') + { + dirpart[0] = *s; + dirpart[1] = 0; + strfcpy (exp_dirpart, NONULL (Maildir), sizeof (exp_dirpart)); + if ((p = strrchr (s, '/'))) + { + *p++ = 0; + sprintf (exp_dirpart + strlen (exp_dirpart), "/%s", s+1); + sprintf (dirpart + strlen (dirpart), "%s/", s+1); + strfcpy (filepart, p, sizeof (filepart)); + } + else + strfcpy (filepart, s + 1, sizeof (filepart)); + dirp = opendir (exp_dirpart); + } + else + { + if ((p = strrchr (s, '/'))) + { + if (p == s) /* absolute path */ + { + p = s + 1; + strfcpy (dirpart, "/", sizeof (dirpart)); + exp_dirpart[0] = 0; + strfcpy (filepart, p, sizeof (filepart)); + dirp = opendir (dirpart); + } + else + { + *p = 0; + len = (size_t)(p - s); + strncpy (dirpart, s, len); + dirpart[len]=0; + p++; + strfcpy (filepart, p, sizeof (filepart)); + strfcpy (exp_dirpart, dirpart, sizeof (exp_dirpart)); + mutt_expand_path (exp_dirpart, sizeof (exp_dirpart)); + dirp = opendir (exp_dirpart); + } + } + else + { + /* no directory name, so assume current directory. */ + dirpart[0] = 0; + strfcpy (filepart, s, sizeof (filepart)); + dirp = opendir ("."); + } + } + + if (dirp == NULL) + { + dprint (1, (debugfile, "mutt_complete(): %s: %s (errno %d).\n", exp_dirpart, strerror (errno), errno)); + return (-1); + } + + /* + * special case to handle when there is no filepart yet. find the first + * file/directory which is not ``.'' or ``..'' + */ + if ((len = strlen (filepart)) == 0) + { + while ((de = readdir (dirp)) != NULL) + { + if (strcmp (".", de->d_name) != 0 && strcmp ("..", de->d_name) != 0) + { + strfcpy (filepart, de->d_name, sizeof (filepart)); + init++; + break; + } + } + } + + while ((de = readdir (dirp)) != NULL) + { + if (strncmp (de->d_name, filepart, len) == 0) + { + if (init) + { + for (i=0; filepart[i] && de->d_name[i]; i++) + { + if (filepart[i] != de->d_name[i]) + { + filepart[i] = 0; + break; + } + } + filepart[i] = 0; + } + else + { + char buf[_POSIX_PATH_MAX]; + struct stat st; + + strfcpy (filepart, de->d_name, sizeof(filepart)); + + /* check to see if it is a directory */ + if (dirpart[0]) + { + strfcpy (buf, exp_dirpart, sizeof (buf)); + strcat (buf, "/"); + } + else + buf[0] = 0; + strcat (buf, filepart); + if (stat (buf, &st) != -1 && (st.st_mode & S_IFDIR)) + strcat (filepart, "/"); + init = 1; + } + } + } + closedir (dirp); + + if (dirpart[0]) + { + strcpy (s, dirpart); + if (strcmp ("/", dirpart) != 0 && dirpart[0] != '=' && dirpart[0] != '+') + strcat (s, "/"); + strcat (s, filepart); + } + else + strcpy (s, filepart); + + return (init ? 0 : -1); +} diff --git a/compose.c b/compose.c new file mode 100644 index 00000000..494cd2c4 --- /dev/null +++ b/compose.c @@ -0,0 +1,898 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "mutt_menu.h" +#include "rfc1524.h" +#include "mime.h" +#include "attach.h" +#include "mapping.h" + +#include +#include +#include +#include +#include + +#define CHECK_COUNT if (idxlen == 0) { mutt_error ("There are no attachments."); break; } + + + +enum +{ + HDR_FROM = 1, + HDR_TO, + HDR_CC, + HDR_BCC, + HDR_SUBJECT, + HDR_REPLYTO, + HDR_FCC, + + +#ifdef _PGPPATH + HDR_PGP, + HDR_PGPSIGINFO, +#endif + + + + HDR_ATTACH = (HDR_FCC + 5) /* where to start printing the attachments */ +}; + +#define HDR_XOFFSET 10 + +static struct mapping_t ComposeHelp[] = { + { "Send", OP_COMPOSE_SEND_MESSAGE }, + { "Abort", OP_EXIT }, + { "To", OP_COMPOSE_EDIT_TO }, + { "CC", OP_COMPOSE_EDIT_CC }, + { "Subj", OP_COMPOSE_EDIT_SUBJECT }, + { "Attach", OP_COMPOSE_ATTACH_FILE }, + { "Descrip", OP_COMPOSE_EDIT_DESCRIPTION }, + { "Help", OP_HELP }, + { NULL } +}; + +void snd_entry (char *b, size_t blen, MUTTMENU *menu, int num) +{ + char t[SHORT_STRING], size[SHORT_STRING]; + char tmp[_POSIX_PATH_MAX]; + BODY *m; + ATTACHPTR **idx = (ATTACHPTR **) menu->data; + struct stat finfo; + + m = idx[num]->content; + + if (m->filename && m->filename[0]) + { + if (stat (m->filename, &finfo) != -1) + mutt_pretty_size (size, sizeof (size), finfo.st_size); + else + strcpy (size, "0K"); + strfcpy (tmp, m->filename, sizeof (tmp)); + } + else + { + strcpy (size, "0K"); + strcpy (tmp, ""); + } + mutt_pretty_mailbox (tmp); + + snprintf (t, sizeof (t), "[%.7s/%.10s, %.6s, %s]", + TYPE (m->type), m->subtype, ENCODING (m->encoding), size); + + snprintf (b, blen, "%c%c%2d %-34.34s %s%s <%s>", + m->unlink ? '-' : ' ', + m->tagged ? '*' : ' ', + num + 1, + t, + idx[num]->tree ? idx[num]->tree : "", + tmp, + m->description ? m->description : "no description"); +} + + + +#ifdef _PGPPATH +#include "pgp.h" + +static int pgp_send_menu (int bits) +{ + int c; + char *p; + char *micalg = NULL; + char input_signas[SHORT_STRING]; + char input_micalg[SHORT_STRING]; + + mvaddstr (LINES-1, 0, "(e)ncrypt, (s)ign, sign (a)s, (b)oth, select (m)ic algorithm, or (f)orget it? "); + clrtoeol (); + do + { + mutt_refresh (); + if ((c = mutt_getch ()) == ERR) + break; + if (c == 'a') + { + unset_option(OPTPGPCHECKTRUST); + if ((p = pgp_ask_for_key (pgp_secring(PGP_SIGN), + NULL, "Sign as: ", NULL, KEYFLAG_CANSIGN, &micalg))) + { + snprintf (input_signas, sizeof (input_signas), "0x%s", p); + safe_free((void **) &PgpSignAs); + PgpSignAs = safe_strdup(input_signas); + safe_free((void **) &PgpSignMicalg); + PgpSignMicalg = micalg; /* micalg is malloc()ed by pgp_ask_for_key */ + pgp_void_passphrase (); /* probably need a different passphrase */ + safe_free ((void **) &p); + bits |= PGPSIGN; + } + } + else if (c == 'm') + { + if(!(bits & PGPSIGN)) + mutt_error("This doesn't make sense if you don't want to sign the message."); + else + { + if(mutt_get_field("MIC algorithm: ", input_micalg, sizeof(input_micalg), 0) == 0) + { + if(strcasecmp(input_micalg, "pgp-md5") && strcasecmp(input_micalg, "pgp-sha1") + && strcasecmp(input_micalg, "pgp-rmd160")) + { + mutt_error("Unknown MIC algorithm!"); + strfcpy(input_micalg, "x-unknown", sizeof(input_micalg)); + } + safe_free((void **) &PgpSignMicalg); + PgpSignMicalg = safe_strdup(input_micalg); + } + } + } + else if (c == 'e') + bits |= PGPENCRYPT; + else if (c == 's') + bits |= PGPSIGN; + else if (c == 'b') + bits = PGPENCRYPT | PGPSIGN; + else if (c == 'f') + bits = 0; + else + { + BEEP (); + c = 0; + } + } + while (c == 0); + CLEARLINE (LINES-1); + mutt_refresh (); + return (bits); +} +#endif /* _PGPPATH */ + + + + + + + +static void draw_envelope (HEADER *msg, char *fcc) +{ + char buf[STRING]; + int w = COLS - HDR_XOFFSET; + + mvaddstr (HDR_FROM, 0, " From: "); + buf[0] = 0; + rfc822_write_address (buf, sizeof (buf), msg->env->from); + printw ("%-*.*s", w, w, buf); + + mvaddstr (HDR_TO, 0, " To: "); + buf[0] = 0; + rfc822_write_address (buf, sizeof (buf), msg->env->to); + printw ("%-*.*s", w, w, buf); + + mvaddstr (HDR_CC, 0, " Cc: "); + buf[0] = 0; + rfc822_write_address (buf, sizeof (buf), msg->env->cc); + printw ("%-*.*s", w, w, buf); + + mvaddstr (HDR_BCC, 0, " Bcc: "); + buf[0] = 0; + rfc822_write_address (buf, sizeof (buf), msg->env->bcc); + printw ("%-*.*s", w, w, buf); + + mvaddstr (HDR_SUBJECT, 0, " Subject: "); + if (msg->env->subject) + printw ("%-*.*s", w, w, msg->env->subject); + else + clrtoeol (); + + mvaddstr (HDR_REPLYTO, 0, "Reply-To: "); + if (msg->env->reply_to) + { + buf[0] = 0; + rfc822_write_address (buf, sizeof (buf), msg->env->reply_to); + printw ("%-*.*s", w, w, buf); + } + else + clrtoeol (); + + mvaddstr (HDR_FCC, 0, " Fcc: "); + addstr (fcc); + + + +#ifdef _PGPPATH + mvaddstr (HDR_PGP, 0, " PGP: "); + if ((msg->pgp & (PGPENCRYPT | PGPSIGN)) == (PGPENCRYPT | PGPSIGN)) + addstr ("Sign, Encrypt"); + else if (msg->pgp & PGPENCRYPT) + addstr ("Encrypt"); + else if (msg->pgp & PGPSIGN) + addstr ("Sign"); + else + addstr ("Clear"); + clrtoeol (); + + if (msg->pgp & PGPSIGN) + { + mvaddstr (HDR_PGPSIGINFO, 0, " sign as: "); + if (PgpSignAs) + printw ("%s", PgpSignAs); + else + printw ("%s", ""); + clrtoeol (); + mvaddstr (HDR_PGPSIGINFO, 40, "MIC algorithm: "); + printw ("%s", NONULL(PgpSignMicalg)); + clrtoeol (); + } + else + { + mvaddstr(HDR_PGPSIGINFO, 0, ""); + clrtoeol(); + } + +#endif /* _PGPPATH */ + + + + + + + + + + + + mvaddstr (HDR_ATTACH - 1, 0, "===== Attachments ====="); +} + +static int edit_address_list (int line, ENVELOPE *env) +{ + char buf[HUGE_STRING] = ""; /* needs to be large for alias expansion */ + ADDRESS **addr; + char *prompt; + + switch (line) + { + case HDR_FROM: + prompt = "From: "; + addr = &env->from; + break; + case HDR_TO: + prompt = "To: "; + addr = &env->to; + break; + case HDR_CC: + prompt = "Cc: "; + addr = &env->cc; + break; + case HDR_BCC: + prompt = "Bcc: "; + addr = &env->bcc; + break; + case HDR_REPLYTO: + prompt = "Reply-To: "; + addr = &env->reply_to; + break; + default: + return 0; + } + + rfc822_write_address (buf, sizeof (buf), *addr); + if (mutt_get_field (prompt, buf, sizeof (buf), M_ALIAS) != 0) + return 0; + + rfc822_free_address (addr); + *addr = mutt_parse_adrlist (*addr, buf); + *addr = mutt_expand_aliases (*addr); + + if (option (OPTNEEDREDRAW)) + { + unset_option (OPTNEEDREDRAW); + return (REDRAW_FULL); + } + + /* redraw the expanded list so the user can see the result */ + buf[0] = 0; + rfc822_write_address (buf, sizeof (buf), *addr); + move (line, HDR_XOFFSET); + printw ("%-*.*s", COLS - HDR_XOFFSET, COLS - HDR_XOFFSET, buf); + + return 0; +} + +static int delete_attachment (MUTTMENU *menu, short *idxlen, int x) +{ + ATTACHPTR **idx = (ATTACHPTR **) menu->data; + int y; + + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + + if (x == 0 && menu->max == 1) + { + mutt_error ("You may not delete the only attachment."); + idx[x]->content->tagged = 0; + return (-1); + } + + for (y = 0; y < *idxlen; y++) + { + if (idx[y]->content->next == idx[x]->content) + { + idx[y]->content->next = idx[x]->content->next; + break; + } + } + + idx[x]->content->next = NULL; + idx[x]->content->parts = NULL; + mutt_free_body (&(idx[x]->content)); + safe_free ((void **) &idx[x]->tree); + safe_free ((void **) &idx[x]); + for (; x < *idxlen - 1; x++) + idx[x] = idx[x+1]; + menu->max = --(*idxlen); + + return (0); +} + +/* return values: + * + * 1 message should be postponed + * 0 normal exit + * -1 abort message + */ +int mutt_send_menu (HEADER *msg, /* structure for new message */ + char *fcc, /* where to save a copy of the message */ + size_t fcclen, + HEADER *cur) /* current message */ +{ + char helpstr[SHORT_STRING]; + char buf[LONG_STRING]; + char fname[_POSIX_PATH_MAX]; + MUTTMENU *menu; + ATTACHPTR **idx = NULL; + short idxlen = 0; + short idxmax = 0; + int i; + int r = -1; /* return value */ + int op = 0; + int loop = 1; + int fccSet = 0; /* has the user edited the Fcc: field ? */ + + idx = mutt_gen_attach_list (msg->content, idx, &idxlen, &idxmax, 0, 1); + + menu = mutt_new_menu (); + menu->menu = MENU_COMPOSE; + menu->offset = HDR_ATTACH; + menu->max = idxlen; + menu->make_entry = snd_entry; + menu->tag = mutt_tag_attach; + menu->title = "Compose"; + menu->data = idx; + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_COMPOSE, ComposeHelp); + + while (loop) + { + switch (op = mutt_menuLoop (menu)) + { + case OP_REDRAW: + draw_envelope (msg, fcc); + menu->offset = HDR_ATTACH; + menu->pagelen = LINES - HDR_ATTACH - 2; + break; + case OP_COMPOSE_EDIT_FROM: + menu->redraw = edit_address_list (HDR_FROM, msg->env); + break; + case OP_COMPOSE_EDIT_TO: + menu->redraw = edit_address_list (HDR_TO, msg->env); + break; + case OP_COMPOSE_EDIT_BCC: + menu->redraw = edit_address_list (HDR_BCC, msg->env); + break; + case OP_COMPOSE_EDIT_CC: + menu->redraw = edit_address_list (HDR_CC, msg->env); + break; + case OP_COMPOSE_EDIT_SUBJECT: + if (msg->env->subject) + strfcpy (buf, msg->env->subject, sizeof (buf)); + else + buf[0] = 0; + if (mutt_get_field ("Subject: ", buf, sizeof (buf), 0) == 0) + { + safe_free ((void **) &msg->env->subject); + msg->env->subject = safe_strdup (buf); + move (HDR_SUBJECT, HDR_XOFFSET); + clrtoeol (); + if (msg->env->subject) + printw ("%-*.*s", COLS-HDR_XOFFSET, COLS-HDR_XOFFSET, + msg->env->subject); + } + break; + case OP_COMPOSE_EDIT_REPLY_TO: + menu->redraw = edit_address_list (HDR_REPLYTO, msg->env); + break; + case OP_COMPOSE_EDIT_FCC: + strfcpy (buf, fcc, sizeof (buf)); + if (mutt_get_field ("Fcc: ", buf, sizeof (buf), M_FILE | M_CLEAR) == 0) + { + strfcpy (fcc, buf, _POSIX_PATH_MAX); + mutt_pretty_mailbox (fcc); + mvprintw (HDR_FCC, HDR_XOFFSET, "%-*.*s", + COLS - HDR_XOFFSET, COLS - HDR_XOFFSET, fcc); + fccSet = 1; + } + MAYBE_REDRAW (menu->redraw); + break; + case OP_COMPOSE_EDIT_MESSAGE: + if (strcmp ("builtin", Editor) != 0 && !option (OPTEDITHDRS)) + { + mutt_edit_file (Editor, msg->content->filename); + mutt_update_encoding (msg->content); + menu->redraw = REDRAW_CURRENT; + break; + } + /* fall through */ + case OP_COMPOSE_EDIT_HEADERS: + if (op == OP_COMPOSE_EDIT_HEADERS || + (op == OP_COMPOSE_EDIT_MESSAGE && option (OPTEDITHDRS))) + { + mutt_edit_headers (strcmp ("builtin", Editor) == 0 ? Visual : Editor, + msg->content->filename, msg, fcc, fcclen); + } + else + { + /* this is grouped with OP_COMPOSE_EDIT_HEADERS because the + attachment list could change if the user invokes ~v to edit + the message with headers, in which we need to execute the + code below to regenerate the index array */ + mutt_builtin_editor (msg->content->filename, msg, cur); + } + mutt_update_encoding (msg->content); + + /* attachments may have been added */ + if (idxlen && idx[idxlen - 1]->content->next) + { + for (i = 0; i < idxlen; i++) + safe_free ((void **) &idx[i]); + idxlen = 0; + idx = mutt_gen_attach_list (msg->content, idx, &idxlen, &idxmax, 0, 1); + menu->max = idxlen; + } + + menu->redraw = REDRAW_FULL; + break; + + + +#ifdef _PGPPATH + case OP_COMPOSE_ATTACH_KEY: + + if (idxlen == idxmax) + { + safe_realloc ((void **) &idx, sizeof (ATTACHPTR *) * (idxmax += 5)); + menu->data = idx; + } + + idx[idxlen] = (ATTACHPTR *) safe_calloc (1, sizeof (ATTACHPTR)); + if ((idx[idxlen]->content = pgp_make_key_attachment(NULL)) != NULL) + { + idx[idxlen]->level = (idxlen > 0) ? idx[idxlen-1]->level : 0; + + if(idxlen) + idx[idxlen - 1]->content->next = idx[idxlen]->content; + + menu->current = idxlen++; + mutt_update_tree (idx, idxlen); + menu->max = idxlen; + menu->redraw |= REDRAW_INDEX; + } + else + safe_free ((void **) &idx[idxlen]); + + menu->redraw |= REDRAW_STATUS; + + if(option(OPTNEEDREDRAW)) + { + menu->redraw = REDRAW_FULL; + unset_option(OPTNEEDREDRAW); + } + + break; +#endif + + + + + + + + + + + + + + case OP_COMPOSE_ATTACH_FILE: + fname[0] = 0; + if (mutt_enter_fname ("Attach file", fname, sizeof (fname), + &menu->redraw, 0) == -1) + break; + if (!fname[0]) + continue; + mutt_expand_path (fname, sizeof (fname)); + + /* check to make sure the file exists and is readable */ + if (access (fname, R_OK) == -1) + { + mutt_perror (fname); + break; + } + + if (idxlen == idxmax) + { + safe_realloc ((void **) &idx, sizeof (ATTACHPTR *) * (idxmax += 5)); + menu->data = idx; + } + + idx[idxlen] = (ATTACHPTR *) safe_calloc (1, sizeof (ATTACHPTR)); + if ((idx[idxlen]->content = mutt_make_attach (fname)) != NULL) + { + idx[idxlen]->level = (idxlen > 0) ? idx[idxlen-1]->level : 0; + + if (idxlen) + idx[idxlen - 1]->content->next = idx[idxlen]->content; + + menu->current = idxlen++; + mutt_update_tree (idx, idxlen); + menu->max = idxlen; + menu->redraw |= REDRAW_INDEX | REDRAW_STATUS; + } + else + { + mutt_error ("Unable to attach file!"); + safe_free ((void **) &idx[idxlen]); + } + break; + + case OP_DELETE: + CHECK_COUNT; + if (delete_attachment (menu, &idxlen, menu->current) == -1) + break; + mutt_update_tree (idx, idxlen); + if (idxlen) + { + if (menu->current > idxlen - 1) + menu->current = idxlen - 1; + } + else + menu->current = 0; + + if (menu->current == 0) + msg->content = idx[0]->content; + break; + + case OP_COMPOSE_EDIT_DESCRIPTION: + CHECK_COUNT; + strfcpy (buf, + idx[menu->current]->content->description ? + idx[menu->current]->content->description : "", + sizeof (buf)); + if (mutt_get_field ("Description: ", buf, sizeof (buf), 0) == 0) + { + safe_free ((void **) &idx[menu->current]->content->description); + idx[menu->current]->content->description = safe_strdup (buf); + menu->redraw = REDRAW_CURRENT; + } + break; + + case OP_COMPOSE_EDIT_TYPE: + CHECK_COUNT; + snprintf (buf, sizeof (buf), "%s/%s", + TYPE (idx[menu->current]->content->type), + idx[menu->current]->content->subtype); + if (mutt_get_field ("Content-Type: ", buf, sizeof (buf), 0) == 0 && buf[0]) + { + char *p = strchr (buf, '/'); + + if (p) + { + *p++ = 0; + if ((i = mutt_check_mime_type (buf)) != TYPEOTHER) + { + idx[menu->current]->content->type = i; + safe_free ((void **) &idx[menu->current]->content->subtype); + idx[menu->current]->content->subtype = safe_strdup (p); + menu->redraw = REDRAW_CURRENT; + } + } + } + break; + + case OP_COMPOSE_EDIT_ENCODING: + CHECK_COUNT; + strfcpy (buf, ENCODING (idx[menu->current]->content->encoding), + sizeof (buf)); + if (mutt_get_field ("Content-Transfer-Encoding: ", buf, + sizeof (buf), 0) == 0 && buf[0]) + { + if ((i = mutt_check_encoding (buf)) != ENCOTHER) + { + idx[menu->current]->content->encoding = i; + menu->redraw = REDRAW_CURRENT; + } + else + mutt_error ("Invalid encoding."); + } + break; + + case OP_COMPOSE_SEND_MESSAGE: + if (!fccSet && *fcc) + { + if ((i = query_quadoption (OPT_COPY, "Save a copy of this message?")) + == -1) + break; + else if (i == M_NO) + *fcc = 0; + } + + loop = 0; + r = 0; + break; + + case OP_COMPOSE_EDIT_FILE: + CHECK_COUNT; + mutt_edit_file (strcmp ("builtin", Editor) == 0 ? Visual : Editor, + idx[menu->current]->content->filename); + mutt_update_encoding (idx[menu->current]->content); + menu->redraw = REDRAW_CURRENT; + break; + + case OP_COMPOSE_TOGGLE_UNLINK: + CHECK_COUNT; + idx[menu->current]->content->unlink = !idx[menu->current]->content->unlink; + menu->redraw = REDRAW_INDEX; + break; + + case OP_COMPOSE_RENAME_FILE: + CHECK_COUNT; + strfcpy (fname, idx[menu->current]->content->filename, sizeof (fname)); + mutt_pretty_mailbox (fname); + if (mutt_get_field ("Rename to: ", fname, sizeof (fname), M_FILE) == 0 + && fname[0]) + { + mutt_expand_path (fname, sizeof (fname)); + if (!mutt_rename_file (idx[menu->current]->content->filename, fname)) + { + safe_free ((void **) &idx[menu->current]->content->filename); + idx[menu->current]->content->filename = safe_strdup (fname); + menu->redraw = REDRAW_CURRENT; + } + } + break; + + case OP_COMPOSE_NEW_MIME: + { + char type[STRING]; + char *p; + int itype; + FILE *fp; + + CLEARLINE (LINES-1); + fname[0] = 0; + if (mutt_get_field ("New file: ", fname, sizeof (fname), M_FILE) != 0 + || !fname[0]) + continue; + mutt_expand_path (fname, sizeof (fname)); + + /* Call to lookup_mime_type () ? maybe later */ + type[0] = 0; + if (mutt_get_field ("Content-Type: ", type, sizeof (type), 0) != 0 + || !type[0]) + continue; + + if (!(p = strchr (type, '/'))) + { + mutt_error ("Content-Type is of the form base/sub"); + continue; + } + *p++ = 0; + if ((itype = mutt_check_mime_type (type)) == TYPEOTHER) + { + mutt_error ("Unknown Content-Type %s", type); + continue; + } + if (idxlen == idxmax) + { + safe_realloc ((void **) &idx, sizeof (ATTACHPTR *) * (idxmax += 5)); + menu->data = idx; + } + + idx[idxlen] = (ATTACHPTR *) safe_calloc (1, sizeof (ATTACHPTR)); + /* Touch the file */ + if (!(fp = safe_fopen (fname, "w"))) + { + mutt_error ("Can't create file %s", fname); + safe_free ((void **) &idx[idxlen]); + continue; + } + fclose (fp); + + if ((idx[idxlen]->content = mutt_make_attach (fname)) == NULL) + { + mutt_error ("What we have here is a failure to make an attachment"); + continue; + } + + idx[idxlen]->level = (idxlen > 0) ? idx[idxlen-1]->level : 0; + if (idxlen) + idx[idxlen - 1]->content->next = idx[idxlen]->content; + + menu->current = idxlen++; + mutt_update_tree (idx, idxlen); + menu->max = idxlen; + + idx[menu->current]->content->type = itype; + safe_free ((void **) &idx[menu->current]->content->subtype); + idx[menu->current]->content->subtype = safe_strdup (p); + idx[menu->current]->content->unlink = 1; + menu->redraw |= REDRAW_INDEX | REDRAW_STATUS; + + if (mutt_compose_attachment (idx[menu->current]->content)) + { + mutt_update_encoding (idx[menu->current]->content); + menu->redraw = REDRAW_FULL; + } + } + break; + + + + + + + case OP_COMPOSE_EDIT_MIME: + CHECK_COUNT; + if (mutt_edit_attachment (idx[menu->current]->content, 0)) + { + mutt_update_encoding (idx[menu->current]->content); + menu->redraw = REDRAW_FULL; + } + break; + + case OP_VIEW_ATTACH: + case OP_DISPLAY_HEADERS: + CHECK_COUNT; + mutt_attach_display_loop (menu, op, NULL, idx); + menu->redraw = REDRAW_FULL; + mutt_clear_error (); + break; + + case OP_SAVE: + CHECK_COUNT; + mutt_save_attachment_list (NULL, menu->tagprefix, menu->tagprefix ? msg->content : idx[menu->current]->content); + break; + + case OP_PRINT: + CHECK_COUNT; + mutt_print_attachment_list (NULL, menu->tagprefix, menu->tagprefix ? msg->content : idx[menu->current]->content); + break; + + case OP_PIPE: + case OP_FILTER: + CHECK_COUNT; + mutt_pipe_attachment_list (NULL, menu->tagprefix, menu->tagprefix ? msg->content : idx[menu->current]->content, op == OP_FILTER); + if (op == OP_FILTER) + menu->redraw = REDRAW_CURRENT; /* cte might have changed */ + break; + + + + + + + case OP_EXIT: + if ((i = query_quadoption (OPT_POSTPONE, "Postpone this message?")) == M_NO) + { + while (idxlen-- > 0) + { + /* avoid freeing other attachments */ + idx[idxlen]->content->next = NULL; + idx[idxlen]->content->parts = NULL; + mutt_free_body (&idx[idxlen]->content); + safe_free ((void **) &idx[idxlen]->tree); + safe_free ((void **) &idx[idxlen]); + } + safe_free ((void **) &idx); + idxlen = 0; + idxmax = 0; + r = -1; + loop = 0; + break; + } + else if (i == -1) + break; /* abort */ + + /* fall through to postpone! */ + + case OP_COMPOSE_POSTPONE_MESSAGE: + loop = 0; + r = 1; + break; + + case OP_COMPOSE_ISPELL: + endwin (); + snprintf (buf, sizeof (buf), "%s -x %s", Ispell, msg->content->filename); + mutt_system (buf); + break; + + + +#ifdef _PGPPATH + case OP_COMPOSE_PGP_MENU: + + msg->pgp = pgp_send_menu (msg->pgp); + menu->redraw = REDRAW_FULL; + break; + + case OP_FORGET_PASSPHRASE: + + mutt_forget_passphrase (); + break; + +#endif /* _PGPPATH */ + + + + } + } + + mutt_menuDestroy (&menu); + + if (idxlen) + { + msg->content = idx[0]->content; + for (i = 0; i < idxlen; i++) + safe_free ((void **) &idx[i]); + } + else + msg->content = NULL; + + safe_free ((void **) &idx); + + return (r); +} diff --git a/config.h.in b/config.h.in new file mode 100644 index 00000000..8b888139 --- /dev/null +++ b/config.h.in @@ -0,0 +1,176 @@ +/* config.h.in. Generated automatically from configure.in by autoheader. */ + +/* Enable debugging info */ +#define DEBUG + +/* Does your version of PGP support the PGPPASSFD environment variable? */ +#define HAVE_PGPPASSFD + +/* Disable the X-Mailer header? */ +#undef NO_XMAILER + +/* What is your domain name? */ +#undef DOMAIN + +/* Define to `int' if doesn't define. */ +#undef pid_t + +/* Define as the return type of signal handlers (int or void). */ +#undef RETSIGTYPE + +/* Define if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define if `sys_siglist' is declared by . */ +#undef SYS_SIGLIST_DECLARED + +/* Mutt version info */ +#undef VERSION + +/* use dotlocking to lock mailboxes? */ +#undef USE_DOTLOCK + +/* use flock() to lock mailboxes? */ +#undef USE_FLOCK + +/* Use fcntl() to lock folders? */ +#undef USE_FCNTL + +/* + * Define if you have problems with mutt not detecting new/old mailboxes + * over NFS. Some NFS implementations incorrectly cache the attributes + * of small files. + */ +#undef NFS_ATTRIBUTE_HACK + +/* Do you want support for the POP3 protocol? (--enable-pop) */ +#undef USE_POP + +/* + * Is mail spooled to the user's home directory? If defined, MAILPATH should + * be set to the filename of the spool mailbox relative the the home + * directory. + * use: configure --with-homespool=FILE + */ +#undef HOMESPOOL + +/* Where new mail is spooled */ +#undef MAILPATH + +/* Should I just use the domain name? (--enable-hidden-host) */ +#undef HIDDEN_HOST + +/* Where to find sendmail on your system */ +#undef SENDMAIL + +/* Where is PGP located on your system? */ +#undef _PGPPATH + +/* Where is PGP 2.* located on your system? */ +#undef _PGPV2PATH + +/* Where is PGP 5 located on your system? */ +#undef _PGPV3PATH + +/* Do we have PGP 2.*? */ +#undef HAVE_PGP2 + +/* Do we have PGP 5.0 or up? */ +#undef HAVE_PGP5 + +/* Where to find ispell on your system? */ +#undef ISPELL + +/* Should Mutt run setgid "mail" ? */ +#undef USE_SETGID + +/* Does your curses library support color? */ +#undef HAVE_COLOR + +/* Are we using GNU rx? */ +#undef USE_GNU_RX + +/* Compiling with SLang instead of curses/ncurses? */ +#undef USE_SLANG_CURSES + +/* program to use for shell commands */ +#define EXECSHELL "/bin/sh" + +/* The "buffy_size" feature */ +#undef BUFFY_SIZE + +/* The result of isprint() is unreliable? */ +#undef LOCALES_HACK + +/* Enable exact regeneration of email addresses as parsed? NOTE: this requires + significant more memory when defined. */ +#undef EXACT_ADDRESS + +/* Does your system have the snprintf() call? */ +#undef HAVE_SNPRINTF + +/* Does your system have the vsnprintf() call? */ +#undef HAVE_VSNPRINTF + +/* The number of bytes in a long. */ +#undef SIZEOF_LONG + +/* Define if you have the bkgdset function. */ +#undef HAVE_BKGDSET + +/* Define if you have the curs_set function. */ +#undef HAVE_CURS_SET + +/* Define if you have the ftruncate function. */ +#undef HAVE_FTRUNCATE + +/* Define if you have the meta function. */ +#undef HAVE_META + +/* Define if you have the regcomp function. */ +#undef HAVE_REGCOMP + +/* Define if you have the resizeterm function. */ +#undef HAVE_RESIZETERM + +/* Define if you have the setegid function. */ +#undef HAVE_SETEGID + +/* Define if you have the srand48 function. */ +#undef HAVE_SRAND48 + +/* Define if you have the strcasecmp function. */ +#undef HAVE_STRCASECMP + +/* Define if you have the strerror function. */ +#undef HAVE_STRERROR + +/* Define if you have the strftime function. */ +#undef HAVE_STRFTIME + +/* Define if you have the typeahead function. */ +#undef HAVE_TYPEAHEAD + +/* Define if you have the use_default_colors function. */ +#undef HAVE_USE_DEFAULT_COLORS + +/* Define if you have the header file. */ +#undef HAVE_NCURSES_H + +/* Define if you have the header file. */ +#undef HAVE_STDARG_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_IOCTL_H + +/* Define if you have the intl library (-lintl). */ +#undef HAVE_LIBINTL + +/* Define if you have the nsl library (-lnsl). */ +#undef HAVE_LIBNSL + +/* Define if you have the socket library (-lsocket). */ +#undef HAVE_LIBSOCKET + +/* Define if you have the x library (-lx). */ +#undef HAVE_LIBX diff --git a/configure b/configure new file mode 100755 index 00000000..4fd0e178 --- /dev/null +++ b/configure @@ -0,0 +1,3093 @@ +#! /bin/sh + +# Guess values for system-dependent variables and create Makefiles. +# Generated automatically using autoconf version 2.12 +# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc. +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. + +# Defaults: +ac_help= +ac_default_prefix=/usr/local +# Any additions from configure.in: +ac_help="$ac_help + --with-slang[=DIR] use S-Lang instead of ncurses" +ac_help="$ac_help + --with-curses=DIR ncurses is installed in " +ac_help="$ac_help + --with-rx[=DIR] Use GNU rx " +ac_help="$ac_help + --with-homespool[=FILE] file in user's directory where new mail is spooled" +ac_help="$ac_help + --with-mailpath=DIR directory where spool mailboxes are located" +ac_help="$ac_help + --with-sharedir=PATH specify where to put arch independent files" +ac_help="$ac_help + --with-domain=DOMAIN Specify your DNS domain name " +ac_help="$ac_help + --enable-hidden-host Only use the domain name for local addresses" +ac_help="$ac_help + --enable-pop Enable POP3 support" +ac_help="$ac_help + --enable-flock Use flock() to lock files" +ac_help="$ac_help + --disable-fcntl Do NOT use fcntl() to lock files " +ac_help="$ac_help + --disable-warnings turn off compiler warnings (not recommended)" +ac_help="$ac_help + --enable-nfs-fix Work around an NFS with broken attributes caching " +ac_help="$ac_help + --enable-buffy-size Use file size attribute instead of access time " +ac_help="$ac_help + --enable-locales-fix The result of isprint() is unreliable " +ac_help="$ac_help + --with-exec-shell=SHELL Specify alternate shell (ONLY if /bin/sh is broken)" +ac_help="$ac_help + --enable-exact-address enable regeneration of email addresses" + +# Initialize some variables set by options. +# The variables have the same names as the options, with +# dashes changed to underlines. +build=NONE +cache_file=./config.cache +exec_prefix=NONE +host=NONE +no_create= +nonopt=NONE +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +target=NONE +verbose= +x_includes=NONE +x_libraries=NONE +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datadir='${prefix}/share' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +libdir='${exec_prefix}/lib' +includedir='${prefix}/include' +oldincludedir='/usr/include' +infodir='${prefix}/info' +mandir='${prefix}/man' + +# Initialize some other variables. +subdirs= +MFLAGS= MAKEFLAGS= +# Maximum number of lines to put in a shell here document. +ac_max_here_lines=12 + +ac_prev= +for ac_option +do + + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval "$ac_prev=\$ac_option" + ac_prev= + continue + fi + + case "$ac_option" in + -*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;; + *) ac_optarg= ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case "$ac_option" in + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir="$ac_optarg" ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build="$ac_optarg" ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file="$ac_optarg" ;; + + -datadir | --datadir | --datadi | --datad | --data | --dat | --da) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \ + | --da=*) + datadir="$ac_optarg" ;; + + -disable-* | --disable-*) + ac_feature=`echo $ac_option|sed -e 's/-*disable-//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_feature| sed 's/[-a-zA-Z0-9_]//g'`"; then + { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; } + fi + ac_feature=`echo $ac_feature| sed 's/-/_/g'` + eval "enable_${ac_feature}=no" ;; + + -enable-* | --enable-*) + ac_feature=`echo $ac_option|sed -e 's/-*enable-//' -e 's/=.*//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_feature| sed 's/[-_a-zA-Z0-9]//g'`"; then + { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; } + fi + ac_feature=`echo $ac_feature| sed 's/-/_/g'` + case "$ac_option" in + *=*) ;; + *) ac_optarg=yes ;; + esac + eval "enable_${ac_feature}='$ac_optarg'" ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix="$ac_optarg" ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he) + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat << EOF +Usage: configure [options] [host] +Options: [defaults in brackets after descriptions] +Configuration: + --cache-file=FILE cache test results in FILE + --help print this message + --no-create do not create output files + --quiet, --silent do not print \`checking...' messages + --version print the version of autoconf that created configure +Directory and file names: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [same as prefix] + --bindir=DIR user executables in DIR [EPREFIX/bin] + --sbindir=DIR system admin executables in DIR [EPREFIX/sbin] + --libexecdir=DIR program executables in DIR [EPREFIX/libexec] + --datadir=DIR read-only architecture-independent data in DIR + [PREFIX/share] + --sysconfdir=DIR read-only single-machine data in DIR [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data in DIR + [PREFIX/com] + --localstatedir=DIR modifiable single-machine data in DIR [PREFIX/var] + --libdir=DIR object code libraries in DIR [EPREFIX/lib] + --includedir=DIR C header files in DIR [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc in DIR [/usr/include] + --infodir=DIR info documentation in DIR [PREFIX/info] + --mandir=DIR man documentation in DIR [PREFIX/man] + --srcdir=DIR find the sources in DIR [configure dir or ..] + --program-prefix=PREFIX prepend PREFIX to installed program names + --program-suffix=SUFFIX append SUFFIX to installed program names + --program-transform-name=PROGRAM + run sed PROGRAM on installed program names +EOF + cat << EOF +Host type: + --build=BUILD configure for building on BUILD [BUILD=HOST] + --host=HOST configure for HOST [guessed] + --target=TARGET configure for TARGET [TARGET=HOST] +Features and packages: + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --x-includes=DIR X include files are in DIR + --x-libraries=DIR X library files are in DIR +EOF + if test -n "$ac_help"; then + echo "--enable and --with options recognized:$ac_help" + fi + exit 0 ;; + + -host | --host | --hos | --ho) + ac_prev=host ;; + -host=* | --host=* | --hos=* | --ho=*) + host="$ac_optarg" ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir="$ac_optarg" ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir="$ac_optarg" ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir="$ac_optarg" ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir="$ac_optarg" ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst \ + | --locals | --local | --loca | --loc | --lo) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* \ + | --locals=* | --local=* | --loca=* | --loc=* | --lo=*) + localstatedir="$ac_optarg" ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir="$ac_optarg" ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir="$ac_optarg" ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix="$ac_optarg" ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix="$ac_optarg" ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix="$ac_optarg" ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name="$ac_optarg" ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir="$ac_optarg" ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir="$ac_optarg" ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site="$ac_optarg" ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir="$ac_optarg" ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir="$ac_optarg" ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target="$ac_optarg" ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers) + echo "configure generated by autoconf version 2.12" + exit 0 ;; + + -with-* | --with-*) + ac_package=`echo $ac_option|sed -e 's/-*with-//' -e 's/=.*//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_package| sed 's/[-_a-zA-Z0-9]//g'`"; then + { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; } + fi + ac_package=`echo $ac_package| sed 's/-/_/g'` + case "$ac_option" in + *=*) ;; + *) ac_optarg=yes ;; + esac + eval "with_${ac_package}='$ac_optarg'" ;; + + -without-* | --without-*) + ac_package=`echo $ac_option|sed -e 's/-*without-//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_package| sed 's/[-a-zA-Z0-9_]//g'`"; then + { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; } + fi + ac_package=`echo $ac_package| sed 's/-/_/g'` + eval "with_${ac_package}=no" ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes="$ac_optarg" ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries="$ac_optarg" ;; + + -*) { echo "configure: error: $ac_option: invalid option; use --help to show usage" 1>&2; exit 1; } + ;; + + *) + if test -n "`echo $ac_option| sed 's/[-a-z0-9.]//g'`"; then + echo "configure: warning: $ac_option: invalid host type" 1>&2 + fi + if test "x$nonopt" != xNONE; then + { echo "configure: error: can only configure for one host and one target at a time" 1>&2; exit 1; } + fi + nonopt="$ac_option" + ;; + + esac +done + +if test -n "$ac_prev"; then + { echo "configure: error: missing argument to --`echo $ac_prev | sed 's/_/-/g'`" 1>&2; exit 1; } +fi + +trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 + +# File descriptor usage: +# 0 standard input +# 1 file creation +# 2 errors and warnings +# 3 some systems may open it to /dev/tty +# 4 used on the Kubota Titan +# 6 checking for... messages and results +# 5 compiler messages saved in config.log +if test "$silent" = yes; then + exec 6>/dev/null +else + exec 6>&1 +fi +exec 5>./config.log + +echo "\ +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. +" 1>&5 + +# Strip out --no-create and --no-recursion so they do not pile up. +# Also quote any args containing shell metacharacters. +ac_configure_args= +for ac_arg +do + case "$ac_arg" in + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c) ;; + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) ;; + *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?]*) + ac_configure_args="$ac_configure_args '$ac_arg'" ;; + *) ac_configure_args="$ac_configure_args $ac_arg" ;; + esac +done + +# NLS nuisances. +# Only set these to C if already set. These must not be set unconditionally +# because not all systems understand e.g. LANG=C (notably SCO). +# Fixing LC_MESSAGES prevents Solaris sh from translating var values in `set'! +# Non-C LC_CTYPE values break the ctype check. +if test "${LANG+set}" = set; then LANG=C; export LANG; fi +if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi +if test "${LC_MESSAGES+set}" = set; then LC_MESSAGES=C; export LC_MESSAGES; fi +if test "${LC_CTYPE+set}" = set; then LC_CTYPE=C; export LC_CTYPE; fi + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -rf conftest* confdefs.h +# AIX cpp loses on an empty file, so make sure it contains at least a newline. +echo > confdefs.h + +# A filename unique to this package, relative to the directory that +# configure is in, which we can look for to find out if srcdir is correct. +ac_unique_file=mutt.h + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then its parent. + ac_prog=$0 + ac_confdir=`echo $ac_prog|sed 's%/[^/][^/]*$%%'` + test "x$ac_confdir" = "x$ac_prog" && ac_confdir=. + srcdir=$ac_confdir + if test ! -r $srcdir/$ac_unique_file; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r $srcdir/$ac_unique_file; then + if test "$ac_srcdir_defaulted" = yes; then + { echo "configure: error: can not find sources in $ac_confdir or .." 1>&2; exit 1; } + else + { echo "configure: error: can not find sources in $srcdir" 1>&2; exit 1; } + fi +fi +srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'` + +# Prefer explicitly selected file to automatically selected ones. +if test -z "$CONFIG_SITE"; then + if test "x$prefix" != xNONE; then + CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site" + else + CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" + fi +fi +for ac_site_file in $CONFIG_SITE; do + if test -r "$ac_site_file"; then + echo "loading site script $ac_site_file" + . "$ac_site_file" + fi +done + +if test -r "$cache_file"; then + echo "loading cache $cache_file" + . $cache_file +else + echo "creating cache $cache_file" + > $cache_file +fi + +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then + # Stardent Vistra SVR4 grep lacks -e, says ghazi@caip.rutgers.edu. + if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then + ac_n= ac_c=' +' ac_t=' ' + else + ac_n=-n ac_c= ac_t= + fi +else + ac_n= ac_c='\c' ac_t= +fi + + + +VERSION=0.92.8 +SUBVERSION='' + +echo $ac_n "checking for prefix""... $ac_c" 1>&6 +echo "configure:561: checking for prefix" >&5 +if test x$prefix = xNONE; then + mutt_cv_prefix=$ac_default_prefix +else + mutt_cv_prefix=$prefix +fi +echo "$ac_t""$mutt_cv_prefix" 1>&6 + +# Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:572: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_CC="gcc" + break + fi + done + IFS="$ac_save_ifs" +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:601: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + ac_prog_rejected=no + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + if test "$ac_dir/$ac_word" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + break + fi + done + IFS="$ac_save_ifs" +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# -gt 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + set dummy "$ac_dir/$ac_word" "$@" + shift + ac_cv_prog_CC="$@" + fi +fi +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + test -z "$CC" && { echo "configure: error: no acceptable cc found in \$PATH" 1>&2; exit 1; } +fi + +echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works""... $ac_c" 1>&6 +echo "configure:649: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5 + +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + ac_cv_prog_cc_works=yes + # If we can't run a trivial program, we are probably using a cross compiler. + if (./conftest; exit) 2>/dev/null; then + ac_cv_prog_cc_cross=no + else + ac_cv_prog_cc_cross=yes + fi +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + ac_cv_prog_cc_works=no +fi +rm -fr conftest* + +echo "$ac_t""$ac_cv_prog_cc_works" 1>&6 +if test $ac_cv_prog_cc_works = no; then + { echo "configure: error: installation or configuration problem: C compiler cannot create executables." 1>&2; exit 1; } +fi +echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler""... $ac_c" 1>&6 +echo "configure:683: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5 +echo "$ac_t""$ac_cv_prog_cc_cross" 1>&6 +cross_compiling=$ac_cv_prog_cc_cross + +echo $ac_n "checking whether we are using GNU C""... $ac_c" 1>&6 +echo "configure:688: checking whether we are using GNU C" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_gcc'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.c <&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then + ac_cv_prog_gcc=yes +else + ac_cv_prog_gcc=no +fi +fi + +echo "$ac_t""$ac_cv_prog_gcc" 1>&6 + +if test $ac_cv_prog_gcc = yes; then + GCC=yes + ac_test_CFLAGS="${CFLAGS+set}" + ac_save_CFLAGS="$CFLAGS" + CFLAGS= + echo $ac_n "checking whether ${CC-cc} accepts -g""... $ac_c" 1>&6 +echo "configure:712: checking whether ${CC-cc} accepts -g" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_cc_g'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + echo 'void f(){}' > conftest.c +if test -z "`${CC-cc} -g -c conftest.c 2>&1`"; then + ac_cv_prog_cc_g=yes +else + ac_cv_prog_cc_g=no +fi +rm -f conftest* + +fi + +echo "$ac_t""$ac_cv_prog_cc_g" 1>&6 + if test "$ac_test_CFLAGS" = set; then + CFLAGS="$ac_save_CFLAGS" + elif test $ac_cv_prog_cc_g = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-O2" + fi +else + GCC= + test "${CFLAGS+set}" = set || CFLAGS="-g" +fi + +echo $ac_n "checking whether ${MAKE-make} sets \${MAKE}""... $ac_c" 1>&6 +echo "configure:740: checking whether ${MAKE-make} sets \${MAKE}" >&5 +set dummy ${MAKE-make}; ac_make=`echo "$2" | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_prog_make_${ac_make}_set'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftestmake <<\EOF +all: + @echo 'ac_maketemp="${MAKE}"' +EOF +# GNU make sometimes prints "make[1]: Entering...", which would confuse us. +eval `${MAKE-make} -f conftestmake 2>/dev/null | grep temp=` +if test -n "$ac_maketemp"; then + eval ac_cv_prog_make_${ac_make}_set=yes +else + eval ac_cv_prog_make_${ac_make}_set=no +fi +rm -f conftestmake +fi +if eval "test \"`echo '$ac_cv_prog_make_'${ac_make}_set`\" = yes"; then + echo "$ac_t""yes" 1>&6 + SET_MAKE= +else + echo "$ac_t""no" 1>&6 + SET_MAKE="MAKE=${MAKE-make}" +fi + +ac_aux_dir= +for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do + if test -f $ac_dir/install-sh; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f $ac_dir/install.sh; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + fi +done +if test -z "$ac_aux_dir"; then + { echo "configure: error: can not find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." 1>&2; exit 1; } +fi +ac_config_guess=$ac_aux_dir/config.guess +ac_config_sub=$ac_aux_dir/config.sub +ac_configure=$ac_aux_dir/configure # This should be Cygnus configure. + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# ./install, which can be erroneously created by make from ./install.sh. +echo $ac_n "checking for a BSD compatible install""... $ac_c" 1>&6 +echo "configure:796: checking for a BSD compatible install" >&5 +if test -z "$INSTALL"; then +if eval "test \"`echo '$''{'ac_cv_path_install'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + IFS="${IFS= }"; ac_save_IFS="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + # Account for people who put trailing slashes in PATH elements. + case "$ac_dir/" in + /|./|.//|/etc/*|/usr/sbin/*|/usr/etc/*|/sbin/*|/usr/afsws/bin/*|/usr/ucb/*) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + for ac_prog in ginstall installbsd scoinst install; do + if test -f $ac_dir/$ac_prog; then + if test $ac_prog = install && + grep dspmsg $ac_dir/$ac_prog >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + # OSF/1 installbsd also uses dspmsg, but is usable. + : + else + ac_cv_path_install="$ac_dir/$ac_prog -c" + break 2 + fi + fi + done + ;; + esac + done + IFS="$ac_save_IFS" + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL="$ac_cv_path_install" + else + # As a last resort, use the slow shell script. We don't cache a + # path for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the path is relative. + INSTALL="$ac_install_sh" + fi +fi +echo "$ac_t""$INSTALL" 1>&6 + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + + +# Extract the first word of "sendmail", so it can be a program name with args. +set dummy sendmail; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:849: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_path_SENDMAIL'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + case "$SENDMAIL" in + /*) + ac_cv_path_SENDMAIL="$SENDMAIL" # Let the user override the test with a path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in `echo $PATH | sed "s/:/ /"` /usr/sbin /usr/lib$ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_path_SENDMAIL="$ac_dir/$ac_word" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_path_SENDMAIL" && ac_cv_path_SENDMAIL="no" + ;; +esac +fi +SENDMAIL="$ac_cv_path_SENDMAIL" +if test -n "$SENDMAIL"; then + echo "$ac_t""$SENDMAIL" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +cat >> confdefs.h <&6 +echo "configure:890: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_path_PGPK'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + case "$PGPK" in + /*) + ac_cv_path_PGPK="$PGPK" # Let the user override the test with a path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_path_PGPK="$ac_dir/$ac_word" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_path_PGPK" && ac_cv_path_PGPK="no" + ;; +esac +fi +PGPK="$ac_cv_path_PGPK" +if test -n "$PGPK"; then + echo "$ac_t""$PGPK" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + if test $PGPK != no ; then + PGPK=`echo $PGPK | sed 's,.$,,'` + cat >> confdefs.h <> confdefs.h <<\EOF +#define HAVE_PGP5 1 +EOF + + fi + + # Extract the first word of "pgp", so it can be a program name with args. +set dummy pgp; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:935: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_path_PGP'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + case "$PGP" in + /*) + ac_cv_path_PGP="$PGP" # Let the user override the test with a path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_path_PGP="$ac_dir/$ac_word" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_path_PGP" && ac_cv_path_PGP="no" + ;; +esac +fi +PGP="$ac_cv_path_PGP" +if test -n "$PGP"; then + echo "$ac_t""$PGP" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + if test $PGP != no ; then + cat >> confdefs.h <> confdefs.h <<\EOF +#define HAVE_PGP2 1 +EOF + + fi + + if test $PGPPATH != no ; then + cat >> confdefs.h <> confdefs.h <&6 +echo "configure:999: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_path_ISPELL'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + case "$ISPELL" in + /*) + ac_cv_path_ISPELL="$ISPELL" # Let the user override the test with a path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_path_ISPELL="$ac_dir/$ac_word" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_path_ISPELL" && ac_cv_path_ISPELL="no" + ;; +esac +fi +ISPELL="$ac_cv_path_ISPELL" +if test -n "$ISPELL"; then + echo "$ac_t""$ISPELL" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +if test $ISPELL != no; then + cat >> confdefs.h <&6 +echo "configure:1036: checking how to run the C preprocessor" >&5 +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then +if eval "test \"`echo '$''{'ac_cv_prog_CPP'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + # This must be in double quotes, not single quotes, because CPP may get + # substituted into the Makefile and "${CC-cc}" will confuse make. + CPP="${CC-cc} -E" + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. + cat > conftest.$ac_ext < +Syntax Error +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1057: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out` +if test -z "$ac_err"; then + : +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + CPP="${CC-cc} -E -traditional-cpp" + cat > conftest.$ac_ext < +Syntax Error +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1074: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out` +if test -z "$ac_err"; then + : +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + CPP=/lib/cpp +fi +rm -f conftest* +fi +rm -f conftest* + ac_cv_prog_CPP="$CPP" +fi + CPP="$ac_cv_prog_CPP" +else + ac_cv_prog_CPP="$CPP" +fi +echo "$ac_t""$CPP" 1>&6 + +# Check whether --with-slang or --without-slang was given. +if test "${with_slang+set}" = set; then + withval="$with_slang" + echo $ac_n "checking if -ltermlib is required""... $ac_c" 1>&6 +echo "configure:1100: checking if -ltermlib is required" >&5 +if eval "test \"`echo '$''{'mutt_cv_bsdish'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test "$cross_compiling" = yes; then + { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; } +else + cat > conftest.$ac_ext < + +main () +{ +#ifdef BSD + exit (0); +#else + exit (1); +#endif +} +EOF +if { (eval echo configure:1121: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest && (./conftest; exit) 2>/dev/null +then + mutt_cv_bsdish=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + mutt_cv_bsdish=no +fi +rm -fr conftest* +fi + +fi + +echo "$ac_t""$mutt_cv_bsdish" 1>&6 + + echo $ac_n "checking for S-Lang""... $ac_c" 1>&6 +echo "configure:1138: checking for S-Lang" >&5 + if test $withval = yes; then + if test -d $srcdir/../slang; then + mutt_cv_slang=$srcdir/../slang/src + CPPFLAGS="$CPPFLAGS -I${mutt_cv_slang}" + LDFLAGS="$LDFLAGS -L${mutt_cv_slang}/objs" + else + if test -d $mutt_cv_prefix/include/slang; then + CPPFLAGS="$CPPFLAGS -I$mutt_cv_prefix/include/slang" + elif test -d /usr/include/slang; then + CPPFLAGS="$CPPFLAGS -I/usr/include/slang" + fi + mutt_cv_slang=yes + fi + else + if test -f $withval/src/slang.h; then + mutt_cv_slang=$withval/src + CPPFLAGS="$CPPFLAGS -I${mutt_cv_slang}" + LDFLAGS="$LDFLAGS -L${mutt_cv_slang}/objs" + else + mutt_cv_slang=$withval + if test -d $withval/include/slang; then + CPPFLAGS="$CPPFLAGS -I${withval}/include/slang" + elif test -d $withval/include; then + CPPFLAGS="$CPPFLAGS -I${withval}/include" + fi + LDFLAGS="$LDFLAGS -L${withval}/lib" + fi + fi + echo "$ac_t""$mutt_cv_slang" 1>&6 + LIBS="$LIBS -lslang -lm" + if test $mutt_cv_bsdish = yes; then + LIBS="$LIBS -ltermlib" + fi + cat >> confdefs.h <<\EOF +#define USE_SLANG_CURSES 1 +EOF + + cat >> confdefs.h <<\EOF +#define HAVE_COLOR 1 +EOF + + LIBOBJS="$LIBOBJS resize.o" + + + echo $ac_n "checking if I can compile a test SLang program""... $ac_c" 1>&6 +echo "configure:1184: checking if I can compile a test SLang program" >&5 + cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + echo "$ac_t""yes" 1>&6 +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + { echo "configure: error: unable to compile. check config.log" 1>&2; exit 1; } +fi +rm -f conftest* + + +else + mutt_cv_curses=/usr + # Check whether --with-curses or --without-curses was given. +if test "${with_curses+set}" = set; then + withval="$with_curses" + if test $withval != yes; then + mutt_cv_curses=$withval + fi + if test x$mutt_cv_curses != x/usr; then + LDFLAGS="-L${mutt_cv_curses}/lib $LDFLAGS" + CPPFLAGS="$CPPFLAGS -I${mutt_cv_curses}/include" + fi +fi + + + echo $ac_n "checking for initscr in -lncurses""... $ac_c" 1>&6 +echo "configure:1221: checking for initscr in -lncurses" >&5 +ac_lib_var=`echo ncurses'_'initscr | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lncurses $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + LIBS="$LIBS -lncurses" + if test x$mutt_cv_curses = x/usr -a -d /usr/include/ncurses; then + CPPFLAGS="$CPPFLAGS -I/usr/include/ncurses" + fi + for ac_hdr in ncurses.h +do +ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'` +echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6 +echo "configure:1263: checking for $ac_hdr" >&5 +if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1273: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out` +if test -z "$ac_err"; then + rm -rf conftest* + eval "ac_cv_header_$ac_safe=yes" +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_header_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'` + cat >> confdefs.h <&6 +fi +done + +else + echo "$ac_t""no" 1>&6 +LIBS="$LIBS -lcurses" + if test -f /usr/ccs/lib/libcurses.a; then + LDFLAGS="$LDFLAGS -L/usr/ccs/lib" + else + if test -f /usr/5lib/libcurses.a; then + LDFLAGS="$LDFLAGS -L/usr/5lib" + CPPFLAGS="$CPPFLAGS -I/usr/5include" + fi + fi +fi + + + echo $ac_n "checking for start_color""... $ac_c" 1>&6 +echo "configure:1314: checking for start_color" >&5 +if eval "test \"`echo '$''{'ac_cv_func_start_color'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char start_color(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_start_color) || defined (__stub___start_color) +choke me +#else +start_color(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1342: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_start_color=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_start_color=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'start_color`\" = yes"; then + echo "$ac_t""yes" 1>&6 + cat >> confdefs.h <<\EOF +#define HAVE_COLOR 1 +EOF + +else + echo "$ac_t""no" 1>&6 +fi + + for ac_func in typeahead bkgdset curs_set meta use_default_colors +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:1367: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1395: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +fi +done + + for ac_func in resizeterm +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:1422: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1450: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +fi +done + + +fi + + +echo $ac_n "checking for ANSI C header files""... $ac_c" 1>&6 +echo "configure:1479: checking for ANSI C header files" >&5 +if eval "test \"`echo '$''{'ac_cv_header_stdc'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include +#include +#include +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1492: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out` +if test -z "$ac_err"; then + rm -rf conftest* + ac_cv_header_stdc=yes +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + ac_cv_header_stdc=no +fi +rm -f conftest* + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. +cat > conftest.$ac_ext < +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "memchr" >/dev/null 2>&1; then + : +else + rm -rf conftest* + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. +cat > conftest.$ac_ext < +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "free" >/dev/null 2>&1; then + : +else + rm -rf conftest* + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. +if test "$cross_compiling" = yes; then + : +else + cat > conftest.$ac_ext < +#define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +#define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int main () { int i; for (i = 0; i < 256; i++) +if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) exit(2); +exit (0); } + +EOF +if { (eval echo configure:1559: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest && (./conftest; exit) 2>/dev/null +then + : +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + ac_cv_header_stdc=no +fi +rm -fr conftest* +fi + +fi +fi + +echo "$ac_t""$ac_cv_header_stdc" 1>&6 +if test $ac_cv_header_stdc = yes; then + cat >> confdefs.h <<\EOF +#define STDC_HEADERS 1 +EOF + +fi + + +for ac_hdr in stdarg.h sys/ioctl.h +do +ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'` +echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6 +echo "configure:1587: checking for $ac_hdr" >&5 +if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1597: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out` +if test -z "$ac_err"; then + rm -rf conftest* + eval "ac_cv_header_$ac_safe=yes" +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_header_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'` + cat >> confdefs.h <&6 +fi +done + + +echo $ac_n "checking return type of signal handlers""... $ac_c" 1>&6 +echo "configure:1625: checking return type of signal handlers" >&5 +if eval "test \"`echo '$''{'ac_cv_type_signal'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include +#ifdef signal +#undef signal +#endif +#ifdef __cplusplus +extern "C" void (*signal (int, void (*)(int)))(int); +#else +void (*signal ()) (); +#endif + +int main() { +int i; +; return 0; } +EOF +if { (eval echo configure:1647: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_type_signal=void +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + ac_cv_type_signal=int +fi +rm -f conftest* +fi + +echo "$ac_t""$ac_cv_type_signal" 1>&6 +cat >> confdefs.h <&6 +echo "configure:1667: checking for sys_siglist declaration in signal.h or unistd.h" >&5 +if eval "test \"`echo '$''{'ac_cv_decl_sys_siglist'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include +/* NetBSD declares sys_siglist in unistd.h. */ +#ifdef HAVE_UNISTD_H +#include +#endif +int main() { +char *msg = *(sys_siglist + 1); +; return 0; } +EOF +if { (eval echo configure:1684: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_decl_sys_siglist=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + ac_cv_decl_sys_siglist=no +fi +rm -f conftest* +fi + +echo "$ac_t""$ac_cv_decl_sys_siglist" 1>&6 +if test $ac_cv_decl_sys_siglist = yes; then + cat >> confdefs.h <<\EOF +#define SYS_SIGLIST_DECLARED 1 +EOF + +fi + + +echo $ac_n "checking size of long""... $ac_c" 1>&6 +echo "configure:1706: checking size of long" >&5 +if eval "test \"`echo '$''{'ac_cv_sizeof_long'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test "$cross_compiling" = yes; then + { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; } +else + cat > conftest.$ac_ext < +main() +{ + FILE *f=fopen("conftestval", "w"); + if (!f) exit(1); + fprintf(f, "%d\n", sizeof(long)); + exit(0); +} +EOF +if { (eval echo configure:1725: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest && (./conftest; exit) 2>/dev/null +then + ac_cv_sizeof_long=`cat conftestval` +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + ac_cv_sizeof_long=0 +fi +rm -fr conftest* +fi + +fi +echo "$ac_t""$ac_cv_sizeof_long" 1>&6 +cat >> confdefs.h <&6 +echo "configure:1746: checking for pid_t" >&5 +if eval "test \"`echo '$''{'ac_cv_type_pid_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "pid_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_pid_t=yes +else + rm -rf conftest* + ac_cv_type_pid_t=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_pid_t" 1>&6 +if test $ac_cv_type_pid_t = no; then + cat >> confdefs.h <<\EOF +#define pid_t int +EOF + +fi + + +for ac_func in setegid srand48 strerror +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:1782: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1810: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +fi +done + + +for ac_func in strcasecmp +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:1838: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1866: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +LIBOBJS="$LIBOBJS ${ac_func}.o" +fi +done + + + +mutt_cv_snprintf=no +echo $ac_n "checking for snprintf""... $ac_c" 1>&6 +echo "configure:1895: checking for snprintf" >&5 +if eval "test \"`echo '$''{'ac_cv_func_snprintf'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char snprintf(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_snprintf) || defined (__stub___snprintf) +choke me +#else +snprintf(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1923: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_snprintf=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_snprintf=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'snprintf`\" = yes"; then + echo "$ac_t""yes" 1>&6 + cat >> confdefs.h <<\EOF +#define HAVE_SNPRINTF 1 +EOF + +else + echo "$ac_t""no" 1>&6 +mutt_cv_snprintf=yes +fi + +echo $ac_n "checking for vsnprintf""... $ac_c" 1>&6 +echo "configure:1947: checking for vsnprintf" >&5 +if eval "test \"`echo '$''{'ac_cv_func_vsnprintf'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char vsnprintf(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_vsnprintf) || defined (__stub___vsnprintf) +choke me +#else +vsnprintf(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1975: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_vsnprintf=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_vsnprintf=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'vsnprintf`\" = yes"; then + echo "$ac_t""yes" 1>&6 + cat >> confdefs.h <<\EOF +#define HAVE_VSNPRINTF 1 +EOF + +else + echo "$ac_t""no" 1>&6 +mutt_cv_snprintf=yes +fi + +if test $mutt_cv_snprintf = yes; then + LIBOBJS="$LIBOBJS snprintf.o" +fi + +for ac_func in ftruncate +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:2005: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2033: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +echo $ac_n "checking for chsize in -lx""... $ac_c" 1>&6 +echo "configure:2055: checking for chsize in -lx" >&5 +ac_lib_var=`echo x'_'chsize | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lx $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo x | sed -e 's/^a-zA-Z0-9_/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +fi + +fi +done + + +for ac_func in strftime +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:2108: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2136: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +echo $ac_n "checking for strftime in -lintl""... $ac_c" 1>&6 +echo "configure:2158: checking for strftime in -lintl" >&5 +ac_lib_var=`echo intl'_'strftime | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lintl $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo intl | sed -e 's/^a-zA-Z0-9_/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +fi + +fi +done + + +mutt_cv_regex=yes +# Check whether --with-rx or --without-rx was given. +if test "${with_rx+set}" = set; then + withval="$with_rx" + if test $withval != yes; then + if test -d $withval/lib; then + LIBS="$LIBS -L$withval/lib -lrx" + CPPFLAGS="-I$withval/include $CPPFLAGS" + else + LIBS="$LIBS -L$withval -lrx" + CPPFLAGS="-I$withval $CPPFLAGS" + fi + cat >> confdefs.h <<\EOF +#define USE_GNU_RX 1 +EOF + + mutt_cv_regex=no + fi +else + for ac_func in regcomp +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:2230: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2258: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +fi +done + +fi + + +if test $mutt_cv_regex = yes; then + if test -d ../rx-1.5; then + LIBS="$LIBS ../rx-1.5/rx/librx.a" + CPPFLAGS="-I../rx-1.5/rx $CPPFLAGS" + else + LIBOBJS="$LIBOBJS rx/librx.a" + CPPFLAGS="-I\$(srcdir)/rx $CPPFLAGS" + fi + cat >> confdefs.h <<\EOF +#define USE_GNU_RX 1 +EOF + +fi + +# Check whether --with-homespool or --without-homespool was given. +if test "${with_homespool+set}" = set; then + withval="$with_homespool" + with_homespool=${withval} +fi + +if test x$with_homespool != x; then + if test $with_homespool = yes; then + with_homespool=mailbox + fi + cat >> confdefs.h <> confdefs.h <<\EOF +#define HOMESPOOL 1 +EOF + + cat >> confdefs.h <<\EOF +#define USE_DOTLOCK 1 +EOF + + mutt_cv_setgid=no +else + # Check whether --with-mailpath or --without-mailpath was given. +if test "${with_mailpath+set}" = set; then + withval="$with_mailpath" + mutt_cv_mailpath=$withval +else + echo $ac_n "checking where new mail is stored""... $ac_c" 1>&6 +echo "configure:2329: checking where new mail is stored" >&5 +if eval "test \"`echo '$''{'mutt_cv_mailpath'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + mutt_cv_mailpath=no + if test -d /var/mail; then + mutt_cv_mailpath=/var/mail + elif test -d /var/spool/mail; then + mutt_cv_mailpath=/var/spool/mail + elif test -d /usr/spool/mail; then + mutt_cv_mailpath=/usr/spool/mail + elif test -d /usr/mail; then + mutt_cv_mailpath=/usr/mail + fi +fi + +echo "$ac_t""$mutt_cv_mailpath" 1>&6 + +fi + + if test $mutt_cv_mailpath = no; then + { echo "configure: error: "Could not determine where new mail is stored."" 1>&2; exit 1; } + fi + cat >> confdefs.h <&6 +echo "configure:2358: checking if $mutt_cv_mailpath is world writable" >&5 +if eval "test \"`echo '$''{'mutt_cv_worldwrite'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test "$cross_compiling" = yes; then + { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; } +else + cat > conftest.$ac_ext < +#include + +int main (int argc, char **argv) +{ + struct stat s; + + stat ("$mutt_cv_mailpath", &s); + if (s.st_mode & S_IWOTH) exit (0); + exit (1); +} +EOF +if { (eval echo configure:2380: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest && (./conftest; exit) 2>/dev/null +then + mutt_cv_worldwrite=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + mutt_cv_worldwrite=no +fi +rm -fr conftest* +fi + +fi + +echo "$ac_t""$mutt_cv_worldwrite" 1>&6 + + mutt_cv_setgid=no + if test $mutt_cv_worldwrite = yes; then + cat >> confdefs.h <<\EOF +#define USE_DOTLOCK 1 +EOF + + else + + echo $ac_n "checking if $mutt_cv_mailpath is group writable""... $ac_c" 1>&6 +echo "configure:2405: checking if $mutt_cv_mailpath is group writable" >&5 +if eval "test \"`echo '$''{'mutt_cv_groupwrite'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test "$cross_compiling" = yes; then + { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; } +else + cat > conftest.$ac_ext < +#include + +int main (int argc, char **argv) +{ + struct stat s; + + stat ("$mutt_cv_mailpath", &s); + if (s.st_mode & S_IWGRP) exit (0); + exit (1); +} +EOF +if { (eval echo configure:2427: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest && (./conftest; exit) 2>/dev/null +then + mutt_cv_groupwrite=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + mutt_cv_groupwrite=no +fi +rm -fr conftest* +fi + +fi + +echo "$ac_t""$mutt_cv_groupwrite" 1>&6 + + if test $mutt_cv_groupwrite = yes; then + cat >> confdefs.h <<\EOF +#define USE_DOTLOCK 1 +EOF + + cat >> confdefs.h <<\EOF +#define USE_SETGID 1 +EOF + + mutt_cv_setgid=yes + fi + fi +fi + +# Check whether --with-sharedir or --without-sharedir was given. +if test "${with_sharedir+set}" = set; then + withval="$with_sharedir" + mutt_cv_sharedir=$withval +else + echo $ac_n "checking where to put architecture-independent data files""... $ac_c" 1>&6 +echo "configure:2463: checking where to put architecture-independent data files" >&5 +if eval "test \"`echo '$''{'mutt_cv_sharedir'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test x$prefix = xNONE; then + mutt_cv_prefix=$ac_default_prefix + else + mutt_cv_prefix=$prefix + fi + if test -d ${mutt_cv_prefix}/share; then + if test -d ${mutt_cv_prefix}/share/misc; then + mutt_cv_sharedir='${prefix}/share/misc' + else + mutt_cv_sharedir='${prefix}/share' + fi + else + mutt_cv_sharedir='${libdir}' + fi +fi + +echo "$ac_t""$mutt_cv_sharedir" 1>&6 + +fi + + +sharedir=$mutt_cv_sharedir + + +if test x$mutt_cv_setgid = xyes; then + MUTT_GROUP='-g mail' + MUTT_PERMISSION=2755 +else + MUTT_GROUP='' + MUTT_PERMISSION=755 +fi + + + +# Check whether --with-domain or --without-domain was given. +if test "${with_domain+set}" = set; then + withval="$with_domain" + if test $withval != yes; then + cat >> confdefs.h <> confdefs.h <<\EOF +#define HIDDEN_HOST 1 +EOF + +fi + + +# Check whether --enable-pop or --disable-pop was given. +if test "${enable_pop+set}" = set; then + enableval="$enable_pop" + cat >> confdefs.h <<\EOF +#define USE_POP 1 +EOF + + echo $ac_n "checking for socket in -lsocket""... $ac_c" 1>&6 +echo "configure:2531: checking for socket in -lsocket" >&5 +ac_lib_var=`echo socket'_'socket | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lsocket $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo socket | sed -e 's/[^a-zA-Z0-9_]/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +fi + + echo $ac_n "checking for gethostbyname in -lnsl""... $ac_c" 1>&6 +echo "configure:2578: checking for gethostbyname in -lnsl" >&5 +ac_lib_var=`echo nsl'_'gethostbyname | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lnsl $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo nsl | sed -e 's/[^a-zA-Z0-9_]/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +fi + + LIBOBJS="$LIBOBJS pop.o" + +fi + + +# Check whether --enable-flock or --disable-flock was given. +if test "${enable_flock+set}" = set; then + enableval="$enable_flock" + if test $enableval = yes; then + cat >> confdefs.h <<\EOF +#define USE_FLOCK 1 +EOF + + fi +fi + + +mutt_cv_fcntl=yes +# Check whether --enable-fcntl or --disable-fcntl was given. +if test "${enable_fcntl+set}" = set; then + enableval="$enable_fcntl" + if test $enableval = no; then mutt_cv_fcntl=no; fi +fi + + +if test $mutt_cv_fcntl = yes; then + cat >> confdefs.h <<\EOF +#define USE_FCNTL 1 +EOF + +fi + +mutt_cv_warnings=yes +# Check whether --enable-warnings or --disable-warnings was given. +if test "${enable_warnings+set}" = set; then + enableval="$enable_warnings" + if test $enableval = no; then + mutt_cv_warnings=no +fi +fi + + +if test "$ac_cv_prog_CC" = gcc -a $mutt_cv_warnings = yes; then + CFLAGS="-Wall -pedantic $CFLAGS" +fi + +# Check whether --enable-nfs-fix or --disable-nfs-fix was given. +if test "${enable_nfs_fix+set}" = set; then + enableval="$enable_nfs_fix" + if test x$enableval = xyes; then + cat >> confdefs.h <<\EOF +#define NFS_ATTRIBUTE_HACK 1 +EOF + + fi +fi + + +# Check whether --enable-buffy-size or --disable-buffy-size was given. +if test "${enable_buffy_size+set}" = set; then + enableval="$enable_buffy_size" + if test x$enableval = xyes; then + cat >> confdefs.h <<\EOF +#define BUFFY_SIZE 1 +EOF + + fi +fi + + +# Check whether --enable-locales-fix or --disable-locales-fix was given. +if test "${enable_locales_fix+set}" = set; then + enableval="$enable_locales_fix" + if test x$enableval = xyes; then + cat >> confdefs.h <<\EOF +#define LOCALES_HACK 1 +EOF + + fi +fi + + +# Check whether --with-exec-shell or --without-exec-shell was given. +if test "${with_exec_shell+set}" = set; then + withval="$with_exec_shell" + if test $withval != yes; then + cat >> confdefs.h <> confdefs.h <<\EOF +#define EXACT_ADDRESS 1 +EOF + + fi +fi + + +trap '' 1 2 15 +cat > confcache <<\EOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs. It is not useful on other systems. +# If it contains results you don't want to keep, you may remove or edit it. +# +# By default, configure uses ./config.cache as the cache file, +# creating it if it does not exist already. You can give configure +# the --cache-file=FILE option to use a different cache file; that is +# what configure does when it calls configure scripts in +# subdirectories, so they share the cache. +# Giving --cache-file=/dev/null disables caching, for debugging configure. +# config.status only pays attention to the cache file if you give it the +# --recheck option to rerun configure. +# +EOF +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, don't put newlines in cache variables' values. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +(set) 2>&1 | + case `(ac_space=' '; set) 2>&1` in + *ac_space=\ *) + # `set' does not quote correctly, so add quotes (double-quote substitution + # turns \\\\ into \\, and sed turns \\ into \). + sed -n \ + -e "s/'/'\\\\''/g" \ + -e "s/^\\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\\)=\\(.*\\)/\\1=\${\\1='\\2'}/p" + ;; + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n -e 's/^\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\)=\(.*\)/\1=${\1=\2}/p' + ;; + esac >> confcache +if cmp -s $cache_file confcache; then + : +else + if test -w $cache_file; then + echo "updating cache $cache_file" + cat confcache > $cache_file + else + echo "not updating unwritable cache $cache_file" + fi +fi +rm -f confcache + +trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Any assignment to VPATH causes Sun make to only execute +# the first set of double-colon rules, so remove it if not needed. +# If there is a colon in the path, we need to keep it. +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[^:]*$/d' +fi + +trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15 + +DEFS=-DHAVE_CONFIG_H + +# Without the "./", some shells look in PATH for config.status. +: ${CONFIG_STATUS=./config.status} + +echo creating $CONFIG_STATUS +rm -f $CONFIG_STATUS +cat > $CONFIG_STATUS </dev/null | sed 1q`: +# +# $0 $ac_configure_args +# +# Compiler output produced by configure, useful for debugging +# configure, is in ./config.log if it exists. + +ac_cs_usage="Usage: $CONFIG_STATUS [--recheck] [--version] [--help]" +for ac_option +do + case "\$ac_option" in + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + echo "running \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion" + exec \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion ;; + -version | --version | --versio | --versi | --vers | --ver | --ve | --v) + echo "$CONFIG_STATUS generated by autoconf version 2.12" + exit 0 ;; + -help | --help | --hel | --he | --h) + echo "\$ac_cs_usage"; exit 0 ;; + *) echo "\$ac_cs_usage"; exit 1 ;; + esac +done + +ac_given_srcdir=$srcdir +ac_given_INSTALL="$INSTALL" + +trap 'rm -fr `echo "Makefile rx/Makefile config.h" | sed "s/:[^ ]*//g"` conftest*; exit 1' 1 2 15 +EOF +cat >> $CONFIG_STATUS < conftest.subs <<\\CEOF +$ac_vpsub +$extrasub +s%@CFLAGS@%$CFLAGS%g +s%@CPPFLAGS@%$CPPFLAGS%g +s%@CXXFLAGS@%$CXXFLAGS%g +s%@DEFS@%$DEFS%g +s%@LDFLAGS@%$LDFLAGS%g +s%@LIBS@%$LIBS%g +s%@exec_prefix@%$exec_prefix%g +s%@prefix@%$prefix%g +s%@program_transform_name@%$program_transform_name%g +s%@bindir@%$bindir%g +s%@sbindir@%$sbindir%g +s%@libexecdir@%$libexecdir%g +s%@datadir@%$datadir%g +s%@sysconfdir@%$sysconfdir%g +s%@sharedstatedir@%$sharedstatedir%g +s%@localstatedir@%$localstatedir%g +s%@libdir@%$libdir%g +s%@includedir@%$includedir%g +s%@oldincludedir@%$oldincludedir%g +s%@infodir@%$infodir%g +s%@mandir@%$mandir%g +s%@CC@%$CC%g +s%@SET_MAKE@%$SET_MAKE%g +s%@INSTALL_PROGRAM@%$INSTALL_PROGRAM%g +s%@INSTALL_DATA@%$INSTALL_DATA%g +s%@SENDMAIL@%$SENDMAIL%g +s%@PGPK@%$PGPK%g +s%@PGP@%$PGP%g +s%@OPS@%$OPS%g +s%@VERSION@%$VERSION%g +s%@ISPELL@%$ISPELL%g +s%@CPP@%$CPP%g +s%@LIBOBJS@%$LIBOBJS%g +s%@sharedir@%$sharedir%g +s%@MUTT_GROUP@%$MUTT_GROUP%g +s%@MUTT_PERMISSION@%$MUTT_PERMISSION%g + +CEOF +EOF + +cat >> $CONFIG_STATUS <<\EOF + +# Split the substitutions into bite-sized pieces for seds with +# small command number limits, like on Digital OSF/1 and HP-UX. +ac_max_sed_cmds=90 # Maximum number of lines to put in a sed script. +ac_file=1 # Number of current file. +ac_beg=1 # First line for current file. +ac_end=$ac_max_sed_cmds # Line after last line for current file. +ac_more_lines=: +ac_sed_cmds="" +while $ac_more_lines; do + if test $ac_beg -gt 1; then + sed "1,${ac_beg}d; ${ac_end}q" conftest.subs > conftest.s$ac_file + else + sed "${ac_end}q" conftest.subs > conftest.s$ac_file + fi + if test ! -s conftest.s$ac_file; then + ac_more_lines=false + rm -f conftest.s$ac_file + else + if test -z "$ac_sed_cmds"; then + ac_sed_cmds="sed -f conftest.s$ac_file" + else + ac_sed_cmds="$ac_sed_cmds | sed -f conftest.s$ac_file" + fi + ac_file=`expr $ac_file + 1` + ac_beg=$ac_end + ac_end=`expr $ac_end + $ac_max_sed_cmds` + fi +done +if test -z "$ac_sed_cmds"; then + ac_sed_cmds=cat +fi +EOF + +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF +for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then + # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in". + case "$ac_file" in + *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'` + ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; + *) ac_file_in="${ac_file}.in" ;; + esac + + # Adjust a relative srcdir, top_srcdir, and INSTALL for subdirectories. + + # Remove last slash and all that follows it. Not all systems have dirname. + ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'` + if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then + # The file is in a subdirectory. + test ! -d "$ac_dir" && mkdir "$ac_dir" + ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`" + # A "../" for each directory in $ac_dir_suffix. + ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'` + else + ac_dir_suffix= ac_dots= + fi + + case "$ac_given_srcdir" in + .) srcdir=. + if test -z "$ac_dots"; then top_srcdir=. + else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;; + /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;; + *) # Relative path. + srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix" + top_srcdir="$ac_dots$ac_given_srcdir" ;; + esac + + case "$ac_given_INSTALL" in + [/$]*) INSTALL="$ac_given_INSTALL" ;; + *) INSTALL="$ac_dots$ac_given_INSTALL" ;; + esac + + echo creating "$ac_file" + rm -f "$ac_file" + configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure." + case "$ac_file" in + *Makefile*) ac_comsub="1i\\ +# $configure_input" ;; + *) ac_comsub= ;; + esac + + ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"` + sed -e "$ac_comsub +s%@configure_input@%$configure_input%g +s%@srcdir@%$srcdir%g +s%@top_srcdir@%$top_srcdir%g +s%@INSTALL@%$INSTALL%g +" $ac_file_inputs | (eval "$ac_sed_cmds") > $ac_file +fi; done +rm -f conftest.s* + +# These sed commands are passed to sed as "A NAME B NAME C VALUE D", where +# NAME is the cpp macro being defined and VALUE is the value it is being given. +# +# ac_d sets the value in "#define NAME VALUE" lines. +ac_dA='s%^\([ ]*\)#\([ ]*define[ ][ ]*\)' +ac_dB='\([ ][ ]*\)[^ ]*%\1#\2' +ac_dC='\3' +ac_dD='%g' +# ac_u turns "#undef NAME" with trailing blanks into "#define NAME VALUE". +ac_uA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)' +ac_uB='\([ ]\)%\1#\2define\3' +ac_uC=' ' +ac_uD='\4%g' +# ac_e turns "#undef NAME" without trailing blanks into "#define NAME VALUE". +ac_eA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)' +ac_eB='$%\1#\2define\3' +ac_eC=' ' +ac_eD='%g' + +if test "${CONFIG_HEADERS+set}" != set; then +EOF +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF +fi +for ac_file in .. $CONFIG_HEADERS; do if test "x$ac_file" != x..; then + # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in". + case "$ac_file" in + *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'` + ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; + *) ac_file_in="${ac_file}.in" ;; + esac + + echo creating $ac_file + + rm -f conftest.frag conftest.in conftest.out + ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"` + cat $ac_file_inputs > conftest.in + +EOF + +# Transform confdefs.h into a sed script conftest.vals that substitutes +# the proper values into config.h.in to produce config.h. And first: +# Protect against being on the right side of a sed subst in config.status. +# Protect against being in an unquoted here document in config.status. +rm -f conftest.vals +cat > conftest.hdr <<\EOF +s/[\\&%]/\\&/g +s%[\\$`]%\\&%g +s%#define \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%${ac_dA}\1${ac_dB}\1${ac_dC}\2${ac_dD}%gp +s%ac_d%ac_u%gp +s%ac_u%ac_e%gp +EOF +sed -n -f conftest.hdr confdefs.h > conftest.vals +rm -f conftest.hdr + +# This sed command replaces #undef with comments. This is necessary, for +# example, in the case of _POSIX_SOURCE, which is predefined and required +# on some systems where configure will not decide to define it. +cat >> conftest.vals <<\EOF +s%^[ ]*#[ ]*undef[ ][ ]*[a-zA-Z_][a-zA-Z_0-9]*%/* & */% +EOF + +# Break up conftest.vals because some shells have a limit on +# the size of here documents, and old seds have small limits too. + +rm -f conftest.tail +while : +do + ac_lines=`grep -c . conftest.vals` + # grep -c gives empty output for an empty file on some AIX systems. + if test -z "$ac_lines" || test "$ac_lines" -eq 0; then break; fi + # Write a limited-size here document to conftest.frag. + echo ' cat > conftest.frag <> $CONFIG_STATUS + sed ${ac_max_here_lines}q conftest.vals >> $CONFIG_STATUS + echo 'CEOF + sed -f conftest.frag conftest.in > conftest.out + rm -f conftest.in + mv conftest.out conftest.in +' >> $CONFIG_STATUS + sed 1,${ac_max_here_lines}d conftest.vals > conftest.tail + rm -f conftest.vals + mv conftest.tail conftest.vals +done +rm -f conftest.vals + +cat >> $CONFIG_STATUS <<\EOF + rm -f conftest.frag conftest.h + echo "/* $ac_file. Generated automatically by configure. */" > conftest.h + cat conftest.in >> conftest.h + rm -f conftest.in + if cmp -s $ac_file conftest.h 2>/dev/null; then + echo "$ac_file is unchanged" + rm -f conftest.h + else + # Remove last slash and all that follows it. Not all systems have dirname. + ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'` + if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then + # The file is in a subdirectory. + test ! -d "$ac_dir" && mkdir "$ac_dir" + fi + rm -f $ac_file + mv conftest.h $ac_file + fi +fi; done + +EOF +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF + +exit 0 +EOF +chmod +x $CONFIG_STATUS +rm -fr confdefs* $ac_clean_files +test "$no_create" = yes || ${CONFIG_SHELL-/bin/sh} $CONFIG_STATUS || exit 1 + diff --git a/configure.in b/configure.in new file mode 100644 index 00000000..2af4a27b --- /dev/null +++ b/configure.in @@ -0,0 +1,374 @@ +dnl Process this file with autoconf to produce a configure script. +AC_INIT(mutt.h) +AC_CONFIG_HEADER(config.h) +VERSION=0.92.8 +SUBVERSION='' + +AC_MSG_CHECKING(for prefix) +if test x$prefix = xNONE; then + mutt_cv_prefix=$ac_default_prefix +else + mutt_cv_prefix=$prefix +fi +AC_MSG_RESULT($mutt_cv_prefix) + +AC_PROG_CC +AC_PROG_MAKE_SET +AC_PROG_INSTALL + +AC_PATH_PROG(SENDMAIL, sendmail, no, `echo $PATH | sed "s/:/ /"` /usr/sbin /usr/lib) +AC_DEFINE_UNQUOTED(SENDMAIL, "$ac_cv_path_SENDMAIL") + +OPS='$(srcdir)/OPS' +if test -f $srcdir/pgp.c; then + SUBVERSION=i + PGPPATH=no + AC_PATH_PROG(PGPK, pgpk, no) + if test $PGPK != no ; then + PGPK=`echo $PGPK | sed 's,.$,,'` + AC_DEFINE_UNQUOTED(_PGPV3PATH, "$PGPK") + PGPPATH="$PGPK" + AC_DEFINE(HAVE_PGP5) + fi + + AC_PATH_PROG(PGP, pgp, no) + if test $PGP != no ; then + AC_DEFINE_UNQUOTED(_PGPV2PATH, "$PGP") + PGPPATH="$PGP" + AC_DEFINE(HAVE_PGP2) + fi + + if test $PGPPATH != no ; then + AC_DEFINE_UNQUOTED(_PGPPATH, "$PGPPATH") + fi + + if test $PGP != no || test $PGPK != no ; then + LIBOBJS="$LIBOBJS pgp.o pgpinvoke.o pgpkey.o pgppubring.o sha1dgst.o" + OPS="$OPS \$(srcdir)/OPS.PGP" + fi +fi +AC_SUBST(OPS) + +AC_DEFINE_UNQUOTED(VERSION, "$VERSION$SUBVERSION") +AC_SUBST(VERSION) + +AC_PATH_PROG(ISPELL, ispell, no) +if test $ISPELL != no; then + AC_DEFINE_UNQUOTED(ISPELL, "$ISPELL") +fi + +AC_ARG_WITH(slang, [ --with-slang[=DIR] use S-Lang instead of ncurses], + [AC_CACHE_CHECK([if -ltermlib is required], mutt_cv_bsdish, + AC_TRY_RUN([#include + +main () +{ +#ifdef BSD + exit (0); +#else + exit (1); +#endif +}], + [mutt_cv_bsdish=yes], + [mutt_cv_bsdish=no])) + + AC_MSG_CHECKING(for S-Lang) + if test $withval = yes; then + if test -d $srcdir/../slang; then + mutt_cv_slang=$srcdir/../slang/src + CPPFLAGS="$CPPFLAGS -I${mutt_cv_slang}" + LDFLAGS="$LDFLAGS -L${mutt_cv_slang}/objs" + else + if test -d $mutt_cv_prefix/include/slang; then + CPPFLAGS="$CPPFLAGS -I$mutt_cv_prefix/include/slang" + elif test -d /usr/include/slang; then + CPPFLAGS="$CPPFLAGS -I/usr/include/slang" + fi + mutt_cv_slang=yes + fi + else + dnl ---Check to see if $withval is a source directory + if test -f $withval/src/slang.h; then + mutt_cv_slang=$withval/src + CPPFLAGS="$CPPFLAGS -I${mutt_cv_slang}" + LDFLAGS="$LDFLAGS -L${mutt_cv_slang}/objs" + else + dnl ---Must be installed somewhere + mutt_cv_slang=$withval + if test -d $withval/include/slang; then + CPPFLAGS="$CPPFLAGS -I${withval}/include/slang" + elif test -d $withval/include; then + CPPFLAGS="$CPPFLAGS -I${withval}/include" + fi + LDFLAGS="$LDFLAGS -L${withval}/lib" + fi + fi + AC_MSG_RESULT($mutt_cv_slang) + LIBS="$LIBS -lslang -lm" + if test $mutt_cv_bsdish = yes; then + LIBS="$LIBS -ltermlib" + fi + AC_DEFINE(USE_SLANG_CURSES) + AC_DEFINE(HAVE_COLOR) + LIBOBJS="$LIBOBJS resize.o" + + dnl --- try to link a sample program to check if we're ok + + AC_MSG_CHECKING(if I can compile a test SLang program) + AC_TRY_LINK([], [SLtt_get_terminfo ();], + [AC_MSG_RESULT(yes)], + [AC_MSG_ERROR(unable to compile. check config.log)]) + + ], + + [mutt_cv_curses=/usr + AC_ARG_WITH(curses, [ --with-curses=DIR ncurses is installed in ], + [if test $withval != yes; then + mutt_cv_curses=$withval + fi + if test x$mutt_cv_curses != x/usr; then + LDFLAGS="-L${mutt_cv_curses}/lib $LDFLAGS" + CPPFLAGS="$CPPFLAGS -I${mutt_cv_curses}/include" + fi]) + + AC_CHECK_LIB(ncurses, initscr, + + [LIBS="$LIBS -lncurses" + if test x$mutt_cv_curses = x/usr -a -d /usr/include/ncurses; then + CPPFLAGS="$CPPFLAGS -I/usr/include/ncurses" + fi + AC_CHECK_HEADERS(ncurses.h)], + + [LIBS="$LIBS -lcurses" + if test -f /usr/ccs/lib/libcurses.a; then + LDFLAGS="$LDFLAGS -L/usr/ccs/lib" + else + if test -f /usr/5lib/libcurses.a; then + LDFLAGS="$LDFLAGS -L/usr/5lib" + CPPFLAGS="$CPPFLAGS -I/usr/5include" + fi + fi]) + + AC_CHECK_FUNC(start_color, [AC_DEFINE(HAVE_COLOR)]) + AC_CHECK_FUNCS(typeahead bkgdset curs_set meta use_default_colors) + AC_CHECK_FUNCS(resizeterm, [LIBOBJS="$LIBOBJS resize.o"]) + ]) + +AC_HEADER_STDC + +AC_CHECK_HEADERS(stdarg.h sys/ioctl.h) + +AC_TYPE_SIGNAL + +AC_DECL_SYS_SIGLIST + +dnl need this for DEC alpha +AC_CHECK_SIZEOF(long) + +AC_TYPE_PID_T + +AC_CHECK_FUNCS(setegid srand48 strerror) + +AC_REPLACE_FUNCS(strcasecmp) + +mutt_cv_snprintf=no +AC_CHECK_FUNC(snprintf, [AC_DEFINE(HAVE_SNPRINTF)], [mutt_cv_snprintf=yes]) +AC_CHECK_FUNC(vsnprintf, [AC_DEFINE(HAVE_VSNPRINTF)], [mutt_cv_snprintf=yes]) +if test $mutt_cv_snprintf = yes; then + LIBOBJS="$LIBOBJS snprintf.o" +fi + +dnl SCO uses chsize() instead of ftruncate() +AC_CHECK_FUNCS(ftruncate, break, [AC_CHECK_LIB(x, chsize)]) + +dnl SCO has strftime() in libintl +AC_CHECK_FUNCS(strftime, break, [AC_CHECK_LIB(intl, strftime)]) + +mutt_cv_regex=yes +AC_ARG_WITH(rx, [ --with-rx[=DIR] Use GNU rx ], + [if test $withval != yes; then + if test -d $withval/lib; then + LIBS="$LIBS -L$withval/lib -lrx" + CPPFLAGS="-I$withval/include $CPPFLAGS" + else + LIBS="$LIBS -L$withval -lrx" + CPPFLAGS="-I$withval $CPPFLAGS" + fi + AC_DEFINE(USE_GNU_RX) + mutt_cv_regex=no + fi], + [AC_CHECK_FUNCS(regcomp, mutt_cv_regex=no)]) + +if test $mutt_cv_regex = yes; then + if test -d ../rx-1.5; then + LIBS="$LIBS ../rx-1.5/rx/librx.a" + CPPFLAGS="-I../rx-1.5/rx $CPPFLAGS" + else + LIBOBJS="$LIBOBJS rx/librx.a" + CPPFLAGS="-I\$(srcdir)/rx $CPPFLAGS" + fi + AC_DEFINE(USE_GNU_RX) +fi + +AC_ARG_WITH(homespool, [ --with-homespool[=FILE] file in user's directory where new mail is spooled], with_homespool=${withval}) +if test x$with_homespool != x; then + if test $with_homespool = yes; then + with_homespool=mailbox + fi + AC_DEFINE_UNQUOTED(MAILPATH, "$with_homespool") + AC_DEFINE(HOMESPOOL) + AC_DEFINE(USE_DOTLOCK) + mutt_cv_setgid=no +else + AC_ARG_WITH(mailpath, [ --with-mailpath=DIR directory where spool mailboxes are located], + [mutt_cv_mailpath=$withval], + [ AC_CACHE_CHECK(where new mail is stored, mutt_cv_mailpath, + [mutt_cv_mailpath=no + if test -d /var/mail; then + mutt_cv_mailpath=/var/mail + elif test -d /var/spool/mail; then + mutt_cv_mailpath=/var/spool/mail + elif test -d /usr/spool/mail; then + mutt_cv_mailpath=/usr/spool/mail + elif test -d /usr/mail; then + mutt_cv_mailpath=/usr/mail + fi]) + ]) + if test $mutt_cv_mailpath = no; then + AC_MSG_ERROR("Could not determine where new mail is stored.") + fi + AC_DEFINE_UNQUOTED(MAILPATH, "$mutt_cv_mailpath") + + AC_CACHE_CHECK(if $mutt_cv_mailpath is world writable, mutt_cv_worldwrite, AC_TRY_RUN([#include +#include + +int main (int argc, char **argv) +{ + struct stat s; + + stat ("$mutt_cv_mailpath", &s); + if (s.st_mode & S_IWOTH) exit (0); + exit (1); +}], [mutt_cv_worldwrite=yes], [mutt_cv_worldwrite=no])) + + mutt_cv_setgid=no + if test $mutt_cv_worldwrite = yes; then + AC_DEFINE(USE_DOTLOCK) + else + + AC_CACHE_CHECK(if $mutt_cv_mailpath is group writable, mutt_cv_groupwrite, AC_TRY_RUN([#include +#include + +int main (int argc, char **argv) +{ + struct stat s; + + stat ("$mutt_cv_mailpath", &s); + if (s.st_mode & S_IWGRP) exit (0); + exit (1); +}], [mutt_cv_groupwrite=yes], [mutt_cv_groupwrite=no])) + + if test $mutt_cv_groupwrite = yes; then + AC_DEFINE(USE_DOTLOCK) + AC_DEFINE(USE_SETGID) + mutt_cv_setgid=yes + fi + fi +fi + +AC_ARG_WITH(sharedir, [ --with-sharedir=PATH specify where to put arch independent files], + [mutt_cv_sharedir=$withval], + [ AC_CACHE_CHECK(where to put architecture-independent data files, + mutt_cv_sharedir, + [if test x$prefix = xNONE; then + mutt_cv_prefix=$ac_default_prefix + else + mutt_cv_prefix=$prefix + fi + if test -d ${mutt_cv_prefix}/share; then + if test -d ${mutt_cv_prefix}/share/misc; then + mutt_cv_sharedir='${prefix}/share/misc' + else + mutt_cv_sharedir='${prefix}/share' + fi + else + mutt_cv_sharedir='${libdir}' + fi]) + ]) + +sharedir=$mutt_cv_sharedir +AC_SUBST(sharedir) + +if test x$mutt_cv_setgid = xyes; then + MUTT_GROUP='-g mail' + MUTT_PERMISSION=2755 +else + MUTT_GROUP='' + MUTT_PERMISSION=755 +fi +AC_SUBST(MUTT_GROUP) +AC_SUBST(MUTT_PERMISSION) + +AC_ARG_WITH(domain, [ --with-domain=DOMAIN Specify your DNS domain name ], + [if test $withval != yes; then + AC_DEFINE_UNQUOTED(DOMAIN, "$withval") + fi]) + +AC_ARG_ENABLE(hidden-host, [ --enable-hidden-host Only use the domain name for local addresses], AC_DEFINE(HIDDEN_HOST)) + +AC_ARG_ENABLE(pop, [ --enable-pop Enable POP3 support], +[ AC_DEFINE(USE_POP) + AC_CHECK_LIB(socket, socket) + AC_CHECK_LIB(nsl, gethostbyname) + LIBOBJS="$LIBOBJS pop.o" +]) + +AC_ARG_ENABLE(flock, [ --enable-flock Use flock() to lock files], + [if test $enableval = yes; then + AC_DEFINE(USE_FLOCK) + fi]) + +mutt_cv_fcntl=yes +AC_ARG_ENABLE(fcntl, [ --disable-fcntl Do NOT use fcntl() to lock files ], + [if test $enableval = no; then mutt_cv_fcntl=no; fi]) + +if test $mutt_cv_fcntl = yes; then + AC_DEFINE(USE_FCNTL) +fi + +mutt_cv_warnings=yes +AC_ARG_ENABLE(warnings, [ --disable-warnings turn off compiler warnings (not recommended)], +[if test $enableval = no; then + mutt_cv_warnings=no +fi]) + +if test "$ac_cv_prog_CC" = gcc -a $mutt_cv_warnings = yes; then + CFLAGS="-Wall -pedantic $CFLAGS" +fi + +AC_ARG_ENABLE(nfs-fix, [ --enable-nfs-fix Work around an NFS with broken attributes caching ], + [if test x$enableval = xyes; then + AC_DEFINE(NFS_ATTRIBUTE_HACK) + fi]) + +AC_ARG_ENABLE(buffy-size, [ --enable-buffy-size Use file size attribute instead of access time ], + [if test x$enableval = xyes; then + AC_DEFINE(BUFFY_SIZE) + fi]) + +AC_ARG_ENABLE(locales-fix, [ --enable-locales-fix The result of isprint() is unreliable ], + [if test x$enableval = xyes; then + AC_DEFINE(LOCALES_HACK) + fi]) + +AC_ARG_WITH(exec-shell, [ --with-exec-shell=SHELL Specify alternate shell (ONLY if /bin/sh is broken)], + [if test $withval != yes; then + AC_DEFINE_UNQUOTED(EXECSHELL, "$withval") + fi]) + +AC_ARG_ENABLE(exact-address, [ --enable-exact-address enable regeneration of email addresses], + [if test $enableval = yes; then + AC_DEFINE(EXACT_ADDRESS) + fi]) + +AC_OUTPUT(Makefile rx/Makefile) diff --git a/copy.c b/copy.c new file mode 100644 index 00000000..c05e2eb2 --- /dev/null +++ b/copy.c @@ -0,0 +1,630 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mailbox.h" +#include "mx.h" +#include "copy.h" +#include "rfc2047.h" +#include "parse.h" +#include +#include +#include +#include /* needed for SEEK_SET under SunOS 4.1.4 */ + +static int copy_delete_attach(HEADER *h, HEADER *p, BODY *m, FILE *fpin, + FILE *fpout, int flags); + +/* Ok, the only reason for not merging this with mutt_copy_header() + * below is to avoid creating a HEADER structure in message_handler(). + */ +int +mutt_copy_hdr (FILE *in, FILE *out, long off_start, long off_end, int flags, + const char *prefix) +{ + int from = 0; + int ignore = 0; + char buf[STRING]; /* should be long enough to get most fields in one pass */ + char *nl; + LIST *t; + char **headers; + int hdr_count; + int x; + int error; + + if (ftell (in) != off_start) + fseek (in, off_start, 0); + + buf[0] = '\n'; + buf[1] = 0; + + if ((flags & (CH_REORDER | CH_WEED | CH_MIME | CH_DECODE | CH_PREFIX)) == 0) + { + /* Without these flags to complicate things + * we can do a more efficient line to line copying + */ + while (ftell (in) < off_end) + { + nl = strchr (buf, '\n'); + + if ((fgets (buf, sizeof (buf), in)) == NULL) + break; + + /* Is it the begining of a header? */ + if (nl && buf[0] != ' ' && buf[0] != '\t') + { + ignore = 1; + if (!from && strncmp ("From ", buf, 5) == 0) + { + if ((flags & CH_FROM) == 0) + continue; + from = 1; + } + else if (buf[0] == '\n' || (buf[0] == '\r' && buf[1] == '\n')) + break; /* end of header */ + + if ((flags & (CH_UPDATE | CH_XMIT | CH_NOSTATUS)) && + (strncasecmp ("Status:", buf, 7) == 0 || + strncasecmp ("X-Status:", buf, 9) == 0)) + continue; + if ((flags & (CH_UPDATE_LEN | CH_XMIT)) && + (strncasecmp ("Content-Length:", buf, 15) == 0 || + strncasecmp ("Lines:", buf, 6) == 0)) + continue; + ignore = 0; + } + + if (!ignore && fputs (buf, out) == EOF) + return (-1); + } + return 0; + } + + hdr_count = 1; + x = 0; + error = FALSE; + + /* We are going to read and collect the headers in an array + * so we are able to do re-ordering. + * First count the number of entries in the array + */ + if (flags & CH_REORDER) + { + for (t = HeaderOrderList; t; t = t->next) + { + dprint(1, (debugfile, "Reorder list: %s\n", t->data)); + hdr_count++; + } + } + + dprint (1, (debugfile, "WEED is %s\n", (flags & CH_WEED) ? "Set" : "Not")); + + headers = safe_calloc (hdr_count, sizeof (char *)); + + /* Read all the headers into the array */ + while (ftell (in) < off_end) + { + nl = strchr (buf, '\n'); + + /* Read a line */ + if ((fgets (buf, sizeof (buf), in)) == NULL) + break; + + /* Is it the begining of a header? */ + if (nl && buf[0] != ' ' && buf[0] != '\t') + { + ignore = 1; + if (!from && strncmp ("From ", buf, 5) == 0) + { + if ((flags & CH_FROM) == 0) + continue; + from = 1; + } + else if (buf[0] == '\n' || (buf[0] == '\r' && buf[1] == '\n')) + break; /* end of header */ + + if ((flags & CH_WEED) && + mutt_matches_ignore (buf, Ignore) && + !mutt_matches_ignore (buf, UnIgnore)) + continue; + if ((flags & (CH_UPDATE | CH_XMIT | CH_NOSTATUS)) && + (strncasecmp ("Status:", buf, 7) == 0 || + strncasecmp ("X-Status:", buf, 9) == 0)) + continue; + if ((flags & (CH_UPDATE_LEN | CH_XMIT)) && + (strncasecmp ("Content-Length:", buf, 15) == 0 || + strncasecmp ("Lines:", buf, 6) == 0)) + continue; + if ((flags & CH_MIME) && + ((strncasecmp ("content-", buf, 8) == 0 && + (strncasecmp ("transfer-encoding:", buf + 8, 18) == 0 || + strncasecmp ("type:", buf + 8, 5) == 0)) || + strncasecmp ("mime-version:", buf, 13) == 0)) + continue; + + /* Find x -- the array entry where this header is to be saved */ + if (flags & CH_REORDER) + { + for (t = HeaderOrderList, x = 0 ; (t) ; t = t->next, x++) + { + if (!strncasecmp (buf, t->data, strlen (t->data))) + { + dprint(2, (debugfile, "Reorder: %s matches %s\n", t->data, + headers[x])); + break; + } + } + } + + ignore = 0; + } /* If beginning of header */ + + if (!ignore) + { + /* Save the header in headers[x] */ + if (!headers[x]) + headers[x] = safe_strdup (buf); + else + { + safe_realloc ((void **) &headers[x], + strlen (headers[x]) + strlen (buf) + sizeof (char)); + strcat (headers[x], buf); + } + } + } /* while (ftell (in) < off_end) */ + + /* Now output the headers in order */ + for (x = 0; x < hdr_count; x++) + { + if (headers[x]) + { + if (flags & CH_DECODE) + rfc2047_decode (headers[x], headers[x], strlen (headers[x])); + + /* We couldn't do the prefixing when reading because RFC 2047 + * decoding may have concatenated lines. + */ + if (flags & CH_PREFIX) + { + char *ch = headers[x]; + int print_prefix = 1; + + while (*ch) + { + if (print_prefix) + { + if (fputs (prefix, out) == EOF) + { + error = TRUE; + break; + } + print_prefix = 0; + } + + if (*ch == '\n' && ch[1]) + print_prefix = 1; + + if (putc (*ch++, out) == EOF) + { + error = TRUE; + break; + } + } + if (error) + break; + } + else + { + if (fputs (headers[x], out) == EOF) + { + error = TRUE; + break; + } + } + } + } + + /* Free in a separate loop to be sure that all headers are freed + * in case of error. */ + for (x = 0; x < hdr_count; x++) + safe_free ((void **) &headers[x]); + safe_free ((void **) &headers); + + if (error) + return (-1); + return (0); +} + +/* flags + CH_DECODE RFC2047 header decoding + CH_FROM retain the "From " message separator + CH_MIME ignore MIME fields + CH_NONEWLINE don't output a newline after the header + CH_NOSTATUS ignore the Status: and X-Status: + CH_PREFIX quote header with $indent_str + CH_REORDER output header in order specified by `hdr_order' + CH_TXTPLAIN generate text/plain MIME headers + CH_UPDATE write new Status: and X-Status: + CH_UPDATE_LEN write new Content-Length: and Lines: + CH_XMIT ignore Lines: and Content-Length: + CH_WEED do header weeding + + prefix + string to use if CH_PREFIX is set + */ + +int +mutt_copy_header (FILE *in, HEADER *h, FILE *out, int flags, const char *prefix) +{ + if (mutt_copy_hdr (in, out, h->offset, h->content->offset, flags, prefix) == -1) + return (-1); + + if (flags & CH_TXTPLAIN) + { + fputs ("Mime-Version: 1.0\n", out); + fputs ("Content-Type: text/plain\n", out); + fputs ("Content-Transfer-Encoding: 8bit\n", out); + } + + if (flags & CH_UPDATE) + { + if ((flags & CH_NOSTATUS) == 0) + { + if (h->old || h->read) + { + if (fputs ("Status: ", out) == EOF) + return (-1); + + if (h->read) + { + if (fputs ("RO", out) == EOF) + return (-1); + } + else if (h->old) + { + if (fputc ('O', out) == EOF) + return (-1); + } + + if (fputc ('\n', out) == EOF) + return (-1); + } + + if (h->flagged || h->replied) + { + if (fputs ("X-Status: ", out) == EOF) + return (-1); + + if (h->replied) + { + if (fputc ('A', out) == EOF) + return (-1); + } + + if (h->flagged) + { + if (fputc ('F', out) == EOF) + return (-1); + } + + if (fputc ('\n', out) == EOF) + return (-1); + } + } + } + + if (flags & CH_UPDATE_LEN) + { + fprintf (out, "Content-Length: %ld\n", h->content->length); + if (h->lines != 0 || h->content->length == 0) + fprintf (out, "Lines: %d\n", h->lines); + } + + if ((flags & CH_NONEWLINE) == 0) + { + if (fputc ('\n', out) == EOF) /* add header terminator */ + return (-1); + } + + return (0); +} + +/* make a copy of a message + + fpout where to write output + fpin where to get input + hdr header of message being copied + body structure of message being copied + flags + M_CM_NOHEADER don't copy header + M_CM_PREFIX quote header and body + M_CM_DECODE decode message body to text/plain + M_CM_DISPLAY displaying output to the user + M_CM_UPDATE update structures in memory after syncing + chflags flags to mutt_copy_header() + */ + +int +_mutt_copy_message (FILE *fpout, FILE *fpin, HEADER *hdr, BODY *body, + int flags, int chflags) +{ + char prefix[SHORT_STRING]; + STATE s; + + if (flags & M_CM_PREFIX) + _mutt_make_string (prefix, sizeof (prefix), NONULL (Prefix), hdr, 0); + + if ((flags & M_CM_NOHEADER) == 0) + { + if (flags & M_CM_PREFIX) + chflags |= CH_PREFIX; + + /* eventually update Content-Length/Lines: count in HEADER, + * for now we punt (don't copy them in mutt_copy_header() */ + if (hdr->attach_del) + { + dprint (1, (debugfile, "changing flags\n")); + chflags |= CH_XMIT; + chflags &= ~CH_UPDATE_LEN; + } + + if (mutt_copy_header (fpin, hdr, fpout, chflags, + (chflags & CH_PREFIX) ? prefix : NULL) == -1) + return -1; + } + + if (flags & M_CM_DECODE) + { + /* now make a text/plain version of the message */ + memset (&s, 0, sizeof (STATE)); + s.fpin = fpin; + s.fpout = fpout; + if (flags & M_CM_PREFIX) + s.prefix = prefix; + if (flags & M_CM_DISPLAY) + s.flags |= M_DISPLAY; + + + +#ifdef _PGPPATH + if (flags & M_CM_VERIFY) + s.flags |= M_VERIFY; +#endif + + + + mutt_body_handler (body, &s); + } + else + { + fseek (fpin, body->offset, 0); + if (flags & M_CM_PREFIX) + { + char buf[LONG_STRING]; + size_t bytes = body->length; + + buf[sizeof (buf) - 1] = 0; + while (bytes > 0) + { + if (fgets (buf, sizeof (buf) - 1, fpin) == NULL) + break; + bytes -= strlen (buf); + fputs (prefix, fpout); + fputs (buf, fpout); + } + } + else + { + if (hdr->attach_del) + { + if (copy_delete_attach (hdr, NULL, NULL, fpin, fpout, flags) == -1) + return -1; + } + else + { + if (mutt_copy_bytes (fpin, fpout, body->length) == -1) + return -1; + } + } + } + return 0; +} + +int +mutt_copy_message (FILE *fpout, CONTEXT *src, HEADER *hdr, int flags, + int chflags) +{ + MESSAGE *msg; + int r; + + if ((msg = mx_open_message (src, hdr->msgno)) == NULL) + return -1; + r = _mutt_copy_message (fpout, msg->fp, hdr, hdr->content, flags, chflags); + mx_close_message (&msg); + return r; +} + +/* appends a copy of the given message to a mailbox + * + * dest destination mailbox + * fpin where to get input + * src source mailbox + * hdr message being copied + * body structure of message being copied + * flags mutt_copy_message() flags + * chflags mutt_copy_header() flags + */ + +int +_mutt_append_message (CONTEXT *dest, FILE *fpin, CONTEXT *src, HEADER *hdr, + BODY *body, int flags, int chflags) +{ + MESSAGE *msg; + int r; + + if ((msg = mx_open_new_message (dest, hdr, (src->magic == M_MBOX || src->magic == M_MMDF) ? 0 : M_ADD_FROM)) == NULL) + return -1; + if (dest->magic == M_MBOX || dest->magic == M_MMDF) + chflags |= CH_FROM; + chflags |= (dest->magic == M_MAILDIR ? CH_NOSTATUS : CH_UPDATE); + r = _mutt_copy_message (msg->fp, fpin, hdr, body, flags, chflags); + mx_close_message (&msg); + return r; +} + +int +mutt_append_message (CONTEXT *dest, CONTEXT *src, HEADER *hdr, int cmflags, + int chflags) +{ + MESSAGE *msg; + int r; + + if ((msg = mx_open_message (src, hdr->msgno)) == NULL) + return -1; + r = _mutt_append_message (dest, msg->fp, src, hdr, hdr->content, cmflags, chflags); + mx_close_message (&msg); + return r; +} + +/* + * copy_delete_attach() + * + * This function copies a message into an mbox folder and deletes + * any attachments which are marked for deletion + * + * A side effect of this is that any message copied using this function + * will not have content-length: and lines: headers, but these will be updated + * on the next sync of this mailbox + * + * This function will return 0 on success, -1 on failure. + */ +static int copy_delete_attach(HEADER *h, HEADER *p, BODY *m, FILE *fpin, + FILE *fpout, int flags) +{ + long offset = 0; + BODY *b; + char buf[STRING]; + long orig_length = 0; + long orig_offset = 0; + long new_length = 0; + long new_offset = 0; + int x; + + new_offset = ftell (fpout); + if (h == NULL) + { + if (m == NULL) + { + mutt_error ("Confused when attempting to delete attachment, h & m can't be NULL"); + return -1; + } + b = m; + } + else + { + b = h->content; + } + orig_length = b->length; + orig_offset = b->offset; + dprint (1, (debugfile, "orig length: %ld orig offset: %ld\n", orig_length, orig_offset)); + /* Find first deleted attachment */ + if (b->parts == NULL) + { + mutt_error ("Deleting non-multipart messages not yet supported"); + return -1; + } + b = b->parts; + while (b != NULL) + { + while (b && !b->deleted && !b->parts) b = b->next; + + if (b) + { + /* Copy message from current to deleted attachment headers */ + offset = ftell (fpin); + mutt_copy_bytes (fpin, fpout, b->hdr_offset - offset); + new_length += b->hdr_offset - offset; + + if (!b->deleted && b->parts) + { + x = ftell (fpout); + if (mutt_copy_hdr (fpin, fpout, ftell (fpin), b->offset, + CH_UPDATE_LEN, NULL) == -1) + return -1; + new_length += (ftell (fpout) - x); + x = fprintf (fpout, "\n"); + if (x > 0) + new_length += x; + + if (copy_delete_attach(b->hdr, h, b, fpin, fpout, flags) == -1) + return -1; + new_length += b->parts->length; + } + else + { + + if (h) h->lines = 0; + mutt_make_string (buf, sizeof (buf), NONULL (DeleteFmt), (p) ? p : h); + + /* Go through deleted attachment headers, weed Content-Length, + * Content-Type and Content-Transfer-Encoding + * Also, keep track of what we write to update the length */ + x = ftell (fpout); + if (mutt_copy_hdr (fpin, fpout, ftell (fpin), b->offset, + CH_UPDATE_LEN | CH_MIME , NULL) == -1) + return -1; + new_length += (ftell (fpout) - x); + x = fprintf (fpout, "\n%s\n", buf); + if (x > 0) + new_length += x; + /* Skip the deleted body */ + fseek (fpin, (b->offset + b->length), SEEK_SET); + if (flags & M_CM_UPDATE) + b->deleted = 0; + } + b = b->next; + } + } + /* Copy til end of message */ + offset = ftell(fpin); + mutt_copy_bytes (fpin, fpout, (orig_offset+orig_length) - offset); + new_length += (orig_offset+orig_length) - offset; + + /* + * Update the content-length and offset. This offset will be wrong for + * mbox/mh type folders, but will be corrected in mbox_sync_mailbox() + * + */ + if (flags & M_CM_UPDATE) + { + dprint (1, (debugfile, "new length: %ld new offset: %ld\n", new_length, new_offset)); + if (h) + { + h->content->length = new_length; + h->content->offset = new_offset; + h->attach_del = 0; + } + else if (m) + { + m->parts->length = new_length; + m->parts->offset = new_offset; + } + } + + return 0; +} diff --git a/copy.h b/copy.h new file mode 100644 index 00000000..ff78a3c4 --- /dev/null +++ b/copy.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* flags to _mutt_copy_message */ +#define M_CM_NOHEADER 1 /* don't copy the message header */ +#define M_CM_PREFIX (1<<1) /* quote the message */ +#define M_CM_DECODE (1<<2) /* decode the message body into text/plain */ +#define M_CM_DISPLAY (1<<3) /* output is displayed to the user */ +#define M_CM_UPDATE (1<<4) /* update structs on sync */ + + + +#ifdef _PGPPATH +#define M_CM_VERIFY (1<<4) /* do signature verification */ +#endif + + + +int mutt_copy_hdr (FILE *, FILE *, long, long, int, const char *); + +int mutt_copy_header (FILE *, HEADER *, FILE *, int, const char *); + +int _mutt_copy_message (FILE *fpout, + FILE *fpin, + HEADER *hdr, + BODY *body, + int flags, + int chflags); + +int mutt_copy_message (FILE *fpout, + CONTEXT *src, + HEADER *hdr, + int flags, + int chflags); + +int _mutt_append_message (CONTEXT *dest, + FILE *fpin, + CONTEXT *src, + HEADER *hdr, + BODY *body, + int flags, + int chflags); + +int mutt_append_message (CONTEXT *dest, + CONTEXT *src, + HEADER *hdr, + int cmflags, + int chflags); diff --git a/curs_lib.c b/curs_lib.c new file mode 100644 index 00000000..c2177fc2 --- /dev/null +++ b/curs_lib.c @@ -0,0 +1,359 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_menu.h" +#include "mutt_curses.h" +#include "pager.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* not possible to unget more than one char under some curses libs, and it + * is impossible to unget function keys in SLang, so roll our own input + * buffering routines. + */ +static short UngetCount = 0; + +#define UngetBufLen 128 +static int UngetBuf[UngetBufLen]; + +void mutt_refresh (void) +{ + /* don't refresh in the middle of macros unless necessary */ + if (!UngetCount || option (OPTFORCEREFRESH)) + refresh (); +} + +int mutt_getch (void) +{ + int ch; + + if (UngetCount) + return (UngetBuf[--UngetCount]); + + Signals &= ~S_INTERRUPT; + +#ifdef KEY_RESIZE + /* ncurses 4.2 sends this when the screen is resized */ + ch = KEY_RESIZE; + while (ch == KEY_RESIZE) +#endif /* KEY_RESIZE */ + ch = getch (); + + if (Signals & S_INTERRUPT) + mutt_query_exit (); + + if ((ch & 0x80) && option (OPTMETAKEY)) + { + /* send ALT-x as ESC-x */ + ch &= ~0x80; + mutt_ungetch (ch); + return ('\033'); + } + + return (ch == ctrl ('G') ? ERR : ch); +} + +int mutt_get_field (/* const */ char *field, char *buf, size_t buflen, int complete) +{ + int ret; + int len = strlen (field); /* in case field==buffer */ + + do + { + CLEARLINE (LINES-1); + addstr (field); + mutt_refresh (); + ret = mutt_enter_string ((unsigned char *) buf, buflen, LINES-1, len, complete); + } + while (ret == 1); + CLEARLINE (LINES-1); + return (ret); +} + +int mutt_get_password (char *msg, char *buf, size_t buflen) +{ + int rc; + + CLEARLINE (LINES-1); + addstr (msg); + rc = mutt_enter_string ((unsigned char *) buf, buflen, LINES - 1, strlen (msg), M_PASS); + CLEARLINE (LINES-1); + return (rc); +} + +void mutt_clear_error (void) +{ + Errorbuf[0] = 0; + CLEARLINE (LINES-1); +} + +void mutt_edit_file (const char *editor, const char *data) +{ + char cmd[LONG_STRING]; + + endwin (); + mutt_expand_fmt (cmd, sizeof (cmd), editor, data); + mutt_system (cmd); + keypad (stdscr, TRUE); + clearok (stdscr, TRUE); +} + +int mutt_yesorno (const char *msg, int def) +{ + int ch; + + CLEARLINE(LINES-1); + printw("%s: [%c] ", msg, def ? 'y' : 'n'); + FOREVER + { + mutt_refresh (); + ch = mutt_getch (); + if (ch == ERR) return(-1); + if (CI_is_return (ch)) + break; + else if (ch == 'y') + { + def = 1; + break; + } + else if (ch == 'n') + { + def = 0; + break; + } + else + { + BEEP(); + } + } + addstr (def ? "Yes" : "No"); + mutt_refresh (); + return (def); +} + +/* this function is called when the user presses the abort key */ +void mutt_query_exit (void) +{ + mutt_flushinp (); + curs_set (1); + if (Timeout) + timeout (-1); /* restore blocking operation */ + if (mutt_yesorno ("Exit Mutt?", 1) == 1) + { + endwin (); + exit (0); + } + mutt_curs_set (-1); + Signals &= ~S_INTERRUPT; +} + +void mutt_curses_error (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap); + va_end (ap); + + Errorbuf[ (COLS < sizeof (Errorbuf) ? COLS : sizeof (Errorbuf)) - 2 ] = 0; + + BEEP (); + SETCOLOR (MT_COLOR_ERROR); + mvaddstr (LINES-1, 0, Errorbuf); + clrtoeol (); + SETCOLOR (MT_COLOR_NORMAL); + mutt_refresh (); + + set_option (OPTMSGERR); +} + +void mutt_message (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap); + va_end (ap); + + Errorbuf[ (COLS < sizeof (Errorbuf) ? COLS : sizeof (Errorbuf)) - 2 ] = 0; + + SETCOLOR (MT_COLOR_MESSAGE); + mvaddstr (LINES - 1, 0, Errorbuf); + clrtoeol (); + SETCOLOR (MT_COLOR_NORMAL); + mutt_refresh (); + + unset_option (OPTMSGERR); +} + +void mutt_show_error (void) +{ + SETCOLOR (option (OPTMSGERR) ? MT_COLOR_ERROR : MT_COLOR_MESSAGE); + CLEARLINE (LINES-1); + addstr (Errorbuf); + SETCOLOR (MT_COLOR_NORMAL); +} + +void mutt_endwin (const char *msg) +{ + move (LINES - 1, COLS - 1); + attrset (A_NORMAL); + mutt_refresh (); + endwin (); + fputc ('\n', stdout); + if (msg) + puts (msg); +} + +void mutt_perror (const char *s) +{ + char *p = strerror (errno); + + mutt_error ("%s: %s (errno = %d)", s, p ? p : "unknown error", errno); +} + +int mutt_any_key_to_continue (const char *s) +{ + struct termios t; + struct termios old; + int f, ch; + + f = open ("/dev/tty", O_RDONLY); + tcgetattr (f, &t); + memcpy ((void *)&old, (void *)&t, sizeof(struct termios)); /* save original state */ + t.c_lflag &= ~(ICANON | ECHO); + t.c_cc[VMIN] = 1; + t.c_cc[VTIME] = 0; + tcsetattr (f, TCSADRAIN, &t); + fflush (stdout); + if (s) + fputs (s, stdout); + else + fputs ("Press any key to continue...", stdout); + fflush (stdout); + ch = fgetc (stdin); + fflush (stdin); + tcsetattr (f, TCSADRAIN, &old); + close (f); + fputs ("\r\n", stdout); + return (ch); +} + +int mutt_do_pager (const char *banner, + const char *tempfile, + int do_color, + pager_t *info) +{ + int rc; + + if (strcmp (Pager, "builtin") == 0) + rc = mutt_pager (banner, tempfile, do_color, info); + else + { + char cmd[STRING]; + + endwin (); + snprintf (cmd, sizeof (cmd), "%s %s", Pager, tempfile); + mutt_system (cmd); + mutt_unlink (tempfile); + rc = 0; + } + + return rc; +} + +int mutt_enter_fname (const char *prompt, char *buf, size_t blen, int *redraw, int buffy) +{ + int i; + + mvaddstr (LINES-1, 0, (char *) prompt); + addstr (" ('?' for list): "); + if (buf[0]) + addstr (buf); + clrtoeol (); + mutt_refresh (); + + if ((i = mutt_getch ()) == ERR) + { + CLEARLINE (LINES-1); + return (-1); + } + else if (i == '?') + { + mutt_refresh (); + buf[0] = 0; + mutt_select_file (buf, blen, 0); + *redraw = REDRAW_FULL; + } + else + { + char *pc = safe_malloc (strlen (prompt) + 3); + + sprintf (pc, "%s: ", prompt); + mutt_ungetch (i); + if (mutt_get_field (pc, buf, blen, (buffy ? M_EFILE : M_FILE) | M_CLEAR) + != 0) + buf[0] = 0; + MAYBE_REDRAW (*redraw); + free (pc); + } + + return 0; +} + +/* FOO - this could be made more efficient by allocating/deallocating memory + * instead of using a fixed array + */ +void mutt_ungetch (int ch) +{ + if (UngetCount < UngetBufLen) /* make sure not to overflow */ + UngetBuf[UngetCount++] = ch; +} + +void mutt_flushinp (void) +{ + UngetCount = 0; + flushinp (); +} + +#if (defined(USE_SLANG_CURSES) || defined(HAVE_CURS_SET)) +/* The argument can take 3 values: + * -1: restore the value of the last call + * 0: make the cursor invisible + * 1: make the cursor visible + */ +void mutt_curs_set (int cursor) +{ + static int SavedCursor = 1; + + if (cursor < 0) + cursor = SavedCursor; + else + SavedCursor = cursor; + + curs_set (cursor); +} +#endif diff --git a/curs_main.c b/curs_main.c new file mode 100644 index 00000000..7dbadcae --- /dev/null +++ b/curs_main.c @@ -0,0 +1,1539 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "mutt_menu.h" +#include "mailbox.h" +#include "sort.h" +#include "buffy.h" + + + +#ifdef _PGPPATH +#include "pgp.h" +#endif + + + + + + + + + + +#include +#include +#include +#include +#include +#include +#include + +#define CHECK_MSGCOUNT if (!Context) \ + { \ + mutt_flushinp (); \ + mutt_error ("No mailbox is open."); \ + break; \ + } \ + else if (!Context->msgcount) \ + { \ + mutt_flushinp (); \ + mutt_error ("There are no messages."); \ + break; \ + } + +#define CHECK_READONLY if (Context->readonly) \ + { \ + mutt_flushinp (); \ + mutt_error ("Mailbox is read-only."); \ + break; \ + } + +#define CURHDR Context->hdrs[Context->v2r[menu->current]] +#define OLDHDR Context->hdrs[Context->v2r[menu->oldcurrent]] + +extern const char *ReleaseDate; + +void index_make_entry (char *s, size_t l, MUTTMENU *menu, int num) +{ + format_flag flag = M_FORMAT_MAKEPRINT; + int reverse = Sort & SORT_REVERSE, edgemsgno; + HEADER *tmp, *h = Context->hdrs[Context->v2r[num]]; + + if ((Sort & SORT_MASK) == SORT_THREADS && h->tree) + { + flag |= M_FORMAT_TREE; /* display the thread tree */ + + if (h->display_subject) + flag |= M_FORMAT_FORCESUBJ; + else + { + edgemsgno = Context->v2r[menu->top + (reverse ? menu->pagelen - 1 : 0)]; + for (tmp = h->parent; tmp; tmp = tmp->parent) + { + if ((reverse && tmp->msgno > edgemsgno) + || (!reverse && tmp->msgno < edgemsgno)) + { + flag |= M_FORMAT_FORCESUBJ; + break; + } + else if (tmp->virtual >= 0) + break; + } + if ((flag & M_FORMAT_FORCESUBJ) && h->prev) + { + for (tmp = h->prev; tmp; tmp = tmp->prev) + { + if ((reverse && tmp->msgno > edgemsgno) + || (!reverse && tmp->msgno < edgemsgno)) + break; + else if (tmp->virtual >= 0) + { + flag &= ~M_FORMAT_FORCESUBJ; + break; + } + } + } + } + } + + _mutt_make_string (s, l, NONULL (HdrFmt), h, flag); +} + +int index_color (int index_no) +{ + return Context->hdrs[Context->v2r[index_no]]->pair; +} + +static int ci_next_undeleted (int msgno) +{ + int i; + + for (i=msgno+1; i < Context->vcount; i++) + if (! Context->hdrs[Context->v2r[i]]->deleted) + return (i); + return (-1); +} + +static int ci_previous_undeleted (int msgno) +{ + int i; + + for (i=msgno-1; i>=0; i--) + if (! Context->hdrs[Context->v2r[i]]->deleted) + return (i); + return (-1); +} + +/* Return the index of the first new message, or failing that, the first + * unread message. + */ +static int ci_first_message (void) +{ + int old = -1, i; + + if (Context && Context->msgcount) + { + for (i=0; i < Context->vcount; i++) + { + if (! Context->hdrs[Context->v2r[i]]->read && + ! Context->hdrs[Context->v2r[i]]->deleted) + { + if (! Context->hdrs[Context->v2r[i]]->old) + return (i); + else if (old == -1) + old = i; + } + } + if (old != -1) + return (old); + + /* If Sort is reverse and not threaded, the latest message is first. + * If Sort is threaded, the latest message is first iff exactly one + * of Sort and SortAux are reverse. + */ + if (((Sort & SORT_REVERSE) && (Sort & SORT_MASK) != SORT_THREADS) || + ((Sort & SORT_MASK) == SORT_THREADS && + ((Sort ^ SortAux) & SORT_REVERSE))) + return 0; + else + return (Context->vcount ? Context->vcount - 1 : 0); + } + return 0; +} + +/* This should be in mx.c, but it only gets used here. */ +static int mx_toggle_write (CONTEXT *ctx) +{ + if (!ctx) + return -1; + + if (ctx->readonly) + { + mutt_error ("Cannot toggle write on a readonly mailbox!"); + return -1; + } + + if (ctx->dontwrite) + { + ctx->dontwrite = 0; + mutt_message ("Changes to folder will be written on folder exit."); + } + else + { + ctx->dontwrite = 1; + mutt_message ("Changes to folder will not be written."); + } + + return 0; +} + +struct mapping_t IndexHelp[] = { + { "Quit", OP_QUIT }, + { "Del", OP_DELETE }, + { "Undel", OP_UNDELETE }, + { "Save", OP_SAVE }, + { "Mail", OP_MAIL }, + { "Reply", OP_REPLY }, + { "Group", OP_GROUP_REPLY }, + { "Help", OP_HELP }, + { NULL } +}; + +/* This function handles the message index window as well as commands returned + * from the pager (MENU_PAGER). + */ +void mutt_index_menu (void) +{ + char buf[LONG_STRING], helpstr[SHORT_STRING]; + int op = OP_NULL; /* function to execute */ + int done = 0; /* controls when to exit the "event" loop */ + int i = 0, j; + int tag = 0; /* has the tag-prefix command been pressed? */ + int newcount = -1; + int oldcount = -1; + int rc = -1; + MUTTMENU *menu; + char *cp; /* temporary variable. */ + int index_hint; /* used to restore cursor position */ + int do_buffy_notify = 1; + + menu = mutt_new_menu (); + menu->menu = MENU_MAIN; + menu->offset = 1; + menu->pagelen = LINES - 3; + menu->make_entry = index_make_entry; + menu->color = index_color; + menu->current = ci_first_message (); + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_MAIN, IndexHelp); + + mutt_buffy_check(1); /* force the buffy check after we enter the folder */ + + FOREVER + { + tag = 0; /* clear the tag-prefix */ + + menu->max = Context ? Context->vcount : 0; + oldcount = Context ? Context->msgcount : 0; + + if (Context) + { + /* check for new mail in the mailbox. If nonzero, then something has + * changed about the file (either we got new mail or the file was + * modified underneath us.) + */ + index_hint = (Context->vcount) ? CURHDR->index : 0; + + if ((i = mx_check_mailbox (Context, &index_hint)) < 0) + { + if (!Context->path) + { + /* fatal error occurred */ + safe_free ((void **) &Context); + menu->redraw = REDRAW_FULL; + } + + set_option (OPTSEARCHINVALID); + } + else if (i == M_NEW_MAIL || i == M_REOPENED) + { + /* take note of the current message */ + if (oldcount) + { + if (menu->current < Context->vcount) + menu->oldcurrent = index_hint; + else + oldcount = 0; /* invalid message number! */ + } + + /* We are in a limited view. Check if the new message(s) satisfy + * the limit criteria. If they do, set their virtual msgno so that + * they will be visible in the limited view */ + if (Context->pattern) + { + #define this_body Context->hdrs[i]->content + if (oldcount) + for (i = oldcount; i < Context->msgcount; i++) + { + if (mutt_pattern_exec (Context->limit_pattern, + M_MATCH_FULL_ADDRESS, + Context, Context->hdrs[i])) + { + Context->hdrs[i]->virtual = Context->vcount; + Context->v2r[Context->vcount] = i; + Context->vcount++; + Context->vsize+=this_body->length + this_body->offset - + this_body->hdr_offset; + } + } + #undef this_body + } + + /* if the mailbox was reopened, need to rethread from scratch */ + mutt_sort_headers (Context, (i == M_REOPENED)); + + menu->current = -1; + if (oldcount) + { + int j; + + /* restore the current message to the message it was pointing to */ + for (j = 0; j < Context->vcount; j++) + if (Context->hdrs[Context->v2r[j]]->index == menu->oldcurrent) + { + menu->current = j; + break; + } + } + + if (menu->current < 0) + menu->current = ci_first_message (); + + /* notify the user of new mail */ + if (i == M_REOPENED) + mutt_error ("Mailbox was externally modified. Flags may be wrong."); + else + { + mutt_message ("New mail in this mailbox."); + if (option (OPTBEEPNEW)) + beep (); + } + /* avoid the message being overwritten by buffy */ + do_buffy_notify = 0; + + menu->redraw = REDRAW_FULL; + menu->max = Context->vcount; + + set_option (OPTSEARCHINVALID); + } + } + + /* check for new mail in the incoming folders */ + oldcount = newcount; + if ((newcount = mutt_buffy_check (0)) != oldcount) + menu->redraw |= REDRAW_STATUS; + if (do_buffy_notify) + { + if (mutt_buffy_notify () && option (OPTBEEPNEW)) + beep (); + } + else + do_buffy_notify = 1; + + mutt_curs_set (0); + + if (menu->redraw & REDRAW_FULL) + { + menu_redraw_full (menu); + mutt_show_error (); + } + + if (menu->menu == MENU_MAIN) + { + if (Context) + { + menu_check_recenter (menu); + + if (menu->redraw & REDRAW_INDEX) + { + menu_redraw_index (menu); + menu->redraw |= REDRAW_STATUS; + } + else if (menu->redraw & (REDRAW_MOTION_RESYNCH | REDRAW_MOTION)) + menu_redraw_motion (menu); + else if (menu->redraw & REDRAW_CURRENT) + menu_redraw_current (menu); + } + + if (menu->redraw & REDRAW_STATUS) + { + menu_status_line (buf, sizeof (buf), menu, NONULL (Status)); + CLEARLINE (option (OPTSTATUSONTOP) ? 0 : LINES-2); + SETCOLOR (MT_COLOR_STATUS); + printw ("%-*.*s", COLS, COLS, buf); + SETCOLOR (MT_COLOR_NORMAL); + menu->redraw &= ~REDRAW_STATUS; + } + + menu->redraw = 0; + menu->oldcurrent = menu->current; + + if (option (OPTARROWCURSOR)) + move (menu->current - menu->top + menu->offset, 2); + else + move (menu->current - menu->top + menu->offset, COLS - 1); + mutt_refresh (); + + if (Timeout > 0) + { + timeout (Timeout * 1000); /* milliseconds */ + op = mutt_getch (); + timeout (-1); /* restore blocking operation */ + if (op != -1) + { + mutt_ungetch (op); + op = km_dokey (MENU_MAIN); + } + } + else + op = km_dokey (MENU_MAIN); + mutt_curs_set (1); + +#if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM) + if (Signals & S_SIGWINCH) + { + mutt_flushinp (); + mutt_resize_screen (); + menu->redraw = REDRAW_FULL; + menu->menu = MENU_MAIN; + Signals &= ~S_SIGWINCH; + menu->top = 0; /* so we scroll the right amount */ + continue; + } +#endif + + if (op == -1) + continue; /* either user abort or timeout */ + + /* special handling for the tag-prefix function */ + if (op == OP_TAG_PREFIX) + { + if (!Context) + { + mutt_error ("No mailbox is open."); + continue; + } + + if (!Context->tagged) + { + mutt_error ("No tagged messages."); + continue; + } + tag = 1; + + /* give visual indication that the next command is a tag- command */ + mvaddstr (LINES - 1, 0, "tag-"); + clrtoeol (); + + /* get the real command */ + if ((op = km_dokey (MENU_MAIN)) == OP_TAG_PREFIX) + { + /* abort tag sequence */ + CLEARLINE (LINES-1); + continue; + } + } + else if (option (OPTAUTOTAG) && Context && Context->tagged) + tag = 1; + + mutt_clear_error (); + } + else + mutt_curs_set (1); /* fallback from the pager */ + + switch (op) + { + + /* ---------------------------------------------------------------------- + * movement commands + */ + + case OP_BOTTOM_PAGE: + menu_bottom_page (menu); + break; + case OP_FIRST_ENTRY: + menu_first_entry (menu); + break; + case OP_MIDDLE_PAGE: + menu_middle_page (menu); + break; + case OP_HALF_UP: + menu_half_up (menu); + break; + case OP_HALF_DOWN: + menu_half_down (menu); + break; + case OP_NEXT_LINE: + menu_next_line (menu); + break; + case OP_PREV_LINE: + menu_prev_line (menu); + break; + case OP_NEXT_PAGE: + menu_next_page (menu); + break; + case OP_PREV_PAGE: + menu_prev_page (menu); + break; + case OP_LAST_ENTRY: + menu_last_entry (menu); + break; + case OP_TOP_PAGE: + menu_top_page (menu); + break; + case OP_CURRENT_TOP: + menu_current_top (menu); + break; + case OP_CURRENT_MIDDLE: + menu_current_middle (menu); + break; + case OP_CURRENT_BOTTOM: + menu_current_bottom (menu); + break; + + case OP_JUMP: + + CHECK_MSGCOUNT; + mutt_ungetch (LastKey); + buf[0] = 0; + if (mutt_get_field ("Jump to message: ", buf, sizeof (buf), 0) != 0 || + !buf[0]) + break; + + if (! isdigit (buf[0])) + { + mutt_error ("Argument must be a message number."); + break; + } + + i = atoi (buf); + if (i > 0 && i <= Context->msgcount) + { + for (j = i-1; j < Context->msgcount; j++) + { + if (Context->hdrs[j]->virtual != -1) + break; + } + if (j >= Context->msgcount) + { + for (j = i-2; j >= 0; j--) + { + if (Context->hdrs[j]->virtual != -1) + break; + } + } + + if (j >= 0) + { + menu->current = Context->hdrs[j]->virtual; + if (menu->menu == MENU_PAGER) + { + op = OP_DISPLAY_MESSAGE; + continue; + } + else + menu->redraw = REDRAW_MOTION; + } + else + mutt_error ("That message is not visible."); + } + else + mutt_error ("Invalid message number."); + + break; + + /* -------------------------------------------------------------------- + * `index' specific commands + */ + + case OP_MAIN_DELETE_PATTERN: + + CHECK_MSGCOUNT; + CHECK_READONLY; + mutt_pattern_func (M_DELETE, "Delete messages matching: ", CURHDR); + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + break; + +#ifdef USE_POP + case OP_MAIN_FETCH_MAIL: + + mutt_fetchPopMail (); + menu->redraw = REDRAW_FULL; + break; +#endif /* USE_POP */ + + case OP_HELP: + + mutt_help (MENU_MAIN); + menu->redraw = REDRAW_FULL; + break; + + case OP_MAIN_SHOW_LIMIT: + CHECK_MSGCOUNT; + if (!Context->pattern) + mutt_message ("No limit pattern is in effect."); + else + { + char buf[STRING]; + snprintf (buf, sizeof(buf), "Limit: %s",Context->pattern); + mutt_message ("%s", buf); + } + break; + + case OP_MAIN_LIMIT: + + CHECK_MSGCOUNT; + menu->oldcurrent = Context->vcount ? CURHDR->index : -1; + if (mutt_pattern_func (M_LIMIT, "Limit to messages matching: ", CURHDR) == 0) + { + if (menu->oldcurrent >= 0) + { + /* try to find what used to be the current message */ + menu->current = -1; + for (i = 0; i < Context->vcount; i++) + if (Context->hdrs[Context->v2r[i]]->index == menu->oldcurrent) + { + menu->current = i; + break; + } + if (menu->current < 0) menu->current = 0; + } + else + menu->current = 0; + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + mutt_linearize_tree (Context, 0); + } + break; + + case OP_QUIT: + + if (query_quadoption (OPT_QUIT, "Quit Mutt?") == M_YES) + { + if (!Context || mx_close_mailbox (Context) == 0) + done = 1; + else + menu->redraw = REDRAW_FULL; /* new mail arrived? */ + } + break; + + case OP_REDRAW: + + clearok (stdscr, TRUE); + menu->redraw = REDRAW_FULL; + break; + + case OP_SEARCH: + case OP_SEARCH_REVERSE: + case OP_SEARCH_NEXT: + case OP_SEARCH_OPPOSITE: + + CHECK_MSGCOUNT; + if ((menu->current = mutt_search_command (menu->current, op)) == -1) + menu->current = menu->oldcurrent; + else + menu->redraw = REDRAW_MOTION; + break; + + case OP_SORT: + case OP_SORT_REVERSE: + + if (mutt_select_sort ((op == OP_SORT_REVERSE)) == 0) + { + if (Context && Context->msgcount) + { + menu->oldcurrent = CURHDR->index; + mutt_sort_headers (Context, 0); + + /* try to restore the current message */ + for (i = 0; i < Context->vcount; i++) + { + if (Context->hdrs[Context->v2r[i]]->index == menu->oldcurrent) + menu->current = i; + } + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + set_option (OPTSEARCHINVALID); + } + } + break; + + case OP_MAIN_SYNC_FOLDER: + + CHECK_MSGCOUNT; + CHECK_READONLY; + { + int oldvcount = Context->vcount; + int dcount = 0; + + /* calculate the number of messages _above_ the cursor, + * so we can keep the cursor on the current message + */ + for (j = 0; j < menu->current; j++) + { + if (Context->hdrs[Context->v2r[j]]->deleted) + dcount++; + } + if (mx_sync_mailbox (Context) == 0) + { + if (Context->vcount != oldvcount) + { + menu->current -= dcount; + if (menu->current < 0 || menu->current >= Context->vcount) + menu->current = ci_first_message (); + } + set_option (OPTSEARCHINVALID); + } + } + + /* check for a fatal error, or all messages deleted */ + if (!Context->path) + safe_free ((void **) &Context); + menu->redraw = REDRAW_FULL; + break; + + case OP_TAG: + + CHECK_MSGCOUNT; + if (tag && !option (OPTAUTOTAG)) + { + for (j = 0; j < Context->vcount; j++) + mutt_set_flag (Context, Context->hdrs[Context->v2r[j]], M_TAG, 0); + menu->redraw = REDRAW_STATUS | REDRAW_INDEX; + } + else + { + mutt_set_flag (Context, CURHDR, M_TAG, !CURHDR->tagged); + menu->redraw = REDRAW_STATUS; + if (option (OPTRESOLVE) && menu->current < Context->vcount - 1) + { + menu->current++; + menu->redraw |= REDRAW_MOTION_RESYNCH; + } + else + menu->redraw |= REDRAW_CURRENT; + } + break; + + case OP_MAIN_TAG_PATTERN: + + CHECK_MSGCOUNT; + mutt_pattern_func (M_TAG, "Tag messages matching: ", CURHDR); + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + break; + + case OP_MAIN_UNDELETE_PATTERN: + + CHECK_MSGCOUNT; + CHECK_READONLY; + if (mutt_pattern_func (M_UNDELETE, "Undelete messages matching: ", CURHDR) == 0) + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + break; + + case OP_MAIN_UNTAG_PATTERN: + + CHECK_MSGCOUNT; + if (mutt_pattern_func (M_UNTAG, "Untag messages matching: ", CURHDR) == 0) + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + break; + + /* -------------------------------------------------------------------- + * The following operations can be performed inside of the pager. + */ + + case OP_MAIN_CHANGE_FOLDER: + + if (option (OPTREADONLY)) + op = OP_MAIN_CHANGE_FOLDER_READONLY; + + /* fallback to the readonly case */ + + case OP_MAIN_CHANGE_FOLDER_READONLY: + + if (op == OP_MAIN_CHANGE_FOLDER) + cp = "Open mailbox"; + else + cp = "Open mailbox in read-only mode"; + + buf[0] = '\0'; + mutt_buffy (buf); + + if (mutt_enter_fname (cp, buf, sizeof (buf), &menu->redraw, 1) == -1) + break; + if (!buf[0]) + { + CLEARLINE (LINES-1); + break; + } + + mutt_expand_path (buf, sizeof (buf)); + if (mx_get_magic (buf) <= 0) + { + mutt_error ("%s is not a mailbox.", buf); + break; + } + + if (Context) + { + FREE (&LastFolder); + LastFolder = safe_strdup (Context->path); + } + + if (Context) + { + if (mx_close_mailbox (Context) != 0) + { + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + break; + } + safe_free ((void **) &Context); + } + + sleep (1); /* give a second to read the mailbox status */ + + mutt_folder_hook (buf); + + if ((Context = mx_open_mailbox (buf, + (option (OPTREADONLY) || op == OP_MAIN_CHANGE_FOLDER_READONLY) ? + M_READONLY : 0, NULL)) != NULL) + { + menu->current = ci_first_message (); + } + else + menu->current = 0; + + mutt_clear_error (); + mutt_buffy_check(1); /* force the buffy check after we have changed + the folder */ + menu->redraw = REDRAW_FULL; + set_option (OPTSEARCHINVALID); + break; + + case OP_DISPLAY_MESSAGE: + case OP_DISPLAY_HEADERS: /* don't weed the headers */ + + CHECK_MSGCOUNT; + /* + * toggle the weeding of headers so that a user can press the key + * again while reading the message. + */ + if (op == OP_DISPLAY_HEADERS) + toggle_option (OPTWEED); + + unset_option (OPTNEEDRESORT); + + if ((op = mutt_display_message (CURHDR)) == -1) + { + unset_option (OPTNEEDRESORT); + break; + } + + if (option (OPTNEEDRESORT) && Context && Context->msgcount) + { + menu->oldcurrent = CURHDR->index; + mutt_sort_headers (Context, 0); + + /* try to restore the current message */ + for (i = 0; i < Context->vcount; i++) + { + if (Context->hdrs[Context->v2r[i]]->index == menu->oldcurrent) + menu->current = i; + } + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + } + + menu->menu = MENU_PAGER; + menu->oldcurrent = menu->current; + continue; + + case OP_EXIT: + + if ((menu->menu == MENU_MAIN) + && (query_quadoption (OPT_QUIT, + "Exit Mutt without saving?") == M_YES)) + { + if (Context) + { + mx_fastclose_mailbox (Context); + safe_free ((void **)&Context); + } + done = 1; + } + break; + + case OP_MAIN_NEXT_UNDELETED: + + CHECK_MSGCOUNT; + if (menu->current >= Context->vcount - 1) + { + if (menu->menu == MENU_MAIN) + mutt_error ("You are on the last message."); + break; + } + if ((menu->current = ci_next_undeleted (menu->current)) == -1) + { + menu->current = menu->oldcurrent; + if (menu->menu == MENU_MAIN) + mutt_error ("No undeleted messages."); + } + else if (menu->menu == MENU_PAGER) + { + op = OP_DISPLAY_MESSAGE; + continue; + } + else + menu->redraw = REDRAW_MOTION; + break; + + case OP_NEXT_ENTRY: + + CHECK_MSGCOUNT; + if (menu->current >= Context->vcount - 1) + { + if (menu->menu == MENU_MAIN) + mutt_error ("You are on the last message."); + break; + } + menu->current++; + if (menu->menu == MENU_PAGER) + { + op = OP_DISPLAY_MESSAGE; + continue; + } + else + menu->redraw = REDRAW_MOTION; + break; + + case OP_MAIN_PREV_UNDELETED: + + CHECK_MSGCOUNT; + if (menu->current < 1) + { + mutt_error ("You are on the first message."); + break; + } + if ((menu->current = ci_previous_undeleted (menu->current)) == -1) + { + menu->current = menu->oldcurrent; + if (menu->menu == MENU_MAIN) + mutt_error ("No undeleted messages."); + } + else if (menu->menu == MENU_PAGER) + { + op = OP_DISPLAY_MESSAGE; + continue; + } + else + menu->redraw = REDRAW_MOTION; + break; + + case OP_PREV_ENTRY: + + CHECK_MSGCOUNT; + if (menu->current < 1) + { + if (menu->menu == MENU_MAIN) mutt_error ("You are on the first message."); + break; + } + menu->current--; + if (menu->menu == MENU_PAGER) + { + op = OP_DISPLAY_MESSAGE; + continue; + } + else + menu->redraw = REDRAW_MOTION; + break; + + case OP_COPY_MESSAGE: + case OP_SAVE: + case OP_DECODE_COPY: + case OP_DECODE_SAVE: + + CHECK_MSGCOUNT; + if (mutt_save_message (tag ? NULL : CURHDR, + (op == OP_SAVE || op == OP_DECODE_SAVE), + (op == OP_DECODE_SAVE || op == OP_DECODE_COPY), + &menu->redraw) == 0 && + (op == OP_SAVE || op == OP_DECODE_SAVE)) + { + if (tag) + menu->redraw |= REDRAW_INDEX; + else if (option (OPTRESOLVE)) + { + if ((menu->current = ci_next_undeleted (menu->current)) == -1) + { + menu->current = menu->oldcurrent; + menu->redraw |= REDRAW_CURRENT; + } + else + menu->redraw |= REDRAW_MOTION_RESYNCH; + } + else + menu->redraw |= REDRAW_CURRENT; + } + break; + + case OP_MAIN_NEXT_NEW: + case OP_MAIN_NEXT_UNREAD: + case OP_MAIN_PREV_NEW: + case OP_MAIN_PREV_UNREAD: + + CHECK_MSGCOUNT; + i = menu->current; + menu->current = -1; + for (j = 0; j != Context->vcount; j++) + { + if (op == OP_MAIN_NEXT_NEW || op == OP_MAIN_NEXT_UNREAD) + { + i++; + if (i > Context->vcount - 1) + { + mutt_message ("Search wrapped to top."); + i = 0; + } + } + else + { + i--; + if (i < 0) + { + mutt_message ("Search wrapped to bottom."); + i = Context->vcount - 1; + } + } + + if (! Context->hdrs[Context->v2r[i]]->deleted && + ! Context->hdrs[Context->v2r[i]]->read) + { + if (op == OP_MAIN_NEXT_UNREAD || op == OP_MAIN_PREV_UNREAD || + ! Context->hdrs[Context->v2r[i]]->old) + { + menu->current = i; + break; + } + } + } + if (menu->current == -1) + { + menu->current = menu->oldcurrent; + mutt_error ((op == OP_MAIN_NEXT_NEW || op == OP_MAIN_PREV_NEW) ? + "No new messages." : "No unread messages."); + } + else if (menu->menu == MENU_PAGER) + { + op = OP_DISPLAY_MESSAGE; + continue; + } + else + menu->redraw = REDRAW_MOTION; + break; + + case OP_FLAG_MESSAGE: + + CHECK_MSGCOUNT; + CHECK_READONLY; + mutt_set_flag (Context, CURHDR, M_FLAG, !CURHDR->flagged); + + if (option (OPTRESOLVE)) + { + if ((menu->current = ci_next_undeleted (menu->current)) == -1) + { + menu->current = menu->oldcurrent; + menu->redraw = REDRAW_CURRENT; + } + else + menu->redraw = REDRAW_MOTION_RESYNCH; + } + else + menu->redraw = REDRAW_CURRENT; + menu->redraw |= REDRAW_STATUS; + break; + + case OP_TOGGLE_NEW: + + CHECK_MSGCOUNT; + CHECK_READONLY; + if (tag) + { + for (j = 0; j < Context->vcount; j++) + { + if (Context->hdrs[Context->v2r[j]]->tagged) + { + if (Context->hdrs[Context->v2r[j]]->read || + Context->hdrs[Context->v2r[j]]->old) + mutt_set_flag (Context, Context->hdrs[Context->v2r[j]], M_NEW, 1); + else + mutt_set_flag (Context, Context->hdrs[Context->v2r[j]], M_READ, 1); + } + } + menu->redraw = REDRAW_STATUS | REDRAW_INDEX; + } + else + { + if (CURHDR->read || CURHDR->old) + mutt_set_flag (Context, CURHDR, M_NEW, 1); + else + mutt_set_flag (Context, CURHDR, M_READ, 1); + + if (option (OPTRESOLVE)) + { + if ((menu->current = ci_next_undeleted (menu->current)) == -1) + { + menu->current = menu->oldcurrent; + menu->redraw = REDRAW_CURRENT; + } + else + menu->redraw = REDRAW_MOTION_RESYNCH; + } + else + menu->redraw = REDRAW_CURRENT; + menu->redraw |= REDRAW_STATUS; + } + break; + + case OP_TOGGLE_WRITE: + + CHECK_MSGCOUNT; + if (mx_toggle_write (Context) == 0) + menu->redraw |= REDRAW_STATUS; + break; + + case OP_MAIN_NEXT_THREAD: + case OP_MAIN_NEXT_SUBTHREAD: + case OP_MAIN_PREV_THREAD: + case OP_MAIN_PREV_SUBTHREAD: + + CHECK_MSGCOUNT; + if (Context->msgcount != Context->vcount) + { + mutt_error ("No threads in limit mode."); + break; + } + + switch (op) + { + case OP_MAIN_NEXT_THREAD: + menu->current = mutt_next_thread (CURHDR); + break; + + case OP_MAIN_NEXT_SUBTHREAD: + menu->current = mutt_next_subthread (CURHDR); + break; + + case OP_MAIN_PREV_THREAD: + menu->current = mutt_previous_thread (CURHDR); + break; + + case OP_MAIN_PREV_SUBTHREAD: + menu->current = mutt_previous_subthread (CURHDR); + break; + } + + if (menu->current < 0) + { + menu->current = menu->oldcurrent; + if (op == OP_MAIN_NEXT_THREAD || op == OP_MAIN_NEXT_SUBTHREAD) + mutt_error ("No more threads."); + else + mutt_error ("You are on the first thread."); + } + else if (menu->menu == MENU_PAGER) + { + op = OP_DISPLAY_MESSAGE; + continue; + } + else + menu->redraw = REDRAW_MOTION; + break; + + case OP_MAIN_SET_FLAG: + case OP_MAIN_CLEAR_FLAG: + + CHECK_MSGCOUNT; + CHECK_READONLY; + if (mutt_change_flag (tag ? NULL : CURHDR, (op == OP_MAIN_SET_FLAG)) == 0) + { + menu->redraw = REDRAW_STATUS; + if (tag) + menu->redraw |= REDRAW_INDEX; + else if (option (OPTRESOLVE)) + { + if ((menu->current = ci_next_undeleted (menu->current)) == -1) + { + menu->current = menu->oldcurrent; + menu->redraw |= REDRAW_CURRENT; + } + else + menu->redraw |= REDRAW_MOTION_RESYNCH; + } + else + menu->redraw |= REDRAW_CURRENT; + } + break; + + /* -------------------------------------------------------------------- + * These functions are invoked directly from the internal-pager + */ + + case OP_BOUNCE_MESSAGE: + + CHECK_MSGCOUNT; + ci_bounce_message (tag ? NULL : CURHDR, &menu->redraw); + break; + + case OP_CREATE_ALIAS: + + mutt_create_alias (Context && Context->vcount ? CURHDR->env : NULL, NULL); + MAYBE_REDRAW (menu->redraw); + break; + + case OP_QUERY: + mutt_query_menu (NULL, 0); + MAYBE_REDRAW (menu->redraw); + break; + + case OP_DELETE: + + CHECK_MSGCOUNT; + CHECK_READONLY; + if (tag) + { + mutt_tag_set_flag (M_DELETE, 1); + menu->redraw = REDRAW_INDEX; + } + else + { + mutt_set_flag (Context, CURHDR, M_DELETE, 1); + if (option (OPTRESOLVE)) + { + if ((menu->current = ci_next_undeleted (menu->current)) == -1) + { + menu->current = menu->oldcurrent; + menu->redraw = REDRAW_CURRENT; + } + else if (menu->menu == MENU_PAGER) + { + op = OP_DISPLAY_MESSAGE; + continue; + } + else + menu->redraw |= REDRAW_MOTION_RESYNCH; + } + else + menu->redraw = REDRAW_CURRENT; + } + menu->redraw |= REDRAW_STATUS; + break; + + case OP_DELETE_THREAD: + case OP_DELETE_SUBTHREAD: + + CHECK_MSGCOUNT; + CHECK_READONLY; + + rc = mutt_thread_set_flag (CURHDR, M_DELETE, 1, + op == OP_DELETE_THREAD ? 0 : 1); + + if (rc != -1) + { + if (option (OPTRESOLVE)) + if ((menu->current = ci_next_undeleted (menu->current)) == -1) + menu->current = menu->oldcurrent; + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + } + break; + + case OP_DISPLAY_ADDRESS: + + CHECK_MSGCOUNT; + mutt_display_address (CURHDR->env->from); + break; + + case OP_ENTER_COMMAND: + + mutt_enter_command (); + mutt_check_rescore (Context); + if (option (OPTNEEDRESORT) && Context && Context->msgcount) + { + menu->oldcurrent = CURHDR->index; + mutt_sort_headers (Context, 0); + + /* try to restore the current message */ + for (i = 0; i < Context->vcount; i++) + { + if (Context->hdrs[Context->v2r[i]]->index == menu->oldcurrent) + menu->current = i; + } + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + } + if (option (OPTFORCEREDRAWINDEX)) + menu->redraw = REDRAW_FULL; + unset_option (OPTFORCEREDRAWINDEX); + unset_option (OPTFORCEREDRAWPAGER); + break; + + case OP_FORWARD_MESSAGE: + + CHECK_MSGCOUNT; + ci_send_message (SENDFORWARD, NULL, NULL, Context, tag ? NULL : CURHDR); + menu->redraw = REDRAW_FULL; + break; + + + +#ifdef _PGPPATH + case OP_FORGET_PASSPHRASE: + + mutt_forget_passphrase (); + break; +#endif /* _PGPPATH */ + + + + case OP_GROUP_REPLY: + + CHECK_MSGCOUNT; + ci_send_message (SENDREPLY|SENDGROUPREPLY, NULL, NULL, Context, tag ? NULL : CURHDR); + menu->redraw = REDRAW_FULL; + break; + + case OP_LIST_REPLY: + + CHECK_MSGCOUNT; + ci_send_message (SENDREPLY|SENDLISTREPLY, NULL, NULL, Context, tag ? NULL : CURHDR); + menu->redraw = REDRAW_FULL; + break; + + case OP_MAIL: + + ci_send_message (0, NULL, NULL, NULL, NULL); + menu->redraw = REDRAW_FULL; + break; + + + + + + + +#ifdef _PGPPATH + case OP_MAIL_KEY: + + ci_send_message (SENDKEY, NULL, NULL, NULL, NULL); + menu->redraw = REDRAW_FULL; + break; + + case OP_EXTRACT_KEYS: + + CHECK_MSGCOUNT; + pgp_extract_keys_from_messages(tag ? NULL : CURHDR); + menu->redraw = REDRAW_FULL; + break; + +#endif /* _PGPPATH */ + + + + + + + + case OP_PIPE: + + CHECK_MSGCOUNT; + mutt_pipe_message (tag ? NULL : CURHDR); + break; + + case OP_PRINT: + + CHECK_MSGCOUNT; + mutt_print_message (tag ? NULL : CURHDR); + break; + + case OP_MAIN_READ_THREAD: + case OP_MAIN_READ_SUBTHREAD: + + CHECK_MSGCOUNT; + CHECK_READONLY; + + rc = mutt_thread_set_flag (CURHDR, M_READ, 1, + op == OP_MAIN_READ_THREAD ? 0 : 1); + + if (rc != -1) + { + if (option (OPTRESOLVE)) + { + if ((menu->oldcurrent = ci_next_undeleted (menu->current)) == -1) + menu->oldcurrent = menu->current; + } + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + } + break; + + case OP_RECALL_MESSAGE: + + ci_send_message (SENDPOSTPONED, NULL, NULL, Context, NULL); + menu->redraw = REDRAW_FULL; + break; + + case OP_REPLY: + + CHECK_MSGCOUNT; + ci_send_message (SENDREPLY, NULL, NULL, Context, tag ? NULL : CURHDR); + menu->redraw = REDRAW_FULL; + break; + + case OP_SHELL_ESCAPE: + + mutt_shell_escape (); + MAYBE_REDRAW (menu->redraw); + break; + + case OP_TAG_THREAD: + case OP_TAG_SUBTHREAD: + + CHECK_MSGCOUNT; + rc = mutt_thread_set_flag (CURHDR, M_TAG, !CURHDR->tagged, + op == OP_TAG_THREAD ? 0 : 1); + + if (rc != -1) + { + if (option (OPTRESOLVE)) + { + menu->current = mutt_next_thread (CURHDR); + + if (menu->current == -1) + menu->current = menu->oldcurrent; + } + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + } + break; + + case OP_UNDELETE: + + CHECK_MSGCOUNT; + CHECK_READONLY; + if (tag) + { + mutt_tag_set_flag (M_DELETE, 0); + menu->redraw = REDRAW_INDEX; + } + else + { + mutt_set_flag (Context, CURHDR, M_DELETE, 0); + if (option (OPTRESOLVE) && menu->current < Context->vcount - 1) + { + menu->current++; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + else + menu->redraw = REDRAW_CURRENT; + } + menu->redraw |= REDRAW_STATUS; + break; + + case OP_UNDELETE_THREAD: + case OP_UNDELETE_SUBTHREAD: + + CHECK_MSGCOUNT; + CHECK_READONLY; + + rc = mutt_thread_set_flag (CURHDR, M_DELETE, 0, + op == OP_UNDELETE_THREAD ? 0 : 1); + + if (rc != -1) + { + if (option (OPTRESOLVE)) + { + if (op == OP_UNDELETE_THREAD) + menu->current = mutt_next_thread (CURHDR); + else + menu->current = mutt_next_subthread (CURHDR); + + if (menu->current == -1) + menu->current = menu->oldcurrent; + } + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + } + break; + + case OP_VERSION: + mutt_version (); + break; + + case OP_VIEW_ATTACHMENTS: + CHECK_MSGCOUNT; + mutt_view_attachments (CURHDR); + if (CURHDR->attach_del) + Context->changed = 1; + menu->redraw = REDRAW_FULL; + break; + + default: + if (menu->menu == MENU_MAIN) + km_error_key (MENU_MAIN); + } + + if (menu->menu == MENU_PAGER) + { + menu->menu = MENU_MAIN; + menu->redraw = REDRAW_FULL; + set_option (OPTWEED); /* turn header weeding back on. */ + } + + if (done) break; + } + + mutt_menuDestroy (&menu); +} + +void mutt_set_header_color (CONTEXT *ctx, HEADER *curhdr) +{ + COLOR_LINE *color; + + if (!curhdr) + return; + + for (color = ColorIndexList; color; color = color->next) + if (mutt_pattern_exec (color->color_pattern, M_MATCH_FULL_ADDRESS, ctx, curhdr)) + { + curhdr->pair = color->pair; + return; + } + curhdr->pair = ColorDefs[MT_COLOR_NORMAL]; +} + +void mutt_cache_index_colors (CONTEXT *ctx) +{ + int i; + + if (ctx) + for (i = 0; i < ctx->msgcount; i++) + mutt_set_header_color (ctx, ctx->hdrs[i]); +} diff --git a/date.c b/date.c new file mode 100644 index 00000000..9cd20897 --- /dev/null +++ b/date.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" + +/* returns the seconds west of UTC given `g' and its corresponding gmtime() + representation */ +static time_t mutt_compute_tz (time_t g, struct tm *utc) +{ + struct tm *lt = localtime (&g); + time_t t; + + t = (((utc->tm_hour - lt->tm_hour) * 60) + (utc->tm_min - lt->tm_min)) * 60; + switch (utc->tm_yday - lt->tm_yday) + { + case 0: + break; + + case 1: + case -364: + case -365: + t += 24 * 60 * 60; + break; + + case -1: + case 364: + case 365: + t -= 24 * 60 * 60; + break; + + default: + mutt_error ("Please report this program error in the function mutt_mktime."); + } + + return t; +} + +time_t mutt_local_tz (void) +{ + struct tm *ptm; + struct tm utc; + time_t now; + + now = time (NULL); + ptm = gmtime (&now); + /* need to make a copy because gmtime/localtime return a pointer to + static memory (grr!) */ + memcpy (&utc, ptm, sizeof (utc)); + return (mutt_compute_tz (now, &utc)); +} + +/* converts struct tm to time_t, but does not take the local timezone into + account unless ``local'' is nonzero */ +time_t mutt_mktime (struct tm *t, int local) +{ + time_t g; + + static int AccumDaysPerMonth[12] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 + }; + + /* Compute the number of days since January 1 in the same year */ + g = AccumDaysPerMonth [t->tm_mon % 12]; + + /* The leap years are 1972 and every 4. year until 2096, + * but this algoritm will fail after year 2099 */ + g += t->tm_mday; + if ((t->tm_year % 4) || t->tm_mon < 2) + g--; + t->tm_yday = g; + + /* Compute the number of days since January 1, 1970 */ + g += (t->tm_year - 70) * 365; + g += (t->tm_year - 69) / 4; + + /* Compute the number of hours */ + g *= 24; + g += t->tm_hour; + + /* Compute the number of minutes */ + g *= 60; + g += t->tm_min; + + /* Compute the number of seconds */ + g *= 60; + g += t->tm_sec; + + if (local) + g += mutt_compute_tz (g, t); + + return (g); +} + +void mutt_normalize_time (struct tm *tm) +{ + static char DaysPerMonth[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + + while (tm->tm_sec < 0) + { + tm->tm_sec += 60; + tm->tm_min--; + } + while (tm->tm_min < 0) + { + tm->tm_min += 60; + tm->tm_hour--; + } + while (tm->tm_hour < 0) + { + tm->tm_hour += 24; + tm->tm_mday--; + } + while (tm->tm_mon < 0) + { + tm->tm_mon += 12; + tm->tm_year--; + } + while (tm->tm_mday < 0) + { + if (tm->tm_mon) + tm->tm_mon--; + else + { + tm->tm_mon = 11; + tm->tm_year--; + } + tm->tm_mday += DaysPerMonth[tm->tm_mon]; + } +} diff --git a/depend.awk b/depend.awk new file mode 100644 index 00000000..89385212 --- /dev/null +++ b/depend.awk @@ -0,0 +1,2 @@ +$0 ~ /^# DO NOT REMOVE THIS LINE/ { exit } +{ print } diff --git a/doc/language.txt b/doc/language.txt new file mode 100644 index 00000000..4e9032fc --- /dev/null +++ b/doc/language.txt @@ -0,0 +1,3258 @@ +# Translation file for PGP 2.6.3(i)n. + +# ------------------------------------------------------------------ +# Character set: ISO-Latin/1 (ISO 8859/1) +# Date revised: 7 October 1997 +# ------------------------------------------------------------------ +# Language: German/Deutsch (de) +# Translator: Frank Pruefer +# (based on the German translation for PGP 2.3a by +# Marc Aurel <4-tea-2@bong.saar.de>) +# ------------------------------------------------------------------ +# Language: Spanish/Español (es) +# Translator: Armando Ramos +# ------------------------------------------------------------------ +# Language: French/Francais (fr) +# Translator: Yanik Crépeau +# (based on the French translation for PGP 2.3a by +# Jean-loup Gailly ) +# ------------------------------------------------------------------ +# +# Additional language files may be obtained from: +# +# http://www.ifi.uio.no/pgp/modules.shtml +# ftp://ftp.ifi.uio.no/pub/pgp/lang/ +# +# ------------------------------------------------------------------ + +"\nClear signature file: %s\n" +de: "\nDateiname der Klartext-Unterschrift: %s\n" +es: "\nFichero normal con firma: %s\n" +fr: "\nFichier de signature en clair: %s\n" +muttde: "\nDateiname der Klartext-Unterschrift: %s\n" + +"\nTransport armor file: %s\n" +de: "\nDateiname der Versandhülle: %s\n" +es: "\nFichero con armadura: %s\n" +fr: "\nFichier de transport armure: %s\n" +muttde: "\nDateiname der Versandhülle: %s\n" + +"\nTransport armor files: " +de: "\nDateinamen der Versandhüllen: " +es: "\nFicheros con armadura: " +fr: "\nFichiers de transport armure: " +muttde: "\nDateinamen der Versandhüllen: " + +"Invalid ASCII armor header line: \"%.40s\"\n\ +ASCII armor corrupted.\n" +de: "\nUnzulässige Kopfzeile \"%.40s\"\n\ +in der ASCII-Versandhülle. Die Versandhülle ist deshalb ungültig.\n" +es: "Línea incorrecta en la cabecera de la armadura ASCII:\n\ +\"%.40s\"\n\ +Armadura dañada\n" +fr: "Entête enveloppe ASCII invalide: \"%.40s\"\n\ +l'enveloppe ASCII est corrompue" +muttde: "\nUnzulässige Kopfzeile \"%.40s\"\n\ +in der ASCII-Versandhülle. Die Versandhülle ist deshalb ungültig.\n" + +"Warning: Unrecognized ASCII armor header label \"%.*s:\" ignored.\n" +de: "\nWARNUNG: Der unbekannte Bezeichner \"%.*s:\"\n\ +in der ASCII-Versandhülle wurde überlesen.\n" +es: "Advertencia: Se ignora la etiqueta \"%.*s:\"\n\ +No se reconoce como cabecera de armadura ASCII\n" +fr: "Avertissement: Type d'entête de l'envloppe ASCII \"%.*s:\" ignoré\n" +muttde: "\nWARNUNG: Der unbekannte Bezeichner \"%.*s:\"\n\ +in der ASCII-Versandhülle wurde überlesen.\n" + +"ERROR: Bad ASCII armor checksum in section %d.\n" +de: "\nFEHLER: Falsche Prüfsumme im Abschnitt %d der Versandhülle.\n" +es: "ERROR: Suma incorrecta de comprobación en armadura ASCII,\n\ +sección %d.\n" +fr: "ERREUR: mauvaise vérification de l'armure ASCII dans la section %d.\n" +muttde: "\nFEHLER: Falsche Prüfsumme im Abschnitt %d der Versandhülle.\n" + +"Can't find section %d.\n" +de: "\nAbschnitt %d nicht gefunden.\n" +es: "No se encuentra la sección %d.\n" +fr: "Section %d introuvable.\n" +muttde: "\nAbschnitt %d nicht gefunden.\n" + +"Badly formed section delimiter, part %d.\n" +de: "\nFehlerhafter Abschnitts-Begrenzer im Teil %d.\n" +es: "Delimitador de sección mal formado, parte %d.\n" +fr: "Séparateurs de section mal formés" +muttde: "\nFehlerhafter Abschnitts-Begrenzer im Teil %d.\n" + +"Sections out of order, expected part %d" +de: "\nAbschnitte in falscher Reihenfolge.\nErwartet wurde Teil %d" +es: "Las secciones están desordenadas: se esperaba la parte %d" +fr: "Sections en désordre, partie %d attendue" +muttde: "\nAbschnitte in falscher Reihenfolge.\nErwartet wurde Teil %d" + +", got part %d\n" +de: ", angekommen ist Teil %d.\n" +es: ", se encuentra la parte %d\n" +fr: ", partie %d obtenue\n" +muttde: ", angekommen ist Teil %d.\n" + +"ERROR: Hit EOF in header of section %d.\n" +de: "\nFEHLER: EOF (Dateiende) im Header von Abschnitt %d.\n" +es: "ERROR: Hay un EOF (fin de fichero) en la cabecera de\ +la sección %d.\n" +fr: "ERREUR: find de fichier dans l'en-tête de la section %d.\n" +muttde: "\nFEHLER: EOF (Dateiende) im Header von Abschnitt %d.\n" + +"ERROR: Badly formed ASCII armor checksum, line %d.\n" +de: "\nFEHLER: Falsche Prüfsumme in Zeile %d der Versandhülle.\n" +es: "ERROR: Suma de comprobación mal construida en la armadura ASCII,\n\ +línea %d.\n" +fr: "ERREUR: Verification de l'armure ASCII mal formée, ligne %d.\n" +muttde: "\nFEHLER: Falsche Prüfsumme in Zeile %d der Versandhülle.\n" + +"WARNING: No ASCII armor `END' line.\n" +de: "\nWARNUNG: Keine 'END'-Zeile in der Versandhülle.\n" +es: "ADVERTENCIA: No hay línea `END' en la armadura ASCII.\n" +fr: "ATTENTION: Pas de ligne `END' dans l'armure ASCII.\n" +muttde: "\nWARNUNG: Keine 'END'-Zeile in der Versandhülle.\n" + +"ERROR: Bad ASCII armor character, line %d.\n" +de: "\nFEHLER: Unerlaubtes Zeichen in Zeile %d der Versandhülle.\n" +es: "ERROR: Carácter incorrecto en la armadura ASCII, línea %d.\n" +fr: "ERREUR: Mauvais charactère dans l'armure ASCII, ligne %d.\n" +muttde: "\nFEHLER: Unerlaubtes Zeichen in Zeile %d der Versandhülle.\n" + +"ERROR: Bad ASCII armor line length %d on line %d.\n" +de: "\nFEHLER: Falsche Zeilenlänge (%d) in Zeile %d der Versandhülle.\n" +es: "ERROR: Longitud incorrecta (%d) de línea en la armadura ASCII,\n\ +línea %d.\n" +fr: "ERREUR: Mauvais longueur de ligne %d dans l'armure ASCII, ligne %d.\n" +muttde: "\nFEHLER: Falsche Zeilenlänge (%d) in Zeile %d der Versandhülle.\n" + +"ERROR: Bad ASCII armor checksum" +de: "\nFEHLER: Falsche Prüfsumme der Versandhülle" +es: "ERROR: Suma incorrecta de comprobación en la armadura ASCII" +fr: "ERREUR de vérification dans l'armure ASCII" +muttde: "\nFEHLER: Falsche Prüfsumme der Versandhülle" + +" in section %d" +de: " im Abschnitt %d.\n" +es: " en la sección %d" +fr: " dans la section %d" +muttde: " im Abschnitt %d.\n" + +"Warning: Transport armor lacks a checksum.\n" +de: "\nWARNUNG: Die Prüfsumme der Versandhülle fehlt.\n" +es: "Advertencia: La armadura de transporte no lleva suma de\ +comprobación.\n" +fr: "Attention: l'armure de transport n'a pas de vérification.\n" +muttde: "\nWARNUNG: Die Prüfsumme der Versandhülle fehlt.\n" + +"ERROR: Can't find file %s\n" +de: "\nFEHLER: Datei '%s' nicht gefunden.\n" +es: "ERROR: No se encuentra el fichero %s\n" +fr: "ERREUR: Fichier %s introuvable\n" +muttde: "\nFEHLER: Datei '%s' nicht gefunden.\n" + +"ERROR: No ASCII armor `BEGIN' line!\n" +de: "\nFEHLER: Keine 'BEGIN'-Zeile in der Versandhülle!\n" +es: "ERROR: No hay línea 'BEGIN' en la armadura ASCII\n" +fr: "ERREUR: Pas de ligne `BEGIN' dans l'armure ASCII!\n" +muttde: "\nFEHLER: Keine 'BEGIN'-Zeile in der Versandhülle!\n" + +"ERROR: ASCII armor decode input ended unexpectedly!\n" +de: "\nFEHLER: Vorzeitiges Ende der Versandhülle!\n" +es: "ERROR: La entrada con armadura ASCII termina antes de tiempo\n" +fr: "ERREUR: fin prématurée du fichier armure ASCII!\n" +muttde: "\nFEHLER: Vorzeitiges Ende der Versandhülle!\n" + +"ERROR: Header line added to ASCII armor: \"%s\"\n\ +ASCII armor corrupted.\n" +de: "\nFEHLER: Eine Kopfzeile \"%s\" ist\n\ +in der ASCII-Versandhülle enthalten. Die Versandhülle ist deshalb ungültig.\n" +es: "ERROR: Línea de cabecera añadida a la armadura ASCII:\n\ +\"%s\" Armadura dañada\n" +fr: "Ligne d'entête ajoutée à l'enveloppe ASCII: \"%s\"\n\ +enveloppe ASCII corrompue" +muttde: "\nFEHLER: Eine Kopfzeile \"%s\" ist\n\ +in der ASCII-Versandhülle enthalten. Die Versandhülle ist deshalb ungültig.\n" + +"\n\007Unable to write ciphertext output file '%s'.\n" +de: "\n\007FEHLER beim Schreiben der verschlüsselten\nAusgabedatei '%s'.\n" +es: "\n\007No puede escribirse el fichero de salida cifrado '%s'.\n" +fr: "\n\007Ecriture impossible dans le fichier de sortie chiffré '%s'.\n" +mutt: "\nUnable to write ciphertext output file '%s'.\n" +muttde: "\nFEHLER beim Schreiben der verschlüsselten\nAusgabedatei '%s'.\n" + +"ERROR: Hit EOF in header.\n" +de: "\nFEHLER: EOF (Dateiende) im Header.\n" +es: "ERROR: Hay un EOF (fin de fichero) en la cabecera.\n" +fr: "ERREUR: fin de fichier dans l'en-tête.\n" +muttde: "\nFEHLER: EOF (Dateiende) im Header.\n" + +"Unsupported character set: '%s'\n" +de: "\nKeine Unterstützung für Zeichensatz '%s'.\n" +es: "Conjunto de caracteres no admitido: '%s'\n" +fr: "Table de caractères non supportée: '%s'\n" +muttde: "\nKeine Unterstützung für Zeichensatz '%s'.\n" + +"The legal_kludge cannot be disabled in US version.\n" +de: "LEGAL_KLUDGE kann in der USA-Version nicht abgeschaltet werden!\n" +es: "'legal_kludge' no puede desactivarse en la versión para los EE.UU.\n" +fr: "Les embarras légaux ne peuvent pas être désactivés aux Etats-Unis.\n" +muttde: "LEGAL_KLUDGE kann in der USA-Version nicht abgeschaltet werden!\n" + +"The multiple_recipients flag is unnecessary in this \ +version of MacPGP.\ +\nPlease remove this entry from your configuration file.\n" +de: "Die Kennung \"multiple_recipients\" ist in dieser Version von MacPGP nicht\ +\nnötig. Bitte entferne diesen Eintrag aus Deiner Konfigurationsdatei.\n" +es: "No se necesita la bandera 'multiple_recipients' en esta versión\n\ +de MacPGP.\ +\nElimina esa entrada del fichero de configuración.\n" +fr: "L'indicateur de destinataires multiples n'est pas nécessaire dans \ +version de MacPGP. \ +\nS.V.P. supprimez cette entrée de votre fichier de configuration. \n" +muttde: "Die Kennung \"multiple_recipients\" ist in dieser Version von MacPGP nicht\ +\nnötig. Bitte entferne diesen Eintrag aus Deiner Konfigurationsdatei.\n" + +"\007\nWARNING: This key has been revoked by its owner,\n\ +possibly because the secret key was compromised.\n" +de: "\007\nWARNUNG: Dieser Schlüssel wurde von seinem Besitzer zurückgezogen,\n\ +möglicherweise, weil sein privater Schlüssel nicht mehr sicher ist.\n" +es: "\007\nADVERTENCIA: Esta clave ha sido revocada por su propietario;\n\ +es posible que la clave secreta se haya visto comprometida.\n" +fr: "\007\nATTENTION: cette clé a été révoquée par son propriétaire,\n\ +probablement parce que la clé secrète a été compromise.\n" +mutt: "\nWARNING: This key has been revoked by its owner,\n\ +possibly because the secret key was compromised.\n" +muttde: "\nWARNUNG: Dieser Schlüssel wurde von seinem Besitzer zurückgezogen,\n\ +möglicherweise, weil sein privater Schlüssel nicht mehr sicher ist.\n" + +"This could mean that this signature is a forgery.\n" +de: "Dies könnte bedeuten, daß diese Unterschrift eine Fälschung ist.\n" +es: "Puede significar que la firma está falsificada.\n" +fr: "Ceci peut signifier que cette signature est un faux.\n" +muttde: "Dies könnte bedeuten, daß diese Unterschrift eine Fälschung ist.\n" + +"You cannot use this revoked key.\n" +de: "Du kannst diesen Schlüssel nicht benutzen, weil er zurückgezogen wurde.\n" +es: "No puedes utilizar esta clave revocada.\n" +fr: "Vous ne pouvez pas utiliser cette clé révoquée.\n" +muttde: "Du kannst diesen Schlüssel nicht benutzen, weil er zurückgezogen wurde.\n" + +"\007\nWARNING: Because this public key is not certified with \ +a trusted\nsignature, it is not known with high confidence that this \ +public key\nactually belongs to: \"%s\".\n" +de: "\007\nWARNUNG: Da dieser öffentliche Schlüssel nicht mit einer vertrauenswürdigen\n\ +Unterschrift beglaubigt ist, ist nicht sicher, daß er wirklich zu\n\"%s\" gehört.\n" +es: "\nAVISO: Esta clave pública no está certificada con una firma\ +de confianza,\n\ +por lo que no se sabe con seguridad si realmente pertenece a:\n\ + \"%s\".\n" +fr: "\007\nATTENTION: Cette clé publique n'est pas certifiée avec une\n\ +signature fiable. Il n'est donc pas reconnu avec un haut degré de confiance\n\ +que cette signature appartient effectivement à: \"%s\".\n" +mutt: "\nWARNING: Because this public key is not certified with \ +a trusted\nsignature, it is not known with high confidence that this \ +public key\nactually belongs to: \"%s\".\n" +muttde: "\nWARNUNG: Da dieser öffentliche Schlüssel nicht mit einer vertrauenswürdigen\n\ +Unterschrift beglaubigt ist, ist nicht sicher, daß er wirklich zu\n\"%s\" gehört.\n" + +"\007\nWARNING: This public key is not trusted to actually belong \ +to:\n\"%s\".\n" +de: "\007\nWARNUNG: Es ist nicht sicher, daß dieser öffentliche Schlüssel wirklich\n\ +zu \"%s\" gehört.\n" +es: "\nADVERTENCIA: No se sabe con seguridad si esta clave pública\n\ +pertenece realmente a: \"%s\".\n" +fr: "\007\nATTENTION: Cette clé publique n'est pas reconnue comme\n\ +appartenant à: \"%s\".\n" +mutt: "\nWARNING: This public key is not trusted to actually belong \ +to:\n\"%s\".\n" +muttde: "\nWARNUNG: Es ist nicht sicher, daß dieser öffentliche Schlüssel wirklich\n\ +zu \"%s\" gehört.\n" + +"\007\nWARNING: Because this public key is not certified with enough \ +trusted\nsignatures, it is not known with high confidence that this \ +public key\nactually belongs to: \"%s\".\n" +de: "\007\nWARNUNG: Da dieser öffentliche Schlüssel nicht mit einer ausreichenden\n\ +Anzahl vertrauenswürdiger Unterschriften beglaubigt ist, ist nicht sicher,\n\ +daß er wirklich zu \"%s\" gehört.\n" +es: "\nADVERTENCIA: Como esta clave no está certificada con suficientes\n\ +firmas fiables, no se sabe con seguridad si realmente pertenece a:\n\ + \"%s\".\n" +fr: "\007\nATTENTION: puisque cette clé publique n'est pas certifiée avec\n\ +suffisament de signatures, il n'est pas connu avec un haut niveau de confiance\ +\nque cette clé appartient effectivement à: \"%s\".\n" +mutt: "\nWARNING: Because this public key is not certified with enough \ +trusted\nsignatures, it is not known with high confidence that this \ +public key\nactually belongs to: \"%s\".\n" +muttde: "\nWARNUNG: Da dieser öffentliche Schlüssel nicht mit einer ausreichenden\n\ +Anzahl vertrauenswürdiger Unterschriften beglaubigt ist, ist nicht sicher,\n\ +daß er wirklich zu \"%s\" gehört.\n" + +"But you previously approved using this public key anyway.\n" +de: "Aber Du hast diesen Schlüssel trotzdem bereits benutzt...\n" +es: "Ya has permitido antes que se utilice esta clave p\372blica.\n" +fr: "Mais vous avez déjà accepté l'usage de cette clé publique.\n" +muttde: "Aber Du hast diesen Schlüssel trotzdem bereits benutzt...\n" + +"\nAre you sure you want to use this public key (y/N)? " +de: "\nBist Du sicher, daß Du diesen Schlüssel benutzen willst? (j/N) " +es: "\n¿Estás seguro de querer utilizar esta clave pública (s/N)? " +fr: "\nEtes vous sûr(e) de vouloir utiliser cette clé publique (o/N)? " +muttde: "\nBist Du sicher, daß Du diesen Schlüssel benutzen willst? (j/N) " + +"\n\007Unsupported packet format - you need a newer version of PGP \ +for this file.\n" +de: "\n\007WARNUNG: nicht unterstütztes Datenformat!\n\ +Du brauchst eine neuere PGP-Version für diese Datei.\n" +es: "\n\007Formato desconocido -\ +se necesita una versión más reciente de PGP" +fr: "\n\007Format non-supporté - vous devez utiliser un version nouvelle de PGP +pour ce fichier.\n" +mutt: "\nUnsupported packet format - you need a newer version of PGP \ +for this file.\n" +muttde: "\nWARNUNG: nicht unterstütztes Datenformat!\n\ +Du brauchst eine neuere PGP-Version für diese Datei.\n" + +"Preparing random session key..." +de: "\nVorbereitung des zufälligen IDEA-Schlüssels..." +es: "Preparando la clave aleatoria de la sesión..." +fr: "Préparation de la clé aléatoire..." +muttde: "\nVorbereitung des zufälligen IDEA-Schlüssels..." + +"\n\007Error: System clock/calendar is set wrong.\n" +de: "\n\007FEHLER: Die System-Zeit und/oder das System-Datum sind falsch.\n" +es: "\n\007Error: El reloj/calendario del sistema está equivocado.\n" +fr: "\n\007Erreur: L'horloge du système est incorrecte.\n" +mutt: "\nError: System clock/calendar is set wrong.\n" +muttde: "\nFEHLER: Die System-Zeit und/oder das System-Datum sind falsch.\n" + +"Just a moment..." +de: "\nEinen Augenblick, bitte..." +es: "Un momento..." +fr: "Un moment..." +muttde: "\nEinen Augenblick, bitte..." + +"\n\007Can't open input plaintext file '%s'\n" +de: "\n\007FEHLER beim Öffnen der Eingabedatei '%s'.\n" +es: "\n\007No puede abrirse el fichero normal de entrada '%s'\n" +fr: "\n\007Ouverture du fichier en clair '%s' impossible.\n" +mutt: "\nCan't open input plaintext file '%s'\n" +muttde: "\nFEHLER beim Öffnen der Eingabedatei '%s'.\n" + +"\n\007Can't open plaintext file '%s'\n" +de: "\n\007FEHLER beim Öffnen der Klartextdatei '%s'.\n" +es: "\n\007No puede abrirse el fichero normal '%s'\n" +fr: "\n\007Ouverture du fichier en clair '%s' impossible\n" +mutt: "\nCan't open plaintext file '%s'\n" +muttde: "\nFEHLER beim Öffnen der Klartextdatei '%s'.\n" + +"\n\007Can't create signature file '%s'\n" +de: "\n\007FEHLER beim Erzeugen der Unterschriftsdatei '%s'.\n" +es: "\n\007No puede crearse el fichero de firma '%s'\n" +fr: "\n\007Création du fichier de signature '%s' impossible\n" +mutt: "\nCan't create signature file '%s'\n" +muttde: "\nFEHLER beim Erzeugen der Unterschriftsdatei '%s'.\n" + +"\n\007Can't open key ring file '%s'\n" +de: "\n\007FEHLER beim Öffnen des Schlüsselbunds '%s'.\n" +es: "\n\007No puede abrirse el anillo de claves '%s'\n" +fr: "\n\007Ouverture du fichier de clé '%s' impossible\n" +mutt: "\nCan't open key ring file '%s'\n" +muttde: "\nFEHLER beim Öffnen des Schlüsselbunds '%s'.\n" + +"This key has already been revoked.\n" +de: "Dieser Schlüssel wurde bereits zurückgezogen.\n" +es: "Esta clave ya se había revocado.\n" +fr: "Cette clé a déjà été révoquée.\n" +muttde: "Dieser Schlüssel wurde bereits zurückgezogen.\n" + +"\n\007Can't create output file to update key ring.\n" +de: "\n\007Dateifehler bei der Aktualisierung des Schlüsselbunds.\n" +es: "\n\007No puede crearse el fichero de salida para actualizar\ +el anillo.\n" +fr: "\n\007Impossible de créer le fichier de sortie pour modifier le\ +\nfichier de clés\n" +mutt: "\nCan't create output file to update key ring.\n" +muttde: "\nDateifehler bei der Aktualisierung des Schlüsselbunds.\n" + +"\nKey compromise certificate created.\n" +de: "\nDie Urkunde zum Zurückziehen des Schlüssels wurde erzeugt.\n" +es: "\nCreado el certificado de compromiso de clave.\n" +fr: "\nCertificat de compromission de clé créé.\n" +muttde: "\nDie Urkunde zum Zurückziehen des Schlüssels wurde erzeugt.\n" + +"\n\007Key is already signed by user '%s'.\n" +de: "\n\007Der Schlüssel wurde von \"%s\"\nbereits unterschrieben.\n" +es: "\n\007La clave ya ha sido firmada por '%s'.\n" +fr: "\n\007La clé est déjà signée par l'utilisateur '%s'.\n" +mutt: "\nKey is already signed by user '%s'.\n" +muttde: "\nDer Schlüssel wurde von \"%s\"\nbereits unterschrieben.\n" + +"\n\nREAD CAREFULLY: Based on your own direct first-hand knowledge, \ +are\nyou absolutely certain that you are prepared to solemnly certify \ +that\nthe above public key actually belongs to the user specified by \ +the\nabove user ID (y/N)? " +de: "\nSORGFÄLTIG LESEN: Bist Du, gestützt auf eigenes, direktes Wissen aus\n\ +erster Hand, absolut sicher, daß Du zuverlässig beglaubigen kannst, daß der\n\ +oben angezeigte öffentliche Schlüssel wirklich zu der oben genannten Person\n\ +gehört? (j/N) " +es: "\n\nLEE ATENTAMENTE: Según tu conocimiento directo,\n\ +¿estás absolutamente seguro de poder certificar solemnemente que la\n\ +clave pública pertenece realmente al usuario especificado por\n\ +este identificador (s/N)? " +fr: "\n\nLIRE ATTENTIVEMENT: Selon votre propre connaissance directe,\n\ +êtes vous absoluement certain(e) d'être prêt(e) à certifier\n\ +solennellement que la clé publique ci-dessus appartient effectivement à\n\ +la personne spécifiée par le nom d'utilisateur ci-dessus (o/N)? " +muttde: "\nSORGFÄLTIG LESEN: Bist Du, gestützt auf eigenes, direktes Wissen aus\n\ +erster Hand, absolut sicher, daß Du zuverlässig beglaubigen kannst, daß der\n\ +oben angezeigte öffentliche Schlüssel wirklich zu der oben genannten Person\n\ +gehört? (j/N) " + +"\nKey signature certificate added.\n" +de: "\n\nDer Schlüssel wurde mit Deiner Unterschrift beglaubigt.\n" +es: "\nSe ha añadido el certificado de firma.\n" +fr: "\nCertificat de signature de clé ajouté.\n" +muttde: "\n\nDer Schlüssel wurde mit Deiner Unterschrift beglaubigt.\n" + +"\nError: Key for signing userid '%s'\n\ +does not appear in public keyring '%s'.\n\ +Thus, a signature made with this key cannot be checked on this keyring.\n" +de: "\nFEHLER: Der Schlüssel für eine Unterschrift unter die Benutzer-ID\n\ +\"%s\" ist nicht im öffentlichen\n\ +Schlüsselbund '%s' enthalten. Deshalb ist eine mit diesem\n\ +Schlüssel erzeugte Unterschrift mit diesem Schlüsselbund nicht überprüfbar.\n" +es: "\nError: La clave del firmante '%s'\n\ +no se encuentra en el anillo '%s'.\n\ +No puede comprobarse la firma realizada con esa clave.\n" +fr: "\nErreur: La clef du signataire id '%s'\n\ +est absente du fichier de clefs publiques '%s'\n\ +en consequence, une signature faite avec cette clef ne peut etre verifiee.\n" +muttde: "\nFEHLER: Der Schlüssel für eine Unterschrift unter die Benutzer-ID\n\ +\"%s\" ist nicht im öffentlichen\n\ +Schlüsselbund '%s' enthalten. Deshalb ist eine mit diesem\n\ +Schlüssel erzeugte Unterschrift mit diesem Schlüsselbund nicht überprüfbar.\n" + +"\nLooking for key for user '%s':\n" +de: "\nSuche den Schlüssel für \"%s\":\n" +es: "\nBuscando la clave del usuario '%s':\n" +fr: "\nRecherche de la clé pour l'utilisateur '%s':\n" +muttde: "\nSuche den Schlüssel für \"%s\":\n" + +"\n\007Can't open ciphertext file '%s'\n" +de: "\n\007FEHLER beim Öffnen der verschlüsselten Datei '%s'.\n" +es: "\n\007No puede abrirse el fichero cifrado '%s'\n" +fr: "\n\007Ouverture du fichier chiffré '%s' impossible\n" +mutt: "\nCan't open ciphertext file '%s'\n" +muttde: "\nFEHLER beim Öffnen der verschlüsselten Datei '%s'.\n" + +"\nFile '%s' has signature, but with no text." +de: "\nDie Datei '%s' enthält eine Unterschrift, aber keinen Text." +es: "\nEl fichero '%s' tiene firma, pero no texto." +fr: "\nLe fichier '%s' à une signature, mais pas de texte." +mutt: " " +muttde: " " + +"\nText is assumed to be in file '%s'.\n" +de: "\nDer Text könnte sich in der Datei '%s' befinden.\n" +es: "\nSe asume que el texto se encuentra en el fichero '%s'.\n" +fr: "\nLe texte est supposé être dans le fichier '%s'.\n" +mutt: " " +muttde: " " + +"\nPlease enter filename of material that signature applies to: " +de: "\nName der Datei, zu der die Unterschrift gehört: " +es: "\nIntroduzca el nombre del fichero al que se aplica la firma: " +fr: "SVP indiques le nom du fichier que vous voulez signer: " +muttde: "\nName der Datei, zu der die Unterschrift gehört: " + +"File signature applies to?" +de: "Die Unterschrift gehört zu welcher Datei?" +es: "¿Dónde se aplica la firma?" +fr: "Ce fichier signataire s'applique à quoi? " +muttde: "Die Unterschrift gehört zu welcher Datei?" + +"\n\007Can't open file '%s'\n" +de: "\n\007FEHLER beim Öffnen der Datei '%s'.\n" +es: "\n\007No puede abrirse el fichero '%s'\n" +fr: "\n\007Ouverture du fichier '%s' impossible\n" +mutt: "\nCan't open file '%s'\n" +muttde: "\nFEHLER beim Öffnen der Datei '%s'.\n" + +"File type: '%c'\n" +de: "\nDateityp: '%c'\n" +es: "\nTipo de fichero: '%c'\n" +fr: "Type de fichier: '%c'\n" +muttde: "\nDateityp: '%c'\n" + +"Original plaintext file name was: '%s'\n" +de: "Der ursprüngliche Name der Klartextdatei war: '%s'.\n" +es: "El nombre del fichero original era: '%s'\n" +fr: "Le nom originel du fichier en clair était: '%s'\n" +muttde: "Der ursprüngliche Name der Klartextdatei war: '%s'.\n" + +"\nWARNING: Can't find the right public key-- can't check signature \ +integrity.\n" +de: "\nWARNUNG: Der passende öffentliche Schlüssel wurde nicht gefunden.\n\ +Eine Überprüfung der Unterschrift ist nicht möglich.\n" +es: "\nAVISO: No se encuentra la clave pública necesaria para comprobar\n\ +la integridad de la firma.\n" +fr: "\nATTENTION: impossible de trouver la clé publique adéquate et de\n\ +vérifier l'integrité de la signature.\n" +muttde: "\nWARNUNG: Der passende öffentliche Schlüssel wurde nicht gefunden.\n\ +Eine Überprüfung der Unterschrift ist nicht möglich.\n" + +"\007\nUnrecognized message digest algorithm.\n\ +This may require a newer version of PGP.\n\ +Can't check signature integrity.\n" +de: "\007\nDer Algorithmus für die Textprüfsumme ist unbekannt.\n\ +Du brauchst wahrscheinlich eine neuere Version von PGP.\n\ +Eine Überprüfung der Unterschrift ist nicht möglich.\n" +es: "\007\nAlgoritmo desconocido de resumen de mensaje.\n\ +Puede necesitarse una nueva versión de PGP.\n\ +No puede comprobarse la integridad de la firma.\n" +fr: "\007Algorithme de digest inconnu.\n\ +Ceci peut vouloir dire que vous avez besoin d'une version plus récente\n\ +de PGP. Incapable de vérifier la signature.\n" +mutt: "\nUnrecognized message digest algorithm.\n\ +This may require a newer version of PGP.\n\ +Can't check signature integrity.\n" +muttde: "\nDer Algorithmus für die Textprüfsumme ist unbekannt.\n\ +Du brauchst wahrscheinlich eine neuere Version von PGP.\n\ +Eine Überprüfung der Unterschrift ist nicht möglich.\n" + +"\a\nMalformed or obsolete signature. Can't check signature \ +integrity.\n" +de: "\a\nFehlerhafte oder veraltete Unterschrift! Überprüfung nicht möglich.\n" +es: "\a\nFirma incorrecta u obsoleta.\n\ +No puede comprobarse su integridad.\n" +fr: "\a\nSignature déformée ou obsolète. Vérification impossible. \n" +muttde: "\a\nFehlerhafte oder veraltete Unterschrift! Überprüfung nicht möglich.\n" + +"\a\nSigning key is too large. Can't check signature integrity.\n" +de: "\a\nDer unterschreibende Schlüssel ist zu lang! Eine Überprüfung der\n\ +Unterschrift ist deshalb nicht möglich.\n" +es: "\a\nLa clave para firmar es demasiado grande.\n\ +No puede comprobarse la integridad de la firma." +fr: "La clef signataire est trop grande. Incapable d'en vérifier l'intégrité. \n" +muttde: "\a\nDer unterschreibende Schlüssel ist zu lang! Eine Überprüfung der\n\ +Unterschrift ist deshalb nicht möglich.\n" + +"\n\007Error: RSA-decrypted block is corrupted.\n\ +This may be caused either by corrupted data or by using the wrong RSA key.\n\ +" +de: "\n\007FEHLER: Die mit RSA entschlüsselten Daten sind fehlerhaft.\n\ +Ursache: beschädigte Daten oder ein falscher RSA-Schlüssel.\n" +es: "\n\007Error: El bloque desencriptado RSA está dañado.\n\ +Puede deberse a un problema en los datos o a una clave RSA equivocada.\n" +fr: "\n\007Erreur: le block dechiffré par RSA est endommagé.\n\ +Ceci est peut être causé par des données endommagées our par\n\ +l'utilisation d'une mauvaise clé RSA.\n" +mutt: "\nError: RSA-decrypted block is corrupted.\n\ +This may be caused either by corrupted data or by using the wrong RSA key.\n\ +" +muttde: "\nFEHLER: Die mit RSA entschlüsselten Daten sind fehlerhaft.\n\ +Ursache: beschädigte Daten oder ein falscher RSA-Schlüssel.\n" + +"WARNING: Bad signature, doesn't match file contents!" +de: "WARNUNG: Die Unterschrift stimmt nicht mit dem Datei-Inhalt überein!" +es: "ADVERTENCIA: Firma incorrecta, no coincide con el contenido\ +del fichero\n" +fr: "ATTENTION: Mauvaise signature, ne correspond pas au contenu!" +muttde: "WARNUNG: Die Unterschrift stimmt nicht mit dem Datei-Inhalt überein!" + +"\nBad signature from user \"%s\".\n" +de: "\nFEHLERHAFTE Unterschrift von \"%s\",\n" +es: "\nFirma incorrecta de \"%s\".\n" +fr: "\nMauvaise signature de l'utilisateur \"%s\".\n" +muttde: "\nFEHLERHAFTE Unterschrift von \"%s\",\n" + +"Signature made %s using %d-bit key, key ID %s\n" +de: "Unterschrift erzeugt am %s mit %d-Bit-Schlüssel 0x%s.\n" +es: "Firma realizada el %s con una clave de %d bits, identificador %s\n" +fr: "Signature faite %s en utilisant un clef de %d bits. Id de la clef:%s\n" +muttde: "Unterschrift erzeugt am %s mit %d-Bit-Schlüssel 0x%s.\n" + +"\nPress ENTER to continue..." +de: "\nWeiter mit Return..." +es: "\nPulse 'Enter' para continuar..." +fr: "\nAppuyez sur la touche Retour ou Entrée pour continuer..." +muttde: "\nWeiter mit Return..." + +"\nGood signature from user \"%s\".\n" +de: "\nBESTÄTIGTE Unterschrift von \"%s\",\n" +es: "\nFirma correcta de \"%s\".\n" +fr: "\nBonne signature de l'utilisateur \"%s\".\n" +muttde: "\nBESTÄTIGTE Unterschrift von \"%s\",\n" + +"\nSignature and text are separate. No output file produced. " +de: "\nUnterschrift und Text sind getrennt. Es wurde keine Ausgabedatei erzeugt." +es: "\nLa firma y el texto están separados.\n\ +No se produce fichero de salida. " +fr: "\nLa signature et le texte sont séparés. Fichier de sortie non produit." +muttde: "\nUnterschrift und Text sind getrennt. Es wurde keine Ausgabedatei erzeugt." + +"\n\007Can't create plaintext file '%s'\n" +de: "\n\007FEHLER beim Erzeugen der Klartextdatei '%s'.\n" +es: "\n\007No puede crearse el fichero normal '%s'\n" +fr: "\n\007Creation du fichier en clair '%s' impossible.\n" +mutt: "\nCan't create plaintext file '%s'\n" +muttde: "\nFEHLER beim Erzeugen der Klartextdatei '%s'.\n" + +"\n\007Signature file '%s' already exists. Overwrite (y/N)? " +de: "\n\007Die Unterschriftsdatei '%s'\nexistiert bereits. Überschreiben? (j/N) " +es: "\n\007El fichero de firma '%s' ya existe.\n\ +¿Se sobreescribe (s/N)? " +fr: "\n\007Le fichier de signature '%s' existe déjà. A écraser (o/N)? " +mutt: "\nSignature file '%s' already exists. Overwrite (y/N)? " +muttde: "\nDie Unterschriftsdatei '%s'\nexistiert bereits. Überschreiben? (j/N) " + +"\nWriting signature certificate to '%s'\n" +de: "\nDie Unterschrift wird in die Datei '%s' geschrieben.\n" +es: "\nEscribiendo el certificado de firma para '%s'\n" +fr: "\nEcriture du certificat de signature dans '%s'\n" +muttde: "\nDie Unterschrift wird in die Datei '%s' geschrieben.\n" + +"\n\007Error: Badly-formed or corrupted signature certificate.\n" +de: "\n\007FEHLER: Format- oder Datenfehler in der Unterschrift.\n" +es: "\n\007Error: Certificado de firma incorrecto o dañado.\n" +fr: "\n\007Erreur: certificat de signature mal formé ou endommagé\n" +mutt: "\nError: Badly-formed or corrupted signature certificate.\n" +muttde: "\nFEHLER: Format- oder Datenfehler in der Unterschrift.\n" + +"File \"%s\" does not have a properly-formed signature.\n" +de: "Die Datei '%s' hat keine formal korrekte Unterschrift.\n" +es: "El fichero \"%s\" no tiene una firma construida correctamente.\n" +fr: "Le fichier \"%s\" n'a pas une signature correctement formée.\n" +muttde: "Die Datei '%s' hat keine formal korrekte Unterschrift.\n" + +"compressed. " +de: "gepackt. " +es: "comprimido. " +fr: "comprimé. " +muttde: "gepackt. " + +"\n\007Can't create compressed file '%s'\n" +de: "\n\007FEHLER beim Erzeugen der gepackten Datei '%s'.\n" +es: "\n\007No puede crearse el fichero comprimido '%s'\n" +fr: "\n\007Création du fichier compressé '%s' impossible\n" +mutt: "\nCan't create compressed file '%s'\n" +muttde: "\nFEHLER beim Erzeugen der gepackten Datei '%s'.\n" + +"Compressing file..." +de: "Packen der Datei..." +es: "Comprimiendo el fichero..." +fr: "Compression du fichier..." +muttde: "Packen der Datei..." + +"\n\007Can't create ciphertext file '%s'\n" +de: "\n\007FEHLER beim Erzeugen der verschlüsselten\nAusgabedatei '%s'.\n" +es: "\n\007No puede crearse el fichero cifrado '%s'\n" +fr: "\n\007Création du fichier chiffré '%s' impossible.\n" +mutt: "\nCan't create ciphertext file '%s'\n" +muttde: "\nFEHLER beim Erzeugen der verschlüsselten\nAusgabedatei '%s'.\n" + +"\nYou need a pass phrase to encrypt the file. " +de: "\nDu brauchst ein Mantra zum Verschlüsseln der Datei." +es: "\nSe necesita una contraseña para encriptar el fichero. " +fr: "\nUn mot de passe est nécessaire pour chiffrer ce fichier. " +muttde: "\nDu brauchst ein Mantra zum Verschlüsseln der Datei." + +"\n\007Cannot find the public key matching userid '%s'\n\ +This user will not be able to decrypt this message.\n" +de: "\n\007Der öffentliche Schlüssel zu Benutzer-ID \"%s\"\n\ +ist nicht aufzufinden. Dieser Empfänger wird diese Nachricht nicht\n\ +entschlüsseln können.\n" +es: "\n\007No puede encontrarse la clave pública de '%s'\n\ +Ese usuario no podrá descifrar el mensaje.\n" +fr: "\n\007Impossible de trouver la clé publique pour l'utilisateur '%s'\n\ +Cet utilisateur ne pourra pas déchiffrer ce message.\n" +mutt: "\nCannot find the public key matching userid '%s'\n\ +This user will not be able to decrypt this message.\n" +muttde: "\nDer öffentliche Schlüssel zu Benutzer-ID \"%s\"\n\ +ist nicht aufzufinden. Dieser Empfänger wird diese Nachricht nicht\n\ +entschlüsseln können.\n" + +"Skipping userid %s\n" +de: "Die Benutzer-ID \"%s\" wird übersprungen.\n" +es: "Saltando el identificador %s\n" +fr: "je passe l'utiliateur %s\n" +muttde: "Die Benutzer-ID \"%s\" wird übersprungen.\n" + +"\n\007'%s' is not a cipher file.\n" +de: "\n\007'%s' ist keine verschlüsselte Datei.\n" +es: "\n\007'%s' no es un fichero cifrado.\n" +fr: "\n\007'%s' n'est pas un fichier chiffré.\n" +mutt: "\n'%s' is not a cipher file.\n" +muttde: "\n'%s' ist keine verschlüsselte Datei.\n" + +"\n\007Error: RSA block is possibly malformed. Old format, maybe?\n" +de: "\n\007FEHLER: RSA-Block möglicherweise fehlerhaft. Vielleicht altes Format?\n" +es: "\n\007Error: El bloque RSA está mal formado.\n\ +Quizá se trate de un formato antiguo.\n" +fr: "\n\007Erreur: Block RSA malformé, vieux format ???" +mutt: "\nError: RSA block is possibly malformed. Old format, maybe?\n" +muttde: "\nFEHLER: RSA-Block möglicherweise fehlerhaft. Vielleicht altes Format?\n" + +"\nThis message can only be read by:\n" +de: "\nDiese Nachricht kann nur gelesen werden von:\n" +es: "\nEste mensaje sólo puede leerlo:\n" +fr: "\nCe message ne peut être lu que par:\n" +muttde: "\nDiese Nachricht kann nur gelesen werden von:\n" + +" keyID: %s\n" +de: " Schlüssel-ID: %s\n" +es: " identificador: %s\n" +fr: " Id de la clef: %s \n" +muttde: " Schlüssel-ID: %s\n" + +"\n\007You do not have the secret key needed to decrypt this file.\n" +de: "\n\007Dir fehlt der private Schlüssel zum Entschlüsseln dieser Datei.\n" +es: "\n\007No tienes la clave secreta necesaria para descifrar\ +este fichero.\n" +fr: "\n\007Vous n'avez pas la clé secrète requise pour déchiffrer\ +\nce fichier.\n" +mutt: "\nYou do not have the secret key needed to decrypt this file.\n" +muttde: "\nDir fehlt der private Schlüssel zum Entschlüsseln dieser Datei.\n" + +"\n\007Error: Decrypted plaintext is corrupted.\n" +de: "\n\007FEHLER: Der entschlüsselte Klartext ist fehlerhaft.\n" +es: "\n\007Error: El texto en claro desencriptado está dañado.\n" +fr: "\n\007Erreur: le fichier déchiffré est endommagé.\n" +mutt: "\nError: Decrypted plaintext is corrupted.\n" +muttde: "\nFEHLER: Der entschlüsselte Klartext ist fehlerhaft.\n" + +"\nYou need a pass phrase to decrypt this file. " +de: "\nDu brauchst ein Mantra zum Entschlüsseln dieser Datei." +es: "\nSe necesita la contraseña para desencriptar este fichero. " +fr: "\nUn mot de passe est nécessaire pour déchiffrer ce fichier. " +muttde: "\nDu brauchst ein Mantra zum Entschlüsseln dieser Datei." + +"\n\007Error: Bad pass phrase.\n" +de: "\n\007FEHLER: Falsches Mantra!\n" +es: "\n\007Error: Contraseña incorrecta.\n" +fr: "\n\007Erreur: Mauvais mot de passe.\n" +mutt: "\nError: Bad pass phrase.\n" +muttde: "\nFEHLER: Falsches Mantra!\n" + +"Pass phrase appears good. " +de: "\nDas Mantra scheint zu stimmen.\n" +es: "Parece correcta. " +fr: "Le mot de passe semble correct. " +muttde: "\nDas Mantra scheint zu stimmen.\n" + +"Decompressing plaintext..." +de: "Entpacken des Klartextes..." +es: "Descomprimiendo el texto normal..." +fr: "Decompression du texte en clair..." +muttde: "Entpacken des Klartextes..." + +"\n\007Can't open compressed file '%s'\n" +de: "\n\007FEHLER beim Öffnen der gepackten Datei '%s'.\n" +es: "\n\007No puede abrirse el fichero comprimido '%s'\n" +fr: "\n\007Ouverture du fichier compressé '%s' impossible.\n" +mutt: "\nCan't open compressed file '%s'\n" +muttde: "\nFEHLER beim Öffnen der gepackten Datei '%s'.\n" + +"\007\nUnrecognized compression algorithm.\n\ +This may require a newer version of PGP.\n" +de: "\007\nUnbekanntes Pack-Verfahren. Eine neuere Version von PGP könnte notwendig sein.\n" +es: "\007\nAlgoritmo de compresión no reconocido.\n\ +Puede necesitarse una nueva versión de PGP.\n" +fr: "\007\nAlgorithme de compression non reconnu.\n\ +Ceci peut nécessiter une nouvelle version de PGP.\n" +mutt: "\nUnrecognized compression algorithm.\n\ +This may require a newer version of PGP.\n" +muttde: "\nUnbekanntes Pack-Verfahren. Eine neuere Version von PGP könnte notwendig sein.\n" + +"\n\007Can't create decompressed file '%s'\n" +de: "\n\007FEHLER beim Erzeugen der entpackten Datei '%s'.\n" +es: "\n\007No puede crearse el fichero descomprimido '%s'\n" +fr: "\n\007Création du fichier décompressé '%s' impossible.\n" +mutt: "\nCan't create decompressed file '%s'\n" +muttde: "\nFEHLER beim Erzeugen der entpackten Datei '%s'.\n" + +"\n\007Decompression error. Probable corrupted input.\n" +de: "\n\007FEHLER beim Entpacken! Wahrscheinlich beschädigte Eingangsdaten.\n" +es: "\n007Error en descompresión. Probable entrada dañada.\n" +fr: "\n\007Erreur de Decompression, entrée probablement corrompue" +mutt: "\nDecompression error. Probable corrupted input.\n" +muttde: "\nFEHLER beim Entpacken! Wahrscheinlich beschädigte Eingangsdaten.\n" + +"done. " +de: "fertig. " +es: "finalizado. " +fr: "terminé. " +muttde: "fertig. " + +"Truncating filename '%s' " +de: "Kürzung des Dateinamens '%s' " +es: "Truncando el nombre de fichero '%s' " +fr: "troncation du fichier '%s'" +muttde: "Kürzung des Dateinamens '%s' " + +"y" +de: "j" +es: "s" +fr: "o" +muttde: "j" + +"n" +de: "n" +es: "n" +fr: "n" +muttde: "n" + +"\nShould '%s' be renamed to '%s' (Y/n)? " +de: "\nSoll '%s' in '%s' umbenannt werden? (J/n) " +es: "\n¿Renombrar '%s' como '%s' (S/n)? " +fr: "\nEst-ce que '%s' doit être renommé '%s' (O/n)? " +muttde: "\nSoll '%s' in '%s' umbenannt werden? (J/n) " + +"\nDisk full.\n" +de: "\nDie Platte ist voll!\n" +es: "\nDisco lleno.\n" +fr: "\nDisque plein.\n" +muttde: "\nDie Platte ist voll!\n" + +"\nFile write error.\n" +de: "\nFEHLER beim Schreiben einer Datei.\n" +es: "\nError de escritura del fichero.\n" +fr: "\nErreur d'écriture sur fichier.\n" +muttde: "\nFEHLER beim Schreiben einer Datei.\n" + +"\007Write error on stdout.\n" +de: "\n\007FEHLER beim Schreiben auf stdout (Standard-Ausgabe).\n" +es: "\007Error de escritura en la salida estándar (\"stdout\").\n" +fr: "\007Erreur d'écriture sur la sortie standard.\n" +mutt: "Write error on stdout.\n" +muttde: "\nFEHLER beim Schreiben auf stdout (Standard-Ausgabe).\n" + +"\n\007Cannot create temporary file '%s'\n" +de: "\n\007FEHLER beim Erzeugen der Temporärdatei '%s'.\n" +es: "\n\007No puede crearse el fichero temporal '%s'\n" +fr: "\n\007Création du fichier temporaire '%s' impossible\n" +mutt: "\nCannot create temporary file '%s'\n" +muttde: "\nFEHLER beim Erzeugen der Temporärdatei '%s'.\n" + +"Can't create output file '%s'\n" +de: "\nFEHLER beim Erzeugen der Ausgabedatei '%s'.\n" +es: "No puede crearse el fichero '%s'\n" +fr: "Création du fichier '%s' impossible.\n" +muttde: "\nFEHLER beim Erzeugen der Ausgabedatei '%s'.\n" + +"\n\007Output file '%s' already exists.\n" +de: "\n\007Die Ausgabedatei '%s' existiert bereits.\n" +es: "\n\007El fichero de salida '%s' ya existe.\n" +fr: "\n\007Le ficher de sortie '%s' existe déjà.\n" +mutt: "\nOutput file '%s' already exists.\n" +muttde: "\nDie Ausgabedatei '%s' existiert bereits.\n" + +"\n\007Output file '%s' already exists. Overwrite (y/N)? " +de: "\n\007Die Ausgabedatei '%s' existiert bereits. Überschreiben? (j/N) " +es: "\n\007El fichero de salida '%s' ya existe. ¿Sobrescribir (s/N)? " +fr: "\n\007Le fichier de sortie '%s' existe déjà. A écraser (o/N)? " +mutt: "\nOutput file '%s' already exists. Overwrite (y/N)? " +muttde: "\nDie Ausgabedatei '%s' existiert bereits. Überschreiben? (j/N) " + +"Enter new file name:" +de: "Gib den neuen Dateinamen ein:" +es: "Introduzca el nuevo nombre de fichero: " +fr: "Donnez un nouveau nom de fichier:" +muttde: "Gib den neuen Dateinamen ein:" + +"Replacing signature from keyID %s on userid \"%s\"\n" +de: "Die Unterschrift von der Schlüssel-ID %s unter der\n\ +Benutzer-ID \"%s\" wird ersetzt.\n" +es: "Sustituyendo la firma de la clave %s para el usuario\n\ +\"%s\"\n" +fr: "Remplacement la signature de keyID %s de l'utilisateur '%s'\n" +muttde: "Die Unterschrift von der Schlüssel-ID %s unter der\n\ +Benutzer-ID \"%s\" wird ersetzt.\n" + +"Verifying signature from %s\n" +de: "Überprüfung der Unterschrift von \"%s\"\n" +es: "Verificando la firma de %s\n" +fr: "Vérification de la signature de %s\n" +muttde: "Überprüfung der Unterschrift von \"%s\"\n" + +"on userid \"%s\"\n" +de: "unter \"%s\".\n" +es: "en el identificador \"%s\"\n" +fr: "pour le nom d'utilisateur \"%s\"\n" +muttde: "unter \"%s\".\n" + +"Replacing signature from %s\n" +de: "Ersetzung der Unterschrift von \"%s\"\n" +es: "Sustituyendo la firma de %s\n" +fr: "Remplacement de la signature de %s" +muttde: "Ersetzung der Unterschrift von \"%s\"\n" + +"Verification Failed\n" +de: "\nDie Überprüfung ist fehlgeschlagen!\n" +es: "Verificación fallida\n" +fr: "Echec de la vérification" +muttde: "\nDie Überprüfung ist fehlgeschlagen!\n" + +"New signature from keyID %s on userid \"%s\"\n" +de: "Neue Unterschrift von %s unter \"%s\".\n" +es: "Nueva firma de la clave %s para el usuario \"%s\"\n" +fr: "Nouvelle signature de la clé %s sur l'utilisateur \"%s\"\n" +muttde: "Neue Unterschrift von %s unter \"%s\".\n" + +"New signature from %s\n" +de: "Neue Unterschrift von \"%s\"\n" +es: "Nueva firma de %s\n" +fr: "\nNouvelle signature de %s\n" +muttde: "Neue Unterschrift von \"%s\"\n" + +"Key revocation certificate from \"%s\".\n" +de: "Urkunde zum Zurückziehen eines Schlüssels\nvon \"%s\".\n" +es: "Certificado de revocación de clave de \"%s\".\n" +fr: "Certificat de révocation de clé de \"%s\".\n" +muttde: "Urkunde zum Zurückziehen eines Schlüssels\nvon \"%s\".\n" + +"\n\007WARNING: File '%s' contains bad revocation certificate.\n" +de: "\n\007WARNUNG: Die Datei '%s' enthält eine\n\ +fehlerhafte Urkunde zum Zurückziehen eines Schlüssels.\n" +es: "\n\007AVISO: El fichero '%s' tiene un certificado de revocación\ +incorrecto.\n" +fr: "\n\007ATTENTION: the fichier '%s' contient de mauvais certificats\n\ +de révocation.\n" +mutt: "\nWARNING: File '%s' contains bad revocation certificate.\n" +muttde: "\nWARNUNG: Die Datei '%s' enthält eine\n\ +fehlerhafte Urkunde zum Zurückziehen eines Schlüssels.\n" + +"New userid: \"%s\".\n" +de: "Neue Benutzer-ID: \"%s\".\n" +es: "Nuevo identificador: \"%s\".\n" +fr: "Nouveau nom d'utilisateur: \"%s\".\n" +muttde: "Neue Benutzer-ID: \"%s\".\n" + +"\nWill be added to the following key:\n" +de: "Sie wird zu dem folgenden Schlüssel hinzugefügt:\n" +es: "\nSe añadirá a la clave siguiente:\n" +fr: "\nSera ajouté(e) à la clé suivante:\n" +muttde: "Sie wird zu dem folgenden Schlüssel hinzugefügt:\n" + +"\nAdd this userid (y/N)? " +de: "\nSoll diese Benutzer-ID hinzugefügt werden? (j/N) " +es: "\n¿Añadir este identificador (s/N)? " +fr: "\nAjouter ce nom d'utilisateur (o/N)? " +muttde: "\nSoll diese Benutzer-ID hinzugefügt werden? (j/N) " + +"\n\007Can't open key file '%s'\n" +de: "\n\007FEHLER beim Öffnen der Schlüsseldatei '%s'.\n" +es: "\n\007No puede abrirse el fichero de claves '%s'\n" +fr: "\n\007Ouverture du fichier de clé '%s' impossible\n" +mutt: "\nCan't open key file '%s'\n" +muttde: "\nFEHLER beim Öffnen der Schlüsseldatei '%s'.\n" + +"\nKey ring file '%s' cannot be created.\n" +de: "\nFEHLER beim Anlegen des Schlüsselbunds '%s'.\n" +es: "\nNo se puede crear el anillo de claves '%s'.\n" +fr: "\nCréation du fichier de clés '%s' impossible.\n" +muttde: "\nFEHLER beim Anlegen des Schlüsselbunds '%s'.\n" + +"\nLooking for new keys...\n" +de: "\nSuche nach neuen Schlüsseln...\n" +es: "\nBuscando nuevas claves...\n" +fr: "\nRecherche des nouvelles clés...\n" +muttde: "\nSuche nach neuen Schlüsseln...\n" + +"\n\007Could not read key from file '%s'.\n" +de: "\n\007FEHLER beim Lesen des Schlüssels aus Datei '%s'.\n" +es: "\n\007No ha podido leerse la clave en el fichero '%s'.\n" +fr: "\n\007Lecture impossible de la clé dans le fichier '%s'.\n" +mutt: "\nCould not read key from file '%s'.\n" +muttde: "\nFEHLER beim Lesen des Schlüssels aus Datei '%s'.\n" + +"\n\007Warning: Key ID %s matches key ID of key already on \n\ +key ring '%s', but the keys themselves differ.\n\ +This is highly suspicious. This key will not be added to ring.\n\ +Acknowledge by pressing return: " +de: "\n\007WARNUNG: Die Schlüssel-ID %s stimmt mit einem schon\n\ +im Schlüsselbund '%s' vorhandenen Schlüssel\n\ +überein, aber die Schlüssel selbst sind nicht identisch.\n\ +Das ist höchst verdächtig! Dieser Schlüssel wird nicht zum\n\ +Schlüsselbund hinzugefügt. Mit Return bestätigen: " +es: "\n\007Advertencia: El identificador de clave %s coincide con otro\n\ +en el anillo '%s', pero las claves son distintas.\n\ +Es muy sospechoso. Esta clave no se incluirá en el anillo.\n\ +Confirmar pulsando 'retorno': " +fr: "\n\007Attention: l'identificateur de clé %s correspond à une clé\ +\ndéjà dans le fichier de clés '%s', mais les clés sont différentes.\ +\nCeci est très suspect. Cette clé ne sera pas ajoutée au fichier de clés.\ +\nAppuyez sur la touche Retour ou Entrée: " +mutt: "\nWarning: Key ID %s matches key ID of key already on \n\ +key ring '%s', but the keys themselves differ.\n\ +This is highly suspicious. This key will not be added to ring.\n\ +Acknowledge by pressing return: " +muttde: "\nWARNUNG: Die Schlüssel-ID %s stimmt mit einem schon\n\ +im Schlüsselbund '%s' vorhandenen Schlüssel\n\ +überein, aber die Schlüssel selbst sind nicht identisch.\n\ +Das ist höchst verdächtig! Dieser Schlüssel wird nicht zum\n\ +Schlüsselbund hinzugefügt. Mit Return bestätigen: " + +"\nDo you want to add this key to keyring '%s' (y/N)? " +de: "\nMöchtest Du diesen Schlüssel zum Schlüsselbund\n'%s' hinzufügen? (j/N) " +es: "\n¿Quieres añadir esta clave al anillo '%s' (s/N)? " +fr: "\nVoulez vous ajouter cette clé au fichier de clés '%s' (o/N)? " +muttde: "\nMöchtest Du diesen Schlüssel zum Schlüsselbund\n'%s' hinzufügen? (j/N) " + +"Key has been revoked.\n" +de: "Der Schlüssel wurde zurückgezogen.\n" +es: "La clave se ha revocado.\n" +fr: "La clé a été révoquée.\n" +muttde: "Der Schlüssel wurde zurückgezogen.\n" + +"\n\007Key file contains duplicate keys: cannot be added to keyring\n" +de: "\n\007Die Datei enthält doppelte Schlüssel, die nicht zum Schlüsselbund\n\ +hinzugefügt werden.\n" +es: "\n\007El fichero contiene claves duplicadas: \ +no puede añadirse al anillo\n" +fr: "\n\007Le fichier contient des clés dupliquées: impossible\n\ +de l'ajouter au fichier de clés\n" +mutt: "\nKey file contains duplicate keys: cannot be added to keyring\n" +muttde: "\nDie Datei enthält doppelte Schlüssel, die nicht zum Schlüsselbund\n\ +hinzugefügt werden.\n" + +"No new keys or signatures in keyfile.\n" +de: "Keine neuen Schlüssel oder Unterschriften in der Datei.\n" +es: "No hay nuevas claves ni nuevas firmas en el fichero.\n" +fr: "Pas de nouvelles clés ou signatures dans le fichier de clés.\n" +muttde: "Keine neuen Schlüssel oder Unterschriften in der Datei.\n" + +"\nKeyfile contains:\n" +de: "\nDie Datei enthält folgende Schlüssel:\n" +es: "\nEl fichero de claves contiene:\n" +fr: "\nLe fichier de clés contient:\n" +muttde: "\nDie Datei enthält folgende Schlüssel:\n" + +"%4d new key(s)\n" +de: "%4d neue(n) Schlüssel\n" +es: "%4d nueva(s) clave(s)\n" +fr: "%4d nouvelle(s) clé(s)\n" +muttde: "%4d neue(n) Schlüssel\n" + +"%4d new signatures(s)\n" +de: "%4d neue Unterschrift(en)\n" +es: "%4d nueva(s) firma(s)\n" +fr: "%4d nouvelle(s) signatures(s)\n" +muttde: "%4d neue Unterschrift(en)\n" + +"%4d new user ID(s)\n" +de: "%4d neue Benutzer-ID(s)\n" +es: "%4d nuevo(s) identificador(es) de usuario\n" +fr: "%4d nouveau(x) nom(s) d'utilisateur\n" +muttde: "%4d neue Benutzer-ID(s)\n" + +"%4d new revocation(s)\n" +de: "%4d neue Urkunde(n) zum Zurückziehen von Schlüsseln\n" +es: "%4d nueva(s) revocacion(es)\n" +fr: "%4d nouvelle(s) révocation(s)\n" +muttde: "%4d neue Urkunde(n) zum Zurückziehen von Schlüsseln\n" + +"\nNo keys found in '%s'.\n" +de: "\nKeine Schlüssel in '%s' gefunden.\n" +es: "\nNo se encuentran claves en '%s'.\n" +fr: "\nPas de clés trouvées dans '%s'.\n" +muttde: "\nKeine Schlüssel in '%s' gefunden.\n" + +"\nOne or more of the new keys are not fully certified.\n\ +Do you want to certify any of these keys yourself (y/N)? " +de: "\nEin oder mehrere neue Schlüssel sind nicht ausreichend beglaubigt.\n\ +Willst Du sie selbst beglaubigen? (j/N) " +es: "\nUna o más de las nuevas claves no están completamente certificadas.\n\ +¿Quieres certificar alguna de ellas tú mismo (s/N)? " +fr: "\nUne ou plusieurs des nouvelles clés ne sont pas complètement\ +\ncertifiées. Voulez vous certifier ces clés vous même (o/N)? " +muttde: "\nEin oder mehrere neue Schlüssel sind nicht ausreichend beglaubigt.\n\ +Willst Du sie selbst beglaubigen? (j/N) " + +"\nDo you want to certify this key yourself (y/N)? " +de: "\nWillst Du diesen Schlüssel selbst beglaubigen? (j/N) " +es: "\n¿Quieres certificar esta clave tú mismo (s/N)? " +fr: "\nVoulez vous certifier cette clé vous même (o/N)? " +muttde: "\nWillst Du diesen Schlüssel selbst beglaubigen? (j/N) " + +"undefined" +de: "undefin." +es: "sin definir" +fr: "indéfinie" +muttde: "undefin." + +"unknown" +de: "unbekannt" +es: "desconocida" +fr: "inconnu" +muttde: "unbekannt" + +"untrusted" +de: "kein" +es: "no fiable" +fr: "non sûr" +muttde: "kein" + +"marginal" +de: "teilweise" +es: "relativa" +fr: "marginal" +muttde: "teilweise" + +"complete" +de: "voll" +es: "completa" +fr: "complète" +muttde: "voll" + +"ultimate" +de: "absolut" +es: "fundamental" +fr: "ultime" +muttde: "absolut" + +"\nCan't open backup key ring file '%s'\n" +de: "\nFEHLER beim Öffnen der Schlüsselbund-Kopie '%s'.\n" +es: "\nNo se puede abrir la copia de seguridad del anillo '%s'\n" +fr: "\nImpossible d'ouvrir le fichier de clé de sauvegarde '%s'\n" +muttde: "\nFEHLER beim Öffnen der Schlüsselbund-Kopie '%s'.\n" + +"\n%d \"trust parameter(s)\" need to be changed.\n" +de: "\n%d 'Vertrauens-Einstellung(en)' muß/müssen geändert werden.\n" +es: "\n%d \"parámetro(s) de confianza\" debe(n) cambiarse.\n" +fr: "\n%d \"paramètre(s) de confiance\" doi(ven)t être changé(s).\n" +muttde: "\n%d 'Vertrauens-Einstellung(en)' muß/müssen geändert werden.\n" + +"Continue with '%s' (Y/n)? " +de: "Weiter mit '%s'? (J/n) " +es: "¿Seguir con '%s' (S/n)? " +fr: "Continuer avec '%s' (O/n)? " +muttde: "Weiter mit '%s'? (J/n) " + +"\n%d \"trust parameter(s)\" changed.\n" +de: "\n%d 'Vertrauens-Einstellung(en)' geändert.\n" +es: "\nCambiados %d \"parámetro(s) de confianza.\n" +fr: "\n%d \"paramètre(s) de confiance\" changé(s).\n" +muttde: "\n%d 'Vertrauens-Einstellung(en)' geändert.\n" + +"Update public keyring '%s' (Y/n)? " +de: "Öffentlichen Schlüsselbund '%s' aktualisieren? (J/n) " +es: "¿Actualizar el anillo de claves públicas '%s' (S/n)? " +fr: "Modifier le fichier de clés publiques '%s' (O/n)? " +muttde: "Öffentlichen Schlüsselbund '%s' aktualisieren? (J/n) " + +"\nCan't open secret key ring file '%s'\n" +de: "\nFEHLER beim Öffnen des privaten Schlüsselbunds '%s'.\n" +es: "\nNo puede abrirse el anillo de claves secretas '%s'\n" +fr: "\nOuverture du fichier de clés secrètes '%s' impossible.\n" +muttde: "\nFEHLER beim Öffnen des privaten Schlüsselbunds '%s'.\n" + +"\nPass 1: Looking for the \"ultimately-trusted\" keys...\n" +de: "\nDurchlauf 1: Suche nach 'absolut vertrauenswürdigen' Schlüsseln...\n" +es: "\nProceso 1: Búsqueda de las claves \"fundamentalmente fiables\" ...\n" +fr: "\nPasse 1: Recherche des clés \"de confiance ultime\"...\n" +muttde: "\nDurchlauf 1: Suche nach 'absolut vertrauenswürdigen' Schlüsseln...\n" + +"\nPass 2: Tracing signature chains...\n" +de: "\nDurchlauf 2: Überprüfung von verketteten Unterschriften...\n" +es: "\nProceso 2: Seguimiento de las cadenas de firmas...\n" +fr: "\nPasse 2: Vérification des chaines de signatures...\n" +muttde: "\nDurchlauf 2: Überprüfung von verketteten Unterschriften...\n" + +"Keyring contains duplicate key: %s\n" +de: "\nDer Schlüsselbund enthält den Schlüssel %s doppelt.\n" +es: "El anillo contiene una clave duplicada: %s\n" +fr: "Le fichier de clés contient des clés dupliquées: %s\n" +muttde: "\nDer Schlüsselbund enthält den Schlüssel %s doppelt.\n" + +"No ultimately-trusted keys.\n" +de: "Keine absolut vertrauenswürdigen Schlüssel gefunden.\n" +es: "No hay ninguna clave fundamentalmente fiable.\n" +fr: "Pas de clés de confiance ultime.\n" +muttde: "Keine absolut vertrauenswürdigen Schlüssel gefunden.\n" + +" KeyID Trust Validity User ID\n" +de: "\n ID Vertrauen Gültigk. Benutzer\n" +es: " Clave Confianza Validez Identificador\n" +fr: " IDcle Conf. Validité Utilisateur\n" +muttde: "\n ID Vertrauen Gültigk. Benutzer\n" + +"(KeyID: %s)\n" +de: "(Schlüssel-ID: %s)\n" +es: "(Identificador: %s)\n" +fr: "(IDclef: %s)\n" +muttde: "(Schlüssel-ID: %s)\n" + +"\nAn \"axiomatic\" key is one which does not need certifying by\n\ +anyone else. Usually this special status is reserved only for your\n\ +own keys, which should also appear on your secret keyring. The owner\n\ +of an axiomatic key (who is typically yourself) is \"ultimately trusted\"\n\ +by you to certify any or all other keys.\n" +de: "\nEin 'definitionsgemäß vertrauenswürdiger' Schlüssel braucht nicht von einer\n\ +anderen Person beglaubigt zu werden. Normalerweise ist dieser spezielle Status\n\ +nur für Deine eigenen Schlüssel reserviert, die sich auch in Deinem privaten\n\ +Schlüsselbund befinden sollten. Der Besitzer eines 'definitionsgemäß vertrau-\n\ +enswürdigen' Schlüssels (das bist in der Regel Du selbst) wird von Dir als\n\ +'absolut vertrauenswürdig' betrachtet, beliebige oder sogar alle anderen\n\ +Schlüssel Dir gegenüber zu beglaubigen.\n" +es: "\nUna clave \"axiomatica\" es aquella que no necesita certificación.\n\ +Normalmente este estado se reserva para tus propias claves, que deben\n\ +aparecer también en tu anillo de claves secretas. El propietario de una\n\ +clave axiomática (normalmente tú mismo) es \"fundamentalmente fiable\"\n\ +para certificar cualquier clave.\n" +fr: "\n Une clef 'axiomatique' est un clef qui n'a pas besoin d'être\n\ +signé par personne. Habituellement ce statut est reservé à vos propres \n\ +clefs, celles qui apparaissent dans votre tousseau de clefs secrètes. \n\ +Le propriétaire de ces clefs (vous-même) possède votre 'confiance ultime'\n\ +pour accorder un certificat à l'une au l'autre des autres clefs.\n" +muttde: "\nEin 'definitionsgemäß vertrauenswürdiger' Schlüssel braucht nicht von einer\n\ +anderen Person beglaubigt zu werden. Normalerweise ist dieser spezielle Status\n\ +nur für Deine eigenen Schlüssel reserviert, die sich auch in Deinem privaten\n\ +Schlüsselbund befinden sollten. Der Besitzer eines 'definitionsgemäß vertrau-\n\ +enswürdigen' Schlüssels (das bist in der Regel Du selbst) wird von Dir als\n\ +'absolut vertrauenswürdig' betrachtet, beliebige oder sogar alle anderen\n\ +Schlüssel Dir gegenüber zu beglaubigen.\n" + +"\nKey for user ID \"%s\"\n\ +is designated as an \"ultimately-trusted\" introducer, but the key\n\ +does not appear in the secret keyring.\n\ +Use this key as an ultimately-trusted introducer (y/N)? " +de: "\nDer Schlüssel von \"%s\"\n\ +soll ein 'absolut vertrauenswürdiger Einführer' werden, aber er befindet\n\ +sich nicht im privaten Schlüsselbund. Soll ich diesen Schlüssel trotzdem\n\ +als 'absolut vertrauenswürdigen Einführer' behandeln? (j/N) " +es: "\nLa clave del usuario \"%s\"\n\ +está designada como referencia \"fundamentalmente fiable\", pero +la clave no aparece en el anillo de claves secretas.\n\ +¿Se utiliza como referencia fundamentalmente fiable (s/N)? " +fr: "\nLa clef de l'utilisateur '%s' \n\ +a été désigné comme ayant la 'confiance ulime', mais cette clef \n\ +n'apparraît pas dans le trousseau des clefs secrètes. \n\ +Voulez-vous utiliser cette clef comme ayant la 'confiance ultime'? (o/N) " +muttde: "\nDer Schlüssel von \"%s\"\n\ +soll ein 'absolut vertrauenswürdiger Einführer' werden, aber er befindet\n\ +sich nicht im privaten Schlüsselbund. Soll ich diesen Schlüssel trotzdem\n\ +als 'absolut vertrauenswürdigen Einführer' behandeln? (j/N) " + +"\n\007Cannot read from secret keyring.\n" +de: "\n\007FEHLER beim Lesen des privaten Schlüsselbunds.\n" +es: "\n\007No puede leerse el anillo de claves secretas.\n" +fr: "\n\007Lecture du fichier de clés secrètes impossible.\n" +mutt: "\nCannot read from secret keyring.\n" +muttde: "\nFEHLER beim Lesen des privaten Schlüsselbunds.\n" + +"\n\007WARNING: Public key for user ID: \"%s\"\n\ +does not match the corresponding key in the secret keyring.\n" +de: "\n\007WARNUNG: Der öffentliche Schlüssel\nvon \"%s\" stimmt nicht\n\ +mit dem Gegenstück im privaten Schlüsselbund überein.\n" +es: "\n\007AVISO: La clave pública de \"%s\"\n\ +no coincide con la clave correspondiente en el anillo de claves secretas.\n" +fr: "\n\007ATTENTION: la clé publique pour l'utilisateur: \"%s\"\n\ +ne correspond pas avec la clé respective dans le fichier de clés\ +secrètes.\n" +mutt: "\nWARNING: Public key for user ID: \"%s\"\n\ +does not match the corresponding key in the secret keyring.\n" +muttde: "\nWARNUNG: Der öffentliche Schlüssel\nvon \"%s\" stimmt nicht\n\ +mit dem Gegenstück im privaten Schlüsselbund überein.\n" + +"This is a serious condition, indicating possible keyring tampering.\n" +de: "Dies könnte bedeuten, daß die Schlüsselbunde manipuliert wurden!\n" +es: "Es una situación grave: posible manipulación de anillos.\n" +fr: "Ceci est une condition sérieuse, indiquant une possible manipulation\n\ +du fichier de clés.\n" +muttde: "Dies könnte bedeuten, daß die Schlüsselbunde manipuliert wurden!\n" + +"\nKey for user ID \"%s\"\n\ +also appears in the secret key ring." +de: "\nDer Schlüssel für die Benutzer-ID \"%s\"\n\ +ist auch im privaten Schlüsselbund vorhanden." +es: "\nLa clave del identificador \"%s\"\n\ +también aparece en el anillo de claves secretas." +fr: "\nLa clef pour l'utilisateur avec id: \"%s\"\n\ +apparait egalement dans le repertoire des clefs secretes." +muttde: "\nDer Schlüssel für die Benutzer-ID \"%s\"\n\ +ist auch im privaten Schlüsselbund vorhanden." + +"\nUse this key as an ultimately-trusted introducer (y/N)? " +de: "\nSoll dieser Schlüssel als 'absolut vertrauenswürdigen Einführer'\n\ +behandelt werden? (j/N) " +es: "\n¿Se utiliza esta clave como referencia fundamentalmente fiable (s/N)? " +fr: "\nUtiliser cette clé comme introducteur de confiance ultime (o/N)? " +muttde: "\nSoll dieser Schlüssel als 'absolut vertrauenswürdigen Einführer'\n\ +behandelt werden? (j/N) " + +"Public key for: \"%s\"\n\ +is not present in the backup keyring '%s'.\n" +de: "Der öffentliche Schlüssel von \"%s\"\n\ +ist nicht in der Schlüsselbund-Kopie '%s' enthalten.\n" +es: "La clave pública de: \"%s\"\n\ +no se encuentra en la copia de seguridad del anillo '%s'.\n" +fr: "La clé publique pour: \"%s\"\n\ +n'est pas présente dans le fichier de clés de sauvegarde '%s'.\n" +muttde: "Der öffentliche Schlüssel von \"%s\"\n\ +ist nicht in der Schlüsselbund-Kopie '%s' enthalten.\n" + +"\n\007WARNING: Secret key for: \"%s\"\n\ +does not match the key in the backup keyring '%s'.\n" +de: "\n\007WARNUNG: Der private Schlüssel von \"%s\"\n\ +entspricht nicht der Kopie im Schlüsselbund '%s'.\n" +es: "\n\007AVISO: La clave secreta de: \"%s\"\n\ +no coincide con la clave en la copia de seguridad del anillo '%s'.\n" +fr: "\n\007ATTENTION: la clé secrète pour: \"%s\"\n\ +ne correspond pas avec la clé dans le fichier de clés de sauvegarde.\n" +mutt: "\nWARNING: Secret key for: \"%s\"\n\ +does not match the key in the backup keyring '%s'.\n" +muttde: "\nWARNUNG: Der private Schlüssel von \"%s\"\n\ +entspricht nicht der Kopie im Schlüsselbund '%s'.\n" + +"\nMake a determination in your own mind whether this key actually\n\ +belongs to the person whom you think it belongs to, based on available\n\ +evidence. If you think it does, then based on your estimate of\n\ +that person's integrity and competence in key management, answer\n\ +the following question:\n" +de: "\nEntscheide für Dich, ob dieser Schlüssel tatsächlich zu dieser Person gehört.\n\ +Triff diese Entscheidung unter Berücksichtigung der zur Verfügung stehenden\n\ +Informationen. Wenn Du glaubst, daß der Schlüssel echt ist, dann beantworte\n\ +folgende Frage aufgrund Deiner Einschätzung der Vertrauenswürdigkeit der\n\ +Person und ihrer Kompetenz beim Umgang mit PGP-Schlüsseln:\n" +es: "\nDecide tú mismo si esta clave realmente pertenece, según la\n\ +evidencia a tu alcance, a la persona que crees. Si es así,\n\ +contesta a la siguiente pregunta, basándote en tu estimación de la\n\ +integridad de esa persona y de su conocimiento sobre gestión de claves:\n" +fr: "\nDeterminez vous même si cette clé appartient vraiment à la personne\ +\nà qui vous croyez qu'elle appartient, selon les informations disponibles.\ +\nSi vous le croyez, alors selon votre estimation de l'intégrité de cette\ +\npersonne et de sa compétence dans la gestion de clés, répondez à la\ +\nquestion suivante:\n" +muttde: "\nEntscheide für Dich, ob dieser Schlüssel tatsächlich zu dieser Person gehört.\n\ +Triff diese Entscheidung unter Berücksichtigung der zur Verfügung stehenden\n\ +Informationen. Wenn Du glaubst, daß der Schlüssel echt ist, dann beantworte\n\ +folgende Frage aufgrund Deiner Einschätzung der Vertrauenswürdigkeit der\n\ +Person und ihrer Kompetenz beim Umgang mit PGP-Schlüsseln:\n" + +"\nWould you trust \"%s\"\n\ +to act as an introducer and certify other people's public keys to you?\n\ +(1=I don't know. 2=No. 3=Usually. 4=Yes, always.) ? " +de: "\nWürdest Du \"%s\" als 'Einführer'\n\ +und 'Beglaubiger' für die öffentlichen Schlüssel Dritter vertrauen?\n\ +(1=Ich weiß nicht; 2=Nein; 3=In der Regel; 4=Ja, immer) : " +es: "\n¿Confiarías en \"%s\"\n\ +como referencia y para certificar ante ti otras claves públicas?\n\ +(1=No sé. 2=No. 3=Normalmente. 4=Sí, siempre.) ? " +fr: "\nAuriez vous confiance en \"%s\"\n\ +pour servir d'introducteur et certifier pour vous les clés publiques d'autres\ +\npersonnes? (1=Ne sais pas. 2=Non. 3=Généralement. 4=Oui, toujours.) ? " +muttde: "\nWürdest Du \"%s\" als 'Einführer'\n\ +und 'Beglaubiger' für die öffentlichen Schlüssel Dritter vertrauen?\n\ +(1=Ich weiß nicht; 2=Nein; 3=In der Regel; 4=Ja, immer) : " + +"This user is untrusted to certify other keys.\n" +de: "Dieser Benutzer ist nicht vertrauenswürdig genug,\n\ +um andere Schlüssel zu beglaubigen.\n" +es: "Este usuario no es fiable para certificar otras claves.\n" +fr: "Cet utilisateur n'est pas de confiance pour certifier d'autres clés.\n" +muttde: "Dieser Benutzer ist nicht vertrauenswürdig genug,\n\ +um andere Schlüssel zu beglaubigen.\n" + +"This user is generally trusted to certify other keys.\n" +de: "Dieser Benutzer ist in der Regel vertrauenswürdig genug,\n\ +um andere Schlüssel zu beglaubigen.\n" +es: "Este usuario es de relativa confianza para certificar otras claves.\n" +fr: "Cet utilisateur est généralement de confiance pour certifier d'autres\ + clés.\n" +muttde: "Dieser Benutzer ist in der Regel vertrauenswürdig genug,\n\ +um andere Schlüssel zu beglaubigen.\n" + +"This user is completely trusted to certify other keys.\n" +de: "Dieser Benutzer ist immer vertrauenswürdig genug,\n\ +um andere Schlüssel zu beglaubigen.\n" +es: "Este usuario es de completa confianza para certificar otras claves.\n" +fr: "Cet utilisateur est de confiance totale pour certifier d'autres clés.\n" +muttde: "Dieser Benutzer ist immer vertrauenswürdig genug,\n\ +um andere Schlüssel zu beglaubigen.\n" + +"This axiomatic key is ultimately trusted to certify other keys.\n" +de: "Dieser Schlüssel ist definitionsgemäß vertrauenswürdig genug,\n\ +um andere Schlüssel zu beglaubigen.\n" +es: "Esta clave axiomática es absolutamente fiable para certificar otras.\n" +fr: "Cette clé axiomatique est de confiance ultime pour certifier\n\ +d'autres clés\n" +muttde: "Dieser Schlüssel ist definitionsgemäß vertrauenswürdig genug,\n\ +um andere Schlüssel zu beglaubigen.\n" + +"This key/userID association is not certified.\n" +de: "Diese Schlüssel-/Benutzer-Zuordnung ist nicht bestätigt.\n" +es: "Esta asociación clave/usuario no está certificada.\n" +fr: "Cette association clé/utilisateur n'est pas certifiée.\n" +muttde: "Diese Schlüssel-/Benutzer-Zuordnung ist nicht bestätigt.\n" + +"This key/userID association is marginally certified.\n" +de: "Diese Schlüssel-/Benutzer-Zuordnung ist teilweise bestätigt.\n" +es: "Esta asociación clave/usuario está relativamente certificada.\n" +fr: "Cette association clé/utilisateur est marginalement certifiée.\n" +muttde: "Diese Schlüssel-/Benutzer-Zuordnung ist teilweise bestätigt.\n" + +"This key/userID association is fully certified.\n" +de: "Diese Schlüssel-/Benutzer-Zuordnung ist voll bestätigt.\n" +es: "Esta asociación clave/usuario está completamente certificada.\n" +fr: "Cette association clé/utilisateur est complètement certifiée.\n" +muttde: "Diese Schlüssel-/Benutzer-Zuordnung ist voll bestätigt.\n" + +" Questionable certification from:\n " +de: " Fragwürdige Beglaubigung von:\n " +es: " Certificación cuestionable de:\n " +fr: " Certificat de confiance douteux de:\n " +muttde: " Fragwürdige Beglaubigung von:\n " + +" Untrusted certification from:\n " +de: " Unglaubwürdige Beglaubigung von:\n " +es: " Certificación no fiable de:\n " +fr: " Certificat non sûr de:\n " +muttde: " Unglaubwürdige Beglaubigung von:\n " + +" Generally trusted certification from:\n " +de: " Glaubwürdige Beglaubigung von:\n " +es: " Certificación relativamente fiable de:\n " +fr: " Certificat de confiance relatif de:\n " +muttde: " Glaubwürdige Beglaubigung von:\n " + +" Completely trusted certification from:\n " +de: " Voll glaubwürdige Beglaubigung von:\n " +es: " Certificación completamente fiable de:\n " +fr: " Certificat de confiance complet de:\n " +muttde: " Voll glaubwürdige Beglaubigung von:\n " + +" Axiomatically trusted certification from:\n " +de: " Definitionsgemäß glaubwürdige Beglaubigung von:\n " +es: " Certificación axiomáticamente fiable de:\n " +fr: " Certificat de confiance par axiome de:\n " +muttde: " Definitionsgemäß glaubwürdige Beglaubigung von:\n " + +"\nKey for user ID: %s\n" +de: "\nSchlüssel für Benutzer-ID \"%s\",\n" +es: "\nClave del usuario: %s\n" +fr: "\nClé pour le nom d'utilisateur: %s\n" +muttde: "\nSchlüssel für Benutzer-ID \"%s\",\n" + +"%d-bit key, key ID %s, created %s\n" +de: "%d-Bit-Schlüssel, Schlüssel-ID: %s, erzeugt am: %s.\n" +es: "Clave de %d bits, con identificador %s, creada el %\n" +fr: "Clef de %d bits. Id clef %s créé %s\n" +muttde: "%d-Bit-Schlüssel, Schlüssel-ID: %s, erzeugt am: %s.\n" + +"Bad key format.\n" +de: "Falsches Schlüssel-Format.\n" +es: "Formato incorrecto de clave.\n" +fr: "Mauvais format de clé\n" +muttde: "Falsches Schlüssel-Format.\n" + +"Unrecognized version.\n" +de: "Unbekannte Version.\n" +es: "Versión no reconocida.\n" +fr: "Version non reconnue.\n" +muttde: "Unbekannte Version.\n" + +"Key is disabled.\n" +de: "Der Schlüssel ist gesperrt.\n" +es: "La clave está desactivada.\n" +fr: "La clé est inactivée.\n" +muttde: "Der Schlüssel ist gesperrt.\n" + +"Also known as: %s\n" +de: "Alternative Benutzer-ID: %s\n" +es: "También conocido como: %s\n" +fr: "Egalement connu(e) en tant que: %s\n" +muttde: "Alternative Benutzer-ID: %s\n" + +" Certified by: " +de: " Beglaubigt von: " +es: " Certificado por: " +fr: " Certifiée par: " +muttde: " Beglaubigt von: " + +"\nWarning: keyid %4d/%s %s has no user id!\n" +de: "\nWARNUNG: Der Schlüssel '%4d/%s %s' hat keine Benutzer-ID!\n" +es: "\nAdvertencia: la clave %4d/%s %s no tiene identificador\n" +fr: "\nAttention: IDclef %4d/%s %s n'est pas associé à un utilisateur!\n" +muttde: "\nWARNUNG: Der Schlüssel '%4d/%s %s' hat keine Benutzer-ID!\n" + +"Updated keyID: 0x%s\n" +de: "Die Schlüssel-ID 0x%s wurde aktualisiert.\n" +es: "Identificador actualizado: 0x%s\n" +fr: "Mise à jour de la clef ID: 0x%s\n" +muttde: "Die Schlüssel-ID 0x%s wurde aktualisiert.\n" + +"\n\007Unable to create key file '%s'.\n" +de: "\n\007FEHLER beim Erzeugen der Schlüsseldatei '%s'.\n" +es: "\n\007No puede crearse el fichero de claves '%s'.\n" +fr: "\n\007Impossible de créer le fichier de clés '%s'.\n" +mutt: "\nUnable to create key file '%s'.\n" +muttde: "\nFEHLER beim Erzeugen der Schlüsseldatei '%s'.\n" + +"\n\007Keyring file '%s' does not exist. " +de: "\n\007Der Schlüsselbund '%s' existiert nicht.\n" +es: "\n\007El anillo '%s' no existe. " +fr: "\n\007Le fichier de clés '%s' n'existe pas. " +mutt: "\nKeyring file '%s' does not exist. " +muttde: "\nDer Schlüsselbund '%s' existiert nicht.\n" + +"\n\007Sorry, this key has been revoked by its owner.\n" +de: "\n\007Dieser Schlüssel wurde von seinem Besitzer zurückgezogen.\n" +es: "\n\007Esa clave ha sido revocada por su propietario.\n" +fr: "\n\007Désolé, cette clé a été révoquée par son propriétaire.\n" +mutt: "\nSorry, this key has been revoked by its owner.\n" +muttde: "\nDieser Schlüssel wurde von seinem Besitzer zurückgezogen.\n" + +"\nKey for user ID \"%s\"\n\ +has been revoked. You cannot use this key.\n" +de: "\nDer Schlüssel von \"%s\"\n\ +wurde zurückgezogen und kann nicht verwendet werden.\n" +es: "\nLa clave del usuario \"%s\"\n\ +ha sido revocada. No puede utilizarse.\n" +fr: "\nLa clé pour l'utilisateur \"%s\"\n\ +a été révoquée. Vous ne pouvez utiliser cette clé.\n" +muttde: "\nDer Schlüssel von \"%s\"\n\ +wurde zurückgezogen und kann nicht verwendet werden.\n" + +"\n\007Key matching expected Key ID %s not found in file '%s'.\n" +de: "\n\007Der zur erwarteten Schlüssel-ID %s passende Schlüssel\n\ +ist nicht in der Datei '%s' enthalten.\n" +es: "\n\007Se esperaba una clave %s que no se encuentra en '%s'.\n" +fr: "\n\007Clé correspondant à l'identificateur %s non trouvée\ +\ndans le fichier '%s'.\n" +mutt: "\nKey matching expected Key ID %s not found in file \n\ +'%s'.\n" +muttde: "\nDer zur erwarteten Schlüssel-ID %s passende Schlüssel\n\ +ist nicht in der Datei '%s' enthalten.\n" + +"\n\007Key matching userid '%s' not found in file '%s'.\n" +de: "\n\007Der zur Benutzer-ID \"%s\" passende Schlüssel\n\ +ist nicht in der Datei '%s' enthalten.\n" +es: "\n\007La clave del usuario '%s' no se encuentra en el fichero '%s'.\n" +fr: "\n\007Clé correspondant à l'utilisateur '%s' introuvable\n\ +dans le fichier '%s'.\n" +mutt: "\nKey matching userid '%s' not found in file '%s'.\n" +muttde: "\nDer zur Benutzer-ID \"%s\" passende Schlüssel\n\ +ist nicht in der Datei '%s' enthalten.\n" + +"Enter secret key filename: " +de: "Dateiname des privaten Schlüssels: " +es: "Introduzca el nombre del anillo de claves secretas: " +fr: "Entrez le nom du fichier de clés secrètes: " +muttde: "Dateiname des privaten Schlüssels: " + +"Enter public key filename: " +de: "Dateiname des öffentlichen Schlüssels: " +es: "Introduzca el nombre del anillo de claves públicas: " +fr: "Entrez le nom du fichier de clés publiques: " +muttde: "Dateiname des öffentlichen Schlüssels: " + +"\nYou need a pass phrase to unlock your RSA secret key. " +de: "\nDu brauchst ein Mantra, um Deinen privaten RSA-Schlüssel zu benutzen." +es: "\nSe necesita la contraseña para abrir la clave secreta RSA. " +fr: "\nVous devez avoir un mot de passe pour utiliser votre clé secrète RSA." +muttde: "\nDu brauchst ein Mantra, um Deinen privaten RSA-Schlüssel zu benutzen." + +"No passphrase; secret key unavailable.\n" +de: "\nKein Mantra; der private Schlüssel kann nicht benutzt werden.\n" +es: "\nSin contraseña; la clave secreta no está disponible.\n" +fr: "\nSans la phrase secrete, pas de clef secrete disponible.\n" +muttde: "\nKein Mantra; der private Schlüssel kann nicht benutzt werden.\n" + +"\nAdvisory warning: This RSA secret key is not protected by a \ +passphrase.\n" +de: "\nHinweis: Dieser private Schlüssel ist nicht durch ein Mantra geschützt.\n" +es: "\nAdvertencia: Esta clave secreta RSA no tiene contraseña.\n" +fr: "\nAttention: cette clé secrète RSA n'est pas protégée par un mot \ +de passe" +muttde: "\nHinweis: Dieser private Schlüssel ist nicht durch ein Mantra geschützt.\n" + +"Pass phrase is good. " +de: "\nDas Mantra ist richtig.\n" +es: "La contraseña es correcta. " +fr: "Le mot de passe est correct. " +muttde: "\nDas Mantra ist richtig.\n" + +"\n\007Key file '%s' is not a secret key file.\n" +de: "\n\007Die Datei '%s' ist keine private Schlüsseldatei.\n" +es: "\n\007El fichero '%s' no tiene claves secretas.\n" +fr: "\n\007Le fichier de clé '%s' n'est pas un fichier de clés secrètes.\n" +mutt: "\nKey file '%s' is not a secret key file.\n" +muttde: "\nDie Datei '%s' ist keine private Schlüsseldatei.\n" + +"Key fingerprint =" +de: "Fingerabdruck des Schlüssels:" +es: "Huella dactilar =" +fr: "Empreinte de la clé =" +muttde: "Fingerabdruck des Schlüssels:" + +"\nKey ring: '%s'" +de: "\nSchlüsselbund '%s':\n" +es: "\nAnillo de claves: '%s',\n" +fr: "\nFichier de clé: '%s'" +muttde: "\nSchlüsselbund '%s':\n" + +", looking for user ID \"%s\"." +de: "Suche nach Benutzer-ID \"%s\":\n" +es: "buscando el usuario \"%s\"\n" +fr: ", recherche du nom d'utilisateur \"%s\"." +muttde: "Suche nach Benutzer-ID \"%s\":\n" + +"1 matching key found.\n" +de: "Es wurde ein passender Schlüssel gefunden.\n" +es: "Se ha encontrado una clave.\n" +fr: "1 clef trouvée.\n" +muttde: "Es wurde ein passender Schlüssel gefunden.\n" + +"%d matching keys found.\n" +de: "Es wurden %d passende Schlüssel gefunden.\n" +es: "Se han encontrado %d claves.\n" +fr: "%d clefs trouvées. \n" +muttde: "Es wurden %d passende Schlüssel gefunden.\n" + +"\nChecking signatures...\n" +de: "\nÜberprüfung der Unterschriften...\n" +es: "\nComprobando las firmas...\n" +fr: "\nVérification des signatures...\n" +muttde: "\nÜberprüfung der Unterschriften...\n" + +"*** KEY REVOKED ***\n" +de: "*** ZURÜCKGEZOGEN ***\n" +es: "*** CLAVE REVOCADA ***\n" +fr: "*** CLEF REVOQUÉE ***\n" +muttde: "*** ZURÜCKGEZOGEN ***\n" + +"(Unknown signator, can't be checked)" +de: "(Unterschreibender unbekannt, keine Prüfung)" +es: "(Firmante desconocido, no puede comprobarse)" +fr: "(Signataire inconnu, ne peut être vérifié)" +muttde: "(Unterschreibender unbekannt, keine Prüfung)" + +"(Key too long, can't be checked)" +de: "(Schlüssel zu lang, keine Prüfung)" +es: "(Clave demasiado larga, no puede comprobarse)" +fr: "(Clef trop longue, ne peut etre verifiée)" +muttde: "(Schlüssel zu lang, keine Prüfung)" + +"(Malformed or obsolete signature format)" +de: "(Unterschriftsformat fehlerhaft oder veraltet)" +es: "(Formato de firma obsoleto o incorrecto)" +fr: "(Signature malformee ou obsolete)" +muttde: "(Unterschriftsformat fehlerhaft oder veraltet)" + +"(Unknown public-key algorithm)" +de: "(unbek. Algorithmus des öffentl. Schlüssels)" +es: "(Algoritmo desconocido de clave pública)" +fr: "(Algorithme de clef publique inconnu)" +muttde: "(unbek. Algorithmus des öffentl. Schlüssels)" + +"(Unknown hash algorithm)" +de: "(unbekannter Prüfsummen-Algorithmus)" +es: "(Algoritmo desconocido de distribución [hash])" +fr: "(Algorithme de hash inconnu)" +muttde: "(unbekannter Prüfsummen-Algorithmus)" + +"(Unknown signature packet version)" +de: "(unbekannte Version des Unterschriftsblocks)" +es: "(Versión desconocida de firma)" +fr: "Version de paquet de signature inconnue" +muttde: "(unbekannte Version des Unterschriftsblocks)" + +"(Malformed signature)" +de: "(fehlerhafte Unterschrift)" +es: "(Firma mal formada)" +fr: "(Signature deformée)" +muttde: "(fehlerhafte Unterschrift)" + +"(Corrupted signature packet)" +de: "(beschädigter Unterschriftsblock)" +es: "(Firma dañada)" +fr: "(Signature corrompue)" +muttde: "(beschädigter Unterschriftsblock)" + +"\007**** BAD SIGNATURE! ****" +de: "\007**** FALSCHE UNTERSCHRIFT! ****" +es: "\007**** FIRMA INCORRECTA ****" +fr: "\007**** MAUVAISE SIGNATURE! ****" +mutt: "**** BAD SIGNATURE! ****" +muttde: "**** FALSCHE UNTERSCHRIFT! ****" + +"Remove bad signatures (Y/n)? " +de: "Falsche Unterschriften löschen? (J/n) " +es: "¿Suprimir las firmas incorrectas (S/n)? " +fr: "Supprimer les mauvaises signatures (O/n)? " +muttde: "Falsche Unterschriften löschen? (J/n) " + +"\nRemoving signatures from userid '%s' in key ring '%s'\n" +de: "\nLöschen der Unterschriften unter ID \"%s\"\naus dem Schlüsselbund '%s'.\n" +es: "\nSuprimiendo las firmas del usuario '%s' del\n\ +anillo de claves '%s'\n" +fr: "\nSuppression des signatures de l'utilisateur '%s'\n\ +dans le fichier de clés '%s'\n" +muttde: "\nLöschen der Unterschriften unter ID \"%s\"\naus dem Schlüsselbund '%s'.\n" + +"\n\007Key not found in key ring '%s'.\n" +de: "\n\007Der Schlüssel ist nicht im Schlüsselbund\n'%s' enthalten.\n" +es: "\n\007No se ha encontrado la clave en el anillo '%s'.\n" +fr: "\n\007Clé introuvable dans le fichier de clés '%s'.\n" +mutt: "\nKey not found in key ring '%s'.\n" +muttde: "\nDer Schlüssel ist nicht im Schlüsselbund\n'%s' enthalten.\n" + +"\nKey has no signatures to remove.\n" +de: "\nDer Schlüssel trägt keine Unterschriften, die gelöscht werden könnten.\n" +es: "\nLa clave no tiene ninguna firma por borrar.\n" +fr: "\nLa clé n'a pas de signatures à supprimer.\n" +muttde: "\nDer Schlüssel trägt keine Unterschriften, die gelöscht werden könnten.\n" + +"\nKey has %d signature(s):\n" +de: "\nDer Schlüssel trägt %d Unterschrift(en):\n" +es: "\nLa clave tiene %d firma(s):\n" +fr: "\nLa clé a %d signature(s):\n" +muttde: "\nDer Schlüssel trägt %d Unterschrift(en):\n" + +"(Unknown signator, can't be checked)\n" +de: "(Unterschreibender unbekannt, keine Prüfung)\n" +es: "(Firmante desconocido, no puede comprobarse)\n" +fr: "(Signataire inconnu, ne peut être vérifié)\n" +muttde: "(Unterschreibender unbekannt, keine Prüfung)\n" + +"Remove this signature (y/N)? " +de: "Diese Unterschrift löschen? (j/N) " +es: "\277Suprimir esta firma (s/N)? " +fr: "Suppression de cette signature (o/N)? " +muttde: "Diese Unterschrift löschen? (j/N) " + +"\nNo key signatures removed.\n" +de: "\nKeine Unterschriften gelöscht.\n" +es: "\nNo se ha suprimido ninguna firma.\n" +fr: "\nPas de supression de signature de clé.\n" +muttde: "\nKeine Unterschriften gelöscht.\n" + +"\n%d key signature(s) removed.\n" +de: "\n%d Unterschrift(en) gelöscht.\n" +es: "\nSuprimidas %d firma(s) de clave.\n" +fr: "\n%d signature(s) de clé supprimée(s).\n" +muttde: "\n%d Unterschrift(en) gelöscht.\n" + +"\nRemoving from key ring: '%s'" +de: "\nLöschen aus Schlüsselbund '%s'\n" +es: "\nSuprimiendo del anillo: '%s'" +fr: "\nSuppression du ficher de clés: '%s'" +muttde: "\nLöschen aus Schlüsselbund '%s'\n" + +", userid \"%s\".\n" +de: "Benutzer-ID \"%s\".\n" +es: ", identificador \"%s\".\n" +fr: ", utilisateur \"%s\".\n" +muttde: "Benutzer-ID \"%s\".\n" + +"\nKey has more than one user ID.\n\ +Do you want to remove the whole key (y/N)? " +de: "\nDer Schlüssel hat mehr als eine Benutzer-ID.\n\ +Soll der ganze Schlüssel vollständig gelöscht werden? (j/N) " +es: "\nLa clave tiene más de un identificador de usuario.\n\ +¿Quieres suprimirla por completo (s/N)? " +fr: "\nLa clé a plus d'un nom d'utilisateur.\n\ +Voulez vous supprimier toute la clé (o/N)? " +muttde: "\nDer Schlüssel hat mehr als eine Benutzer-ID.\n\ +Soll der ganze Schlüssel vollständig gelöscht werden? (j/N) " + +"\nNo more user ID's\n" +de: "\nKeine weiteren Benutzer-IDs.\n" +es: "\nNo hay más identificadores de usuario\n" +fr: "\nPlus de noms d'utilisateur\n" +muttde: "\nKeine weiteren Benutzer-IDs.\n" + +"Remove \"%s\" (y/N)? " +de: "\"%s\" löschen? (j/N) " +es: "¿Suprimir \"%s\" (s/N)? " +fr: "Supprimer \"%s\" (o/N)? " +muttde: "\"%s\" löschen? (j/N) " + +"\nAre you sure you want this key removed (y/N)? " +de: "\nBist Du sicher, daß Du diesen Schlüssel löschen willst? (j/N) " +es: "\n¿Estás seguro de querer suprimir esta clave (s/N)? " +fr: "\nEtes vous sûr(e) de vouloir supprimer cette clé (o/N)? " +muttde: "\nBist Du sicher, daß Du diesen Schlüssel löschen willst? (j/N) " + +"\nUser ID removed from key ring.\n" +de: "\nDie Benutzer-ID wurde aus dem Schlüsselbund gelöscht.\n" +es: "\nIdentificador suprimido del anillo.\n" +fr: "\nNom d'utilisateur supprimé du fichier de clés.\n" +muttde: "\nDie Benutzer-ID wurde aus dem Schlüsselbund gelöscht.\n" + +"\nKey removed from key ring.\n" +de: "\nDer Schlüssel wurde aus dem Schlüsselbund gelöscht.\n" +es: "\nClave suprimida del anillo.\n" +fr: "\nClé supprimée du fichier de clés.\n" +muttde: "\nDer Schlüssel wurde aus dem Schlüsselbund gelöscht.\n" + +"\nKey or user ID is also present in secret keyring.\n\ +Do you also want to remove it from the secret keyring (y/N)? " +de: "\nDer Schlüssel oder die Benutzer-ID sind auch im privaten Schlüsselbund\n\ +enthalten. Sollen sie dort ebenfalls gelöscht werden? (j/N) " +es: "\nEl identificador se encuentra además en el anillo de\n\ +claves secretas. ¿Quieres borrarlo también de ahí (s/N)? " +fr: "\nLa clé ou le nom d'utilisateur est aussi dans le fichier de clés\n\ +secrètes. Voulez vous l'enlever du ficher de clés secrètes (o/N)? " +muttde: "\nDer Schlüssel oder die Benutzer-ID sind auch im privaten Schlüsselbund\n\ +enthalten. Sollen sie dort ebenfalls gelöscht werden? (j/N) " + +"\nExtracting from key ring: '%s'" +de: "\nExtrahieren aus dem Schlüsselbund: '%s'\n" +es: "\nExtrayendo del anillo de claves: '%s'" +fr: "\nExtraction du fichier de clés: '%s'" +muttde: "\nExtrahieren aus dem Schlüsselbund: '%s'\n" + +"Extract the above key into which file?" +de: "Dateiname dieses extrahierten Schlüssels?" +es: "¿En qué fichero se extrae la clave anterior?" +fr: "Extraire la clef suivant de quel fichier?" +muttde: "Dateiname dieses extrahierten Schlüssels?" + +"Key ID %s is already included in key ring '%s'.\n" +de: "Die Schlüssel-ID %s ist bereits im Schlüsselbund\n'%s' enthalten.\n" +es: "La clave %s ya está en el anillo '%s'.\n" +fr: "L'identificateur de clé %s est déjà présent\ +\ndans le fichier de clés '%s'.\n" +muttde: "Die Schlüssel-ID %s ist bereits im Schlüsselbund\n'%s' enthalten.\n" + +"\nKey extracted to file '%s'.\n" +de: "\nSchlüssel extrahiert in Datei '%s'.\n" +es: "\nClave extraída en el fichero '%s'.\n" +fr: "\nClé mise dans le fichier '%s'.\n" +muttde: "\nSchlüssel extrahiert in Datei '%s'.\n" + +"\nThis operation may not be performed on a secret keyring.\n\ +Defaulting to public keyring." +de: "\nDiese Operation kann mit einem privaten Schlüsselbund nicht ausgeführt\n\ +werden. Der öffentliche Schlüsselbund wird versucht." +es: "\nEsta operacion no puede realizarse sobre el anillo de \ +claves secretas.\nSe pasa al anillo de claves públicas." +fr: "\nCette operation ne peut pas être effectuée sur un fichier de clés\n\ +secrètes. Le fichier de clés publiques sera utilisé à la place." +muttde: "\nDiese Operation kann mit einem privaten Schlüsselbund nicht ausgeführt\n\ +werden. Der öffentliche Schlüsselbund wird versucht." + +"\nEditing userid \"%s\" in key ring: '%s'.\n" +de: "\nBearbeitung der Benutzer-ID \"%s\"\nim Schlüsselbund '%s'.\n" +es: "\nModificación del identificador \"%s\"\n\ +en el anillo: '%s'.\n" +fr: "\nModification du nom d'utilsateur \"%s\"\n\ +dans le fichier de clé: '%s'.\n" +muttde: "\nBearbeitung der Benutzer-ID \"%s\"\nim Schlüsselbund '%s'.\n" + +"\nCan't open public key ring file '%s'\n" +de: "\nFEHLER beim Öffnen des öffentlichen Schlüsselbunds '%s'.\n" +es: "\nNo puede abrirse el anillo de claves públicas '%s'\n" +fr: "\nOuverture du fichier de clés publiques '%s' impossible.\n" +muttde: "\nFEHLER beim Öffnen des öffentlichen Schlüsselbunds '%s'.\n" + +"\n\007File '%s' is not a public keyring.\n" +de: "\n\007Die Datei '%s' ist kein öffentlicher Schlüsselbund.\n" +es: "\n\007El fichero '%s' no es un anillo de claves públicas.\n" +fr: "\n\007Le fichier '%s' n'est pas un fichier de clés publiques.\n" +mutt: "\nFile '%s' is not a public keyring.\n" +muttde: "\nDie Datei '%s' ist kein öffentlicher Schlüsselbund.\n" + +"\n\007This key has been revoked by its owner.\n" +de: "\n\007Dieser Schlüssel wurde von seinem Besitzer zurückgezogen.\n" +es: "\n\007Esta clave ha sido revocada por su propietario.\n" +fr: "\n\007Cette clé a été révoquée par son propriétaire.\n" +mutt: "\nThis key has been revoked by its owner.\n" +muttde: "\nDieser Schlüssel wurde von seinem Besitzer zurückgezogen.\n" + +"\nNo secret key available. Editing public key trust parameter.\n" +de: "\nKein privater Schlüssel vorhanden. Die 'Vertrauens-Einstellungen' des\n\ +öffentlichen Schlüssels werden bearbeitet.\n" +es: "\nNo hay clave secreta disponible. Modificando el parámetro \ +de confianza\n\ +de la clave pública.\n" +fr: "\nPas de clé secrète disponible. Modification du paramètre de\n\ +confiance de la clé publique.\n" +muttde: "\nKein privater Schlüssel vorhanden. Die 'Vertrauens-Einstellungen' des\n\ +öffentlichen Schlüssels werden bearbeitet.\n" + +"Current trust for this key's owner is: %s\n" +de: "\nAktuelles 'Vertrauen' zum Besitzer dieses Schlüssels: %s\n" +es: "La confianza actual en el propietario de esta clave es: %s\n" +fr: "Le niveau de confiance courant pour le propriétaire de\n\ +cette clé est: %s\n" +muttde: "\nAktuelles 'Vertrauen' zum Besitzer dieses Schlüssels: %s\n" + +"Public key ring updated.\n" +de: "\nDer öffentliche Schlüsselbund wurde aktualisiert.\n" +es: "Actualizado el anillo de claves públicas.\n" +fr: "Fichier de clés publiques modifié...\n" +muttde: "\nDer öffentliche Schlüsselbund wurde aktualisiert.\n" + +"\nCurrent user ID: %s" +de: "\nAktuelle Benutzer-ID: %s" +es: "\nIdentificador actual de usuario: %s" +fr: "\nNom d'utilisateur courant: %s" +muttde: "\nAktuelle Benutzer-ID: %s" + +"\nDo you want to add a new user ID (y/N)? " +de: "\nWillst Du eine neue Benutzer-ID hinzufügen? (j/N) " +es: "\n¿Quieres añadir un nuevo identificador de usuario (s/N)? " +fr: "\nVoulez-vous ajouter ce nouvel ID (o/N)?" +muttde: "\nWillst Du eine neue Benutzer-ID hinzufügen? (j/N) " + +"\nEnter the new user ID: " +de: "\nGib die neue Benutzer-ID ein: " +es: "\nIntroduzca el nuevo identificador: " +fr: "\nEntrez le nouveau nom d'utilisateur: " +muttde: "\nGib die neue Benutzer-ID ein: " + +"\nMake this user ID the primary user ID for this key (y/N)? " +de: "\nSoll dies die vorrangige Benutzer-ID für diesen Schlüssel werden? (j/N) " +es: "\n¿Se establece este identificador como primario para esta clave (s/N)? " +fr: "\nEtablir ce nom d'utilisateur comme nom principal pour cette clé \ +(o/N)? " +muttde: "\nSoll dies die vorrangige Benutzer-ID für diesen Schlüssel werden? (j/N) " + +"\nDo you want to change your pass phrase (y/N)? " +de: "\nWillst Du Dein Mantra ändern? (j/N) " +es: "\n¿Quieres cambiar la contraseña (s/N)? " +fr: "\nVoulez vous changer votre mot de passe (o/N)? " +muttde: "\nWillst Du Dein Mantra ändern? (j/N) " + +"(No changes will be made.)\n" +de: "\n(Es werden keine Änderungen vorgenommen.)\n" +es: "(No se efectuará ningún cambio.)\n" +fr: "(Aucun changement ne sera effectué.)\n" +muttde: "\n(Es werden keine Änderungen vorgenommen.)\n" + +"\n\007Unable to update secret key ring.\n" +de: "\n\007FEHLER beim Aktualisieren des privaten Schlüsselbunds.\n" +es: "\n\007No puede actualizarse el anillo de claves secretas.\n" +fr: "\n\007Impossible de modifier le fichier de clés secrètes.\n" +mutt: "\nUnable to update secret key ring.\n" +muttde: "\nFEHLER beim Aktualisieren des privaten Schlüsselbunds.\n" + +"\nSecret key ring updated...\n" +de: "\n\nDer private Schlüsselbund wurde aktualisiert.\n" +es: "\nActualizado el anillo de claves secretas ...\n" +fr: "\nFichier de clés secrètes modifié...\n" +muttde: "\n\nDer private Schlüsselbund wurde aktualisiert.\n" + +"\n\007Unable to update public key ring.\n" +de: "\n\007FEHLER beim Aktualisieren des öffentlichen Schlüsselbunds.\n" +es: "\n\007No puede actualizarse el anillo de claves públicas.\n" +fr: "\n\007Impossible de modifier le fichier de clés publiques.\n" +mutt: "\nUnable to update public key ring.\n" +muttde: "\nFEHLER beim Aktualisieren des öffentlichen Schlüsselbunds.\n" + +"(No need to update public key ring)\n" +de: "\n(Keine Änderung am öffentlichen Schlüsselbund notwendig.)\n" +es: "(No es necesario actualizar el anillo de claves públicas)\n" +fr: "(Pas besoin de modifier le fichier de clés publiques)\n" +muttde: "\n(Keine Änderung am öffentlichen Schlüsselbund notwendig.)\n" + +"\nDo you want to permanently revoke your public key\n\ +by issuing a secret key compromise certificate\n\ +for \"%s\" (y/N)? " +de: "\nWillst Du Deinen öffentlichen Schlüssel wirklich durch das Versenden\n\ +einer Widerrufs-Urkunde für \"%s\"\nzurückziehen, d.h. für ungültig erklären? (j/N) " +es: "\n¿Quieres revocar permanentemente tu clave pública\n\ +emitiendo un certificado de compromiso de clave secreta\n\ +para \"%s\" (s/N)? " +fr: "\nVoulez vous révoquer de façon permanente votre clé publique\n\ +en émettant un certificat de compromission de clé secrète\n\ +pour \"%s\" (o/N)? " +muttde: "\nWillst Du Deinen öffentlichen Schlüssel wirklich durch das Versenden\n\ +einer Widerrufs-Urkunde für \"%s\"\nzurückziehen, d.h. für ungültig erklären? (j/N) " + +"You can only disable keys on your public keyring.\n" +de: "Du kannst nur Schlüssel aus Deinem öffentlichen Schlüsselbund sperren.\n" +es: "Sólo puedes desactivar claves en el anillo de claves públicas.\n" +fr: "Vous ne pouvez inactiver des clés que sur votre fichier de clés\ +\npubliques.\n" +muttde: "Du kannst nur Schlüssel aus Deinem öffentlichen Schlüsselbund sperren.\n" + +"\nKey is already disabled.\n\ +Do you want to enable this key again (y/N)? " +de: "\nDieser Schlüssel ist schon gesperrt.\nMöchtest Du ihn wieder freigeben? (j/N) " +es: "\nLa clave ya está desactivada.\n\ +¿Quieres activarla otra vez (s/N)? " +fr: "\nLa clé est déjà inactivée.\n\ +Voulez vous réactiver cette clé (o/N)? " +muttde: "\nDieser Schlüssel ist schon gesperrt.\nMöchtest Du ihn wieder freigeben? (j/N) " + +"\nDisable this key (y/N)? " +de: "\nSoll dieser Schlüssel gesperrt werden? (j/N) " +es: "\n¿Desactivar esta clave (s/N)? " +fr: "Désactiver cette clé (o/N)? " +muttde: "\nSoll dieser Schlüssel gesperrt werden? (j/N) " + +"Pick your RSA key size:\n\ + 1) 1024 bits- User grade, fast but less secure\n\ + 2) 1535 bits- Regional CA grade, medium speed, good security\n\ + 3) 2048 bits- Root CA grade, slow, high security\n\ +Choose 1, 2, or 3, or enter desired number of bits (384...8192): " +de: "Wähle die Länge Deines RSA-Schlüssels aus:\ +\n 1) 1024 Bits: für Nutzer: schnell, aber nicht ganz so sicher\ +\n 2) 1535 Bits: für regionale CAs: mittelmäßig schnell, recht sicher\ +\n 3) 2048 Bits: für Root CAs: langsam, jedoch sehr sicher\ +\nAuswahl (1, 2, 3 oder die Länge des Schlüssels in Bits (384...8192)): " +es: "Elija un tamaño de clave RSA:\n\ + 1) 1024 bits- Nivel comercial bajo, r\0341pido pero menos seguro\n\ + 2) 1535 bits- Nivel comercial alto, velocidad media con buena seguridad\n\ + 3) 2048 bits- Nivel \"militar\", lento con alta seguridad\n\ +Escoge 1, 2, 3, o el número requerido de bits (384...8192): " +fr: "Choisissez la taille de votre clef RSA:\n\ + 1) 1024 bits- Niveau de base, rapide mais moins securitaire\n\ + 2) 1535 bits- Niveau de securité eleve - vitesse moyenne \n\ + 3) 2048 bits- Pour les militaires, les diplomates... les paranoiaques \n\ +Choisissez 1, 2, ou 3, ou entrez le nombre de bits desires (384...8192): " +muttde: "Wähle die Länge Deines RSA-Schlüssels aus:\ +\n 1) 1024 Bits: für Nutzer: schnell, aber nicht ganz so sicher\ +\n 2) 1535 Bits: für regionale CAs: mittelmäßig schnell, recht sicher\ +\n 3) 2048 Bits: für Root CAs: langsam, jedoch sehr sicher\ +\nAuswahl (1, 2, 3 oder die Länge des Schlüssels in Bits (384...8192)): " + +"Generating an RSA key with a %d-bit modulus.\n" +de: "Erzeugung eines RSA-Schlüssels mit einem %d-Bit-Modulus.\n" +es: "Generando una clave RSA con módulo de %d bits.\n" +fr: "Generation d'une clé RSA avec un module de %d bits.\n" +muttde: "Erzeugung eines RSA-Schlüssels mit einem %d-Bit-Modulus.\n" + +"\nYou need a user ID for your public key. The desired form for this\n\ +user ID is your name, followed by your E-mail address enclosed in\n\ +, if you have an E-mail address.\n\ +Form: Real Name (comment) (options)\n\ + Optional options: ENCR, SIGN, EXPIRE:yyyy-mm-dd\n\ +Enter a user ID for your public key: \n" +de: "\nDu brauchst eine Benutzer-ID für Deinen öffentlichen Schlüssel. Das übliche\n\ +Format für diese Benutzer-ID ist Dein Realname, gefolgt von Deinem Usernamen\n\ +in , falls Du per E-Mail erreichbar bist.\n\ +Format: Bürgerlicher Name (Kommentar) (Optionen)\n\ + Freiwillige Optionen: ENCR, SIGN, EXPIRE:yyyy-mm-dd\n\ +Gib die Benutzer-ID für Deinen öffentlichen Schlüssel ein:\n" +es: "\nNecesitas un identificador para tu clave pública. El formato preferido\n\ +consiste en tu nombre, seguido de tu dirección de correo electrónico,\n\ +si tienes, entre <ángulos>.\n\ +Form: Real Name (comment) (options)\n\ + Optional options: ENCR, SIGN, EXPIRE:yyyy-mm-dd\n\ +Introduce un identificador de usuario para tu clave pública: \n" +fr: "\nIl vous faut un nom d'utilisateur pour votre clé publique. La forme\n\ +désirée pour ce nom d'utilisateur est votre nom, suivi de votre addresse\n\ +de courrier électronique entre , si vous en avez une.\n\ +Form: Real Name (comment) (options)\n\ + Optional options: ENCR, SIGN, EXPIRE:yyyy-mm-dd\n\ +Entrez un nom d'utilisateur pour votre clé publique\n\ +(votre nom): " +muttde: "\nDu brauchst eine Benutzer-ID für Deinen öffentlichen Schlüssel. Das übliche\n\ +Format für diese Benutzer-ID ist Dein Realname, gefolgt von Deinem Usernamen\n\ +in , falls Du per E-Mail erreichbar bist.\n\ +Format: Bürgerlicher Name (Kommentar) (Optionen)\n\ + Freiwillige Optionen: ENCR, SIGN, EXPIRE:yyyy-mm-dd\n\ +Gib die Benutzer-ID für Deinen öffentlichen Schlüssel ein:\n" + +"Generating RSA key-pair with UserID \"%s\".\n" +de: "Erzeugung eines RSA-Schlüsselpaares mit der\nBenutzer-ID \"%s\".\n" +es: "Generando el par de claves RSA con identificador \"%s\".\n" +fr: "Génération d'une paire de clefs RSA de l'utilisateur '%s'.\n" +muttde: "Erzeugung eines RSA-Schlüsselpaares mit der\nBenutzer-ID \"%s\".\n" + +"\nYou need a pass phrase to protect your RSA secret key.\n\ +Your pass phrase can be any sentence or phrase and may have many\n\ +words, spaces, punctuation, or any other printable characters.\n" +de: "\nDu brauchst ein Mantra, um Deinen privaten RSA-Schlüssel zu schützen.\n\ +Dein Mantra kann jeder beliebige Satz oder Zeichenfolge sein und darf aus\n\ +vielen Worten, Leerzeichen oder anderen druckbaren Zeichen bestehen.\n" +es: "\nNecesitas una contraseña para proteger tu clave secreta RSA.\n\ +Puede ser cualquier expresión formada por varias palabras, espacios,\n\ +signos de puntuación o cualquier otro carácter imprimible.\n" +fr: "\nVous devez avoir un mot de passe pour protéger votre clé RSA \n\ +secrète. Votre mot de passe peut être n'importe quelle phrase ou portion\n\ +de phrase et peut avoir plusieurs mots, espaces, caractères de ponctuation\n\ +ou tout autre caractère imprimable.\n" +muttde: "\nDu brauchst ein Mantra, um Deinen privaten RSA-Schlüssel zu schützen.\n\ +Dein Mantra kann jeder beliebige Satz oder Zeichenfolge sein und darf aus\n\ +vielen Worten, Leerzeichen oder anderen druckbaren Zeichen bestehen.\n" + +"\nNote that key generation is a lengthy process.\n" +de: "\n\nBeachte, daß die Schlüsselerzeugung eine zeitaufwendige Sache ist.\n" +es: "\nTen en cuenta que la generación de claves es un proceso lento.\n" +fr: "\nNotez que la génération de clé est une procédure lente.\n" +muttde: "\n\nBeachte, daß die Schlüsselerzeugung eine zeitaufwendige Sache ist.\n" + +"Key generation stopped at user request.\n" +de: "Die Schlüsselerzeugung wurde durch den Benutzer abgebrochen!\n" +es: "Generación de claves interrumpida a petición del usuario.\n" +fr: "Le génération de la clef a été stoppée à la demande de l'utilisateur.\n" +muttde: "Die Schlüsselerzeugung wurde durch den Benutzer abgebrochen!\n" + +"\n\007Keygen failed!\n" +de: "\n\007FEHLER bei der Schlüssel-Erzeugung!\n" +es: "\n\007Error en la generación de claves\n" +fr: "\n\007Generation de clé non réussie!\n" +mutt: "\nKeygen failed!\n" +muttde: "\nFEHLER bei der Schlüssel-Erzeugung!\n" + +"Key ID %s\n" +de: "Schlüssel-ID: %s\n" +es: "Identificador de clave %s\n" +fr: "Identificateur de clé %s\n" +muttde: "Schlüssel-ID: %s\n" + +"Display secret components (y/N)?" +de: "Geheime Bestandteile anzeigen? (j/N) " +es: "¿Mostrar los componentes secretos (s/N)?" +fr: "Affichier les composantes secretes (o/N)?" +muttde: "Geheime Bestandteile anzeigen? (j/N) " + +"\007Key generation completed.\n" +de: "\n\007Die Erzeugung des Schlüssels ist beendet.\n" +es: "\007Finalizada la generación de claves.\n" +fr: "\007Génération de clé terminée.\n" +mutt: "Key generation completed.\n" +muttde: "\nDie Erzeugung des Schlüssels ist beendet.\n" + +"Type Bits/KeyID Date User ID\n" +de: "Typ Bits/KeyID Datum NutzerID\n" +es: "Tipo Bits/Clave Fecha Identificador\n" +fr: "Type Bits/Clef Date ID utilisateur\n" +muttde: "Typ Bits/KeyID Datum NutzerID\n" + +"\n\007File '%s' is not a text file; cannot display.\n" +de: "\n\007Die Datei '%s' ist keine Textdatei\n\ +und kann deshalb nicht angezeigt werden!\n" +es: "\n\007El fichero '%s' no es de texto; no puede mostrarse.\n" +fr: "\n\007Le fichier '%s' n'est pas un fichier de texte et ne peut être \ +visualisé\n" +mutt: "\nFile '%s' is not a text file; cannot display.\n" +muttde: "\nDie Datei '%s' ist keine Textdatei\n\ +und kann deshalb nicht angezeigt werden!\n" + +"\nDone...hit any key\r" +de: "\nFertig... Bitte drücke eine Taste.\r" +es: "\nFinalizado...pulse cualquier tecla\r" +fr: "\nTerminé... appuyez sur n'importe quelle touche \r" +muttde: "\nFertig... Bitte drücke eine Taste.\r" + +"-- More -- Space: next screen, Enter: next line\ +, 'B': back, 'Q': quit --\r" +de: "-- Weiter -- Vorwärts: Leertaste oder Return; Rückwärts: 'B'; Ende: 'Q' --\r" +es: "-- más -- espacio: otra pantalla, enter: otra línea,\ + B: atrás, Q: salir\r" +fr: "- Plus - Espace: procahin écran , Chariot: prochaine ligne\ +, 'B': retour, 'Q': quitter - \r" +muttde: "-- Weiter -- Vorwärts: Leertaste oder Return; Rückwärts: 'B'; Ende: 'Q' --\r" + +"More -- %d%% -- Space: next screen, Enter: next line\ +, 'B': back, 'Q': quit --\r" +de: "Weiter -- %d%% -- Vorwärts: Leertaste oder Return; Rückwärts: 'B'; Ende: 'Q' --\r" +es: "Más -- %d%% -- espacio: otra pantalla, enter: otra línea,\ + B: atrás, Q: salir\r" +fr: "- Plus - %D%% - Espace: procahin écran , Chariot: prochaine ligne\ +, 'B': retour, 'Q': quitter - \r" +muttde: "Weiter -- %d%% -- Vorwärts: Leertaste oder Return; Rückwärts: 'B'; Ende: 'Q' --\r" + +"\nEnter pass phrase: " +de: "\nGib das Mantra ein: " +es: "\nIntroduce la contraseña: " +fr: "\nEntrez votre mot de passe: " +muttde: "\nGib das Mantra ein: " + +"\nEnter same pass phrase again: " +de: "\nWiederhole das Mantra: " +es: "\nEscríbela otra vez: " +fr: "\nEntrez le même mot de passe de nouveau: " +muttde: "\nWiederhole das Mantra: " + +"\n\007Error: Pass phrases were different. Try again." +de: "\n\007FEHLER: Die beiden Eingaben waren unterschiedlich! Bitte noch einmal." +es: "\n\007Error: Las contraseñas son diferentes. Prueba otra vez." +fr: "\n\007Erreur: Les mots de passe étaient différents. Essayez encore." +mutt: "\nError: Pass phrases were different. Try again." +muttde: "\nFEHLER: Die beiden Eingaben waren unterschiedlich! Bitte noch einmal." + +"\nStopped at user request\n" +de: "\nAbbruch durch Benutzer\n" +es: "\nInterrupción por petición del usuario\n" +fr: "\nArrêt par demande de l'utilisateur\n" +muttde: "\nAbbruch durch Benutzer\n" + +"Pretty Good Privacy(tm) %s - Public-key encryption for the masses.\n" +de: "Pretty Good Privacy(tm) %s - Public-key-Verschlüsselung für die Massen.\n" +es: "Pretty Good Privacy(tm) %s - Criptografía de clave pública para todos.\n" +fr: "Pretty Good Privacy(tm) %s - Cryptographie à clé publique pour tous.\n" +muttde: "Pretty Good Privacy(tm) %s - Public-key-Verschlüsselung für die Massen.\n" + +"(c) 1990-96 Philip Zimmermann, Phil's Pretty Good Software." +de: "(c) 1990-96 Philip Zimmermann, Phil's Pretty Good Software." +es: "(c) 1990-96 Philip Zimmermann, Phil's Pretty Good Software." +fr: "( c ) 1990-96 Philip Zimmermann, Phil's Pretty Good Software. " +muttde: "(c) 1990-96 Philip Zimmermann, Phil's Pretty Good Software." + +"Export of this software may be restricted by the U.S. government.\n" +de: "Der Export dieser Software aus den USA kann Beschränkungen unterliegen.\n" +es: "La exportación de este programa puede estar restringida por\n\ +el gobierno de los EE.UU." +fr: "L'exportation de ce logiciel peut être restreint par le \ +gouvernement des États-Unis" +muttde: "Der Export dieser Software aus den USA kann Beschränkungen unterliegen.\n" + +"International version - not for use in the USA. Does not use RSAREF.\n" +de: "Internationale Version - nicht in den USA verwenden! Benutzt nicht RSAREF.\n" +es: "Versión internacional - no apta para los EE.UU. No utiliza RSAREF.\n" +fr: "Version internationale - ne pas utiliser aux Etats-Unis. N'utilise pas le +RSAREF.\n" +muttde: "Internationale Version - nicht in den USA verwenden! Benutzt nicht RSAREF.\n" + +"Current time: %s\n" +de: "Aktuelles Datum und Uhrzeit: %s\n" +es: "Hora actual: %s\n" +fr: "Heure actuelle: %s\n" +muttde: "Aktuelles Datum und Uhrzeit: %s\n" + +"\007No configuration file found.\n" +de: "\007Keine Konfigurationsdatei gefunden!\n" +es: "\007No se encuentra el fichero de configuración.\n" +fr: "\007Fichier de configuration introuvable.\n" +mutt: "No configuration file found.\n" +muttde: "Keine Konfigurationsdatei gefunden!\n" + +"\007WARNING: Environmental variable TZ is not \ +defined, so GMT timestamps\n\ +may be wrong. See the PGP User's Guide to properly define TZ\n\ +in AUTOEXEC.BAT file.\n" +de: "\007WARNUNG: Die Umgebungsvariable TZ ist nicht definiert, daher könnten\n\ +die GMT-Zeitangaben falsch sein. Beachte den Abschnitt in der PGP-Anleitung\n\ +über das richtige Setzen von TZ in AUTOEXEC.BAT.\n\n" +es: "\007ADVERTENCIA: La variable TZ no está definida, por lo que\n\ +los sellos de fecha GMT pueden estar equivocados. Consulta la Guía del\n\ +usuario de PGP para definir adecuadamente TZ en AUTOEXEC.BAT.\n" +fr: "\007ATTENTION: La variable d'environnement TZ n'est pas définie, les\n\ +temps GMT peuvent donc êtres faussés. Voir le guide de l'utilisateur PGP pour\n\ +définir correctement TZ dans le fichier AUTOEXEC.BAT.\n" +mutt: "WARNING: Environmental variable TZ is not \ +defined, so GMT timestamps\n\ +may be wrong. See the PGP User's Guide to properly define TZ\n\ +in AUTOEXEC.BAT file.\n" +muttde: "WARNUNG: Die Umgebungsvariable TZ ist nicht definiert, daher könnten\n\ +die GMT-Zeitangaben falsch sein. Beachte den Abschnitt in der PGP-Anleitung\n\ +über das richtige Setzen von TZ in AUTOEXEC.BAT.\n\n" + +"\nFile %s wiped and deleted. " +de: "\nDie Datei '%s' wurde überschrieben und gelöscht." +es: "\nEl fichero %s ha sido borrado y destruido. " +fr: "\nFichier %s effacé et détruit. " +muttde: "\nDie Datei '%s' wurde überschrieben und gelöscht." + +"\n\007Error: Can't wipe out file '%s' - read only, maybe?\n" +de: "\n\007FEHLER: Die Datei '%s' kann nicht überschrieben werden.\n\ +Ist sie vielleicht schreibgeschützt?\n" +es: "\n\007Error: No puede eliminarse el fichero '%s' - \ +quizá sea sólo de lectura\n" +fr: "\n\007Erreur: Incapable de détruire le fichier '%s' - lectu4re seulement peut être?\n" +mutt: "\nError: Can't wipe out file '%s' - read only, maybe?\n" +muttde: "\nFEHLER: Die Datei '%s' kann nicht überschrieben werden.\n\ +Ist sie vielleicht schreibgeschützt?\n" + +"\n\007File '%s' does not exist.\n" +de: "\n\007Die Datei '%s' existiert nicht!\n" +es: "\n\007El fichero '%s' no existe.\n" +fr: "Le fichier '%s' n'existe pas. \n" +mutt: "\nFile '%s' does not exist.\n" +muttde: "\nDie Datei '%s' existiert nicht!\n" + +"\nFor details on licensing and distribution, see the PGP User's Guide.\ +\nFor other cryptography products and custom development services, contact:\ +\nPhilip Zimmermann, 3021 11th St, Boulder CO 80304 USA, \ +phone +1 303 541-0140\n" +de: "\nInformationen über Lizenzen und Verteilung finden sich in der PGP-Anleitung.\ +\nInformationen über Verschlüsselungs-Produkte und Auftrags-Entwicklungen:\ +\nPhilip Zimmermann, 3021 11th St, Boulder CO 80304 USA, Tel. +1-(303)-541-0140\n" +es: "\nInformación sobre licencia y distribución en la Guía del usuario de PGP.\ +\nMás información sobre otros productos y servicios criptográficos a medida:\ +\nPhilip Zimmermann, 3021 11th St, Boulder CO 80304 USA, tel +1 303 541-0140\n" +fr: "\nConsulter le guide de l'utilisateur de PGP pour les détails de\n\ +license et de distribution. Pour d'autres produits de cryptographie\n\ +et services de développement personalisés, contacter: Philip Zimmermann,\n\ +3021 11th St, Boulder CO 80304 USA, téléphone +1 303 541-0140\n" +muttde: "\nInformationen über Lizenzen und Verteilung finden sich in der PGP-Anleitung.\ +\nInformationen über Verschlüsselungs-Produkte und Auftrags-Entwicklungen:\ +\nPhilip Zimmermann, 3021 11th St, Boulder CO 80304 USA, Tel. +1-(303)-541-0140\n" + +"@translator@" +de: "\nÜbersetzer:\ +\n Frank Pruefer ; Stand: 07.10.1997\ +\n (basierend auf der deutschen Übersetzung der LANGUAGE.TXT von\ +\n Marc Aurel <4-tea-2@bong.saar.de> vom 19.01.1994)\n" +es: "\nTraducido al castellano por Armando Ramos .\n" +fr: "\nTraduction française de Jean-loup Gailly et Yanik \ +Crépeau \n" +muttde: "\nÜbersetzer:\ +\n Frank Pruefer ; Stand: 07.10.1997\ +\n (basierend auf der deutschen Übersetzung der LANGUAGE.TXT von\ +\n Marc Aurel <4-tea-2@bong.saar.de> vom 19.01.1994)\n" + +"\nFor a usage summary, type: pgp -h\n" +de: "\nEine Übersicht der PGP-Befehle erhältst Du mit: pgp -h\n" +es: "\nPara ver un resumen de las instrucciones, escribe: pgp -h\n" +fr: "\nPour un sommaire d'utilisation, tapez: pgp -h\n" +mutt: " " +muttde: " " + +"File %s created containing %d random bytes.\n" +de: "\nDie Datei '%s', die %d Bytes Zufallszahlen enthält,\n\ +wurde erzeugt.\n" +es: "Generado el fichero %s con %d bytes aleatorios.\n" +fr: "Le fichier %s est créé et contient %d octets aléatoires" +muttde: "\nDie Datei '%s', die %d Bytes Zufallszahlen enthält,\n\ +wurde erzeugt.\n" + +"\007Invalid filename: '%s' too long\n" +de: "\007Ungültiger Dateiname: '%s' ist zu lang.\n" +es: "\007Nombre incorrecto: '%s' es demasiado largo\n" +fr: "\007Nom incalide: '%s' trop long\n" +mutt: "Invalid filename: '%s' too long\n" +muttde: "Ungültiger Dateiname: '%s' ist zu lang.\n" + +"\n\007Input file '%s' looks like it may have been created by PGP. " +de: "\n\007Die Eingabedatei '%s' könnte von PGP erzeugt worden sein." +es: "\n\007El fichero de entrada '%s' parece haber sido creado por PGP. " +fr: "\n\007Le fichier d'entrée '%s' semble avoir été créé par PGP. " +mutt: "\nInput file '%s' looks like it may have been created by PGP. " +muttde: "\nDie Eingabedatei '%s' könnte von PGP erzeugt worden sein." + +"\nIs it safe to assume that it was created by PGP (y/N)? " +de: "\nWurde diese von PGP erzeugt? (j/N) " +es: "\n¿Puede asumirse con seguridad que ha sido así (s/N)? " +fr: "\nEtes vous sûr qu'il a été créé par PGP (o/N)? " +muttde: "\nWurde diese von PGP erzeugt? (j/N) " + +"\nNote: '%s' is not a pure text file.\n\ +File will be treated as binary data.\n" +de: "\nHinweis: '%s' ist keine reine Textdatei.\n\ +Die Datei wird als Binärdatei behandelt.\n" +es: "\nNota: '%s' no es un fichero de texto puro.\n\ +Se tratará como datos binarios.\n" +fr: "Note: '%s' n'est pas un fichier texte. \n\ +Il sera traité comme données binaires" +muttde: "\nHinweis: '%s' ist keine reine Textdatei.\n\ +Die Datei wird als Binärdatei behandelt.\n" + +"\n\007Error: Only text files may be sent as display-only.\n" +de: "\n\007FEHLER: Nur Textdateien können \"nur zur Ansicht\" verschickt werden.\n" +es: "\n\007Error: Sólo los ficheros de texto pueden enviarse para mostrar.\n" +fr: "\n\007Erreur: seuls les fichiers de texte peuvent être envoyés\n\ +pour affichage exclusivement.\n" +mutt: "\nError: Only text files may be sent as display-only.\n" +muttde: "\nFEHLER: Nur Textdateien können \"nur zur Ansicht\" verschickt werden.\n" + +"\n\007Error: MacBinary failed!\n" +de: "\n\007FEHLER: MacBinary fehlgeschlagen!\n" +es: "\n\007Error: ha fallado MacBinary\n" +fr:"\n\007Erreur:MacBinary a échoué\n" +mutt: "\nError: MacBinary failed!\n" +muttde: "\nFEHLER: MacBinary fehlgeschlagen!\n" + +"\nA secret key is required to make a signature. " +de: "\nFür eine Unterschrift wird ein privater Schlüssel benötigt." +es: "\nSe necesita una clave secreta para generar la firma. " +fr: "\nUne clé secrète est nécessaire pour faire une signature. " +muttde: "\nFür eine Unterschrift wird ein privater Schlüssel benötigt." + +"\nYou specified no user ID to select your secret key,\n\ +so the default user ID and key will be the most recently\n\ +added key on your secret keyring.\n" +de: "\nDa Du keine Benutzer-ID für Deinen privaten Schlüssel angegeben hast,\n\ +wird der letzte zum privaten Schlüsselbund hinzugefügte Schlüssel benutzt.\n" +es: "\nNo has indicado ningún identificador para escoger la clave secreta,\n\ +por lo que el identificador y la clave por omisión serán los últimos\n\ +añadidos al anillo.\n" +fr: "\nVous n'avez pas spécifié de nom d'utilisateur pour sélectionner\n\ +votre clé secrète, donc le nom et la clé par défaut seront ceux les\n\ +plus récemment ajoutés à votre fichier de clés secrètes.\n" +muttde: "\nDa Du keine Benutzer-ID für Deinen privaten Schlüssel angegeben hast,\n\ +wird der letzte zum privaten Schlüsselbund hinzugefügte Schlüssel benutzt.\n" + +"\007Signature error\n" +de: "\n\007FEHLER beim Unterschreiben!\n" +es: "\007Error de firma\n" +fr: "\007Erreur de signature\n" +mutt: "Signature error\n" +muttde: "\nFEHLER beim Unterschreiben!\n" + +"\n\nRecipients' public key(s) will be used to encrypt. " +de: "\n\nVerschlüsselung mit Empfänger-Schlüssel(n).\n" +es: "\n\nSe utilizan las claves públicas de los destinatarios para encriptar. " +fr: "\n\nLa ou les clé(s) publique(s) du destinataire seront utilisées\ + pour chiffrer. " +muttde: "\n\nVerschlüsselung mit Empfänger-Schlüssel(n).\n" + +"\nA user ID is required to select the recipient's public key. " +de: "\nZur Auswahl des Empfänger-Schlüssels wird eine Benutzer-ID benötigt." +es: "\nSe necesita un identificador para encontrar la clave pública\n\ +del destinatario. " +fr: "\nUn nom d'utilisateur est nécessaire pour sélectionner la clé\n\ +publique du destinataire. " +muttde: "\nZur Auswahl des Empfänger-Schlüssels wird eine Benutzer-ID benötigt." + +"\nEnter the recipient's user ID: " +de: "\nBenutzer-ID des Empfängers: " +es: "\nIntroduzca el identificador del destinatario: " +fr: "\nEntrez le nom d'utilisateur du destinataire: " +muttde: "\nBenutzer-ID des Empfängers: " + +"\007Encryption error\n" +de: "\n\007FEHLER beim Verschlüsseln!\n" +es: "\007Error en la encriptación\n" +fr: "\007Erreur de chiffrage\n" +mutt: "Encryption error\n" +muttde: "\nFEHLER beim Verschlüsseln!\n" + +"\nCiphertext file: %s\n" +de: "\nVerschlüsselte Datei: %s\n" +es: "\nFichero cifrado: %s\n" +fr: "\nFichier chiffré: %s\n" +muttde: "\nVerschlüsselte Datei: %s\n" + +"\nSignature file: %s\n" +de: "\nUnterschriftsdatei: %s\n" +es: "\nFichero de firma: %s\n" +fr: "\nFichier de signature: %s\n" +muttde: "\nUnterschriftsdatei: %s\n" + +"\n\007Error: Transport armor stripping failed for file %s\n" +de: "\n\007FEHLER beim Entfernen der Versandhülle von Datei '%s'.\n" +es: "\n\007Error: No se ha podido quitar la armadura de %s\n" +fr: "\n\007Erreur dans la suppression de la protection de transport pour\n\ +le fichier %s\n" +mutt: "\nError: Transport armor stripping failed for file %s\n" +muttde: "\nFEHLER beim Entfernen der Versandhülle von Datei '%s'.\n" + +"Stripped transport armor from '%s', producing '%s'.\n" +de: "\nDie Versandhülle von Datei '%s' wurde entfernt.\nAusgabedatei: %s\n" +es: "Quitada la armadura de '%s', produciendo '%s'.\n" +fr: "Protection de transport supprimée pour '%s', produisant '%s'.\n" +muttde: "\nDie Versandhülle von Datei '%s' wurde entfernt.\nAusgabedatei: %s\n" + +"\nLooking for next packet in '%s'...\n" +de: "\nSuche nach dem nächsten Paket in '%s'...\n" +es: "\nBuscando el siguiente paquete en '%s'...\n" +fr: "\nRecherche du prochain paquet dans '%s'...\n" +muttde: "\nSuche nach dem nächsten Paket in '%s'...\n" + +"\nFile is encrypted. Secret key is required to read it. " +de: "\nDie Datei ist verschlüsselt. Zum Lesen wird der private Schlüssel benötigt.\n" +es: "\nEl fichero está encriptado. Para leerlo se necesita la clave secreta. " +fr: "\nLe fichier est chiffré. La clé secrète est nécessaire pour le lire." +muttde: "\nDie Datei ist verschlüsselt. Zum Lesen wird der private Schlüssel benötigt.\n" + +"\nThis file has a signature, which will be left in place.\n" +de: "\nDiese Datei trägt eine Unterschrift, die nicht entfernt wird.\n" +es: "\nEste fichero tiene firma, que se deja en su sitio.\n" +fr: "\nCe fichier a une signature, qui sera gardée.\n" +muttde: "\nDiese Datei trägt eine Unterschrift, die nicht entfernt wird.\n" + +"\nFile has signature. Public key is required to check signature.\n" +de: "\nDiese Datei trägt eine Unterschrift.\n\ +Zur Überprüfung wird der öffentliche Schlüssel benötigt.\n" +es: "\nEl fichero tiene firma. Se necesita la clave pública para comprobarla.\n" +fr: "Ce fichier est signé. Une clef publique est nécessaire pour sa vérification.\n" +muttde: "\nDiese Datei trägt eine Unterschrift.\n\ +Zur Überprüfung wird der öffentliche Schlüssel benötigt.\n" + +"\nFile is conventionally encrypted. " +de: "\nDiese Datei ist konventionell verschlüsselt.\n" +es: "\nEl fichero ha sido encriptado convencionalmente. " +fr: "\nLe fichier est chiffré de manière conventionnelle. " +muttde: "\nDiese Datei ist konventionell verschlüsselt.\n" + +"\nFile contains key(s). Contents follow..." +de: "\nDiese Datei enthält einen oder mehrere Schlüssel. Hier kommt die Liste:\n" +es: "\nEl fichero contiene claves. Se muestran a continuación ..." +fr: "\nLe fichier contient une ou plusieurs clés. Le contenu suit..." +muttde: "\nDiese Datei enthält einen oder mehrere Schlüssel. Hier kommt die Liste:\n" + +"\nDo you want to add this keyfile to keyring '%s' (y/N)? " +de: "\nWillst Du die Schlüssel dieser Datei zum Schlüsselbund\n'%s' hinzufügen? (j/N) " +es: "\n¿Quieres añadir este fichero de claves al anillo '%s' (s/N)? " +fr: "\nVoulez vous ajouter ce fichier de clé au fichier '%s' (o/N)? " +muttde: "\nWillst Du die Schlüssel dieser Datei zum Schlüsselbund\n'%s' hinzufügen? (j/N) " + +"\007Keyring add error. " +de: "\n\007FEHLER beim Hinzufügen zum Schlüsselbund.\n" +es: "\007Error al añadir en el anillo. " +fr: "\007Erreur dans l'addition au fichier de clés. " +mutt: "Keyring add error. " +muttde: "\nFEHLER beim Hinzufügen zum Schlüsselbund.\n" + +"\007\nError: '%s' is not a ciphertext, signature, or key file.\n" +de: "\007\nFEHLER: Die Datei '%s' ist nicht verschlüsselt\n\ +und enthält weder eine Unterschrift noch einen oder mehrere Schlüssel.\n" +es: "\007\nError: '%s' no es un texto cifrado, una firma ni una clave.\n" +fr: "\007\nErreur: '%s' n'est pas un fichier chiffré, de signatures\ +\nou de clés.\n" +mutt: "\nError: '%s' is not a ciphertext, signature, or key file.\n" +muttde: "\nFEHLER: Die Datei '%s' ist nicht verschlüsselt\n\ +und enthält weder eine Unterschrift noch einen oder mehrere Schlüssel.\n" + +"\n\nThis message is marked \"For your eyes only\". Display now \ +(Y/n)? " +de: "\n\nDiese Nachricht ist als VERTRAULICH markiert! Jetzt anzeigen? (J/n) " +es: "\n\nEste mensaje está marcado como \"Sólo para tus ojos\".\ +¿Mostrar ahora (S/n)? " +fr: "\n\nCe message est marqué \"Pour vos yeux seulement\".\n\ +Afficher maintenant (O/n)? " +muttde: "\n\nDiese Nachricht ist als VERTRAULICH markiert! Jetzt anzeigen? (J/n) " + +"\n\nPlaintext message follows...\n" +de: "\n\nHier kommt die Nachricht im Klartext:\n" +es: "\n\nMensaje en claro a continuación...\n" +fr: "\n\nLe message en clair suit...\n" +muttde: "\n\nHier kommt die Nachricht im Klartext:\n" + +"Save this file permanently (y/N)? " +de: "Klartext sichern? (j/N) " +es: "¿Grabar este fichero de forma permanente (s/N)? " +fr: "Sauvegarde de ce fichier de manière permanente (o/N)? " +muttde: "Klartext sichern? (j/N) " + +"Enter filename to save file as: " +de: "Klartext sichern als: " +es: "Introduzca el nombre para el fichero: " +fr: "Entrez le nom du fichier de sauvegarde: " +muttde: "Klartext sichern als: " + +"Enter filename to save file as:" +de: "Gib den Namen ein, unter dem die Datei zu sichern ist:" +es: "Introduzca el nombre para el fichero:" +fr: "Donner le nom de fichier pour sauvegarder sous:" +muttde: "Gib den Namen ein, unter dem die Datei zu sichern ist:" + +"\nPlaintext filename: %s" +de: "\nDateiname des Klartextes: %s" +es: "\nNombre del fichero normal: %s" +fr: "\nNom du fichier en clair: %s" +muttde: "\nDateiname des Klartextes: %s" + +"\nPlaintext file '%s' looks like it contains a public key." +de: "\nDie Klartextdatei '%s' scheint einen\nöffentlichen Schlüssel zu enthalten." +es: "\nEl fichero normal '%s' parece contener una clave pública." +fr: "\nLe ficher en clair '%s' semble contenir une clé publique." +muttde: "\nDie Klartextdatei '%s' scheint einen\nöffentlichen Schlüssel zu enthalten." + +"\nPlaintext file '%s' looks like a %s file." +de: "\nDie Klartextdatei '%s' scheint eine %s-Datei zu sein." +es: "\nEl fichero normal '%s' parece un fichero %s." +fr: "\nLe fichier en clair '%s' semble être un fichier %s." +muttde: "\nDie Klartextdatei '%s' scheint eine %s-Datei zu sein." + +"\n\007Output file '%s' may contain more ciphertext or signature." +de: "\n\007Die Ausgabedatei '%s' könnte weiteren\n\ +verschlüsselten Text oder eine Unterschrift enthalten." +es: "\n\007El fichero de salida '%s' puede contener más texto \ +cifrado o una firma." +fr: "\n\007Le fichier de sortie '%s' peut contenir d'autre texte chiffré\n\ +ou signature." +mutt: "\nOutput file '%s' may contain more ciphertext or signature." +muttde: "\nDie Ausgabedatei '%s' könnte weiteren\n\ +verschlüsselten Text oder eine Unterschrift enthalten." + +"\a\nError: PGP User's Guide not found.\n\ +PGP looked for it in the following directories:\n" +de: "\a\nFEHLER: PGP-Benutzerhandbuch nicht gefunden!\n\ +PGP hat danach in den folgenden Verzeichnissen gesucht:\n" +es: "\a\nError: No se encuentra la Guía del usuario.\n\ +Se ha buscado en estos directorios:\n" +fr: "\n\aErreur: Le manuel d'utilisation de PGP est introuvable.\n\ +PGP a examiné les repertoires suivants:\n" +muttde: "\a\nFEHLER: PGP-Benutzerhandbuch nicht gefunden!\n\ +PGP hat danach in den folgenden Verzeichnissen gesucht:\n" + +"and the doc subdirectory of each of the above. Please put a copy of\n\ +both volumes of the User's Guide in one of these directories.\n\ +\n\ +Under NO CIRCUMSTANCES should PGP ever be distributed without the PGP\n\ +User's Guide, which is included in the standard distribution package.\n\ +If you got a copy of PGP without the manual, please inform whomever you\n\ +got it from that this is an incomplete package that should not be\n\ +distributed further.\n\ +\n\ +PGP will not generate a key without finding the User's Guide.\n\ +There is a simple way to override this restriction. See the\n\ +PGP User's Guide for details on how to do it.\n\ +\n" +de: "sowie jeweils im Unterverzeichnis 'DOC' der oben genannten Verzeichnisse.\n\ +Bitte lege eine Kopie beider Teile des Benutzerhandbuches (Dateien PGPDOC1.TXT\n\ +und PGPDOC2.TXT) in eines der genannten Verzeichnisse.\n\ +Unter GAR KEINEN UMSTÄNDEN sollte PGP jemals ohne das PGP-Benutzerhandbuch\n\ +ausgeliefert werden, das sich im Standard-Auslieferungspaket befindet. Wenn\n\ +Du eine Kopie von PGP ohne das Handbuch erhalten hast, dann informiere bitte\n\ +denjenigen, von dem Du sie bekommen hast, daß es sich um ein unvollständiges\n\ +Paket handelt, das künftig nicht mehr ausgeliefert werden sollte.\n\ +PGP generiert keine Schlüssel, ohne das Handbuch gefunden zu haben!\n\ +Im PGP-Handbuch steht übrigens auch, wie diese Einschränkung zu umgehen ist...\n" +es: "y el subdirectorio doc de cada uno de ellos. Pon una copia de\n\ +ambos volúmenes en uno de esos directorios.\n\ +\n\ +Bajo *ninguna circunstancia* debe distribuirse PGP sin la Guía del usuario,\n\ +incluida con la distribución habitual.\n\ +Si tienes una copia de PGP sin manual, informa a quien te la suministró de\n\ +que es un paquete incompleto que no debe seguir distribuyéndose.\n\ +\n\ +PGP no genera ninguna clave si no encuentra la Guía del usuario.\n\ +Hay una forma sencilla de saltarse esta restricción. Consulta la\n\ +Guía para ver cómo hacerlo.\n\ +\n" +fr: "et leur(s) sous-repertoire(s). Veuillez placer une copie des\n\ +deux (2) documents constituant le manuel d'utilisation dans l'un de\n\ +ces repertoires.\n\ +En aucune cinconstances PGP ne devrait etre distribué sans que les\n\ +documents constituant le manuel ne soit inclus avec le reste.\n\ +Si vous avez obtenu PGP sans les documents constituant le manuel\n\ +veuillez SVP en aviser votre fournisseur que PGP est incomplet et\n\ +que cette maniere de distribuer PGP doit cesser.\n\ +\n\ +PGP ne fonctionnera pas sans la presence des deux documents constituant le +manuel \n\ +La facon de circonvenir a cette restriction est de consulter le manuel\n\ +\n" +muttde: "sowie jeweils im Unterverzeichnis 'DOC' der oben genannten Verzeichnisse.\n\ +Bitte lege eine Kopie beider Teile des Benutzerhandbuches (Dateien PGPDOC1.TXT\n\ +und PGPDOC2.TXT) in eines der genannten Verzeichnisse.\n\ +Unter GAR KEINEN UMSTÄNDEN sollte PGP jemals ohne das PGP-Benutzerhandbuch\n\ +ausgeliefert werden, das sich im Standard-Auslieferungspaket befindet. Wenn\n\ +Du eine Kopie von PGP ohne das Handbuch erhalten hast, dann informiere bitte\n\ +denjenigen, von dem Du sie bekommen hast, daß es sich um ein unvollständiges\n\ +Paket handelt, das künftig nicht mehr ausgeliefert werden sollte.\n\ +PGP generiert keine Schlüssel, ohne das Handbuch gefunden zu haben!\n\ +Im PGP-Handbuch steht übrigens auch, wie diese Einschränkung zu umgehen ist...\n" + +"\007Keygen error. " +de: "\n\007FEHLER bei der Erzeugung des Schlüssels.\n" +es: "\007Error en la generación de claves. " +fr: "\007Erreur dans la génération de clé. " +mutt: "Keygen error. " +muttde: "\nFEHLER bei der Erzeugung des Schlüssels.\n" + +"\007Keyring check error.\n" +de: "\n\007FEHLER bei der Überprüfung des Schlüsselbunds.\n" +es: "\007Error en la comprobación del anillo.\n" +fr: "Erreur dans la vérification du trousseau de clef." +mutt: "Keyring check error.\n" +muttde: "\nFEHLER bei der Überprüfung des Schlüsselbunds.\n" + +"\007Maintenance pass error. " +de: "\n\007FEHLER beim Verwaltungsdurchgang.\n" +es: "\007Error en el proceso de mantenimiento. " +fr: "\007Erreur dans la passe de maintenance. " +mutt: "Maintenance pass error. " +muttde: "\nFEHLER beim Verwaltungsdurchgang.\n" + +"File '%s' is not a public keyring\n" +de: "Die Datei '%s' ist kein öffentlicher Schlüsselbund.\n" +es: "El fichero '%s' no es un anillo de claves públicas\n" +fr: "Le fichier '%s' n'est pas un fichier de clés publiques\n" +muttde: "Die Datei '%s' ist kein öffentlicher Schlüsselbund.\n" + +"\nA user ID is required to select the public key you want to sign. " +de: "\nZur Auswahl des zu unterschreibenden Schlüssels wird\n\ +eine Benutzer-ID benötigt." +es: "\nSe necesita un identificador para elegir\n\ +la clave pública por firmar. " +fr: "\nUn nom d'utilisateur est nécessaire pour sélectionner la clé\n\ +publique que vous voulez signer. " +muttde: "\nZur Auswahl des zu unterschreibenden Schlüssels wird\n\ +eine Benutzer-ID benötigt." + +"\nEnter the public key's user ID: " +de: "\nBenutzer-ID des öffentlichen Schlüssels: " +es: "\nIntroduzca el identificador de la clave pública: " +fr: "\nEntrez le nom d'utilisateur pour la clé publique: " +muttde: "\nBenutzer-ID des öffentlichen Schlüssels: " + +"\007Key signature error. " +de: "\n\007FEHLER beim Unterschreiben des Schlüssels.\n" +es: "\007Error en firma de clave. " +fr: "\007Erreur dans la signature de clé. " +mutt: "Key signature error. " +muttde: "\nFEHLER beim Unterschreiben des Schlüssels.\n" + +"\nA user ID is required to select the key you want to revoke or \ +disable. " +de: "\nZur Auswahl des zurückzuziehenden oder zu sperrenden Schlüssels wird\n\ +eine Benutzer-ID benötigt." +es: "\nSe necesita un identificador de usuario para elegir la clave \ +que quieras\n\revocar o desactivar. " +fr: "\nUn nom d'utilisateur est requis pour sélectionner la clé que vous\ +voulez révoquer ou inactiver. " +muttde: "\nZur Auswahl des zurückzuziehenden oder zu sperrenden Schlüssels wird\n\ +eine Benutzer-ID benötigt." + +"\nEnter user ID: " +de: "\nBenutzer-ID: " +es: "\nIntroduce el identificador: " +fr: "\nEntrez le nom d'utilisateur: " +muttde: "\nBenutzer-ID: " + +"\nA user ID is required to select the key you want to edit. " +de: "\nZur Auswahl des zu bearbeitenden Schlüssels wird eine Benutzer-ID benötigt." +es: "\nSe necesita el identificador de usuario para elegir la clave que \ +quieras\nmodificar. " +fr: "\nUn nom d'utilisateur est nécessaire pour sélectionner la clé que\n\ +vous voulez modifier. " +muttde: "\nZur Auswahl des zu bearbeitenden Schlüssels wird eine Benutzer-ID benötigt." + +"\nEnter the key's user ID: " +de: "\nBenutzer-ID des Schlüssels: " +es: "\nIntroduce el identificador: " +fr: "\nEntrez le nom d'utilisateur pour la clé: " +muttde: "\nBenutzer-ID des Schlüssels: " + +"\007Keyring edit error. " +de: "\n\007FEHLER beim Bearbeiten des Schlüsselbunds.\n" +es: "\007Error en la modificación del anillo. " +fr: "\007Erreur dans la modification du fichier de clés. " +mutt: "Keyring edit error. " +muttde: "\nFEHLER beim Bearbeiten des Schlüsselbunds.\n" + +"\n\007Key file '%s' does not exist.\n" +de: "\n\007Die Datei '%s' existiert nicht.\n" +es: "\n\007No existe el anillo de claves '%s.\n" +fr: "\n\007Le fichier de clés '%s' n'existe pas.\n" +mutt: "\nKey file '%s' does not exist.\n" +muttde: "\nDie Datei '%s' existiert nicht.\n" + +"\nA user ID is required to select the key you want to extract. " +de: "\nZur Auswahl des zu extrahierenden Schlüssels wird eine Benutzer-ID benötigt." +es: "\nSe necesita el identificador de usuario para elegir la clave que \ +quieras\n\extraer. " +fr: "\nUn nom d'utilisateur est nécessaire pour sélectionner la clé que\n\ +vous voulez extraire. " +muttde: "\nZur Auswahl des zu extrahierenden Schlüssels wird eine Benutzer-ID benötigt." + +"\007Keyring extract error. " +de: "\n\007FEHLER beim Extrahieren aus dem Schlüsselbund.\n" +es: "\007Error al extraer del anillo. " +fr: "\007Erreur dans l'extraction du fichier de clés. " +mutt: "Keyring extract error. " +muttde: "\nFEHLER beim Extrahieren aus dem Schlüsselbund.\n" + +"\nA user ID is required to select the public key you want to\n\ +remove certifying signatures from. " +de: "\nZur Auswahl des Schlüssels, von dem Beglaubigungen entfernt werden sollen,\n\ +wird eine Benutzer-ID benötigt." +es: "\nSe necesita el identificador de usuario para elegir la clave pública\n\ +de la que suprimir firmas. " +fr: "\nUn nom d'utilisateur est nécessaire pour sélectionner la clé\ + publique\n\ +pour laquelle vous voulez supprimer des signatures de certification. " +muttde: "\nZur Auswahl des Schlüssels, von dem Beglaubigungen entfernt werden sollen,\n\ +wird eine Benutzer-ID benötigt." + +"\nA user ID is required to select the key you want to remove. " +de: "\nZur Auswahl des zu löschenden Schlüssels wird eine Benutzer-ID benötigt." +es: "\nSe necesita el identificador de usuario para elegir la clave que \ +quieras\nsuprimir . " +fr: "\nUn nom d'utilisateur est nécessaire pour sélectionner la clé que\n\ +vous voulez supprimer. " +muttde: "\nZur Auswahl des zu löschenden Schlüssels wird eine Benutzer-ID benötigt." + +"\007Key signature remove error. " +de: "\n\007FEHLER beim Entfernen der Beglaubigung.\n" +es: "\007Error en la supresión de la firma de una clave. " +fr: "\007Erreur dans la suppression de signature d'une clé. " +mutt: "Key signature remove error. " +muttde: "\nFEHLER beim Entfernen der Beglaubigung.\n" + +"\007Keyring remove error. " +de: "\n\007FEHLER beim Löschen aus dem Schlüsselbund.\n" +es: "\007Error al suprimir del anillo. " +fr: "\007Erreur dans la suppression du fichier de clés. " +mutt: "Keyring remove error. " +muttde: "\nFEHLER beim Löschen aus dem Schlüsselbund.\n" + +"\007Keyring view error. " +de: "\n\007FEHLER beim Anzeigen des Schlüsselbunds.\n" +es: "\007Error al visualizar el anillo. " +fr: "\007Erreur dans la visualisation du fichier de clés. " +mutt: "Keyring view error. " +muttde: "\nFEHLER beim Anzeigen des Schlüsselbunds.\n" + +"For more detailed help, consult the PGP User's Guide.\n" +de: "Ausführlichere Hilfe findet sich in der PGP-Anleitung.\n" +es: "Para obtener más ayuda, consulta la Guía del usuario de PGP.\n" +fr: "Pour une aide plus détaillée, consultez le guide de l'utilisateur de +PGP.\n" +mutt: "\n" +muttde: "\n" + +"\nInvalid arguments.\n" +de: "\nUngültige Argumente!\n" +es: "\nArgumentos incorrectos.\n" +fr: "\nArguments invalides.\n" +muttde: "\nUngültige Argumente!\n" + +"\nUsage summary:\ +\nTo encrypt a plaintext file with recipent's public key, type:\ +\n pgp -e textfile her_userid [other userids] (produces textfile.pgp)\ +\nTo sign a plaintext file with your secret key:\ +\n pgp -s textfile [-u your_userid] (produces textfile.pgp)\ +\nTo sign a plaintext file with your secret key, and then encrypt it\ +\n with recipent's public key, producing a .pgp file:\ +\n pgp -es textfile her_userid [other userids] [-u your_userid]\ +\nTo encrypt with conventional encryption only:\ +\n pgp -c textfile\ +\nTo decrypt or check a signature for a ciphertext (.pgp) file:\ +\n pgp ciphertextfile [-o plaintextfile]\ +\nTo produce output in ASCII for email, add the -a option to other options.\ +\nTo generate your own unique public/secret key pair: pgp -kg\ +\nFor help on other key management functions, type: pgp -k\n" +de: "\nÜbersicht der PGP-Befehle:\ +\nVerschlüsseln eines Textes mit dem öffentlichen Schlüssel des Empfängers:\ +\n pgp -e {Text} {Benutzer-ID des Empfängers} (Ergebnis: {Text}.pgp)\ +\nUnterschreiben eines Textes mit Deinem privaten Schlüssel:\ +\n pgp -s {Text} [-u {Deine Benutzer-ID}] (Ergebnis: {Text}.pgp)\ +\nUnterschreiben eines Textes mit Deinem privaten Schlüssel und anschließend\ +\nVerschlüsseln mit dem öffentlichen Schlüssel des Empfängers:\ +\n pgp -es {Text} {Benutzer-ID des Empfängers} [weitere Benutzer-IDs]\ +\n [-u {Deine Benutzer-ID}] (Ergebnis: {Text}.pgp)\ +\nVerschlüsseln mit konventioneller Verschlüsselung:\ +\n pgp -c {Text}\ +\nEntschlüsseln oder Überprüfen der Unterschrift:\ +\n pgp {verschlüsselter Text} [-o {Klartext}]\ +\nVerpacken der Ausgabe in ASCII (für E-Mail): pgp {...} -a {...}\ +\nErzeugen eines eigenen Schlüssel-Paares: pgp -kg\ +\nHilfe zur Schlüsselverwaltung: pgp -k\n" +es: "\nResumen de las instrucciones:\ +\nEncriptar fichero normal con la clave pública del destinatario:\ +\n pgp -e ftexto su_identificador (produce ftexto.pgp)\ +\nFirmar un fichero de texto normal con tu clave secreta:\ +\n pgp -s ftexto [-u tu_identificador] (produce ftexto.pgp)\ +\nFirmar un fichero normal con tu clave secreta y después encriptarlo\ +\n con la clave pública del destinatario, produciendo un fichero .pgp:\ +\n pgp -es ftexto su_identificador [otros] [-u tu_identificador]\ +\nEncriptar sólo con cifrado convencional:\ +\n pgp -c ftexto\ +\nDesencriptar o comprobar la firma en un fichero cifrado (.pgp):\ +\n pgp fcifrado [-o fnormal]\ +\nProducir resultado en ASCII para correo electrónico: añadir la opción -a.\ +\nGenerar tu propio par único de claves pública/secreta: pgp -kg\ +\nAyuda sobre otras funciones de gestión de claves: pgp -k\n" +fr: "\nSommaire:\ +\nPour chiffrer un fichier en clair avec la clé publique du destinataire, \ +tapez:\ +\n pgp -e fichier son_nom [autres noms] (produit fichier.pgp)\ +\nPour signer un texte en clair avec votre clé secrète:\ +\n pgp -s fichier [-u votre_nom] (produit fichier.pgp)\ +\nPour signer un texte en clair avec votre clé secrète, puis le chiffrer\ +\n avec la clé publique du destinataire, produisant un fichier .pgp:\ +\n pgp -es fichier son_nom [autres noms] [-u votre_nom]\ +\nPour chiffrer de manière conventionelle seulement:\ +\n pgp -c fichier\ +\nPour déchiffrer ou vérifier une signature pour un fichier chiffré (.pgp):\ +\n pgp fichier_chiffré [-o fichier_en_clair]\ +\nPour produire une sortie en ASCII pour courrier électronique, ajouter\ +\nl'option -a aux autres options.\ +\nPour générer votre propre paire de clés publique/secrète:\ +\n pgp -kg\ +\nPour de l'aide sur les autres fonctions de gestion de clé, tapez: pgp -k\n" +muttde: "\nÜbersicht der PGP-Befehle:\ +\nVerschlüsseln eines Textes mit dem öffentlichen Schlüssel des Empfängers:\ +\n pgp -e {Text} {Benutzer-ID des Empfängers} (Ergebnis: {Text}.pgp)\ +\nUnterschreiben eines Textes mit Deinem privaten Schlüssel:\ +\n pgp -s {Text} [-u {Deine Benutzer-ID}] (Ergebnis: {Text}.pgp)\ +\nUnterschreiben eines Textes mit Deinem privaten Schlüssel und anschließend\ +\nVerschlüsseln mit dem öffentlichen Schlüssel des Empfängers:\ +\n pgp -es {Text} {Benutzer-ID des Empfängers} [weitere Benutzer-IDs]\ +\n [-u {Deine Benutzer-ID}] (Ergebnis: {Text}.pgp)\ +\nVerschlüsseln mit konventioneller Verschlüsselung:\ +\n pgp -c {Text}\ +\nEntschlüsseln oder Überprüfen der Unterschrift:\ +\n pgp {verschlüsselter Text} [-o {Klartext}]\ +\nVerpacken der Ausgabe in ASCII (für E-Mail): pgp {...} -a {...}\ +\nErzeugen eines eigenen Schlüssel-Paares: pgp -kg\ +\nHilfe zur Schlüsselverwaltung: pgp -k\n" + +"\nKey management functions:\ +\nTo generate your own unique public/secret key pair:\ +\n pgp -kg\ +\nTo add a key file's contents to your public or secret key ring:\ +\n pgp -ka keyfile [keyring]\ +\nTo remove a key or a user ID from your public or secret key ring:\ +\n pgp -kr userid [keyring]\ +\nTo edit your user ID or pass phrase:\ +\n pgp -ke your_userid [keyring]\ +\nTo extract (copy) a key from your public or secret key ring:\ +\n pgp -kx userid keyfile [keyring]\ +\nTo view the contents of your public key ring:\ +\n pgp -kv[v] [userid] [keyring]\ +\nTo check signatures on your public key ring:\ +\n pgp -kc [userid] [keyring]\ +\nTo sign someone else's public key on your public key ring:\ +\n pgp -ks her_userid [-u your_userid] [keyring]\ +\nTo remove selected signatures from a userid on a keyring:\ +\n pgp -krs userid [keyring]\ +\n" +de: "\nSchlüssel-Verwaltung:\ +\nErzeugen eines eigenen, eindeutigen Schlüssel-Paares (privat/öffentlich):\ +\n pgp -kg\ +\nHinzufügen von Schlüsseln zum privaten oder öffentlichen Schlüsselbund:\ +\n pgp -ka {Datei mit Schlüsseln} [{Schlüsselbund}]\ +\nLöschen eines Schlüssels oder einer Benutzer-ID aus einem Schlüsselbund:\ +\n pgp -kr {Benutzer-ID} [{Schlüsselbund}]\ +\nÄndern Deiner Benutzer-ID oder Deines Mantras:\ +\n pgp -ke {Deine Benutzer-ID} [{Schlüsselbund}]\ +\nHerauskopieren eines Schlüssels aus einem Schlüsselbund:\ +\n pgp -kx {Benutzer-ID} {Ausgabe-Datei} [{Schlüsselbund}]\ +\nAnzeigen des Inhaltes des öffentlichen Schlüsselbunds:\ +\n pgp -kv[v] [{Benutzer-ID}] [{Schlüsselbund}]\ +\nÜberprüfen der Beglaubigungen im öffentlichen Schlüsselbund:\ +\n pgp -kc [{Benutzer-ID}] [{Schlüsselbund}]\ +\nBeglaubigen eines öffentlichen Schlüssels eines anderen Benutzers:\ +\n pgp -ks {seine Benutzer-ID} [-u {Deine Benutzer-ID}] [{Schlüsselbund}]\ +\nEntfernen ausgewählter Beglaubigungen von einem Schlüssel:\ +\n pgp -krs {Benutzer-ID} [{Schlüsselbund}]\n" +es: "\nFunciones para la gestión de claves:\ +\nGenerar tu propio par único de claves pública/secreta:\ +\n pgp -kg\ +\nAñadir contenido de fichero de clave al anillo de claves públicas o secretas:\ +\n pgp -ka fdclaves [anillo]\ +\nSuprimir una clave o identificador de usuario de un anillo de claves:\ +\n pgp -kr identificador [anillo]\ +\nModificar tu identificador de usuario o tu contraseña:\ +\n pgp -ke tu_identificador [anillo]\ +\nExtraer (copiar) una clave del anillo de claves públicas o secretas:\ +\n pgp -kx identificador fdclaves [anillo]\ +\nVisualizar el contenido del anillo de claves públicas:\ +\n pgp -kv[v] [identificador] [anillo]\ +\nComprobar las firmas del anillo de claves públicas:\ +\n pgp -kc [identificador] [anillo]\ +\nFirmar la clave pública de alguien en el anillo de claves correspondiente:\ +\n pgp -ks otro_identificador [-u tu_identificador] [anillo]\ +\nSuprimir ciertas firmas de un idusuario en un anillo:\ +\n pgp -krs identificador [anillo]\ +\n" +fr: "Fonctions de gestion des clés:\ +\nPour générer votre propre paire de clés publique/secrète:\ +\n pgp -kg\ +\nPour ajouter le contenu d'un fichier de clés à votre fichier de clés\ +\n public ou secret:\ +\n pgp -ka fichier_de_clés [votre_fichier_de_clés]\ +\nPour retirer une clé de votre fichier de clés public ou secret:\ +\n pgp -kr nom_d_utilisateur [fichier_de_clés]\ +\nPour extraire (copier) une clé de votre fichier de clés public ou secret:\ +\n pgp -kx nom_d_utilisateur fichier_de_la_clé [fichier_de_clés]\ +\nPour visualiser le contenu de votre fichier de clés:\ +\n pgp -kv[v] [nom_d_utilisateur] [ficher_de_clés]\ +\nPour vérifier les signatures sur votre fichier de clés publiques:\ +\n pgp -kc [nom_d_utilisateur] [ficher_de_clés]\ +\nPour signer la clé publique de quelqu'un d'autre sur votre fichier de\ +\n clés publiques:\ +\n pgp -ks son_nom votre_nom [fichier_de_clés]\ +\nPour enlever certaines signatures d'une personne sur un fichier de clés:\ +\n pgp -krs son_nom [fichier_de_clés]\n" +muttde: "\nSchlüssel-Verwaltung:\ +\nErzeugen eines eigenen, eindeutigen Schlüssel-Paares (privat/öffentlich):\ +\n pgp -kg\ +\nHinzufügen von Schlüsseln zum privaten oder öffentlichen Schlüsselbund:\ +\n pgp -ka {Datei mit Schlüsseln} [{Schlüsselbund}]\ +\nLöschen eines Schlüssels oder einer Benutzer-ID aus einem Schlüsselbund:\ +\n pgp -kr {Benutzer-ID} [{Schlüsselbund}]\ +\nÄndern Deiner Benutzer-ID oder Deines Mantras:\ +\n pgp -ke {Deine Benutzer-ID} [{Schlüsselbund}]\ +\nHerauskopieren eines Schlüssels aus einem Schlüsselbund:\ +\n pgp -kx {Benutzer-ID} {Ausgabe-Datei} [{Schlüsselbund}]\ +\nAnzeigen des Inhaltes des öffentlichen Schlüsselbunds:\ +\n pgp -kv[v] [{Benutzer-ID}] [{Schlüsselbund}]\ +\nÜberprüfen der Beglaubigungen im öffentlichen Schlüsselbund:\ +\n pgp -kc [{Benutzer-ID}] [{Schlüsselbund}]\ +\nBeglaubigen eines öffentlichen Schlüssels eines anderen Benutzers:\ +\n pgp -ks {seine Benutzer-ID} [-u {Deine Benutzer-ID}] [{Schlüsselbund}]\ +\nEntfernen ausgewählter Beglaubigungen von einem Schlüssel:\ +\n pgp -krs {Benutzer-ID} [{Schlüsselbund}]\n" + +"\nIncluding \"%s\"...\n" +de: "\nEmpfängerliste aus der Datei '%s' wird eingelesen...\n" +es: "\nIncluyendo \"%s\"...\n" +fr: "\nIncluant '%s'...\n" +muttde: "\nEmpfängerliste aus der Datei '%s' wird eingelesen...\n" + +"\nWe need to generate %u random bits. This is done by measuring the\ +\ntime intervals between your keystrokes. Please enter some random text\ +\non your keyboard until you hear the beep:\n" +de: "\nWir müssen %u zufällige Bits erzeugen. Dies wird durch Messung\ +\nder Abstände zwischen Deinen Anschlägen bewerkstelligt. Bitte gib\ +\nirgendwelchen beliebigen Text auf der Tastatur ein, bis es piepst:\n" +es: "\nNecesitamos generar %d bits aleatorios. Se hace midiendo los\ +\nintervalos de tiempo entre pulsaciones de tecla. Escribe\ +\ntexto al azar en el teclado hasta que oigas un pitido:\n" +fr: "\nNous devons générer %d bits aléatoires. Ceci est fait en mesurant\ +\nl'intervalle de temps entre les frappes de touches. Veuillez tapper du\ +\ntexte aléatoire sur votre clavier jusqu'à ce que vous entendiez le\ +\nsignal sonore:\n" +muttde: "\nWir müssen %u zufällige Bits erzeugen. Dies wird durch Messung\ +\nder Abstände zwischen Deinen Anschlägen bewerkstelligt. Bitte gib\ +\nirgendwelchen beliebigen Text auf der Tastatur ein, bis es piepst:\n" + +"\007 -Enough, thank you.\n" +de: "\007 -Danke, das genügt!\n" +es: "\007 -Es suficiente.\n" +fr: "\007 -Assez, merci.\n" +mutt: " -Enough, thank you.\n" +muttde: " -Danke, das genügt!\n" + +"\ +Uses the RSAREF(tm) Toolkit, which is copyright RSA Data Security, Inc.\n\ +Distributed by the Massachusetts Institute of Technology.\n" +de: "Benutzt das RSAREF(tm) Toolkit, (c) RSA Data Security, Inc.\n\ +Ausgeliefert vom Massachusetts Institute of Technology.\n" +es: "\ +Utiliza RSAREF(tm), copyright de RSA Data Security, Inc.\n\ +Distribuido por el Massachusetts Institute of Technology.\n" +fr: "Ce logiciel utilise RSAREF(tm) Toolkit, Copyright RSA Data Security, Inc\n\ +Distribué par le Massachusetts Institute of Technology.\n" +muttde: "Benutzt das RSAREF(tm) Toolkit, (c) RSA Data Security, Inc.\n\ +Ausgeliefert vom Massachusetts Institute of Technology.\n" + +"Out of memory" +de: "Zu wenig Speicher!" +es: "No queda memoria" +fr: "Mémoire insufisante" +muttde: "Zu wenig Speicher!" + +"\nOut of memory\n" +de: "\nZu wenig Speicher!\n" +es: "\nNo queda memoria\n" +fr: "\nMémoire insuffisante\n" +muttde: "\nZu wenig Speicher!\n" + +"\n\007Out of memory.\n" +de: "\n\007Zu wenig Speicher!\n" +es: "\n\007No queda memoria.\n" +fr: "\n\007Mémoire insuffisante.\n" +mutt: "\nOut of memory.\n" +muttde: "\nZu wenig Speicher!\n" + +"\nCompression/decompression error\n" +de: "\nFEHLER beim Packen/Entpacken!\n" +es: "\nError en compresión/descompresión\n" +fr: "\nErreur de compression/decompression\n" +muttde: "\nFEHLER beim Packen/Entpacken!\n" + +"\nERROR: unexpected end of compressed data input.\n" +de: "\nFEHLER: vorzeitiges Ende der ZIP-gepackten Eingangsdaten.\n" +es: "\nERROR: los datos comprimidos terminan antes de tiempo.\n" +fr: "\nERREUR: fin innopinée des données d'entrée compressées.\n" +muttde: "\nFEHLER: vorzeitiges Ende der ZIP-gepackten Eingangsdaten.\n" + +# The following 4 translations MUST be exactly 3 characters long! + +"pub" +de: "öff" +muttde: "öff" + +"sec" +de: "prv" +muttde: "prv" + +"sig" +de: "Unt" +es: "fir" +muttde: "Unt" + +"com" +de: "Wid" +muttde: "Wid" + +# IN-CH extentions. + +# translation must not change the string length +"rev" +de: "zur" +muttde: "zur" + +# translation must not change the string length +" Expire: %s%s" +de: " Verfall: %s%s" +muttde: " Verfall: %s%s" + +# translation must not change the string length +" no expire " +de: " kein Verfall " +muttde: " kein Verfall " + +"ENCRyption only\n" +de: "*** nur Verschlüsselung! ***\n" +muttde: "*** nur Verschlüsselung! ***\n" + +"Key ID %s is SIGN only. Decryption avoided.\n" +de: "Schlüssel ID %s ist nur zum Unterschreiben. Entschlüsselung verhindert.\n" +muttde: "Schlüssel ID %s ist nur zum Unterschreiben. Entschlüsselung verhindert.\n" + +"Key ID %s is not valid.\n" +de: "Schlüssel ID %s ist ungültig.\n" +muttde: "Schlüssel ID %s ist ungültig.\n" + +"Key is an ENCRyption only key.\n" +de: "Der Schlüssel ist nur zur Verschlüsselung.\n" +muttde: "Der Schlüssel ist nur zur Verschlüsselung.\n" + +"Key is a SIGNature only key.\n" +de: "Der Schlüssel ist nur für Unterschriften.\n" +muttde: "Der Schlüssel ist nur für Unterschriften.\n" + +"Key is out of use.\n" +de: "Der Schlüssel ist nicht mehr in Benutzung.\n" +muttde: "Der Schlüssel ist nicht mehr in Benutzung.\n" + +"SIGNature only\n" +de: "*** nur Unterschriften! ***\n" +muttde: "*** nur Unterschriften! ***\n" + +" Revoked by: " +de: " Zurückgezogen von: " +muttde: " Zurückgezogen von: " + +" Low Cert by: " +de: "Niedrige Beglaubigung von: " +muttde: "Niedrige Beglaubigung von: " + +"Medium Cert by: " +de: "Mittlere Beglaubigung von: " +muttde: "Mittlere Beglaubigung von: " + +" High Cert by: " +de: " Hohe Beglaubigung von: " +muttde: " Hohe Beglaubigung von: " + +" Unknown type: " +de: " Unbekannter Typ: " +muttde: " Unbekannter Typ: " + +"Try to obtain the corresponding SIGN key.\n" +de: "Es wird versucht, den zugehörigen Unterschriftsschlüssel zu verwenden.\n" +muttde: "Es wird versucht, den zugehörigen Unterschriftsschlüssel zu verwenden.\n" + +"\n\007Key cert is already revoked by user '%s'.\n" +de: "\n\007Die Unterschrift unter dem Schlüssel wurde bereits\n\ +von '%s' zurückgezogen.\n" +mutt: "\nKey cert is already revoked by user '%s'.\n" +muttde: "\nDie Unterschrift unter dem Schlüssel wurde bereits\n\ +von '%s' zurückgezogen.\n" + +"\n\007Key is not signed by user '%s'.\n" +de: "\n\007Der Schlüssel ist nicht von '%s' unterschrieben.\n" +mutt: "\nKey is not signed by user '%s'.\n" +muttde: "\nDer Schlüssel ist nicht von '%s' unterschrieben.\n" + +"\nKey signature certificate revoked.\n" +de: "\nDie Unterschrift unter dem Schlüssel wurde zurückgezogen.\n" +muttde: "\nDie Unterschrift unter dem Schlüssel wurde zurückgezogen.\n" + +"This key is already out of use.\n" +de: "Dieser Schlüssel ist bereits nicht mehr in Benutzung.\n" +muttde: "Dieser Schlüssel ist bereits nicht mehr in Benutzung.\n" + +"\007 Key ID %s is SIGN only. (protest against decryption)\n" +de: "\007Die Schlüssel-ID %s ist nur für Unterschriften.\n\ +Keine Entschlüsselung möglich!\n" +mutt: " Key ID %s is SIGN only. (protest against decryption)\n" +muttde: "Die Schlüssel-ID %s ist nur für Unterschriften.\n\ +Keine Entschlüsselung möglich!\n" + +"Signature was made after the key ID %s was expired.\n" +de: "Unterschrift wurde mit dem verfallen Schlüssel ID %s erstellt.\n" +muttde: "Unterschrift wurde mit dem verfallen Schlüssel ID %s erstellt.\n" + +"Signature was made before the key ID %s was valid.\n" +de: "Unterschrift wurde mit dem noch ungültigen Schlüssel ID %s erstellt.\n" +muttde: "Unterschrift wurde mit dem noch ungültigen Schlüssel ID %s erstellt.\n" + +"Signature was made using the ENCR key ID %s.\n" +de: "Unterschrift wurde mit dem Verschlüsselungsschlüssel ID %s erstellt.\n" +muttde: "Unterschrift wurde mit dem Verschlüsselungsschlüssel ID %s erstellt.\n" + +"Something goes wrong, may be an unlucky User ID?\n" +de: "Irgendwas ist schiefgegangen. Liegt's an der NutzerID?\n" +muttde: "Irgendwas ist schiefgegangen. Liegt's an der NutzerID?\n" + +"Something goes wrong, may be this userid '%s' is unlucky?\n" +de: "Irgendwas ist schiefgegangen. Liegt's an der NutzerID '%s'?\n" +muttde: "Irgendwas ist schiefgegangen. Liegt's an der NutzerID '%s'?\n" + +"The compromise certificate might not be accepted on newer versions.\n\ +Revoke anyway? (y/N)" +de: "Das Widerrufszertifikat kann von neueren Versionen zurückgewiesen\ +werden.\nTrotzdem widerrufen? (j/N)" +muttde: "Das Widerrufszertifikat kann von neueren Versionen zurückgewiesen\ +werden.\nTrotzdem widerrufen? (j/N)" + +"The key for the userid '%s' is SIGN only.\nI'll try the corresponding ENCR key.\n" +de: "Schlüssel ID '%s' ist nur zum Unterschreiben.\n\ +Der zugehörige Verschlüsselungsschlüssel wird versucht.\n" +muttde: "Schlüssel ID '%s' ist nur zum Unterschreiben.\n\ +Der zugehörige Verschlüsselungsschlüssel wird versucht.\n" + +"The key for the userid '%s' is not valid, skipped.\n" +de: "Schlüssel ID '%s' ist ungültig.\nÜbersprungen.\n" +muttde: "Schlüssel ID '%s' ist ungültig.\nÜbersprungen.\n" + +"The user ID typed in has garbled options!\n" +de: "Die NutzerID enthält fehlerhafte Optionseinträge!\n" +muttde: "Die NutzerID enthält fehlerhafte Optionseinträge!\n" + +"This key is an ENCRyption only key.\n" +de: "Das ist ein Verschlüsselungsschlüssel.\n" +muttde: "Das ist ein Verschlüsselungsschlüssel.\n" + +"This key is too old to be revoked.\n" +de: "Der Schlüssel ist zu alt, um zurückgezogen zu werden.\n" +muttde: "Der Schlüssel ist zu alt, um zurückgezogen zu werden.\n" + +"This key is too old to be signed.\n" +de: "Der Schlüssel ist zu alt, um unterschrieben zu werden.\n" +muttde: "Der Schlüssel ist zu alt, um unterschrieben zu werden.\n" + +"This key will be generated in future. *oops*\n" +de: "Der Schlüssel wird in der Zukunft erstellt. *huch*\n" +muttde: "Der Schlüssel wird in der Zukunft erstellt. *huch*\n" + +"Your key is too old to sign with.\n" +de: "Der Schlüssel ist zu alt zum unterschreiben.\n" +muttde: "Der Schlüssel ist zu alt zum unterschreiben.\n" + +"Your key will be generated in future. *oops*\n" +de: "Dein Schlüssel wird in der Zukunft erstellt. *huch*\n" +muttde: "Dein Schlüssel wird in der Zukunft erstellt. *huch*\n" + +"\007**** Signed with an ENCRyption only key ****" +de: "\007**** Unterschr. mit einem Verschl.-Schlüssel ****" +mutt: "**** Signed with an ENCRyption only key ****" +muttde: "**** Unterschr. mit einem Verschl.-Schlüssel ****" + +"\007**** Signed with an invalid key ****" +de: "\007**** Unterschr. mit einem ungültigen Schlüssel ****" +mutt: "**** Signed with an invalid key ****" +muttde: "**** Unterschr. mit einem ungültigen Schlüssel ****" + +"\007**** Signed with a revoked key ****" +de: "\007**** Unterschr. mit einem zurückgez. Schlüssel ****" +mutt: "**** Signed with a revoked key ****" +muttde: "**** Unterschr. mit einem zurückgez. Schlüssel ****" + +"\n\nREAD CAREFULLY: How did you prove the users real identity ?\n\ + 0) What? I do not understand this question.\n\ + 1) No attempt made at all to identify the user with a real name.\n\ + 2) Some casual attempt made to identify user with his name.\n\ + 3) Heavy-duty identification efforts, photo ID, direct contact...\n" +de: "\n\nSORGFÄLTIG LESEN: Wie hast Du die Identität des Nutzers geprüft ?\n\ + 0) Bitte? Ich verstehe die Frage nicht.\n\ + 1) Keine Überprüfung des wirklichen Namens des Nutzers.\n\ + 2) Einige Bemühungen, die Person mit diesem Namen zu identifizieren.\n\ + 3) Große Identifizierungsaufwendungen, Ausweis, direkter Kontakt...\n" +muttde: "\n\nSORGFÄLTIG LESEN: Wie hast Du die Identität des Nutzers geprüft ?\n\ + 0) Bitte? Ich verstehe die Frage nicht.\n\ + 1) Keine Überprüfung des wirklichen Namens des Nutzers.\n\ + 2) Einige Bemühungen, die Person mit diesem Namen zu identifizieren.\n\ + 3) Große Identifizierungsaufwendungen, Ausweis, direkter Kontakt...\n" diff --git a/doc/language50.txt b/doc/language50.txt new file mode 100644 index 00000000..a573c105 --- /dev/null +++ b/doc/language50.txt @@ -0,0 +1,1339 @@ +#This file contains the strings used by PGP. + +[DIFFERENT_EXES] +us=\ +PGP is now invoked from different executables for different operations:\n\n\ +pgpe Encrypt (including Encrypt/Sign)\n\ +pgps Sign\n\ +pgpv Verify/Decrypt\n\ +pgpk Key management\n\ +pgpo PGP 2.6.2 command-line simulator (not yet implemented)\n\n\ +See each application's respective man page or the general PGP documentation\n\ +for more information.\n + +[CREATING_OUTPUT_FILE] +us=Creating output file %s\n + +#Untested +[COPYING_KEYFILE_AND_RUNNING_PGPK] +us=Copying key file to \"%s\", running pgpk to process it...\n\n +mutt= + +#Untested +[NEED_PASSPHRASE] +us=You need a passphrase to encrypt the file\n + +[MUST_SPECIFY_A_RECIPIENT] +us=You must specify at least one recipient for encryption!\n + +#Untested +[NEED_PASSPHRASE_AGAIN] +us=Enter same passphrase again\n + +#Untested +[PASSPHRASES_DIFFERENT] +us=Error: Passphrases were different. Try again.\n + +#Untested +[ZERO_LEN_PASSPHRASE] +us=Encryption error\n + +#Untested +[TREAT_AS_PGP] +us=This is a PGP File. Treat it as such? [y/N]\n + +#Untested +[PRIVATE_KEY_MISSING] +us=Cannot find a private key for signing: %s\n + +#Untested +[CANNOT_CONVERT_TO_PRIVATE_KEY] +us=Cannot convert to private key\n + +#Untested +[PRIVATE_KEY_CANNOT_SIGN] +us=Private Key cannot sign\n + +#Untested +[CANNOT_UNLOCK_PRIVATE_KEY] +us=Cannot unlock private key\n + +#Untested +[NO_KEYRINGS] +us=No keyrings to use + +#Untested +[NO_ENCRYPTION_KEYS_FOUND_FOR] +us=No encryption keys found for: %s\n + +#Untested +[CANNOT_FIND_KEY] +us=Cannot find key: %s\n + +#Untested +[CANNOT_ADD_MY_KEY] +us=Cannot add my key to set\n + +#Untested +[NO_VALID_RECIPIENTS] +us=No valid keys found for any recipients, exiting...\n + +#Untested +[USING_STDIN] +us=No files specified. Using stdin.\n\n +mutt= + +#Untested +[CANNOT_OPEN_INPUT_FILE] +us=Cannot open input file %s\n + +#Untested +[CANNOT_SETUP_PROCESSING_PIPELINE] +us=Cannot Setup Processing Pipeline\n + +#Untested +[UNRECOGNIZED_OPTION_STRING] +us=Unrecognized option %s\n + +#Untested +[UNRECOGNIZED_OPTION_STRING_DASH] +us=Unrecognized option -%s\n + +#Untested +[UNRECOGNIZED_OPTION_CHAR] +us=Unrecognized option -%c\n + +#Untested +[ARGS_INCOMPATABLE] +us="Cannot use -%c and -%c together\n" + +#Untested +[ONLY_ONE_OUTPUT_FILE] +us="Only one -o option allowed\n" + +#Untested +[ONLY_ONE_USERNAME] +us="Only one -u option allowed\n" + +#Untested +[NO_OUTPUT_FILENAME] +us=-o option requires an output file name argument\n + +[NO_RECIPIENT_SPECIFIED] +us=-r option requires a recipient name argument\n + +#Untested +[NO_USERID_SPECIFIED] +us=-u option requires a userid argument\n + +#Untested, and probably going away +[NO_PASSPHRASE_SPECIFIED_IN_BATCHMODE] +us=-z option requires a passphrase argument\n + +#Untested +[CANNOT_COMBINE_CONVENTIONAL_AND_PK] +us=Cannot combine -c and -r arguments\n + +#Untested +[PGPK_IS_SEPERATE] +us=pgpk is a seperate program, not a symlink to pgp!\n + +#Untested +[UNKNOWN_SYMLINK] +us=Invoked with unknown symlink\n + +#Untested +[PRIVATE_KEY_NEEDED_FOR_SIGNATURE] +us=A private key is required to make a signature.\n + +[ENTER_Y_OR_N] +us="Invalid response. Please enter Y or N [default %c]: \n" + +#Untested +[GENERIC_KEYRING_ERROR] +us="Error on keyring \"%s\": " + +#Untested +[UNABLE_TO_OPEN_DEFAULT_KEYRINGS] +us="Unable to open default keyrings: " + +#Untested +[UNABLE_TO_OPEN_KEYRING] +us="Unable to open keyring: " + +#Untested +[KEY_CORRUPTED] +us="Key Corrupted (%s): " + +#Untested +[NEED_SIG_FILE] +us="File to check signature against [%s]: " + +#untested +[GOOD_SIGNATURE] +us="Good signature made %s by key:\n" + +#untested +[BAD_SIGNATURE] +us="BAD signature made %s by key:\n" + +#untested +[ERROR_SIGNATURE] +us="Error %s checking signature: %s\n" + +#Untested +[UNKNOWN_SIGNATURE] +us="Signature by unknown keyid: " + +#untested +[ENTER_PASSPHRASE] +us="Enter pass phrase: " + +#Untested +[RANDOM_BITS_FROM_DEVICE] +us="\n\ +We need to generate %u random bits. This is done by reading\n\ +%s. Depending on your system, you may be able\n\ +to speed this process by typing on your keyboard and/or moving your mouse.\n" + +#Untested +[RANDOM_BITS_FROM_DEVICE_OLD_KERNEL] +us="\n\ +/dev/random detected; however, on Linux kernel versions < 1.3.33, it is not\n\ +cryptographically usable. If you wish to use /dev/random as an entropy\n\ +source, it is recommended that you upgrade your kernel version. If you feel\n\ +that you received this message in error, add ForceRandomDevice=1 to your\n\ +pgp.cfg file, but be warned that by doing so without know what you are\n\ +doing, you could compromise the security of your key.\n" + +#Untested +[RANDOM_BITS_FROM_KEYBOARD] +us="\n\ +We need to generate %u random bits. This is done by measuring the\n\ +time intervals between your keystrokes. Please enter some random text\n\ +on your keyboard until you hear the beep:\n" + +#Untested +[NO_INPUT_FILE_IN_BATCHMODE] +us="Cannot request input file in batchmode\n" + +#Untested +[UNABLE_TO_OPEN_FILE] +us="Unable to open file \"%s\"\n" + +#Untested +[UNABLE_TO_CREATE_READ_MODULE] +us="Unable to create file read module.\n" + +#Untested +[UNKNOWN_FILE_TYPE] +us="Unknown file type (clearsigned?). Assuming text\n" + +#Untested +[OPENING_FILE_WITH_TYPE] +us="Opening file \"%s\" type %s.\n" +mutt= + +#Untested +[ERROR_CLOSING_OLD_FILE] +us="Error closing old file: %d\n" + +#Untested +[NEED_PASSPHRASE_TO_DECRYPT_KEY] +us="Need a pass phrase to decrypt private key:\n" + +#Untested +[GOOD_PASSPHRASE] +us="Pass phrase is good.\n" + +#Untested +[BAD_PASSPHRASE] +us="Error: Bad pass phrase.\n\n" + +#Untested +[PASSPHRASE_INCORRECT] +us="Password Incorrect." + +#Untested +[TRY_AGAIN] +us=" Try Again." + +#Untested +[UNKNOWN_ESK] +us="Unknown ESK type: %d\n" + +#Untested +[CANNOT_DECRYPT] +us="Cannot decrypt message. It can only be decrypted by:\n" + +#Untested +[A_PASSPHRASE] +us=" A Pass Phrase\n" + +#Untested +[KEY_ID] +us=" Key ID " + +#Untested +[FORCE_OVERWRITE] +us="File \"%s\" already exists. Overwrite? [y/N] " + +#Untested +[UNABLE_TO_OVERWRITE_FILE] +us="Unable to overwrite file \"%s\"\n" + +#Untested +[RANDOM_DEVICE_NOT_DEFAULT] +us="Warning! Random device is something other than %s!\n\ +This MAY be a security hole.\n" + +#Untested +[RANDOM_DEVICE_WRITABLE] +us="Warning! %s is writable by users other than root!\n\ +This is probably OK, but you should have your sysadmin fix it.\n\ +Proceeding.\n" + +#Untested +[RANDOM_DEVICE_UNREADABLE] +us="\ +Warning! Random device %s found, but you can't read it!\n" + +#Untested +[BITS_AND_KEYID] +us="%6u bits, Key ID " + +#Untested +[KEY_NOT_FOUND] +us=Key not found: \"%s\"\n + +#Untested +[PGPERR_TROUBLE_BADTRUST_LONG] +us="Trust packet too long: %lu bytes long" + +#Untested +[PGPERR_TROUBLE_UNKPKTBYTE_LONG] +us="Unknown packet byte: %02X" + +#Untested +[PGPERR_TROUBLE_KEY2BIG_LONG] +us="Key grossly oversized: %lu bytes long" + +#Untested +[PGPERR_TROUBLE_NAME2BIG_LONG] +us="User ID too long: %lu bytes long" + +#Untested +[PGPERR_TROUBLE_SIG2BIG_LONG] +us="Signature grossly oversized: %lu bytes long" + +#Untested +[PGPERR_TROUBLE_DUPKEYID_LONG] +us="Duplicate keyID found. Two keys have the same keyID,\n\ +but they are different. This is highly suspicious. The first key +is:" + +#Untested +[PGPERR_TROUBLE_DUPKEY_LONG]: +us="A key was found twice in one keyring file. It is a duplicate of:\n" + +#Untested +[PGPERR_TROUBLE_DUPNAME_LONG] +us="A name was found twice in one keyring file. It is a duplicate of:\n" + +#Untested +[PGPERR_TROUBLE_BAREKEY_LONG] +us="A key was found twice in one keyring file. It is a duplicate of: " + +#Untested +[PGPERR_TROUBLE_VERSION_BUG_CUR_LONG] +us="This private key's version number appears to be incorrect.\n\ +PGP version 2.6 had a bug wherein it would improperly change the\n\ +version number of a private key generated by older versions of PGP\n\ +when it was edited. The private key in this key file has a version\n\ +byte that is different from a copy in another key file, and appears\n\ +to be improper. PGP will fix this by changing the version byte in\n\ +the private key to the previous value. The key with the problem +is:\n" + +#Untested +[PGPERR_TROUBLE_VERSION_BUG_PREV_LONG] +us="A previously seen private key's version number appears to be\n\ +incorrect. PGP version 2.6 had a bug wherein it would improperly\n\ +change the version byte of a private key generated by older versions\n\ +of PGP when it was edited. The public key in this key file has\n\ +a version byte that is different from a private key elsewhere,\n\ +which appears to be improper. PGP will fix this by changing the\n\ +version byte in the private key to the previous value. The key\n\ +with the problem is:\n" + +#Untested +[PGPERR_KEYIO_READING_LONG] +us="I/O error reading file: %s" + +#Untested +[PGPERR_KEYIO_FTELL_LONG] +us="I/O error during call to ftell(): %s" + +#Untested +[PGPERR_PRECEDING_ASSOCIATED_WITH] +us="The preceeding error was associated with: " + +#Untested +[NOT_PGP_KEYFILE] +us="File is not a PGP key file. Aborting.\n" + +#Untested +[FOLLOWING_KEYRING_PROBLEMS] +us="The following problems were encountered while reading the keyring:\n" + +#Untested +[OFFSET_DESCRIPTION] +us="Offset Description\n" + +#Untested +[UNKNOWN_SIGNATOR] +us=" (Unknown signator, can't be checked)\n" + +#Untested +[OPEN_PAREN_KEYID] +us=" (KeyID:" + +#Untested +[REVOKED] +us="*REVOKED*" + +#Untested +[ABOVE_KEY_REVOKED] +us="\ +WARNING: The above key has been revoked by its owner,\n\ +possibly because the private key was compromised.\n\ +You cannot use a revoked key for encryption.\n" + +#Untested +[ABOVE_KEY_DISABLED] +us="\ +WARNING: The above key has been disabled on your keyring. If you\n\ +wish to use it, use \"pgpk -d\" to reenable it.\n" + +[ABOVE_KEY_EXPIRED] +us="\ +WARNING: The above key is not valid for use after %s.\n" + +#Untested +[STILL_USE_EXPIRED_KEY] +us="\ +WARNING: This key is not valid for use after %s.\n\ +Do you still want to use it? [y/N] " + +#Untested +[PGP_NAMETRUST_UNKNOWN] +us="\ +WARNING: Because the following name has not been certified\n\ +by a trusted signature, it is not known with a high\n\ +degree of confidence that the above key belongs to:\n" + +#Untested +[PGP_NAMETRUST_UNTRUSTED] +us="WARNING: The above key is not trusted to belong to:\n" + +#Untested +[PGP_NAMETRUST_MARGINAL] +us="\ +WARNING: Because the following name is not certified with sufficient\n\ +trusted signatures, it is not known with high confidence that the\n\ +above key actually belongs to:\n" + +#Untested +[PGP_NEWTRUST_NOT_TRUSTED] +us="\n\ +WARNING: The above key is not trusted to belong to:\n" + +#Untested +[PGP_NEWTRUST_PARTIAL_TRUST] +us="\n\ +WARNING: Because the following name is not certified with sufficient\n\ +trusted signatures, there is an estimated 1/%-ld probability that\n\ +the above key may not belong to:\n" + +#Untested +[PGP_NEWTRUST_NOT_TRUSTED_SIGNING_KET] +us="\n\ +WARNING: The signing key is not trusted to belong to:\n" + +#Untested +[PREVIOUSLY_APPROVED_KEY] +us="\nBut you previously approved using the key with this name.\n" + +#Untested +[DO_YOU_WISH_TO_USE_UNTRUSTED_KEY] +us="\nDo you want to use the key with this name? [y/N] " + +#Untested +[DONT_TRUST_SIGS_FROM_REVOKED_KEYS] +us="\ +WARNING: The signing key has been revoked by its owner,\n\ +possibly because the private key was compromised.\n\ +A signature made by this key should not be trusted.\n" + +#Untested +[YOU_HAVE_DISABLED_SIGNING_KEY] +us="WARNING: You have disabled the signing key\n" + +#Untested +[KEY_HAS_EXPIRED] +us="WARNING: This key is not valid for use after %s.\n" + +#Untested +[PGP_NAMETRUST_UNTRUSTED_SIGNING_KEY] +us="\nWARNING: The signing key is not trusted to belong to:\n" + +[MESSAGE_IS_ENCRYPTED] +us="Message is encrypted.\n" + +[GETTING_KEY_FOR] +us="Getting key for %s.\n" + +[LOOKING_UP_HOST] +us="Looking up host %s\n" + +[ESTABLISHING_CONNECTION] +us="Establishing connection\n" + +[SENDING_REQUEST] +us="Sending request\n" + +[RECEIVING_DATA] +us="Receiving data\n" + +[CLEANING_UP] +us="Cleaning up\n" + +[COMPLETE] +us="Complete.\n" + +[ONE_KEY_RECEIVED] +us="One key received. Adding it to your keyring...\n" + +[MANY_KEYS_RECEIVED] +us="%li keys received. Adding them to your keyring...\n" + +[UNKNOWN_PROTOCOL] +us="Unknown protocol %s.\n" + +[SENDING_KEY] +us="Sending key \r" + +[RECEIVING_RESPONSE] +us="Receiving response \r" + +#Untested +[NO_KEYFILE_SPECIFIED] +us="-a argument requires a key file or URL to add to your keyring." + +#Untested +[UNABLE_TO_IMPORT_KEYFILE] +us="Unable to import keyfile \"%s\".\n" + +#Untested +[ADDING_KEYS] +us="Adding keys:\n\n" + +#Untested +[UNABLE_TO_CREATE_KEYLIST] +us="Unable to create keylist\n" + +#Untested +[NO_KEYS_TO_ADD] +us="No keys to add \n" + +#Untested +[KEYS_ADDED_SUCCESSFULLY] +us="Keys added successfully.\n" + +#Untested +[INVALID_SELECTION] +us="Invalid Selection. Please try again.\n" + +[TOO_MANY_MATCHES] +us="Too many matches; aborting!\n" + +[CHOOSE_ONE_ABOVE] +us="Choose one of the above: " + +[PLEASE_SELECT_A_USER_ID] +us="Please select a user ID %s:\n" + +[PLEASE_SELECT_A_USER_ID_WITH_SIG] +us="Please select a user ID with a signature %s:\n" + +[PLEASE_SELECT_A_KEY_WITH_USERID] +us="Please select a key with a userid %s:" + +[PLEASE_SELECT_A_KEY_WITH_SIG] +us="Please select a key with a signature %s:" + +[NO_USER_IDS_SELECTED] +us="No user IDs selected %s.\n" + +[PLEASE_SELECT_A_SIGNATURE] +us="Please select a signature %s:" + +[NO_SIGNATURES_SELECTED] +us="No signatures selected %s.\n" + +[NO_KEYS_SELECTED] +us="No keys selected %s.\n" + +[A_USERID_IS_REQUIRED] +us="A user ID is required to select the key you want %s.\n\ +Enter the key's user ID: " + +[UNABLE_TO_ORDER_KEYSET] +us="Unable to order keyset\n" + +[PLEASE_SELECT_A_KEY] +us="Please select a key %s:" + +[UNABLE_TO_CREATE_ITER] +us="Unable to create key iterator\n" + +[NO_HTTP_SEND] +us="HTTP cannot be used as a sending protocol at this time.\n" + +[UNKNOWN_PROTOCOL] +us="Unknown protocol %s.\n" + +[NO_KEYS_SELECTED_FOR_EXTRACTION] +us="No keys were selected for extraction.\n" + +[ENABLE_THIS_KEY] +us="\nEnable this key? [y/N] " + +[DISABLE_THIS_KEY] +us="\nDisable this key? [y/N] " + +[KEY_ENABLED] +us="\nKey enabled.\n" + +[KEY_DISABLED] +us="\nKey disabled.\n" + +[CANNOT_TRUST_INVALID_KEYS] +us="This key is not valid, and cannot be assigned trust\n" + +[DO_YOU_WISH_TO_CHANGE_INTRODUCER_RELIABITY] +us="Do you want to change your estimate of this key owner's reliability\n\ +as an introducer of other keys [y/N]? " + +[NO_CHANGES_MADE] +us="No changes made.\n" + +[DETERMINE_IN_YOUR_MIND] +us="\n"\ +"Make a determination in your own mind whether this key actually\n"\ +"belongs to the person whom you think it belongs to, based on available\n"\ +"evidence. If you think it does, then based on your estimate of\n"\ +"that person's integrity and competence in key management, answer\n"\ +"the following question:\n" + +[WOULD_YOU_TRUST_THIS_KEY_AND_OWNER] +us="\nWould you trust this key owner to act as an introducer and\n\ +certify other people's public keys to you?\n\ +(1=I don't know. 2=No. 3=Usually. 4=Yes, always? " + +[UNRECOGNIZED_RESPONSE] +us="Unrecognized response.\n" + +[UNABLE_TO_SET_TRUST] +us="Unable to set trust\n" + +[DESCRIBE_CONFIDENCE_AS_INTRODUCER] +us="\nDescribe the confidence you have in this person as an introducer.\n\ +What are the odds that this key owner is going to be wrong about\n\ +a key which she has signed as an introducer?\n" + +[CURRENTLY_INFINITE_TRUST] +us="(Currently she is listed as having essentially zero chance\ + of being wrong.)\n" + +[CURRENTLY_ZERO_TRUST] +us="(Currently he is listed as not having any confidence as an\ + introducer.)\n" + +[CURRENTLY_HAS_PERCENT_TRUST_START] +us="(Currently she is listed as having a one in " + +[CURRENTLY_HAS_PERCENT_TRUST_END] +us=" chance of being wrong.)\n" + +[ENTER_A_TRUST_RANGE] +us="Enter a number from 1 to 2 million" + +[OR_HIT_RETURN_TO_LEAVE_UNCHANGED] +us=", or hit return to leave unchanged." + +[WILL_BE_WRONG_TIME_TIME_IN] +us="\nShe will be wrong one time in: " + +[DO_YOU_WANT_THIS_KEY_AXIOMATIC] +us="\nDo you want to set this key as axiomatic [y/N]? " + +[PUBLIC_KEYRING_UPDATED] +us="Public keyring updated.\n" + +[NEED_OLD_PASSPHRASE] +us="Need old passphrase. " + +[NEED_NEW_PASSPHRASE] +us="Need new passphrase. " + +[ENTER_IT_A_SECOND_TIME] +us="Enter it a second time. " + +[PASSPHRASES_ARE_DIFFERENT] +us="Passphrases are different\n" + +[CHANGING_MASTER_KEY_PASSPHRASE] +us="Changing master key passphrase...\n" + +[PASSPHRASE_CHANGE_FAILED_MASTER] +us="Passphrase change failed for master key.\n" + +[CHANGING_SUBKEY_PASSPHRASE] +us="Changing subkey passphrase...\n" + +[PASSPHRASE_CHANGE_FAILED_SUBKEY] +us="Passphrase change failed for subkey.\n" + +[CONFIRM_NON_AXIOMATIC] +us="\nDo you want to unset this key as axiomatic [y/N]? " + +[CONFIRM_ADD_NEW_USERID] +us="\nDo you want to add a new user ID [y/N]? " + +[ENTER_NEW_USERID] +us="Enter the new user ID: " + +[NO_NAME_ENTERED] +us="No name entered.\n" + +[UNABLE_TO_ADD_NEW_USERID] +us="Unable to add new User ID (%d)\n" + +[CONFIRM_CHANGE_PASSPHRASE] +us="\nDo you want to change your pass phrase (y/N)? " + +[CHANGE_PASSPHRASE_FAILED] +us="Change passphrase failed (%d)\n" + +[CONFIRM_SET_DEFAULT_KEY] +us="\nDo want to set this as your default key [y/N]? " + +[KEYRINGS_UPDATED] +us="Keyrings updated.\n" + +[TO_BE_REMOVED_FRAGMENT] +us="to be removed" + +[SIGNATURE_FRAGMENT] +us="signature" + +[USERID_FRAGMENT] +us="user ID" + +[KEY_FRAGMENT] +us="key" + +[SELECTED_KEY_HAS_ONLY_ONE_USERID"] +us="Selected key has only one user ID; can't be selected %s\n" + +[FOLLOWING_OBJECT_HAS_BEEN_SELECTED] +us="\nThe following %s has been selected %s:\n" + +[UNABLE_TO_REMOVE_OBJECT] +us="Unable to remove object\n" + +[TO_BE_SIGNED_FRAGMENT] +us="to be signed" + +[VALIDITY_CERTIFICATION_WARNING] +us="\n\n\ +READ CAREFULLY: Based on your own direct first-hand knowledge, are\n\ +you absolutely certain that you are prepared to solemnly certify that\n\ +the above public key actually belongs to the user specified by the\n\ +above user ID [y/N]? " + +[KEY_SIGNING_CANCELED] +us="Key sign operation cancelled.\n" + +[KEY_SELECTED_FOR_SIGNING_IS] +us="Key selected for signing is:\n" + +[KEY_SIGN_OPERATION_FAILED] +us="Key sign operation failed\n" + +[KEY_SIG_CERT_ADDED] +us="Key signature certificate added.\n" + +[TO_BE_REVOKED_FRAGMENT] +us="to be revoked" + +[YOU_DONT_HAVE_THE_PRIVATE_KEY] +us="You don't have the private key corresponding to that key\n" + +[SIG_ALREADY_REVOKED] +us="That signature has already been revoked.\n\ +Are you sure you want to add another revocation certificate [y/N]? " + +[SIG_REVOCATION_CANCELLED] +us="Signature revocation cancelled.\n" + +[CONFIRM_REVOKE_KEY] +us="Do you want to permanently revoke your public key\n\ +by issuing a secret key compromise certificate on this key [y/N]? " + +[CONFIRM_REVOKE_SIG] +us="Do you want to revoke this signature [y/N]? " + +[REVOKE_CANCELLED] +us="Revoke cancelled.\n" + +[UNABLE_TO_GENERATE_REVOCATION_SIGNATURE] +us="Unable to generate revocation signature\n" + +[KEY_REVOCATION_CERT_ADDED] +us="Key revocation certificate added.\n" + +[SELECT_SIGNING_KEY] +us="Please select a key to sign with:" + +[UNABLE_TO_OPEN_KEYRING] +us="Unable to open keyring\n" + +[PGPINITAPP_FAILED] +us="pgpInitApp failed\n" + +[KEY_IS_ALREADY_REVOKED] +us="That key has already been revoked\n" + +[USE_FORCE_TO_ALLOW_OVERWRITING] +us="In batchmode, use +force to allow overwriting of output files\n" + +[INCONSISTENT_RECIPIENT_SET] +us="No algorithm available that all keys support.\n" + +[UNKNOWN_ERROR] +us="Unknown error code %i!\n" + +[VERIFY_REMOVE_KEY_PUBLIC_PRIVATE] +us="\nDo you wish to remove this key from your public and private \ +keyrings?\n[y/N]? " + +[UNABLE_TO_ITERATE_KEY] +us="Unable to iterate key!\n"; + +[CANCELED] +us="Canceled.\n" + +[REMOVED] +us="Removed.\n" + +[NEED_FILE_TO_SAVE] +us="Save file as [%s] " + +[PGP_NEWTRUST_NOT_TRUSTED_SIGNING_KEY] +us="WARNING: The signing key is not trusted to belong to:\n" + +[TO_DISABLE_OR_ENABLE] +us="to disable or enable" + +[TO_EDIT] +us="to edit" + +[SELECTED_KEY_HAS_ONLY_ONE_USERID] +us="Selected key has only one user ID, can't be selected %s\n" + +[NO_DEFAULT_PRIVATE_KEY] +us="No default private key\n" + +[MULTIPLE_RECIPIENTS_MATCHED] +us="WARNING: %i matches were found for recipient %s.\n\ +This may not be what you intended.\n" + +[ENOUGH_THANK_YOU] +us="\a -Enough, thank you.\n" + +[SEEDING_RANDPOOL_FROM_DEVICE] +us="Seeding entropy pool with up to %u bits from %s...\n" + +[COMPLETE_READ_NUM_BITS] +us="Complete. Read %u bits.\n" + +[RSA_AND_DH_RECIPS] +us="WARNING: You are encrypting to both RSA and Diffie-Hellman keys.\n\ +If the RSA user is still using PGP version 2.6.3 or earlier; 4.0; or 4.5,\n\ +she will not be able to decrypt this message.\n" + +[ONLY_ONE_USER_ALLOWED] +us=Specified operation may only be performed on one argument per execution.\n + +[CANNOT_DISABLE_AXIOMATIC_KEYS] +us=You cannot disable an axiomatic key. Use pgpk -e to change your\n\ +trust of this key, first.\n + +[RETRIEVING_URL] +us="Retreiving %s:/%s:%i%s\n" + +[ADD_THESE_KEYS] +us="\nAdd these keys to your keyring? [Y/n] " + +[ABORTED] +us="\nAborted.\n" + +[WARNING_NO_MRK] +us="A requested Message Recovery Key (MRK) for this key was not\ +found.\n" + +[MRK_FOUND] +us="Message Recovery Key (MRK) found. Will also encrypt this message\n\ +to Key ID %s.\n" + +#Everything from here down is automatically generated. + +[PGPERR_OK] +us="No errors\n" + +[PGPERR_GENERIC] +us="Generic error (should be changed)\n" + +[PGPERR_NOMEM] +us="Out of Memory\n" + +[PGPERR_BADPARAM] +us="Invalid Parameter\n" + +[PGPERR_NO_FILE] +us="Cannot open file\n" + +[PGPERR_NO_KEYBITS] +us="Internal keyring bits exhausted\n" + +[PGPERR_BAD_HASHNUM] +us="Unknown hash number\n" + +[PGPERR_BAD_CIPHERNUM] +us="Unknown cipher number\n" + +[PGPERR_BAD_KEYLEN] +us="Illegal key length for cipher\n" + +[PGPERR_SIZEADVISE] +us="SizeAdvise promise not kept\n" + +[PGPERR_CONFIG] +us="Error parsing configuration\n" + +[PGPERR_CONFIG_BADFUNC] +us="Invalid configuration function\n" + +[PGPERR_CONFIG_BADOPT] +us="Unknown configuration option\n" + +[PGPERR_STRING_NOT_FOUND] +us="Requested string not found\n" + +[PGPERR_STRING_NOT_IN_LANGUAGE] +us="Requested string not in language\n" + +[PGPERR_KEY_ISLOCKED] +us="Key requires passphrase to unlock\n" + +[PGPERR_KEY_UNUNLOCKABLE] +us="Key requires passphrase each time\n" + +[PGPERR_SIG_ERROR] +us="Error while processing signature\n" + +[PGPERR_ADDSIG_ERROR] +us="Cannot add signature\n" + +[PGPERR_CANNOT_DECRYPT] +us="Cannot decrypt message\n" + +[PGPERR_ADDESK_ERROR] +us="Cannot add encrypted session key\n" + +[PGPERR_UNK_STRING2KEY] +us="Don't know how to convert pass\n" + +[PGPERR_BAD_STRING2KEY] +us="Invalid conversion from pass\n" + +[PGPERR_ESK_BADTYPE] +us="Unknown encrypted session key type\n" + +[PGPERR_ESK_TOOSHORT] +us="Encrypted session key too short\n" + +[PGPERR_ESK_TOOLONG] +us="Encrypted session key too long\n" + +[PGPERR_ESK_BADVERSION] +us="Encrypted session key version\n" + +[PGPERR_ESK_BADALGORITHM] +us="Encrypted session key algorithm\n" + +[PGPERR_ESK_BITSWRONG] +us="Wrong number of bits in ESK\n" + +[PGPERR_ESK_NOKEY] +us="Can't find key to decrypt session key\n" + +[PGPERR_ESK_NODECRYPT] +us="Can't decrypt this session key\n" + +[PGPERR_ESK_BADPASS] +us="Passphrase incorrect\n" + +[PGPERR_SIG_BADTYPE] +us="Unknown signature type\n" + +[PGPERR_SIG_TOOSHORT] +us="Signature too short\n" + +[PGPERR_SIG_TOOLONG] +us="Signature too long\n" + +[PGPERR_SIG_BADVERSION] +us="Signature version unknown\n" + +[PGPERR_SIG_BADALGORITHM] +us="Signature algorithm unknown\n" + +[PGPERR_SIG_BITSWRONG] +us="Wrong number of bits in signature\n" + +[PGPERR_SIG_NOKEY] +us="Can't find necessary key to check sig\n" + +[PGPERR_SIG_BADEXTRA] +us="Invalid Extra Data for Signature\n" + +[PGPERR_NO_PUBKEY] +us="No public key found\n" + +[PGPERR_NO_SECKEY] +us="No secret key found\n" + +[PGPERR_UNKNOWN_KEYID] +us="No matching keyid found\n" + +[PGPERR_NO_RECOVERYKEY] +us="Requested message recovery key\n" + +[PGPERR_COMMIT_INVALID] +us="Invalid commit response\n" + +[PGPERR_CANNOT_HASH] +us="Cannot hash message\n" + +[PGPERR_UNBALANCED_SCOPE] +us="Unbalanced scope\n" + +[PGPERR_WRONG_SCOPE] +us="Data sent in wrong scope\n" + +[PGPERR_UI_INVALID] +us="Invalid UI Callback Object\n" + +[PGPERR_CB_INVALID] +us="Invalid Parser Callback\n" + +[PGPERR_INTERRUPTED] +us="Interrupted encrypt/decrypt\n" + +[PGPERR_PUBKEY_TOOSMALL] +us="Public Key too small for data\n" + +[PGPERR_PUBKEY_TOOBIG] +us="Public key is too big for this version\n" + +[PGPERR_PUBKEY_UNIMP] +us="Unimplemented public key operation\n" + +[PGPERR_RSA_CORRUPT] +us="Corrupt data decrypting RSA block\n" + +[PGPERR_PK_CORRUPT] +us="Corrupt data decrypting public\n" + +[PGPERR_CMD_TOOBIG] +us="Command to Buffer too big\n" + +[PGPERR_FIFO_READ] +us="Incomplete read from Fifo\n" + +[PGPERR_VRFYSIG_WRITE] +us="Data illegally written into\n" + +[PGPERR_VRFYSIG_BADANN] +us="Invalid annotation to signature\n" + +[PGPERR_ADDHDR_FLUSH] +us="Cannot flush buffer until size\n" + +[PGPERR_JOIN_BADANN] +us="Invalid annotation to join module\n" + +[PGPERR_RANDSEED_TOOSMALL] +us="Not enough data in randseed file\n" + +[PGPERR_ENV_LOWPRI] +us="Env Var not set: priority too low\n" + +[PGPERR_ENV_BADVAR] +us="Invalid environment variable\n" + +[PGPERR_CHARMAP_UNKNOWN] +us="Unknown Charset\n" + +[PGPERR_FILE_PERMISSIONS] +us="Unsufficient file permissions\n" + +[PGPERR_FILE_WRITELOCKED] +us="File already open for writing\n" + +[PGPERR_FILE_BADOP] +us="Invalid PgpFile Operation\n" + +[PGPERR_FILE_OPFAIL] +us="PgpFile Operation Failed\n" + +[PGPERR_IMMUTABLE] +us="Attempt to change an\n" + +[PGPERR_PARSEASC_INCOMPLETE] +us="Ascii Armor Input Incomplete\n" + +[PGPERR_PARSEASC_BADINPUT] +us="PGP text input is corrupted\n" + +[PGPERR_FILEFIFO_SEEK] +us="Temp-File Seek Error\n" + +[PGPERR_FILEFIFO_WRITE] +us="Temp-File Write Error\n" + +[PGPERR_FILEFIFO_READ] +us="Temp-File Read Error\n" + +[PGPERR_FILEIO_BADPKT] +us="Corrupted or bad packet in\n" + +[PGPERR_SYSTEM_PGPK] +us="Error Executing PGPK Program\n" + +[PGPERR_KEYIO_READING] +us="I/O error reading keyring\n" + +[PGPERR_KEYIO_WRITING] +us="I/O error writing keyring\n" + +[PGPERR_KEYIO_FTELL] +us="I/O error finding keyring position\n" + +[PGPERR_KEYIO_SEEKING] +us="I/O error seeking keyring\n" + +[PGPERR_KEYIO_OPENING] +us="I/O error opening keyring\n" + +[PGPERR_KEYIO_CLOSING] +us="I/O error closing keyring\n" + +[PGPERR_KEYIO_FLUSHING] +us="I/O error flushing keyring\n" + +[PGPERR_KEYIO_EOF] +us="Unexpected EOF fetching key packet\n" + +[PGPERR_KEYIO_BADPKT] +us="Bad data found where key\n" + +[PGPERR_KEYIO_BADFILE] +us="Not a keyring file\n" + +[PGPERR_TROUBLE_KEYSUBKEY] +us="Key matches subkey\n" + +[PGPERR_TROUBLE_SIGSUBKEY] +us="Signature by subkey\n" + +[PGPERR_TROUBLE_BADTRUST] +us="Trust packet malformed\n" + +[PGPERR_TROUBLE_UNKPKTBYTE] +us="Unknown packet byte in keyring\n" + +[PGPERR_TROUBLE_UNXSUBKEY] +us="Unexpected subkey (before key)\n" + +[PGPERR_TROUBLE_UNXNAME] +us="Unexpected name (before key)\n" + +[PGPERR_TROUBLE_UNXSIG] +us="Unexpected sig (before key)\n" + +[PGPERR_TROUBLE_UNXUNK] +us="Packet of unknown type in unexpected\n" + +[PGPERR_TROUBLE_UNXTRUST] +us="Unexpected trust packet\n" + +[PGPERR_TROUBLE_KEY2BIG] +us="Key grossly oversized\n" + +[PGPERR_TROUBLE_SEC2BIG] +us="Secret key grossly oversized\n" + +[PGPERR_TROUBLE_NAME2BIG] +us="Name grossly oversized\n" + +[PGPERR_TROUBLE_SIG2BIG] +us="Sig grossly oversized\n" + +[PGPERR_TROUBLE_UNK2BIG] +us="Packet of unknown type too large\n" + +[PGPERR_TROUBLE_DUPKEYID] +us="Duplicate KeyID, different keys\n" + +[PGPERR_TROUBLE_DUPKEY] +us="Duplicate key (in same keyring)\n" + +[PGPERR_TROUBLE_DUPSEC] +us="Duplicate secret (in same keyring)\n" + +[PGPERR_TROUBLE_DUPNAME] +us="Duplicate name (in same keyring)\n" + +[PGPERR_TROUBLE_DUPSIG] +us="Duplicate signature (in same keyring)\n" + +[PGPERR_TROUBLE_DUPUNK] +us="Duplicate unknown \"thing\" in keyring\n" + +[PGPERR_TROUBLE_BAREKEY] +us="Key found with no names\n" + +[PGPERR_TROUBLE_VERSION_BUG_PREV] +us="Bug introduced by legal_kludge\n" + +[PGPERR_TROUBLE_VERSION_BUG_CUR] +us="Bug introduced by legal_kludge\n" + +[PGPERR_TROUBLE_OLDSEC] +us="Passphrase is out of date\n" + +[PGPERR_TROUBLE_NEWSEC] +us="Passphrase is newer than another\n" + +[PGPERR_KEY_NO_RSA_ENCRYPT] +us="No RSA Encryption/Signature support\n" + +[PGPERR_KEY_NO_RSA_DECRYPT] +us="No RSA Decryption/Verification support\n" + +[PGPERR_KEY_NO_RSA] +us="No RSA key support\n" + +[PGPERR_KEY_LONG] +us="Key packet has trailing junk\n" + +[PGPERR_KEY_SHORT] +us="Key packet truncated\n" + +[PGPERR_KEY_VERSION] +us="Key version unknown\n" + +[PGPERR_KEY_PKALG] +us="Key algorithm unknown\n" + +[PGPERR_KEY_MODMPI] +us="Key modulus mis-formatted\n" + +[PGPERR_KEY_EXPMPI] +us="Key exponent mis-formatted\n" + +[PGPERR_KEY_MODEVEN] +us="RSA public modulus is even\n" + +[PGPERR_KEY_EXPEVEN] +us="RSA public exponent is even\n" + +[PGPERR_KEY_MPI] +us="Key component mis-formatted\n" + +[PGPERR_KEY_UNSUPP] +us="Key is not supported by this version of PGP\n" + +[PGPERR_SIG_LONG] +us="Signature packet has trailing junk\n" + +[PGPERR_SIG_SHORT] +us="Signature truncated\n" + +[PGPERR_SIG_MPI] +us="Signature integer mis-formatted\n" + +[PGPERR_SIG_PKALG] +us="Signature algorithm unknown\n" + +[PGPERR_SIG_EXTRALEN] +us="Bad signature extra material (not 5)\n" + +[PGPERR_SIG_VERSION] +us="Signature version unknown\n" + +[PGPERR_KEYDB_BADPASSPHRASE] +us="Bad passphrase\n" + +[PGPERR_KEYDB_KEYDBREADONLY] +us="Key database is read-only\n" + +[PGPERR_KEYDB_NEEDMOREBITS] +us="Insufficient random bits\n" + +[PGPERR_KEYDB_OBJECTREADONLY] +us="Object is read-only\n" + +[PGPERR_KEYDB_INVALIDPROPERTY] +us="Invalid property name\n" + +[PGPERR_KEYDB_BUFFERTOOSHORT] +us="Buffer too short\n" + +[PGPERR_KEYDB_CORRUPT] +us="Key database is corrupt\n" + +[PGPERR_KEYDB_VERSIONTOONEW] +us="Data is too new to be read\n" + +[PGPERR_KEYDB_IOERROR] +us="Input/output error\n" + +[PGPERR_KEYDB_VALUETOOLONG] +us="Value too long\n" + +[PGPERR_KEYDB_DUPLICATE_CERT] +us="Duplicate certification\n" + +[PGPERR_KEYDB_DUPLICATE_USERID] +us="Duplicate UserID\n" + +[PGPERR_KEYDB_CERTIFYINGKEY_DEAD] +us="Certifying key no longer\n" + +[PGPERR_KEYDB_OBJECT_DELETED] +us="Object has been deleted\n" diff --git a/doc/makefile b/doc/makefile new file mode 100644 index 00000000..7d85b315 --- /dev/null +++ b/doc/makefile @@ -0,0 +1,13 @@ +all: manual.txt mutt.man manual.html + +manual.txt: manual.sgml + sgml2txt manual + +manual.html: manual.sgml + sgml2html manual + +mutt.man: mutt.sgml + sgml2txt -man mutt + +clean clean-real distclean: + rm -f *~ *.html *.orig *.rej diff --git a/doc/manual.sgml b/doc/manual.sgml new file mode 100644 index 00000000..ef54e694 --- /dev/null +++ b/doc/manual.sgml @@ -0,0 +1,4293 @@ + + +
+ +The Mutt E-Mail Client +<author>by Michael Elkins <htmlurl url="mailto:me@cs.hmc.edu" name="<me@cs.hmc.edu>"> +<date>v0.92.2, 23 April 1998 +<abstract> +``All mail clients suck. This one just sucks less.'' -me, circa 1995 +</abstract> + +<sect>Introduction +<p> +<bf/Mutt/ is a small but very powerful text-based MIME mail client. Mutt is +highly configurable, and is well suited to the mail power user with advanced +features like key bindings, keyboard macros, mail threading, regular +expression searches and a powerful pattern matching language for selecting +groups of messages. + +<sect1>Mutt Home Page +<p> +<htmlurl url="http://www.cs.hmc.edu/~me/mutt/index.html" +name="http://www.cs.hmc.edu/~me/mutt/index.html"> + +<sect1>Mailing Lists +<p> +To subscribe to one of the following mailing lists, send a message with the +word <em/subscribe/ in the subject to +<tt/list-name/<em/-request/<tt/@cs.hmc.edu/. + +<itemize> +<item><htmlurl url="mailto:mutt-announce-request@cs.hmc.edu" +name="mutt-announce@cs.hmc.edu"> -- low traffic list for announcements +<item><htmlurl url="mailto:mutt-users-request@cs.hmc.edu" +name="mutt-users@cs.hmc.edu"> -- help, bug reports and feature requests +<item><htmlurl url="mailto:mutt-dev-request@cs.hmc.edu" name="mutt-dev@cs.hmc.edu"> -- development mailing list +</itemize> + +<bf/Note:/ all messages posted to <em/mutt-announce/ are automatically +forwarded to <em/mutt-users/, so you do not need to be subscribed to both +lists. + +<sect1>Software Distribution Sites +<p> +<itemize> +<item><htmlurl url="ftp://ftp.cs.hmc.edu/pub/me/mutt/" +name="ftp://ftp.cs.hmc.edu/pub/me/mutt/"> +</itemize> + +<sect1>IRC +<p> +Visit channel <em/#mutt/ on <htmlurl url="http://www.dal.net" name="DALnet +(www.dal.net)"> to chat with other people interested in Mutt. + +<sect1>USENET +<p> +See the newsgroup <htmlurl url="news:comp.mail.mutt" name="comp.mail.mutt">. + +<sect1>Copyright +<p> +Mutt is Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + +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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +<sect>Getting Started +<p> + +This section is intended as a brief overview of how to use Mutt. There are +many other features which are described elsewhere in the manual. There +is even more information available in the Mutt FAQ and various web +pages. See the <htmlurl url="http://www.cs.hmc.edu/~me/mutt/" +name="Mutt Page"> for more details. + +The keybindings described in this section are the defaults as distributed. +Your local system administrator may have altered the defaults for your site. +You can always type ``?'' in any menu to display the current bindings. + +The first thing you need to do is invoke mutt, simply by typing mutt +at the command line. There are various command-line options, see +either the mutt man page or the <ref id="commandline" name="reference">. + +<sect1>Moving Around in Menus +<p> + +Information is presented in menus, very similar to ELM. Here is a table +showing the common keys used to navigate menus in Mutt. + +<tscreen><verb> +j or Down next-entry move to the next entry +k or Up previous-entry move to the previous entry +z or PageDn page-down go to the next page +Z or PageUp page-up go to the previous page += or Home first-entry jump to the first entry +* or End last-entry jump to the last entry +q quit exit the current menu +? help list all keybindings for the current menu +</verb></tscreen> + +<sect1>Editing Input Fields<label id="editing"> +<p> +Mutt has a builtin line editor which is used as the primary way to input +textual data such as email addresses or filenames. The keys used to move +around while editing are very similar to those of Emacs. + +<tscreen><verb> +^A or <Home> bol move to the start of the line +^B or <Left> backward-char move back one char +^D or <Delete> delete-char delete the char under the cursor +^E or <End> eol move to the end of the line +^F or <Right> forward-char move forward one char +^K kill-eol delete to the end of the line +^U kill-line delete entire line +^W kill-word kill the word in front of the cursor +<Up> history-up recall previous string from history +<Down> history-down recall next string from history +<BackSpace> backspace kill the char in front of the cursor +^G n/a abort +<Tab> n/a complete filename (only when prompting for a file) +<Return> n/a finish editing +</verb></tscreen> + +You can remap the <em/editor/ functions using the <ref id="bind" name="bind"> +command. For example, to make the <em/Delete/ key delete the character in +front of the cursor rather than under, you could use + +<tt/bind editor delete backspace/ + +<sect1>Reading Mail - The Index and Pager +<p> + +Similar to many other mail clients, there are two modes in which mail is +read in Mutt. The first is the index of messages in the mailbox, which is +called the ``index'' in Mutt. The second mode is the display of the +message contents. This is called the ``pager.'' + +The next few sections describe the functions provided in each of these +modes. + +<sect2>The Message Index +<p> + +<tscreen><verb> +c change to a different mailbox +ESC c change to a folder in read-only mode +C copy the current message to another mailbox +ESC C decode a message and copy it to a folder +ESC s decode a message and save it to a folder +D delete messages matching a pattern +d delete the current message +F mark as important +l show messages matching a pattern +N mark message as new +o change the current sort method +O reverse sort the mailbox +q save changes and exit +s save-message +t toggle the tag on a message +ESC t toggle tag on entire message thread +u undelete-message +v view-attachments +x abort changes and exit +<Return> display-message +<Tab> jump to the next new message +@ show the author's full e-mail address +$ save changes to mailbox +/ search +ESC / search-reverse +^L clear and redraw the screen +^T tag messages matching a pattern +^U undelete messages matching a pattern +</verb></tscreen> + +<sect3>Status Flags +<p> + +In addition to who sent the message and the subject, a short summary of +the disposition of each message is printed beside the message number. +Zero or more of the following ``flags'' may appear, which mean: + +<p> +<tscreen><verb> +D message is deleted +K contains a PGP public key +M requires mailcap to view +N message is new +O message is old +P message is PGP encrypted +r message has been replied to +S message is PGP signed +! message is flagged +* message is tagged +</verb></tscreen> + +Some of the status flags can be turned on or off using +<itemize> +<item><bf/set-flag/ (default: w) +<item><bf/clear-flag/ (default: W) +</itemize> + +<p> +Furthermore, the following flags reflect who the message is addressed +to. They can be customized with the +<ref id="to_chars" name="$to_chars"> variable. + +<p> +<tscreen><verb> ++ message is to you and you only +T message is to you, but also to or cc'ed to others +C message is cc'ed to you +F message is from you +</verb></tscreen> + +<sect2>The Pager +<p> + +By default, Mutt uses its builtin pager to display the body of messages. +The pager is very similar to the Unix program <em/less/ though not nearly as +featureful. + +<tscreen><verb> +<Return> go down one line +<Space> display the next page (or next message if at the end of a message) +- go back to the previous page +n display the next message +? show keybindings +/ search for a regular expression (pattern) +\ toggle search pattern coloring +</verb></tscreen> + +In addition, many of the functions from the <em/index/ are available in +the pager, such as <em/delete-message/ or <em/copy-message/ (this is one +advantage over using an external pager to view messages). + +Also, the internal pager supports a couple other advanced features. For +one, it will accept and translate the ``standard'' nroff sequences for +bold and underline. These sequences are a series of either the letter, +backspace (^H), the letter again for bold or the letter, backspace, +``_'' for denoting underline. Mutt will attempt to display these +in bold and underline respectively if your terminal supports them. If +not, you can use the bold and underline <ref id="color" name="color"> +objects to specify a color or mono attribute for them. + +Additionally, the internal pager supports the ANSI escape sequences for +character attributes. Mutt translates them into the correct color and +character settings. The sequences Mutt supports are: + +<p> +<tscreen><verb> +ESC [ Ps;Ps;Ps;...;Ps m +where Ps = +0 All Attributes Off +1 Bold on +4 Underline on +5 Blink on +7 Reverse video on +3x Foreground color is x +4x Background color is x + +Colors are +0 black +1 red +2 green +3 yellow +4 blue +5 magenta +6 cyan +7 white +</verb></tscreen> + +Mutt uses these attributes for handling text/enriched messages, and they +can also be used by an external <ref id="auto_view" name="autoview"> +script for highlighting purposes. <bf/Note:/ If you change the colors for your +display, for example by changing the color associated with color2 for +your xterm, then that color will be used instead of green. + +<sect2>Threaded Mode<label id="threads"> +<p> +When the mailbox is <ref id="sort" name="sorted"> by <em/threads/, there are +a few additional functions available in the <em/index/ and <em/pager/ modes. + +<tscreen><verb> +^D delete-thread delete all messages in the current thread +^U undelete-thread undelete all messages in the current thread +^N next-thread jump to the start of the next thread +^P previous-thread jump to the start of the previous thread +^R read-thread mark the current thread as read +ESC d delete-subthread delete all messages in the current subthread +ESC u undelete-subthread undelete all messages in the current subthread +ESC n next-subthread jump to the start of the next subthread +ESC p previous-subthread jump to the start of the previous subthread +ESC r read-subthread mark the current subthread as read +ESC t tag-thread toggle the tag on the current thread +</verb></tscreen> + +See also: <ref id="strict_threads" name="$strict_threads">. + +<sect2>Miscellaneous Functions +<p><bf/create-alias/<label id="create-alias"> (default: a)<newline> + +Creates a new alias based upon the current message (or prompts for a +new one). Once editing is complete, an <ref id="alias" name="alias"> +command is added to the file specified by the <ref id="alias_file" +name="$alias_file"> variable for future use. <bf/Note:/ +Specifying an <ref id="alias_file" name="$alias_file"> +does not add the aliases specified there-in, you must also <ref +id="source" name="source"> the file. + +<p><bf/display-headers/<label id="display-headers"> (default: h)<newline> + +Toggles the weeding of message header fields specified by <ref id="ignore" +name="ignore"> commands. + +<p><bf/enter-command/<label id="enter-command"> (default: ``:'')<newline> + +This command is used to execute any command you would normally put in a +configuration file. A common use is to check the settings of variables, or +in conjunction with <ref id="macro" name="macros"> to change settings on the +fly. + +<p><bf/extract-keys/<label id="extract-keys"> (default: ESC k)<newline> + +This command extracts PGP public keys from the current or tagged +message(s) and adds them to your <ref id="pgp_v2_pubring" +name="$pgp_v2_pubring"> or <ref +id="pgp_v5_pubring" name="$pgp_v5_pubring"> +depending on <ref id="pgp_key_version" +name="$pgp_key_version">. + +<p><bf/forget-passphrase/<label id="forget-passphrase"> (default: +^F)<newline> + +This command wipes the PGP passphrase from memory. It is useful, if +you misspelled the passphrase. + +<p><bf/list-reply/<label id="list-reply"> (default: L)<newline> + +Reply to the current or tagged message(s) by extracting any addresses which +match the addresses given by the <ref id="lists" name="lists"> command. +Using this when replying to messages posted to mailing lists help avoid +duplicate copies being sent to the author of the message you are replying +to. + +<bf/pipe-message/<label id="pipe-message"> (default: |)<newline> + +Asks for an external Unix command and pipes the current or +tagged message(s) to it. The variables <ref id="pipe_decode" +name="$pipe_decode">, <ref id="pipe_split" +name="$pipe_split">, <ref id="pipe_sep" +name="$pipe_sep"> and <ref id="wait_key" +name="$wait_key"> control the exact behaviour of this +function. + +<bf/shell-escape/<label id="shell-escape"> (default: !)<newline> + +Asks for an external Unix command and executes it. The <ref +id="wait_key" name="$wait_key"> can be used to control +whether Mutt will wait for a key to be pressed when the command returns +(presumably to let the user read the output of the command), based on +the return status of the named command. + +<bf/toggle-quoted/<label id="toggle-quoted"> (default: T)<newline> + +The <em/pager/ uses the <ref id="quote_regexp" +name="$quote_regexp"> variable to detect quoted text when +displaying the body of the message. This function toggles the display +of the quoted material in the message. It is particularly useful when +are interested in just the response and there is a large amount of +quoted text in the way. + +<bf/skip-quoted/<label id="skip-quoted"> (default: S)<newline> + +This function will go to the next line of non-quoted text which come +after a line of quoted text in the internal pager. + +<sect1>Sending Mail +<p> + +The following bindings are available in the <em/index/ for sending +messages. + +<tscreen><verb> +m compose compose a new message +r reply reply to sender +g group-reply reply to all recipients +L list-reply reply to mailing list address +f forward forward message +b bounce bounce (remail) message +ESC k mail-key mail a PGP public key to someone +</verb></tscreen> + +Bouncing a message sends the message as is to the recipient you +specify. Forwarding a message allows you to add comments or +modify the message you are forwarding. Bouncing a message uses +the <ref id="sendmail" name="sendmail"> +command to send a copy of a message to recipients as if they were +original recipients of the message. See also <ref id="mime_forward" +name="$mime_forward">. + +Mutt will then enter the <em/compose/ menu and prompt you for the +recipients to place on the ``To:'' header field. Next, it will ask +you for the ``Subject:'' field for the message, providing a default if +you are replying to or forwarding a message. See also <ref id="askcc" +name="$askcc">, <ref id="askbcc" name="$askbcc">, <ref +id="autoedit" name="$autoedit">, and <ref id="fast_reply" +name="$fast_reply"> for changing how Mutt asks these +questions. + +Mutt will then automatically start your <ref id="editor" +name="$editor"> on the message body. If the <ref id="edit_headers" +name="$edit_headers"> variable is set, the headers will be at +the top of the message in your editor. Any messages you are replying +to will be added in sort order to the message, with appropriate <ref +id="attribution" name="$attribution">, <ref id="indent_string" +name="$indent_string"> and <ref id="post_indent_string" +name="$post_indent_string">. When forwarding a +message, if the <ref id="mime_forward" name="$mime_forward"> +variable is unset, a copy of the forwarded message will be included. If +you have specified a <ref id="signature" name="$signature">, it +will be appended to the message. + +Once you have finished editing the body of your mail message, you are +returned to the <em/compose/ menu. The following options are available: + +<tscreen><verb> +a attach-file attach a file +ESC k attach-key attach a PGP public key +d edit-description edit description on attachment +D detach-file detach a file +T edit-to edit the To field +c edit-cc edit the Cc field +b edit-bcc edit the Bcc field +y send-message send the message +s edit-subject edit the Subject +f edit-fcc specify an ``Fcc'' mailbox +p pgp-menu select PGP options (``i'' version only) +P postpone-message postpone this message until later +q quit quit (abort) sending the message +i ispell check spelling (if available on your system) +^F forget-passphrase whipe PGP passphrase from memory +</verb></tscreen> + +<sect2>Editing the message header<label id="edit_headers"> +<p> +When editing the header of your outgoing message, there are a couple of +special features available. + +If you specify<newline> +<tt/Fcc:/ <em/filename/<newline> +Mutt will pick up <em/filename/ +just as if you had used the <em/edit-fcc/ function in the <em/compose/ menu. + +You can also attach files to your message by specifying<newline> +<tt/Attach:/ <em/filename/ [ <em/description/ ]<newline> +where <em/filename/ is the file to attach and <em/description/ is an +optional string to use as the description of the attached file. + +When replying to messages, if you remove the <em/In-Reply-To:/ field from +the header field, Mutt will not generate a <em/References:/ field, which +allows you to create a new message thread. + +If you want to use PGP, you can specify + +<tt/Pgp:/ [ <tt/E/ | <tt/S/ | <tt/S<id/> ] <newline> + +``E'' encrypts, ``S'' signs and +``S<id>'' signs with the given key, setting <ref +id="pgp_sign_as" name="$pgp_sign_as"> permanently. + +Also see <ref id="edit_headers" name="edit_headers">. + +<sect1>Postponing Mail<label id="postponing_mail"> +<p> + +At times it is desirable to delay sending a message that you have +already begun to compose. When the <em/postpone-message/ function is +used in the <em/compose/ menu, the body of your message and attachments +are stored in the mailbox specified by the <ref id="postponed" +name="$postponed"> variable. This means that you can recall the +message even if you exit Mutt and then restart it at a later time. + +Once a message is postponed, there are several ways to resume it. From the +command line you can use the ``-p'' option, or if you <em/compose/ a new +message from the <em/index/ or <em/pager/ you will be prompted if postponed +messages exist. If multiple messages are currently postponed, the +<em/postponed/ menu will pop up and you can select which message you would +like to resume. + +<bf/Note:/ If you postpone a reply to a message, the reply setting of +the message is only updated when you actually finish the message and +send it. Also, you must be in the same folder with the message you +replied to for the status of the message to be updated. + +See also the <ref id="postpone" name="$postpone"> quad-option. + +<sect>Configuration +<p> + +While the default configuration (or ``preferences'') make Mutt usable right +out of the box, it is often desirable to tailor Mutt to suit your own tastes. +When Mutt is first invoked, it will attempt to read the ``system'' +configuration file (defaults set by your local system administrator), unless +the ``-n'' <ref id="commandline" name="command line"> option is +specified. This file is typically +<tt>/usr/local/share/Muttrc</tt> or <tt>/usr/local/lib/Muttrc</tt>. Next, +it looks for a file in your home directory named <tt/.muttrc/. In this file +is where you place <ref id="commands" name="commands"> to configure Mutt. + +In addition, mutt supports version specific configuration files that are +parsed instead of the default files as explained above. For instance, if +your system has a <tt/Muttrc-0.88/ file in the system configuration +directory, and you are running version 0.88 of mutt, this file will be +sourced instead of the <tt/Muttrc/ file. The same is true of the user +configuration file, if you have a file <tt/.muttrc-0.88.6/ in your home +directory, when you run mutt version 0.88.6, it will source this file +instead of the default <tt/.muttrc/ file. The version number is the +same which is visible using the ``-v'' <ref id="commandline" +name="command line"> switch or using the <tt/show-version/ key (default: +V) from the index menu. + +<sect1>Syntax of Initialization Files +<p> + +An initialization file consists of a series of <ref id="commands" +name="commands">. Each line of the file may contain one or more commands. +When multiple commands are used, they must be separated by a semicolon (;). +<tscreen><verb> + set realname='Mutt user' ; ignore x- +</verb><tscreen> +The hash mark, or pound sign +(``#''), is used as a ``comment'' character. You can use it to +annotate your initialization file. All text after the comment character +to the end of the line is ignored. For example, + +<tscreen><verb> +my_hdr X-Disclaimer: Why are you listening to me? # This is a comment +</verb></tscreen> + +Single quotes (') and double quotes (&dquot;) can be used to quote strings +which contain spaces or other special characters. The difference between +the two types of quotes is similar to that of many popular shell programs, +namely that a single quote is used to specify a literal string (one that is +not interpreted for shell variables or quoting with a backslash [see +next paragraph]), while double quotes indicate a string for which +should be evaluated. For example, backtics are evaluated inside of double +quotes, but <bf/not/ for single quotes. + +\ quotes the next character, just as in shells such as bash and zsh. +For example, if want to put quotes ``&dquot;'' inside of a string, you can use +``\'' to force the next character to be a literal instead of interpreted +character. +<tscreen><verb> +set realname="Michael \"MuttDude\" Elkins" +</verb></tscreen> + +``\\'' means to insert a literal ``\'' into the line. +``\n'' and ``\r'' have their usual C meanings of linefeed and +carriage-return, respectively. + +A \ at the end of a line can be used to split commands over +multiple lines, provided that the split points don't appear in the +middle of command names. + +It is also possible to substitute the output of a Unix command in an +initialization file. This is accomplished by enclosing the command in +backquotes (``). For example, +<tscreen><verb> +my_hdr X-Operating-System: `uname -a` +</verb></tscreen> +The output of the Unix command ``uname -a'' will be substituted before the +line is parsed. Note that since initialization files are line oriented, only +the first line of output from the Unix command will be substituted. + +For a complete list of the commands understood by mutt, see the +<ref id="commands" name="command reference">. + +<sect1>Defining/Using aliases<label id="alias"> +<p> + +Usage: <tt/alias/ <em/key/ <em/address/ [ , <em/address/, ... ] + +It's usually very cumbersome to remember or type out the address of someone +you are communicating with. Mutt allows you to create ``aliases'' which map +a short string to a full address. + +<bf/Note:/ if you want to create an alias for a group (by specifying more than +one address), you <bf/must/ separate the addresses with a comma (``,''). + +To remove an alias or aliases: + +<tt/unalias/ <em/addr/ [ <em/addr/ <em/.../ ] + +<tscreen><verb> +alias muttdude me@cs.hmc.edu (Michael Elkins) +alias theguys manny, moe, jack +</verb></tscreen> + +Unlike other mailers, Mutt doesn't require aliases to be defined +in a special file. The <tt/alias/ command can appear anywhere in +a configuration file, as long as this file is <ref id="source" +name="sourced">. Consequently, you can have multiple alias files, or +you can have all aliases defined in your muttrc. + +On the other hand, the <ref id="create-alias" name="create-alias"> +function can use only one file, the one pointed to by the <ref +id="alias_file" name="$alias_file"> variable (which is +<tt>˜/.muttrc</tt> by default). This file is not special either, +in the sense that Mutt will happily append aliases to any file, but in +order for the new aliases to take effect you need to explicitly <ref +id="source" name="source"> this file too. + +For example: + +<tscreen><verb> +source /usr/local/share/Mutt.aliases +source ~/.mail_aliases +set alias_file=~/.mail_aliases +</verb></tscreen> + +To use aliases, you merely use the alias at any place in mutt where mutt +prompts for addresses, such as the <em/To:/ or <em/Cc:/ prompt. You can +also enter aliases in your editor at the appropriate headers if you have the +<ref id="edit_headers" name="$edit_headers"> variable set. + +In addition, at the various address prompts, you can use the tab character +to expand a partial alias to the full alias. If there are multiple matches, +mutt will bring up a menu with the matching aliases. In order to be +presented with the full list of aliases, you must hit tab with out a partial +alias, such as at the beginning of the prompt or after a comma denoting +multiple addresses. + +In the alias menu, you can select as many aliases as you want with the +<em/select-entry/ key (default: RET), and use the <em/exit/ key +(default: q) to return to the address prompt. + +<sect1>Changing the default key bindings<label id="bind"> +<p> +Usage: <tt/bind/ <em/map/ <em/key/ <em/function/ + +This command allows you to change the default key bindings (operation +invoked when pressing a key). + +<em/map/ specifies in which menu the binding belongs. The currently +defined maps are: + +<itemize> +<item>generic +<item>alias +<item>attach +<item>browser +<item>editor +<item>index +<item>compose +<item>pager +<item>pgp +<item>url +</itemize> + +<em/key/ is the key (or key sequence) you wish to bind. To specify a +control character, use the sequence <em/\Cx/, where <em/x/ is the +letter of the control character (for example, to specify control-A use +``\Ca''). Note that the case of <em/x/ as well as <em/\C/ is +ignored, so that <em/\CA, \Ca, \cA/ and <em/\ca/ are all +equivalent. An alternative form is to specify the key as a three digit +octal number prefixed with a ``\'' (for example <em/\177/ is +equivalent to <em/\c?/). + +In addition, <em/key/ may consist of: + +<tscreen><verb> +\t tab +\r carriage return +\n newline +\e escape +up up arrow +down down arrow +left left arrow +right right arrow +pageup Page Up +pagedown Page Down +backspace Backspace +delete Delete +insert Insert +enter Enter +home Home +end End +f1 function key 1 +f10 function key 10 +</verb></tscreen> + +<em/key/ does not need to be enclosed in quotes unless it contains a +space (`` ''). + +<em/function/ specifies which action to take when <em/key/ is pressed. +For a complete list of functions, see the <ref id="functions" +name="reference">. The special function <tt/noop/ unbinds the specify key +sequence. + +<sect1>Setting variables based upon mailbox<label id="folder-hook"> +<p> +Usage: <tt/folder-hook/ [!]<em/pattern/ <em/command/ + +It is often desirable to change settings based on which mailbox you are +reading. The folder-hook command provides a method by which you can execute +any configuration command. <em/pattern/ is a regular expression specifying +in which mailboxes to execute <em/command/ before loading. If a mailbox +matches multiple folder-hook's, they are executed in the order given in the +muttrc. + +<bf/Note:/ if you use the ``!'' shortcut for <ref id="spoolfile" +name="$spoolfile"> at the beginning of the pattern, you must place it +inside of double or single quotes in order to distinguish it from the +logical <em/not/ operator for the expression. + +Note that the settings are <em/not/ restored when you leave the mailbox. +For example, a command action to perform is to change the sorting method +based upon the mailbox being read: + +<tscreen><verb> +folder-hook mutt set sort=threads +</verb></tscreen> + +However, the sorting method is not restored to its previous value when +reading a different mailbox. To specify a <em/default/ command, use the +pattern ``.'': + +<p> +<tscreen><verb> +folder-hook . set sort=date-sent +</verb></tscreen> + +<sect1>Keyboard macros<label id="macro"> +<p> +Usage: <tt/macro/ <em/menu/ <em/key/ <em/sequence/ + +Macros are useful when you would like a single key to perform a series of +actions. When you press <em/key/ in menu <em/menu/, Mutt will behave as if +you had typed <em/sequence/. So if you have a common sequence of commands +you type, you can create a macro to execute those commands with a single +key. + +<em/key/ and <em/sequence/ are expanded by the same rules as the <ref +id="bind" name="key bindings">, with the addition that control +characters in <em/sequence/ can also be specified as <em/ˆx/. In +order to get a caret (``ˆ'') you need to use <em/ˆˆ/. + +<bf/Note:/ Macro definitions (if any) listed in the help screen(s), are +silently truncated at the screen width, and are not wrapped. + +<sect1>Using color and mono video attributes<label id="color"> +<p> +Usage: <tt/color/ <em/object/ <em/foreground/ <em/background/ [ <em/regexp/ ]<newline> +Usage: <tt/color/ index <em/foreground/ <em/background/ [ <em/pattern/ ]<newline> +Usage: <tt/uncolor/ index <em/pattern/ [ <em/pattern/ ... ]<newline> + +If your terminal supports color, you can spice up Mutt by creating your own +color scheme. To define the color of an object (type of information), you +must specify both a foreground color <bf/and/ a background color (it is not +possible to only specify one or the other). + +<em/object/ can be one of: + +<itemize> +<item>attachment +<item>body (match <em/regexp/ in the body of messages) +<item>bold (hiliting bold patterns in the body of messages) +<item>error (error messages printed by Mutt) +<item>header (match <em/regexp/ in the message header) +<item>hdrdefault (default color of the message header in the pager) +<item>index (match <em/pattern/ in the message index) +<item>indicator (arrow or bar used to indicate the current item in a menu) +<item>markers (the ``+'' markers at the beginning of wrapped lines in the pager) +<item>message (informational messages) +<item>normal +<item>quoted (text matching <ref id="quote_regexp" +name="$quote_regexp"> in the body of a message) +<item>quoted1, quoted2, ..., quoted<bf/N/ (higher levels of quoting) +<item>search (hiliting of words in the pager) +<item>signature +<item>status (mode lines used to display info about the mailbox or message) +<item>tilde (the ``˜'' used to pad blank lines in the pager) +<item>tree (thread tree drawn in the message index and attachment menu) +<item>underline (hiliting underlined patterns in the body of messages) +</itemize> + +<em/foreground/ and <em/background/ can be one of the following: + +<itemize> +<item>white +<item>black +<item>green +<item>magenta +<item>blue +<item>cyan +<item>yellow +<item>red +<item>default +<item>color<em/x/ +</itemize> + +<em/foreground/ can optionally be prefixed with the keyword <tt/bright/ to make +the foreground color boldfaced (e.g., <tt/brightred/). + +If your terminal supports it, the special keyword <em/default/ can be +used as a transparent color. The value <em/brightdefault/ is also valid. +If Mutt is linked against the <em/S-Lang/ library, you also need to set +the <em/COLORFGBG/ environment variable to the default colors of your +terminal for this to work; for example (for Bourne-like shells): + +<tscreen><verb> +set COLORFGBG="green;black" +export COLORFGBG +</verb></tscreen> + +<bf/Note:/ The <em/S-Lang/ library requires you to use the <em/lightgray/ +and <em/brown/ keywords instead of <em/white/ and <em/yellow/ when +setting this variable. + +<bf/Note:/ The uncolor command can be applied to the index object only. It +removes entries from the list. You <bf/must/ specify the same pattern +specified in the color command for it to be removed. The pattern ``*'' is +a special token which means to clear the color index list of all entries. + +Mutt also recognizes the keywords <em/color0/, <em/color1/, …, +<em/color/<bf/N-1/ (<bf/N/ being the number of colors supported +by your terminal). This is useful when you remap the colors for your +display (for example by changing the color associated with <em/color2/ +for your xterm), since color names may then lose their normal meaning. + +If your terminal does not support color, it is still possible change the video +attributes through the use of the ``mono'' command: + +Usage: <tt/mono/ <em/<object> <attribute>/ [ <em/regexp/ ] + +where <em/attribute/ is one of the following: + +<itemize> +<item>none +<item>bold +<item>underline +<item>reverse +<item>standout +</itemize> + +<sect1>Ignoring (weeding) unwanted message headers<label id="ignore"> +<p> +Usage: <tt/[un]ignore/ <em/pattern/ [ <em/pattern/ ... ] + +Messages often have many header fields added by automatic processing systems, +or which may not seem useful to display on the screen. This command allows +you to specify header fields which you don't normally want to see. + +You do not need to specify the full header field name. For example, +``ignore content-'' will ignore all header fields that begin with the pattern +``content-''. + +To remove a previously added token from the list, use the ``unignore'' command. +Note that if you do ``ignore x-'' it is not possible to ``unignore x-mailer,'' +for example. The ``unignore'' command does <bf/not/ make Mutt display headers +with the given pattern. + +``unignore *'' will remove all tokens from the ignore list. + +For example: +<tscreen><verb> +# Sven's draconian header weeding +ignore * +unignore from date subject to cc +unignore organization organisation x-mailer: x-newsreader: x-mailing-list: +unignore posted-to: +</verb></tscreen> + +<sect1>Mailing lists<label id="lists"> +<p> +Usage: <tt/[un]lists/ <em/address/ [ <em/address/ ... ] + +Mutt has a few nice features for <ref id="using_lists" name="handling mailing +lists">. In order to take advantage of them, you must specify which addresses +belong to mailing lists. + +It is important to note that you should <bf/never/ specify the domain name ( +the part after the ``@'') with the lists command. You should only +specify the ``mailbox'' portion of the address (the part before the ``@''). +For example, if you've subscribed to the Mutt mailing list, you will receive +mail addressed to <em/mutt-users@cs.hmc.edu/. So, to tell Mutt that this is a +mailing list, you would add ``lists mutt-users'' to your initialization file. + +The ``unlists'' command is to remove a token from the list of mailing-lists. +Use ``unlists *'' to remove all tokens. + +<sect1>Using Multiple spool mailboxes<label id="mbox-hook"> +<p> +Usage: <tt/mbox-hook/ [!]<em/pattern/ <em/mailbox/ + +This command is used to move read messages from a specified mailbox to a +different mailbox automatically when you quit or change folders. +<em/pattern/ is a regular expression specifying the mailbox to treat as a +``spool'' mailbox and <em/mailbox/ specifies where mail should be saved when +read. + +Unlike some of the other <em/hook/ commands, only the <em/first/ matching +pattern is used (it is not possible to save read mail in more than a single +mailbox). + +<sect1>Defining mailboxes which receive mail<label id="mailboxes"> +<p> +Usage: <tt/mailboxes/ [!]<em/filename/ [ <em/filename/ ... ] + +This command specifies folders which can receive mail and +which will be checked for new messages. By default, the +main menu status bar displays how many of these folders have +new messages. +<p> +When changing folders, pressing <em/space/ will cycle +through folders with new mail. +<p> +Pressing TAB in the directory browser will bring up a menu showing the files +specified by the <tt/mailboxes/ command, and indicate which contain new +messages. Mutt will automatically enter this mode when invoked from the +command line with the <tt/-y/ option. +<p> +<bf/Note:/ new mail is detected by comparing the last modification time to +the last access time. Utilities like <tt/biff/ or <tt/frm/ or any other +program which accesses the mailbox might cause Mutt to never detect new mail +for that mailbox if they do not properly reset the access time. +<p> + +<bf/Note:/ the filenames in the <tt/mailboxes/ command are resolved when +the command is executed, so if these names contain <ref id="shortcuts" +name="shortcut characters"> (such as ``='' and ``!''), any variable +definition that affect these characters (like <ref id="folder" +name="$folder"> and <ref id="spoolfile" name="$spool">) +should be executed before the <tt/mailboxes/ command. + +<sect1>User defined headers<label id="my_hdr"> +<p> +Usage:<newline> +<tt/my_hdr/ <em/string/<newline> +<tt/unmy_hdr/ <em/field/ [ <em/field/ ... ] + +The ``my_hdr'' command allows you to create your own header +fields which will be added to every message you send. + +For example, if you would like to add an ``Organization:'' header field to +all of your outgoing messages, you can put the command + +<quote> +my_hdr Organization: A Really Big Company, Anytown, USA +</quote> + +in your <tt/.muttrc/. + +<bf/Note:/ space characters are <em/not/ allowed between the keyword and +the colon (``:''). The standard for electronic mail (RFC822) says that +space is illegal there, so Mutt enforces the rule. + +If you would like to add a header field to a single message, you should +either set the <ref id="edit_headers" name="edit_headers"> variable, +or use the <em/edit-headers/ function (default: ``E'') in the send-menu so +that you can edit the header of your message along with the body. + +To remove user defined header fields, use the ``unmy_hdr'' +command. You may specify an asterisk (``*'') to remove all header +fields, or the fields to remove. For example, to remove all ``To'' and +``Cc'' header fields, you could use: + +<quote> +unmy_hdr to cc +</quote> + +<sect1>Defining the order of headers when viewing messages<label id="hdr_order"> +<p> +Usage: <tt/hdr_order/ <em/header1/ <em/header2/ <em/header3/ + +With this command, you can specify an order in which mutt will attempt +to present headers to you when viewing messages. + +<tscreen><verb> +hdr_order From Date: From: To: Cc: Subject: +</verb></tscreen> + +<sect1>Specify default save filename<label id="save-hook"> +<p> +Usage: <tt/save-hook/ [!]<em/regexp/ <em/filename/ + +This command is used to override the default filename used when saving +messages. <em/filename/ will be used as the default filename if the message is +<em/From:/ an address matching <em/regexp/ or if you are the author and the +message is addressed <em/to:/ something matching <em/regexp/. + +See <ref id="pattern_hook" name="matching messages"> for information on the +exact format of <em/regexp/. + +Examples: + +<tscreen><verb> +save-hook me@(turing\\.)?cs\\.hmc\\.edu$ +elkins +save-hook aol\\.com$ +spam +</verb></tscreen> + +Also see the <ref id="fcc-save-hook" name="fcc-save-hook"> command. + +<sect1>Specify default Fcc: mailbox when composing<label id="fcc-hook"> +<p> +Usage: <tt/fcc-hook/ [!]<em/regexp/ <em/mailbox/ + +This command is used to save outgoing mail in a mailbox other than +<ref id="record" name="$record">. Mutt searches the initial list of +message recipients for the first matching <em/regexp/ and uses <em/mailbox/ +as the default Fcc: mailbox. If no match is found the message will be saved +to <ref id="record" name="$record"> mailbox. + +See <ref id="pattern_hook" name="matching messages"> for information on the +exact format of <em/regexp/. + +Example: <tt/fcc-hook aol.com$ +spammers/ + +The above will save a copy of all messages going to the aol.com domain to +the `+spammers' mailbox by default. Also see the <ref id="fcc-save-hook" +name="fcc-save-hook"> command. + +<sect1>Specify default save filename and default Fcc: mailbox at once<label +id="fcc-save-hook"> +<p> +Usage: <tt/fcc-save-hook/ [!]<em/regexp/ <em/mailbox/ + +This command is a shortcut, equivalent to doing both a <ref id="fcc-hook" name="fcc-hook"> +and a <ref id="save-hook" name="save-hook"> with its arguments. + +<sect1>Change settings based upon message recipients<label id="send-hook"> +<p> +Usage: <tt/send-hook/ [!]<em/regexp/ <em/command/ + +This command can be used to execute arbitrary configuration commands based +upon recipients of the message. <em/regexp/ is a regular expression +matching the desired address. <em/command/ is executed when <em/regexp/ +matches recipients of the message. When multiple matches occur, commands are +executed in the order they are specified in the muttrc. + +See <ref id="pattern_hook" name="matching messages"> for information on the +exact format of <em/regexp/. + +Example: <tt/send-hook mutt &dquot;set mime_forward signature=''&dquot;/ + +Another typical use for this command is to change the values of the +<ref id="attribution" name="$attribution">, <ref id="signature" +name="$signature"> and <ref id="locale" name="$locale"> +variables in order to change the language of the attributions and +signatures based upon the recipients. + +<bf/Note:/ the send-hook's are only executed ONCE after getting the initial +list of recipients. Adding a recipient after replying or editing the +message will NOT cause any send-hook to be executed. + +<sect1>Adding key sequences to the keyboard buffer<label id="push"> +<p> +Usage: <tt/push/ <em/string/ + +This command adds the named string to the keyboard buffer. You may +use it to automatically run a sequence of commands at startup, or when +entering certain folders. + +<sect1>Message Scoring<label id="score"> +<p> +Usage: <tt/score/ <em/pattern/ <em/value/<newline> +Usage: <tt/unscore/ <em/pattern/ [ <em/pattern/ ... ] + +The <tt/score/ commands adds <em/value/ to a message's score if <em/pattern/ +matches it. <em/pattern/ is a string in the format described in the <ref +id="searching" name="searching"> section. <em/value/ is a positive or +negative integer. A message's final score is the sum total of all matching +<tt/score/ entries. However, you may optionally prefix <em/value/ with an +equal sign (=) to cause evaluation to stop at a particular entry if there is +a match. Negative final scores are rounded up to 0. + +The <tt/unscore/ command removes score entries from the list. You <bf/must/ +specify the same pattern specified in the <tt/score/ command for it to be +removed. The pattern ``*'' is a special token which means to clear the list +of all score entries. + +<sect1>Setting variables<label id="set"> +<p> +Usage: <tt/set/ [no|inv]<em/variable/[=<em/value/] [ <em/variable/ ... ]<newline> +Usage: <tt/toggle/ <em/variable/ [<em/variable/ ... ]<newline> +Usage: <tt/unset/ <em/variable/ [<em/variable/ ... ]<newline> +Usage: <tt/reset/ <em/variable/ [<em/variable/ ... ] + +This command is used to set (and unset) <ref id="variables" +name="configuration variables">. There are four basic types of variables: +boolean, number, string and quadoption. <em/boolean/ variables can be +<em/set/ (true) or <em/unset/ (false). <em/number/ variables can be +assigned a positive integer value. + +<em/string/ variables consist of any number of printable characters. +<em/strings/ must be enclosed in quotes if they contain spaces or tabs. You +may also use the ``C'' escape sequences <bf/\n/ and <bf/\t/ for +newline and tab, respectively. + +<em/quadoption/ variables are used to control whether or not to be prompted +for certain actions, or to specify a default action. A value of <em/yes/ +will cause the action to be carried out automatically as if you had answered +yes to the question. Similarly, a value of <em/no/ will cause the the +action to be carried out as if you had answered ``no.'' A value of +<em/ask-yes/ will cause a prompt with a default answer of ``yes'' and +<em/ask-no/ will provide a default answer of ``no.'' + +Prefixing a variable with ``no'' will unset it. Example: <tt/set noaskbcc/. + +For <em/boolean/ variables, you may optionally prefix the variable name with +<tt/inv/ to toggle the value (on or off). This is useful when writing +macros. Example: <tt/set invsmart_wrap/. + +The <tt/toggle/ command automatically prepends the <tt/inv/ prefix to all +specified variables. + +The <tt/unset/ command automatically prepends the <tt/no/ prefix to all +specified variables. + +Using the enter-command function in the <em/index/ menu, you can query the +value of a variable by prefixing the name of the variable with a question +mark: + +<tscreen><verb> +set ?allow_8bit +</verb></tscreen> + +The question mark is actually only required for boolean variables. + +The <tt/reset/ command resets all given variables to the compile time +defaults (hopefully mentioned in this manual). If you use the command +<tt/set/ and prefix the variable with ``&'' this has the same +behavior as the reset command. + +With the <tt/reset/ command there exists the special variable ``all'', +which allows you to reset all variables to their system defaults. + +<sect1>Reading initialization commands from another file<label id="source"> +<p> +Usage: <tt/source/ <em/filename/ + +This command allows the inclusion of initialization commands +from other files. For example, I place all of my aliases in +<tt>˜/.mail_aliases</tt> so that I can make my +<tt>˜/.muttrc</tt> readable and keep my aliases private. + +If the filename begins with a tilde (``˜''), it will be expanded to the +path of your home directory. + +If the filename ends with a vertical bar (|), then <em/filename/ is +considered to be an executable program from which to read input (eg. +<tt/source ~/bin/myscript|/. + +<sect>Advanced Usage + +<sect1>Searching and Regular Expressions<label id="regex"> +<p> +All text patterns for searching and matching in Mutt must be specified +as regular expressions (regexp) in the ``POSIX extended'' syntax (which +is more or less the syntax used by egrep and GNU awk). For your +convenience, we have included below a brief description of this syntax. + +The search is case sensitive if the pattern contains at least one upper +case letter, and case insensitive otherwise. Note that ``\'' +must be quoted if used for a regular expression in an initialization +command: ``\\''. For more information, see the section on +<ref id="searching" name="searching"> below. + +<sect2>Regular Expressions<label id="regexps"> +<p> +A regular expression is a pattern that describes a set of strings. +Regular expressions are constructed analogously to arithmetic +expressions, by using various operators to combine smaller expressions. + +The fundamental building blocks are the regular expressions that match +a single character. Most characters, including all letters and digits, +are regular expressions that match themselves. Any metacharacter with +special meaning may be quoted by preceding it with a backslash. + +The period ``.'' matches any single character. The caret ``ˆ'' and +the dollar sign ``&dollar'' are metacharacters that respectively match +the empty string at the beginning and end of a line. + +A list of characters enclosed by ``['' and ``&rsqb'' matches any +single character in that list; if the first character of the list +is a caret ``ˆ'' then it matches any character <bf/not/ in the +list. For example, the regular expression <bf/[0123456789&rsqb/ +matches any single digit. A range of ASCII characters may be specified +by giving the first and last characters, separated by a hyphen +``‐''. Most metacharacters lose their special meaning inside +lists. To include a literal ``&rsqb'' place it first in the list. +Similarly, to include a literal ``ˆ'' place it anywhere but first. +Finally, to include a literal hyphen ``‐'' place it last. + +Certain named classes of characters are predefined. Character classes +consist of ``[:'', a keyword denoting the class, and ``:]''. +The following classes are defined by the POSIX standard: + +<descrip> +<tag/[:alnum:]/ +Alphanumeric characters. +<tag/[:alpha:]/ +Alphabetic characters. +<tag/[:blank:]/ +Space or tab characters. +<tag/[:cntrl:]/ +Control characters. +<tag/[:digit:]/ +Numeric characters. +<tag/[:graph:]/ +Characters that are both printable and visible. (A space is printable, +but not visible, while an ``a'' is both.) +<tag/[:lower:]/ +Lower-case alphabetic characters. +<tag/[:print:]/ +Printable characters (characters that are not control characters.) +<tag/[:punct:]/ +Punctuation characters (characters that are not letter, digits, control +characters, or space characters). +<tag/[:space:]/ +Space characters (such as space, tab and formfeed, to name a few). +<tag/[:upper:]/ +Upper-case alphabetic characters. +<tag/[:xdigit:]/ +Characters that are hexadecimal digits. +</descrip> + +A character class is only valid in a regular expression inside the +brackets of a character list. Note that the brackets in these +class names are part of the symbolic names, and must be included +in addition to the brackets delimiting the bracket list. For +example, <bf/[[:digit:]]/ is equivalent to +<bf/[0-9]/. + +Two additional special sequences can appear in character lists. These +apply to non-ASCII character sets, which can have single symbols (called +collating elements) that are represented with more than one character, +as well as several characters that are equivalent for collating or +sorting purposes: + +<descrip> +<tag/Collating Symbols/ +A collating symbols is a multi-character collating element enclosed in +``[.'' and ``.]''. For example, if ``ch'' is a collating +element, then <bf/[[.ch.]]/ is a regexp that matches +this collating element, while <bf/[ch]/ is a regexp that +matches either ``c'' or ``h''. +<tag/Equivalence Classes/ +An equivalence class is a locale-specific name for a list of +characters that are equivalent. The name is enclosed in ``[='' +and ``=]''. For example, the name ``e'' might be used to +represent all of ``è'' ``é'' and ``e''. In this case, +<bf/[[=e=]]/ is a regexp that matches any of +``è'', ``é'' and ``e''. +</descrip> + +A regular expression matching a single character may be followed by one +of several repetition operators: + +<descrip> +<tag/?/ +The preceding item is optional and matched at most once. +<tag/*/ +The preceding item will be matched zero or more times. +<tag/+/ +The preceding item will be matched one or more times. +<tag/{n}/ +The preceding item is matched exactly <em/n/ times. +<tag/{n,}/ +The preceding item is matched <em/n/ or more times. +<tag/{,m}/ +The preceding item is matched at most <em/m/ times. +<tag/{n,m}/ +The preceding item is matched at least <em/n/ times, but no more than +<em/m/ times. +</descrip> + +Two regular expressions may be concatenated; the resulting regular +expression matches any string formed by concatenating two substrings +that respectively match the concatenated subexpressions. + +Two regular expressions may be joined by the infix operator ``|''; +the resulting regular expression matches any string matching either +subexpression. + +Repetition takes precedence over concatenation, which in turn takes +precedence over alternation. A whole subexpression may be enclosed in +parentheses to override these precedence rules. + +<bf/Note:/ If you compile Mutt with the GNU <em/rx/ package, the +following operators may also be used in regular expressions: + +<descrip> +<tag/\\y/ +Matches the empty string at either the beginning or the end of a word. +<tag/\\B/ +Matches the empty string within a word. +<tag/\\</ +Matches the empty string at the beginning of a word. +<tag/\\>/ +Matches the empty string at the end of a word. +<tag/\\w/ +Matches any word-constituent character (letter, digit, or underscore). +<tag/\\W/ +Matches any character that is not word-constituent. +<tag/\\`/ +Matches the empty string at the beginning of a buffer (string). +<tag/\\'/ +Matches the empty string at the end of a buffer. +</descrip> + +Please note however that these operators are not defined by POSIX, so +they may or may not be available in stock libraries on various systems. + +<sect2>Searching<label id="searching"> +<p> +Many of Mutt's commands allow you to specify a pattern to match +(limit, tag-pattern, delete-pattern, etc.). There are several ways to select +messages: + +<tscreen><verb> +~A all messages +~b PATTERN messages which contain PATTERN in the message body +~c USER messages carbon-copied to USER +~C PATTERN message is either to: or cc: PATTERN +~D deleted messages +~d [MIN]-[MAX] messages with ``date-sent'' in a Date range +~E expired messages +~e PATTERN message which contains PATTERN in the ``Sender'' field +~F flagged messages +~f USER messages originating from USER +~h PATTERN messages which contain PATTERN in the message header +~i ID message which match ID in the ``Message-ID'' field +~L PATTERN message is either originated or received by PATTERN +~l message is addressed to a known mailing list +~m [MIN]-[MAX] message in the range MIN to MAX +~n [MIN]-[MAX] messages with a score in the range MIN to MAX +~N new messages +~O old messages +~p message is addressed to you (consults $alternates) +~P message is from you (consults $alternates) +~Q messages which have been replied to +~R read messages +~r [MIN]-[MAX] messages with ``date-received'' in a Date range +~S superseded messages +~s SUBJECT messages having SUBJECT in the ``Subject'' field. +~T tagged messages +~t USER messages addressed to USER +~U unread messages +~x PATTERN messages which contain PATTERN in the `References' field +</verb></tscreen> + +Where PATTERN, USER, ID, and SUBJECT are +<ref id="regex" name="regular expressions">. + +<sect2>Complex Searches +<p> + +Logical AND is performed by specifying more than one criterion. For +example: + +<tscreen><verb> +~t mutt ~f elkins +</verb></tscreen> + +would select messages which contain the word ``mutt'' in the list of +recipients <bf/and/ that have the word ``elkins'' in the ``From'' header +field. + +Mutt also recognizes the following operators to create more complex search +patterns: + +<itemize> +<item>! -- logical NOT operator +<item>| -- logical OR operator +<item>() -- logical grouping operator +</itemize> + +Here is an example illustrating a complex search pattern. This pattern will +select all messages which do not contain ``mutt'' in the ``To'' or ``Cc'' +field and which are from ``elkins''. + +<tscreen><verb> +!(~t mutt|~c mutt) ~f elkins +</verb></tscreen> + +<sect2>Searching by Date +<p> +Mutt supports two types of dates, <em/absolute/ and <em/relative/. + +<bf/Absolute/. Dates <bf/must/ be in DD/MM/YY format (month and year are +optional, defaulting to the current month and year). An example of a valid +range of dates is: + +<tscreen><verb> +Limit to messages matching: ~d 20/1/95-31/10 +</verb></tscreen> + +If you omit the minimum (first) date, and just specify ``-DD/MM/YY'', all +messages <em/before/ the given date will be selected. If you omit the maximum +(second) date, and specify ``DD/MM/YY-'', all messages <em/after/ the given +date will be selected. If you specify a single date with no dash (``-''), +only messages sent on the given date will be selected. + +<bf/Relative/. This type of date is relative to the current date, and may +be specified as: +<itemize> +<item>><em/offset/ (messages older than <em/offset/ units) +<item><<em/offset/ (messages newer than <em/offset/ units) +<item>=<em/offset/ (messages exactly <em/offset/ units old) +</itemize> + +<em/offset/ is specified as a positive number with one of the following +units: +<verb> +y years +m months +w weeks +d days +</verb> + +Example: to select messages less than 1 month old, you would use +<tscreen><verb> +Limit to messages matching: ~d <1m +</verb></tscreen> + +<bf/Note:/ all dates used when searching are relative to the +<bf/local/ time zone, so unless you change the setting of your <ref +id="index_format" name="$index_format"> to include a +<tt/%[...]/ format, these are <bf/not/ the dates shown +in the main index. + +<sect1>Using Tags +<p> + +Sometimes it is desirable to perform an operation on a group of messages all +at once rather than one at a time. An example might be to save messages to +a mailing list to a separate folder, or to delete all messages with a given +subject. To tag all messages matching a pattern, use the tag-pattern +function, which is bound to ``control-T'' by default. Or you can select +individual messages by hand using the ``tag-message'' function, which is +bound to ``t'' by default. See <ref id="searching" name="searching"> for +Mutt's searching syntax. + +Once you have tagged the desired messages, you can use the +``tag-prefix'' operator, which is the ``;'' (semicolon) key by default. +When the ``tag-prefix'' operator is used, the <bf/next/ operation will +be applied to all tagged messages if that operation can be used in that +manner. If the <ref id="auto_tag" name="$auto_tag"> +variable is set, the next operation applies to the tagged messages +automatically, without requiring the ``tag-prefix''. + +<sect1>Using Hooks<label id="hooks"> +<p> +A <em/hook/ is a concept borrowed from the EMACS editor which allows you to +execute arbitrary commands before performing some operation. For example, +you may wish to tailor your configuration based upon which mailbox you are +reading, or to whom you are sending mail. In the Mutt world, a <em/hook/ +consists of a <ref id="regex" name="regular expression"> along with a +configuration option/command. See +<itemize> +<item><ref id="folder-hook" name="folder-hook"> +<item><ref id="send-hook" name="send-hook"> +<item><ref id="save-hook" name="save-hook"> +<item><ref id="mbox-hook" name="mbox-hook"> +<item><ref id="fcc-hook" name="fcc-hook"> +<item><ref id="fcc-save-hook" name="fcc-save-hook"> +</itemize> +for specific details on each type of <em/hook/ available. + +<sect2>Message Matching in Hooks<label id="pattern_hook"> +<p> +Hooks that act upon messages (<tt/send-hook, save-hook, fcc-hook/) are +evaluated in a slightly different manner. For the other types of hooks, a +<ref id="regex" name="regular expression">. But in dealing with messages a +finer grain of control is needed for matching since for different purposes +you want to match different criteria. + +Mutt allows the use of the <ref id="searching" name="search pattern"> +language for matching messages in hook commands. This works in exactly the +same way as it would when <em/limiting/ or <em/searching/ the mailbox, +except that you are restricted to those operators which match information +from the envelope of the message (i.e. from, to, cc, date, subject, etc.). + +For example, if you wanted to set your return address based upon sending +mail to a specific address, you could do something like: +<tscreen><verb> +send-hook '~t ^me@cs\.hmc\.edu$' 'my_hdr From: Mutt User <user@host>' +</verb></tscreen> +which would execute the given command when sending mail to +<em/me@cs.hmc.edu/. + +However, it is not required that you write the pattern to match using the +full searching language. You can still specify a simple <em/regular +expression/ like the other hooks, in which case Mutt will translate your +pattern into the full language, using the translation specified by the +<ref id="default_hook" name="$dfault_hook"> variable. The pattern +is translated at the time the hook is declared, so the value of +<ref id="default_hook" name="$dfault_hook"> that is in effect +at that time will be used. + +<sect1>External Address Queries<label id="query"> +<p> +Mutt supports connecting to external directory databases such as LDAP, +ph/qi, bbdb, or NIS through a wrapper script which connects to mutt +using a simple interface. Using the <ref id="query_command" +name="$query_command"> variable, you specify the wrapper +command to use. For example: + +<tscreen><verb> +set query_command = "mutt_ldap_query.pl '%s'" +</verb></tscreen> + +The wrapper script should accept the query on the command-line. It +should return a one line message, than each matching response on a +single line, each line containing a tab separated address then name then +some other optional information. On error, or if there are no matching +addresses, return a non-zero exit code and a one line error message. + +An example multiple response output: +<tscreen><verb> +Searching database ... 20 entries ... 3 matching: +me@cs.hmc.edu Michael Elkins mutt dude +blong@fiction.net Brandon Long mutt and more +roessler@guug.de Thomas Roessler mutt pgp +</verb></tscreen> + +There are two mechanisms for accessing the query function of mutt. One +is to do a query from the index menu using the query function (default: Q). +This will prompt for a query, then bring up the query menu which will +list the matching responses. From the query menu, you can select +addresses to create aliases, or to mail. You can tag multiple messages +to mail, start a new query, or have a new query appended to the current +responses. + +The other mechanism for accessing the query function is for address +completion, similar to the alias completion. In any prompt for address +entry, you can use the complete-query function (default: ^T) to run a +query based on the current address you have typed. Like aliases, mutt +will look for what you have typed back to the last space or comma. If +there is a single response for that query, mutt will expand the address +in place. If there are multiple responses, mutt will activate the query +menu. At the query menu, you can select one or more addresses to be +added to the prompt. + +<sect1>Mailbox Formats +<p> +Mutt supports reading and writing of four different mailbox formats: +mbox, MMDF, MH and Maildir. The mailbox type is autodetected, so there +is no need to use a flag for different mailbox types. When creating new +mailboxes, Mutt uses the default specified with the <ref id="mbox_type" +name="$mbox_type"> variable. + +<bf/mbox/. This is the most widely used mailbox format for UNIX. All +messages are stored in a single file. Each message has a line of the form: + +<tscreen><verb> +From me@cs.hmc.edu Fri, 11 Apr 1997 11:44:56 PST +</verb></tscreen> + +to denote the start of a new message (this is often referred to as the +``From_'' line). + +<bf/MMDF/. This is a variant of the <em/mbox/ format. Each message is +surrounded by lines containing ``^A^A^A^A'' (four control-A's). + +<bf/MH/. A radical departure from <em/mbox/ and <em/MMDF/, a mailbox +consists of a directory and each message is stored in a separate file. +The filename indicates the message number (however, this is may not +correspond to the message number Mutt displays). Deleted messages are +renamed with a comma (,) prepended to the filename. <bf/Note:/ Mutt +detects this type of mailbox by looking for either <tt/.mh_sequences/ +or <tt/.xmhcache/ (needed to distinguish normal directories from MH +mailboxes). Mutt does not update these files, yet. + +<bf/Maildir/. The newest of the mailbox formats, used by the Qmail MTA (a +replacement for sendmail). Similar to <em/MH/, except that it adds three +subdirectories of the mailbox: <em/tmp/, <em/new/ and <em/cur/. Filenames +for the messages are chosen in such a way they are unique, even when two +programs are writing the mailbox over NFS, which means that no file locking +is needed. + +<sect1>Mailbox Shortcuts<label id="shortcuts"> +<p> +There are a number of built in shortcuts which refer to specific mailboxes. +These shortcuts can be used anywhere you are prompted for a file or mailbox +path. + +<itemize> +<item>! -- refers to your <ref id="spoolfile" name="$spool"> (incoming) mailbox +<item>> -- refers to your <ref id="mbox" name="$mbox"> file +<item>< -- refers to your <ref id="record" name="$record"> file +<item>- -- refers to the file you've last visited +<item>˜ -- refers to your home directory +<item>= or + -- refers to your <ref id="folder" name="$folder"> directory +</itemize> + +<sect1>Handling Mailing Lists<label id="using_lists"> +<p> + +Mutt has a few configuration options that make dealing with large amounts of +mail easier. The first thing you must do is to let Mutt know what addresses +you consider to be mailing lists (technically this does not have to be a +mailing list, but that is what it is most often used for). This is +accomplished through the use of the <ref id="lists" name="lists"> command in +your muttrc. + +Now that Mutt knows what your mailing lists are, it can do several +things, the first of which is the ability to show the list name in +the <em/index/ menu display. This is useful to distinguish between +personal and list mail in the same mailbox. In the <ref id="index_format" +name="$index_format"> variable, the escape ``%L'' +will return the string ``To <list>'' when ``list'' appears in the +``To'' field, and ``Cc <list>'' when it appears in the ``Cc'' +field (otherwise it returns the name of the author). + +Often times the ``To'' and ``Cc'' fields in mailing list messages tend to +get quite large. Most people do not bother to remove the author of the +message they are reply to from the list, resulting in two or more copies +being sent to that person. The ``list-reply'' function, which by default is +bound to ``L'' in the <em/index/ menu and <em/pager/, helps reduce the clutter +by only replying to the mailing list addresses instead of all recipients. + +The other method some mailing list admins use is to generate a +``Reply-To'' field which points back to the mailing list address rather +than the author of the message. This can create problems when trying +to reply directly to the author in private, since most mail clients +will automatically reply to the address given in the ``Reply-To'' +field. Mutt uses the <ref id="reply_to" name="$reply_to"> +variable to help decide which address to use. If set, you will be +prompted as to whether or not you would like to use the address given in +the ``Reply-To'' field, or reply directly to the address given in the +``From'' field. When unset, the ``Reply-To'' field will be used when +present. + +Lastly, Mutt has the ability to <ref id="sort" name="sort"> the mailbox into +<ref id="threads" name="threads">. A thread is a group of messages which all relate to the same +subject. This is usually organized into a tree-like structure where a +message and all of its replies are represented graphically. If you've ever +used a threaded news client, this is the same concept. It makes dealing +with large volume mailing lists easier because you can easily delete +uninteresting threads and quickly find topics of value. + +<sect1>Delivery Status Notification (DSN) Support +<p> +RFC1894 defines a set of MIME content types for relaying information +about the status of electronic mail messages. These can be thought of as +``return receipts.'' Berkeley sendmail 8.8.x currently has some command +line options in which the mail client can make requests as to what type +of status messages should be returned. + +To support this, there are two variables. <ref id="dsn_notify" +name="$dsn_notify"> is used to request receipts for +different results (such as failed message, message delivered, etc.). +<ref id="dsn_return" name="$dsn_return"> requests how much +of your message should be returned with the receipt (headers or full +message). Refer to the man page on sendmail for more details on DSN. + +<sect1>POP3 Support (OPTIONAL) +<p> + +If Mutt was compiled with POP3 support (by running the <em/configure/ +script with the <em/--enable-pop/ flag), it has the ability to fetch +your mail from a remote server for local browsing. When you invoke the +<em/fetch-mail/ function (default: G), Mutt attempts to connect to <ref +id="pop_host" name="pop_host"> and authenticate by logging in +as <ref id="pop_user" name="pop_user">. After the connection +is established, you will be prompted for your password on the remote +system. + +Once you have been authenticated, Mutt will fetch all your new mail and +place it in the local <ref id="spoolfile" name="spoolfile">. After this +point, Mutt runs exactly as if the mail had always been local. + +<bf/Note:/ The POP3 support is there only for convenience, +and it's rather limited. If you need more functionality you +should consider using a specialized program, such as <htmlurl +url="http://www.ccil.org/~esr/fetchmail" name="fetchmail"> + +<sect>Mutt's MIME Support +<p> +Quite a bit of effort has been made to make Mutt the premier text-mode +MIME MUA. Every effort has been made to provide the functionality that +the discerning MIME user requires, and the conformance to the standards +wherever possible. When configuring Mutt for MIME, there are two extra +types of configuration files which Mutt uses. One is the +<tt/mime.types/ file, which contains the mapping of file extensions to +IANA MIME types. The other is the <tt/mailcap/ file, which specifies +the external commands to use for handling specific MIME types. + +<sect1>Using MIME in Mutt +<p> +There are three areas/menus in Mutt which deal with MIME, they are the +pager (while viewing a message), the attachment menu and the compose +menu. + +<sect2>Viewing MIME messages in the pager +<p> +When you select a message from the index and view it in the pager, Mutt +decodes the message to a text representation. Mutt internally supports +a number of MIME types, including <tt>text/plain, text/enriched, +message/rfc822, and message/news</tt>. In addition, the export +controlled version of Mutt recognizes a variety of PGP MIME types, +including PGP/MIME and application/pgp. + +Mutt will denote attachments with a couple lines describing them. +These lines are of the form: +<tscreen><verb> +[-- Attachment #1: Description --] +[-- Type: text/plain, Encoding: 7bit, Size: 10000 --] +</verb></tscreen> +Where the <tt/Description/ is the description or filename given for the +attachment, and the <tt/Encoding/ is one of +<tt>7bit/8bit/quoted-printable/base64/binary</tt>. + +If Mutt cannot deal with a MIME type, it will display a message like: +<tscreen><verb> +[-- image/gif is unsupported (use 'v' to view this part) --] +</verb></tscreen> + +<sect2>The Attachment Menu<label id="attach_menu"> +<p> +The default binding for <tt/view-attachments/ is `v', which displays the +attachment menu for a message. The attachment menu displays a list of +the attachments in a message. From the attachment menu, you can save, +print, pipe, delete, and view attachments. You can apply these +operations to a group of attachments at once, by tagging the attachments +and by using the ``tag-prefix'' operator. You can also reply to the +current message from this menu, and only the current attachment (or the +attachments tagged) will be quoted in your reply. You can view +attachments as text, or view them using the mailcap viewer definition. +See the help on the attachment menu for more information. + +<sect2>The Compose Menu +<p> +The compose menu is the menu you see before you send a message. It +allows you to edit the recipient list, the subject, and other aspects +of your message. It also contains a list of the attachments of your +message, including the main body. From this menu, you can print, copy, +filter, pipe, edit, compose, review, and rename an attachment or a +list of tagged attachments. You can also modifying the attachment +information, notably the type, encoding and description. + +Attachments appear as follows: +<verb> +- 1 [text/plain, 7bit, 1K] /tmp/mutt-euler-8082-0 <no description> + 2 [applica/x-gunzip, base64, 422K] ~/src/mutt-0.85.tar.gz <no description> +</verb> + +The '-' denotes that Mutt will delete the file after sending the +message. It can be toggled with the <tt/toggle-unlink/ command +(default: u). The next field is the MIME content-type, and can be +changed with the <tt/edit-type/ command (default: ^T). The next field +is the encoding for the attachment, which allows a binary message to +be encoded for transmission on 7bit links. It can be changed with the +<tt/edit-encoding/ command (default: ^E). The next field is the size +of the attachment, rounded to kilobytes or megabytes. The next field +is the filename, which can be changed with the <tt/rename-file/ command +(default: R). The final field is the description of the attachment, and +can be changed with the <tt/edit-description/ command (default: d). + +<sect1>MIME Type configuration with <tt/mime.types/ +<p> +When you add an attachment to your mail message, Mutt searches your +personal mime.types file at <tt>${HOME}/.mime.types</tt>, and then +the system mime.types file at <tt>SHAREDIR/mime.types</tt>. +<tt/SHAREDIR/ is defined at compilation time, and can be determined +by typing <tt/mutt -v/ from the command line. + +The mime.types file consist of lines containing a MIME type and a space +separated list of extensions. For example: +<tscreen><verb> +application/postscript ps eps +application/pgp pgp +audio/x-aiff aif aifc aiff +</verb></tscreen> +A sample <tt/mime.types/ file comes with the Mutt distribution, and +should contain most of the MIME types you are likely to use. + +If Mutt can not determine the mime type by the extension of the file you +attach, it will look at the file. If the file is free of binary +information, Mutt will assume that the file is plain text, and mark it +as <tt>text/plain</tt>. If the file contains binary information, then Mutt will +mark it as <tt>application/octect-stream</tt>. You can change the MIME +type that Mutt assigns to an attachment by using the <tt/edit-type/ +command from the compose menu (default: ^T). When typing in the MIME +type, Mutt requires that major type be one of the 5 types: application, +text, image, video, or audio. If you attempt to use a different major +type, Mutt will abort the change. + +<sect1>MIME Viewer configuration with <tt/mailcap/ +<p> +Mutt supports RFC 1524 MIME Configuration, in particular the Unix +specific format specified in Appendix A of RFC 1524. This file format +is commonly referred to as the mailcap format. Many MIME compliant +programs utilize the mailcap format, allowing you to specify handling +for all MIME types in one place for all programs. Programs known to +use this format include Netscape, XMosaic, lynx and metamail. + +In order to handle various MIME types that Mutt can not handle +internally, Mutt parses a series of external configuration files to +find an external handler. The default search string for these files +is a colon delimited list set to +<tscreen><verb> +${HOME}/.mailcap:SHAREDIR/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap +</verb></tscreen> +where <tt/$HOME/ is your home directory and <tt/SHAREDIR/ is the +shared directory defined at compile time (visible from <tt/mutt -v/). + +In particular, the metamail distribution will install a mailcap file, +usually as <tt>/usr/local/etc/mailcap</tt>, which contains some baseline +entries. + +<sect2>The Basics of the mailcap file +<p> +A mailcap file consists of a series of lines which are comments, blank, +or definitions. + +A comment line consists of a # character followed by anything you want. + +A blank line is blank. + +A definition line consists of a content type, a view command, and any +number of optional fields. Each field of a definition line is divided +by a semicolon ';' character. + +The content type is specified in the MIME standard type/subtype method. +For example, +<tt>text/plain, text/html, image/gif, </tt> +etc. In addition, the mailcap format includes two formats for +wildcards, one using the special '*' subtype, the other is the implicit +wild, where you only include the major type. For example, <tt>image/*</tt>, or +<tt>video,</tt> will match all image types and video types, +respectively. + +The view command is a Unix command for viewing the type specified. There +are two different types of commands supported. The default is to send +the body of the MIME message to the command on stdin. You can change +this behaviour by using %s as a parameter to your view command. +This will cause Mutt to save the body of the MIME message to a temporary +file, and then call the view command with the %s replaced by +the name of the temporary file. In both cases, Mutt will turn over the +terminal to the view program until the program quits, at which time Mutt +will remove the temporary file if it exists. + +So, in the simplest form, you can send a text/plain message to the +external pager more on stdin: +<tscreen><verb> +text/plain; more +</verb></tscreen> +Or, you could send the message as a file: +<tscreen><verb> +text/plain; more %s +</verb></tscreen> +Perhaps you would like to use lynx to interactively view a text/html +message: +<tscreen><verb> +text/html; lynx "%s" +</verb></tscreen> +In this case, lynx does not support viewing a file from stdin, so you +must use the %s syntax. +<bf/Note:/ <em>Some older versions of lynx contain a bug where they +will check the mailcap file for a viewer for text/html. They will find +the line which calls lynx, and run it. This causes lynx to continuously +spawn itself to view the object.</em> + +On the other hand, maybe you don't want to use lynx interactively, you +just want to have it convert the text/html to text/plain, then you can +use: +<tscreen><verb> +text/html; lynx -dump "%s" | more +</verb></tscreen> + +Perhaps you wish to use lynx to view text/html files, and a pager on +all other text formats, then you would use the following: +<tscreen><verb> +text/html; lynx "%s" +text/*; more +</verb></tscreen> +This is the simplest form of a mailcap file. + +<sect2>Advanced mailcap Usage +<p> + +<sect3>Optional Fields +<p> +In addition to the required content-type and view command fields, you +can add semi-colon ';' separated fields to set flags and other options. +Mutt recognizes the following optional fields: +<descrip> +<tag/copiousoutput/ +This flag tells Mutt that the command passes possibly large amounts of +text on stdout. This causes Mutt to invoke a pager (either the internal +pager or the external pager defined by the pager variable) on the output +of the view command. Without this flag, Mutt assumes that the command +is interactive. One could use this to replace the pipe to <tt>more</tt> +in the <tt>lynx -dump</tt> example in the Basic section: +<tscreen><verb> +text/html; lynx -dump %s ; copiousoutput +</verb></tscreen> +This will cause lynx to format the text/html output as text/plain +and Mutt will use your standard pager to display the results. +<tag/needsterminal/ +Mutt uses this flag when viewing attachments with <ref id="auto_view" +name="autoview">, in order to decide whether it should honor the setting +of the <ref id="wait_key" name="$wait_key"> variable or +not. When an attachment is viewed using an interactive program, and the +corresponding mailcap entry has a <em/needsterminal/ flag, Mutt will use +<ref id="wait_key" name="$wait_key"> and the exit status +of the program to decide if it will ask you to press a key after the +external program has exited. In all other situations it will not prompt +you for a key. +<tag>compose=<command></tag> +This flag specifies the command to use to create a new attachment of a +specific MIME type. Mutt supports this from the compose menu. +<tag>composetyped=<command></tag> +This flag specifies the command to use to create a new attachment of a +specific MIME type. This command differs from the compose command in +that mutt will expect standard MIME headers on the data. This can be +used to specify parameters, filename, description, etc. for a new +attachment. Mutt supports this from the compose menu. +<tag>print=<command></tag> +This flag specifies the command to use to print a specific MIME type. +Mutt supports this from the attachment and compose menus. +<tag>edit=<command></tag> +This flag specifies the command to use to edit a specific MIME type. +Mutt supports this from the compose menu, and also uses it to compose +new attachments. Mutt will default to the defined editor for text +attachments. +<tag>nametemplate=<template></tag> +This field specifies the format for the file denoted by %s in the +command fields. Certain programs will require a certain file extension, +for instance, to correctly view a file. For instance, lynx will only +interpret a file as <tt>text/html</tt> if the file ends in <tt/.html/. +So, you would specify lynx as a <tt>text/html</tt> viewer with a line in +the mailcap file like: +<tscreen><verb> +text/html; lynx %s; nametemplate=%s.html +</verb></tscreen> +<tag>test=<command></tag> +This field specifies a command to run to test whether this mailcap +entry should be used. The command is defined with the command expansion +rules defined in the next section. If the command returns 0, then the +test passed, and Mutt uses this entry. If the command returns non-zero, +then the test failed, and Mutt continues searching for the right entry. +<bf/Note:/ <em>the content-type must match before Mutt performs the test.</em> +For example: +<tscreen><verb> +text/html; netscape -remote 'openURL(%s)' ; test=RunningX +text/html; lynx %s +</verb></tscreen> +In this example, Mutt will run the program RunningX which will return 0 +if the X Window manager is running, and non-zero if it isn't. If +RunningX returns 0, then Mutt will call netscape to display the +text/html object. If RunningX doesn't return 0, then Mutt will go on +to the next entry and use lynx to display the text/html object. +</descrip> + +<sect3>Search Order +<p> +When searching for an entry in the mailcap file, Mutt will search for +the most useful entry for its purpose. For instance, if you are +attempting to print an <tt>image/gif</tt>, and you have the following +entries in your mailcap file, Mutt will search for an entry with the +print command: +<tscreen><verb> +image/*; xv %s +image/gif; ; print= anytopnm %s | pnmtops | lpr; \ + nametemplate=%s.gif +</verb></tscreen> +Mutt will skip the <tt>image/*</tt> entry and use the <tt>image/gif</tt> +entry with the print command. + +In addition, you can use this with <ref id="auto_view" name="Autoview"> +to denote two commands for viewing an attachment, one to be viewed +automatically, the other to be viewed interactively from the attachment +menu. In addition, you can then use the test feature to determine which +viewer to use interactively depending on your environment. +<tscreen><verb> +text/html; netscape -remote 'openURL(%s)' ; test=RunningX +text/html; lynx %s; nametemplate=%s.html +text/html; lynx -dump %s; nametemplate=%s.html; copiousoutput +</verb></tscreen> +For <ref id="auto_view" name="Autoview">, Mutt will choose the third +entry because of the copiousoutput tag. For interactive viewing, Mutt +will run the program RunningX to determine if it should use the first +entry. If the program returns non-zero, Mutt will use the second entry +for interactive viewing. + +<sect3>Command Expansion +<p> +The various commands defined in the mailcap files are passed to the +<tt>/bin/sh</tt> shell using the system() function. Before the +command is passed to <tt>/bin/sh -c</tt>, it is parsed to expand +various special parameters with information from Mutt. The keywords +Mutt expands are: +<descrip> +<tag/%s/ +As seen in the basic mailcap section, this variable is expanded +to a filename specified by the calling program. This file contains +the body of the message to view/print/edit or where the composing +program should place the results of composition. In addition, the +use of this keyword causes Mutt to not pass the body of the message +to the view/print/edit program on stdin. +<tag/%t/ +Mutt will expand %t to the text representation of the content +type of the message in the same form as the first parameter of the +mailcap definition line, ie <tt>text/html</tt> or +<tt>image/gif</tt>. +<tag>%{<parameter>}</tag> +Mutt will expand this to the value of the specified parameter +from the Content-Type: line of the mail message. For instance, if +Your mail message contains: +<tscreen><verb> +Content-Type: text/plain; charset=iso-8859-1 +</verb></tscreen> +then Mutt will expand %{charset} to iso-8859-1. The default metamail +mailcap file uses this feature to test the charset to spawn an xterm +using the right charset to view the message. +<tag>∖%</tag> +This will be replaced by a % +</descrip> +Mutt does not currently support the %F and %n keywords +specified in RFC 1524. The main purpose of these parameters is for +multipart messages, which is handled internally by Mutt. + +<sect2>Example mailcap files +<p> +This mailcap file is fairly simple and standard: +<code> +# I'm always running X :) +video/*; xanim %s > /dev/null +image/*; xv %s > /dev/null + +# I'm always running netscape (if my computer had more memory, maybe) +text/html; netscape -remote 'openURL(%s)' +</code> + +This mailcap file shows quite a number of examples: + +<code> +# Use xanim to view all videos Xanim produces a header on startup, +# send that to /dev/null so I don't see it +video/*; xanim %s > /dev/null + +# Send html to a running netscape by remote +text/html; netscape -remote 'openURL(%s)'; test=RunningNetscape + +# If I'm not running netscape but I am running X, start netscape on the +# object +text/html; netscape %s; test=RunningX + +# Else use lynx to view it as text +text/html; lynx %s + +# This version would convert the text/html to text/plain +text/html; lynx -dump %s; copiousoutput + +# enriched.sh converts text/enriched to text/html and then uses +# lynx -dump to convert it to text/plain +text/enriched; enriched.sh ; copiousoutput + +# I use enscript to print text in two columns to a page +text/*; more %s; print=enscript -2Gr %s + +# Netscape adds a flag to tell itself to view jpegs internally +image/jpeg;xv %s; x-mozilla-flags=internal + +# Use xv to view images if I'm running X +# In addition, this uses the \ to extend the line and set my editor +# for images +image/*;xv %s; test=RunningX; \ + edit=xpaint %s + +# Convert images to text using the netpbm tools +image/*; (anytopnm %s | pnmscale -xysize 80 46 | ppmtopgm | pgmtopbm | +pbmtoascii -1x2 ) 2>&1 ; copiousoutput + +# Send excel spreadsheets to my NT box +application/ms-excel; open.pl %s +</code> + +<sect1>MIME Autoview<label id="auto_view"> +<p> +In addition to explicitly telling Mutt to view an attachment with the +MIME viewer defined in the mailcap file, Mutt has support for +automatically viewing MIME attachments while in the pager. + +To work, you must define a viewer in the mailcap file which uses the +<tt/copiousoutput/ option to denote that it is non-interactive. +Usually, you also use the entry to convert the attachment to a text +representation which you can view in the pager. + +You then use the <tt/auto_view/ muttrc command to list the +content-types that you wish to view automatically. + +For instance, if you set auto_view to: +<tscreen><verb> +auto_view text/html text/enriched application/x-gunzip application/postscript image/gif application/x-tar-gz +</verb></tscreen> + +Mutt could use the following mailcap entries to automatically view +attachments of these types. +<tscreen><verb> +text/html; lynx -dump %s; copiousoutput; nametemplate=%s.html +text/enriched; enriched.sh ; copiousoutput +image/*; anytopnm %s | pnmscale -xsize 80 -ysize 50 | ppmtopgm | pgmtopbm | pbmtoascii ; copiousoutput +application/x-gunzip; gzcat; copiousoutput +application/x-tar-gz; gunzip -c %s | tar -tf - ; copiousoutput +application/postscript; ps2ascii %s; copiousoutput +</verb></tscreen> +<sect1>MIME Multipart/Alternative<label id="alternative_order"> +<p> +Mutt has some heuristics for determining which attachment of a +multipart/alternative type to display. First, mutt will check the +alternative_order list to determine if one of the available types +is preferred. The alternative_order list consists of a number of +mimetypes in order, including support for implicit and explicit +wildcards, for example: +<tscreen><verb> +alternative_order text/enriched text/plain text application/postscript image/* +</verb></tscreen> + +Next, mutt will check if any of the types have a defined +<ref id="auto_view" name="auto_view">, and use that. Failing +that, Mutt will look for any text type. As a last attempt, mutt will +look for any type it knows how to handle. + +<sect>Reference +<sect1>Command line options<label id="commandline"> +<p> +Running <tt/mutt/ with no arguments will make Mutt attempt to read your spool +mailbox. However, it is possible to read other mailboxes and +to send messages from the command line as well. + +<tscreen><verb> +-a attach a file to a message +-c specify a carbon-copy (Cc) address +-e specify a config command to be run after initilization files are read +-F specify an alternate file to read initialization commands +-f specify a mailbox to load +-h print help on command line options +-H specify a draft file from which to read a header and body +-i specify a file to include in a message composition +-n do not read the system Muttrc +-m specify a default mailbox type +-p recall a postponed message +-R open mailbox in read-only mode +-s specify a subject (enclose in quotes if it contains spaces) +-v show version number and compile-time definitions +-x simulate the mailx(1) compose mode +-y show a menu containing the files specified by the mailboxes command +-z exit immediately if there are no messages in the mailbox +-Z open the first folder with new message,exit immediately if none +</verb></tscreen> + +To read messages in a mailbox + +<tt/mutt/ [ -nz ] [ -F <em/muttrc/ ] [ -m <em/type/ ] [ -f <em/mailbox/ ] + +To compose a new message + +<tt/mutt/ [ -n ] [ -F <em/muttrc/ ] [ -a <em/file/ ] [ -c <em/address/ ] [ -i <em/filename/ ] [ -s <em/subject/ ] <em/address/ [ <em/address/ ... ] + +Mutt also supports a ``batch'' mode to send prepared messages. Simply redirect +input from the file you wish to send. For example, + +<tt>mutt -s &dquot;data set for run #2&dquot; professor@bigschool.edu +< ˜/run2.dat</tt> + +This command will send a message to ``professor@bigschool.edu'' with a subject +of ``data set for run #2''. In the body of the message will be the contents +of the file ``˜/run2.dat''. + +<sect1>Configuration Commands<label id="commands"> +<p> +The following are the commands understood by mutt. + +<itemize> +<item> +<tt><ref id="alias" name="alias"></tt> <em/key/ <em/address/ [ , <em/address/, ... ] +<item> +<tt><ref id="alias" name="unalias"></tt> <em/key/ <em/address/ [ , <em/address/, ... ] +<item> +<tt><ref id="alternative_order" name="alternative_order"></tt> <em/mimetype/ [ <em/mimetype/ ... ] +<item> +<tt><ref id="auto_view" name="auto_view"></tt> <em/mimetype/ [ <em/mimetype/ ... ] +<item> +<tt><ref id="bind" name="bind"></tt> <em/map/ <em/key/ <em/function/ +<item> +<tt><ref id="color" name="color"></tt> <em/object/ <em/foreground/ <em/background/ [ <em/regexp/ ] +<item> +<tt><ref id="folder-hook" name="folder-hook"></tt> <em/pattern/ <em/command/ +<item> +<tt><ref id="ignore" name="ignore"></tt> <em/pattern/ [ <em/pattern/ ... ] +<item> +<tt><ref id="ignore" name="unignore"></tt> <em/pattern/ [ <em/pattern/ ... ] +<item> +<tt><ref id="hdr_order" name="hdr_order"></tt> <em/header/ [ <em/header/ ... ] +<item> +<tt><ref id="lists" name="lists"></tt> <em/address/ [ <em/address/ ... ] +<item> +<tt><ref id="lists" name="unlists"></tt> <em/address/ [ <em/address/ ... ] +<item> +<tt><ref id="macro" name="macro"></tt> <em/menu/ <em/key/ <em/sequence/ +<item> +<tt><ref id="mailboxes" name="mailboxes"></tt> <em/filename/ [ <em/filename/ ... ] +<item> +<tt><ref id="color" name="mono"></tt> <em/object attribute/ [ <em/regexp/ ] +<item> +<tt><ref id="mbox-hook" name="mbox-hook"></tt> <em/pattern/ <em/mailbox/ +<item> +<tt><ref id="my_hdr" name="my_hdr"></tt> <em/string/ +<item> +<tt><ref id="my_hdr" name="unmy_hdr"></tt> <em/field/ [ <em/field/ ... ] +<item> +<tt><ref id="push" name="push"></tt> <em/string/ +<item> +<tt><ref id="save-hook" name="save-hook"></tt> <em/regexp/ <em/filename/ +<item> +<tt><ref id="send-hook" name="send-hook"></tt> <em/regexp/ <em/command/ +<item> +<tt><ref id="set" name="set"></tt> [no|inv]<em/variable/[=<em/value/] [ <em/variable/ ... ] +<item> +<tt><ref id="set" name="toggle"></tt> <em/variable/ [<em/variable/ ... ] +<item> +<tt><ref id="set" name="unset"></tt> <em/variable/ [<em/variable/ ... ] +<item> +<tt><ref id="source" name="source"></tt> <em/filename/ +</itemize> + +<sect1>Configuration variables<label id="variables"> +<p> + +<sect2>abort_nosubject<label id="abort_nosubject"> +<p> +Type: quadoption<newline> +Default: ask-yes + +If set to <em/yes/, when composing messages and no subject is given +at the subject prompt, composition will be aborted. If set to <em/no/, +composing messages with no subject given at the subject prompt will +never be aborted. + +<sect2>abort_unmodified<label id="abort_unmodified"> +<p> +Type: quadoption<newline> +Default: yes + +If set to <em/yes/, composition will automatically abort after editing the +message body if no changes are made to the file (this check only happens +after the <em/first/ edit of the file). When set to <em/no/, composition +will never be aborted. + +<sect2>alias_file<label id="alias_file"> +<p> +Type: string<newline> +Default: ˜/.muttrc + +The default file in which to save aliases created by the +<ref id="create-alias" name="create-alias"> function. + +<bf/Note:/ Mutt will not automatically source this file; you must +explicitly use the <ref id="source" name="source"> command for it to be +executed. + +<sect2>alias_format<label id="alias_format"> +<p> +Type: string<newline> +Default: &dquot;%2n %t %-10a %r&dquot; + +Specifies the format of the data displayed for the `alias' menu. The +following printf(3)-style sequences are available. + +<verb> +%a alias name +%n index number +%r address which alias expands to +%t character which indicates if the alias is tagged for inclusion (*/ ) +</verb> + +<sect2>allow_8bit +<p> +Type: boolean<newline> +Default: set + +Controls whether 8-bit data is converted to 7-bit using either Quoted-Printable +or Base64 encoding when sending mail. + +<sect2>alternates<label id="alternates"> +<p> +Type: string<newline> +Default: none + +A regexp that allows you to specify <em/alternate/ addresses where you +receive mail. This affects Mutt's idea about messages from you and +addressed to you. + +<sect2>arrow_cursor<label id="arrow_cursor"> +<p> +Type: boolean<newline> +Default: unset + +When set, an arrow (``->'') will be used to indicate the current entry in +menus instead of hiliting the whole line. On slow network or modem links +this will make response faster because there is less that has to be redrawn +on the screen when moving to the next or previous entries in the menu. + +<sect2>ascii_chars<label id="ascii_chars"> +<p> +Type: boolean<newline> +Default: unset + +If set, Mutt will use plain ASCII characters when displaying thread and +attachment trees, instead of the default <em/ACS/ characters. + +<sect2>askbcc<label id="askbcc"> +<p> +Type: boolean<newline> +Default: unset + +If set, Mutt will prompt you for blind-carbon-copy (Bcc) recipients before +editing an outgoing message. + +<sect2>askcc<label id="askcc"> +<p> +Type: boolean<newline> +Default: unset + +If set, Mutt will prompt you for carbon-copy (Cc) recipients before editing +the body of an outgoing message. + +<sect2>attribution<label id="attribution"> +<p> +Type: format string<newline> +Default: &dquot;On %d, %n wrote:&dquot; + +This is the string that will precede a message which has been included in +a reply. For a full listing of defined escape sequences +see the section on <ref id="index_format" name="$index_format">. + +<sect2>autoedit<label id="autoedit"> +<p> +Type: boolean<newline> +Default: unset + +When set, Mutt will skip the initial send-menu and allow you to immediately +begin editing the body of your message when replying to another message. +The send-menu may still be accessed once you have finished editing the body +of your message. + +If the <ref id="edit_headers" name="$edit_headers"> variable is +also set, the initial prompts in the send-menu are always skipped, even +when composing a new message. + +<sect2>auto_tag<label id="auto_tag"> +<p> +Type: boolean<newline> +Default: unset + +When set, functions in the <em/index/ menu which affect a message will be +applied to all tagged messages (if there are any). When unset, you must +first use the tag-prefix function (default: ";") to make the next function +apply to all tagged messages. + +<sect2>beep<label id="beep"> +<p> +Type: boolean<newline> +Default: set + +When this variable is set, mutt will beep when an error occurs. + +<sect2>beep_new<label id="beep_new"> +<p> +Type boolean<newline> +Default: unset + +When this variable is set, mutt will beep whenever it prints a +message notifying you of new mail. This is independent of the +setting of the <ref id="beep" name="beep"> variable. + +<sect2>charset<label id="charset"> +<p> +Type: string<newline> +Default: iso-8859-1 + +Character set your terminal uses to display and enter textual data. This +information is required to properly label outgoing messages which contain +8-bit characters so that receiving parties can display your messages in the +correct character set. + +<sect2>check_new<label id="check_new"> +<p> +Type: boolean<newline> +Default: set + +<bf/Note:/ this option only affects <em/maildir/ and <em/MH/ style +mailboxes. + +When <em/set/, Mutt will check for new mail delivered while the mailbox +is open. Especially with MH mailboxes, this operation can take quite +some time since it involves scanning the directory and checking each +file to see if it has already been looked at. If <em/check_new/ +is <em/unset/, no check for new mail is performed while the mailbox is +open. + +<sect2>confirmappend<label id="confirmappend"> +<p> +Type: boolean<newline> +Default: set + +When set, Mutt will prompt for confirmation when appending messages to an +existing mailbox. + +<sect2>confirmcreate<label id="confirmcreate"> +<p> +Type: boolean<newline> +Default: set + +When set, Mutt will prompt for confirmation when saving messages to a +mailbox which does not yet exist before creating it. + +<sect2>copy<label id="copy"> +<p> +Type: quadoption<newline> +Default: yes + +This variable controls whether or not copies of your outgoing messages will be +saved for later references. Also see <ref id="record" name="record">, +<ref id="save_name" name="save_name">, +<ref id="force_name" name="force_name"> and +<ref id="fcc-hook" name="fcc-hook">. + +<sect2>date_format<label id="date_format"> +<p> +Type: string<newline> +Default: &dquot;!%a, %b %d, %Y at %I:%M:%S%p %Z&dquot; + +This variable controls the format of the date printed +by the ``%d'' sequence in <ref id="index_format" +name="$index_format">. This is passed to the <em/strftime/ +call to process the date. See the man page for <em/strftime(3)/ for the +proper syntax. + +Unless the first character in the string is a bang (``!''), the month +and week day names are expanded according to the locale specified in +the variable <ref id="locale" name="locale">. If the first character in +the string is a bang, the bang is discarded, and the month and week day +names in the rest of the string are expanded in the <em/C/ locale (that +is in US English). + +<sect2>default_hook<label id="default_hook"> +<p> +Type: string<newline> +Default: "˜f %s | (˜P (˜c %s | ˜t %s))" + +This variable controls how send-hooks, save-hooks, and fcc-hooks will be +interpreted if they are specified with only a simple regexp, instead of a +matching pattern. The hooks are expanded when they are declared, so a hook +will be interpreted according to the value of this variable at the time the +hook is declared. The default value matches if the message is either from a +user matching the regular expression given, or if it is from you (if the from +address matches <ref id="alternates" name="alternates">) and is to or cc'ed to +a user matching the given regular expression. + +<sect2>delete<label id="delete"> +<p> +Type: quadoption<newline> +Default: ask-yes + +Controls whether or not messages are really deleted when closing or +synchronizing a mailbox. If set to <em/yes/, messages marked for deleting +will automatically be purged without prompting. If set to <em/no/, messages +marked for deletion will be kept in the mailbox. + +<sect2>delete_format<label id="delete_format"> +<p> +Type: string<newline> +Default: "[-- Attachment from %u deleted on %<%D> --]" + +This variable controls the format of the message used to replace an +attachment when the attachment is deleted. It uses the same format +sequences as the <ref id="index_format" name="$index_format"> +variable. + +<sect2>dsn_notify<label id="dsn_notify"> +<p> +Type: string<newline> +Default: none + +<bf/Note:/ you should not enable this unless you are using Sendmail 8.8.x or +greater. + +This variable sets the request for when notification is returned. The +string consists of a comma separated list (no spaces!) of one or more of the +following: <em/never/, to never request notification, <em/failure/, to +request notification on transmission failure, <em/delay/, to be notified of +message delays, <em/success/, to be notified of successful transmission. + +Example: <tt/set dsn_notify=&dquot;failure,delay&dquot;/ + +<sect2>dsn_return<label id="dsn_return"> +<p> +Type: string +Default: none + +<bf/Note:/ you should not enable this unless you are using Sendmail 8.8.x or +greater. + +This variable controls how much of your message is returned in DSN messages. +It may be set to either <em/hdrs/ to return just the message header, or +<em/full/ to return the full message. + +Example: <tt/set dsn_return=hdrs/ + +<sect2>edit_headers<label id="edit_headers"> +<p> +Type: boolean<newline> +Default: unset + +This option allows you to edit the header of your outgoing messages along +with the body of your message. + +<sect2>editor<label id="editor"> +<p> +Type: String<newline> +Default: value of environment variable $VISUAL, $EDITOR, or &dquot;vi&dquot; + +This variable specifies which editor to use when composing messages. + +<sect2>empty_to<label id="empty_to"> +<p> +Type: string<newline> +Default: undisclosed-recipients + +Specifies the text Mutt inserts in the <tt/To:/ header of a message you're +sending, if the <tt/To:/ and <tt/Cc:/ fields are empty. + +<sect2>escape<label id="escape"> +<p> +Type: string<newline> +Default: ˜ + +Escape character to use for functions in the builtin editor. + +<sect2>fast_reply<label id="fast_reply"> +<p> +Type: boolean<newline> +Default: unset + +When set, the initial prompt for recipients and subject are skipped when +replying to messages, and the initial prompt for subject is skipped when +forwarding messages. +<p> +<bf/Note:/ this variable has no effect when the <ref id="autoedit" +name="$autoedit"> variable is set. + +<sect2>fcc_attach<label id="fcc_attach"> +<p> +Type: boolean<newline> +Default: set + +This variable controls whether or not attachments on outgoing messages are +saved along with the main body of your message. + +<sect2>folder<label id="folder"> +<p> +Type: String<newline> +Default: ˜/Mail + +Specifies the default location of your mailboxes. A `+' or `=' at the +beginning of a pathname will be expanded to the value of this variable. +Note that if you change this variable from the default value you need to +make sure that the assignment occurs <em/before/ you use `+' or `=' for any +other variables since expansion takes place during the `set' command. + +<sect2>followup_to<label id="followup_to"> +<p> +Type: boolean<newline> +Default: set + +Controls whether or not the <em/Mail-Followup-To/ header field is generated +when sending mail. When <em/set/, Mutt will generate this field when you +are replying to a known mailing <ref id="lists" name="lists">. + +The purpose of this field is to prevent you from receiving duplicate copies of +replies to messages which you send by specifying that you will receive a copy +of the message if it is addressed to the mailing list (and thus there is no +need to also include your address in a group reply). + +<sect2>force_name<label id="force_name"> +<p> +Type: boolean<newline> +Default: unset + +This variable is similar to <ref id="save_name" +name="$save_name">, except that Mutt will store a copy of +your outgoing message by the username of the address you are sending +to even if that mailbox does not exist. + +Also see the <ref id="record" name="$record"> variable. + +<sect2>forward_decode<label id="forward_decode"> +<p> +Type: boolean<newline> +Default: set + +Controls the decoding of complex MIME messages into text/plain when +forwarding a message and the message header is also RFC2047 decoded. +This variable is only used, if <ref id="mime_forward" +name="mime_forward"> is <em/unset/, otherwise <ref +id="mime_forward_decode" name="mime_forward_decode"> is +used instead. + +<sect2>forward_format<label id="forward_format"> +<p> +Type: format string<newline> +Default: "[%a: %s]" + +This variable controls the default subject when forwarding a message. It +uses the same format sequences as the <ref id="index_format" +name="$index_format"> variable. + +<sect2>forward_quote<label id="forward_quote"> +<p> +Type: boolean<newline> +Default: unset + +When <em/set/ forwarded messages included in the main body of the message +(when <ref id="mime_forward" name="mime_forward"> is <em/unset/) will be +quoted using <ref id="indent_string" name="indent_string">. + +<sect2>index_format<label id="index_format"> +<p> +Type: format string<newline> +Default: &dquot;%4C %Z %{%b %d} %-15.15L (%4l) %s&dquot; + +This variable allows you to customize the message index display to your +personal taste. + +``Format strings'' are similar to the strings used in the ``C'' function +<tt/printf/ to format output (see the man page for more detail). The +following sequences are defined in Mutt: + +<tscreen><verb> +%a address of the author +%b filename of the original message folder (think mailBox) +%B the list to which the letter was sent, or else the folder name (%b). +%c number of characters (bytes) in the message +%C current message number +%d date and time of the message in the format specified by + ``date_format'' +%f entire From: line (address + real name) +%F author name, or recipient name if the message is from you +%i message-id of the current message +%l number of lines in the message +%L list-from function +%m total number of message in the mailbox +%N message score +%n author's real name (or address if missing) +%O (_O_riginal save folder) Where mutt would formerly have stashed the + message: list name or recipient name if no list +%s subject of the message +%S status of the message (N/D/d/!/*/r) +%t `to:' field (recipients) +%T the appropriate character from the $to_chars string +%u user (login) name of the author +%Z message status flags + +%{fmt} the date and time of the message is converted to sender's + time zone, and ``fmt'' is expanded by the system call + ``strftime''; a leading bang disables locales +%[fmt] the date and time of the message is converted to the local + time zone, and ``fmt'' is expanded by the system call + ``strftime''; a leading bang disables locales +%(fmt) the local date and time when the message was received. + ``fmt'' is expanded by the system call ``strftime''; + a leading bang disables locales +%<fmt> the current local time. ``fmt'' is expanded by the system + call ``strftime''; a leading bang disables locales. + +%>X right justify the rest of the string and pad with character "X" +%|X pad to the end of the line with character "X" +</verb></tscreen> + +See also: <ref id="to_chars" name="$to_chars">. + +<sect2>hdrs<label id="hdrs"> +<p> +Type: boolean<newline> +Default: set + +When unset, the header fields normally added by the <ref id="my_hdr" +name="my_hdr"> command are not created. This variable <em/must/ be +unset before composing a new message or replying in order to take effect. If +set, the user defined header fields are added to every new message. + +<sect2>header +<p> +Type: boolean<newline> +Default: unset + +When set, this variable causes Mutt to include the <em/full/ header of +the message you are replying to into the edit buffer. + +<sect2>help<label id="help"> +<p> +Type: boolean<newline> +Default: set + +When set, help lines describing the bindings for the major functions +provided by each menu are displayed on the first line of the screen. + +<bf/Note:/ The binding will not be displayed correctly if the function is +bound to a sequence rather than a single keystroke. Also, the help line +may not be updated if a binding is changed while Mutt is running. Since +this variable is primarily aimed at new users, neither of these should +present a major problem. + +<sect2>history<label id="history"> +<p> +Type: number<newline> +Default: 10 + +This variable controls the size (in number of strings remembered) of the +string history buffer. The buffer is cleared each time the variable is +set. + +<sect2>hostname<label id="hostname"> +<p> +Type: string<newline> +Default: varies + +Specifies the hostname to use after the ``@'' in local e-mail addresses. This +overrides the compile time definition obtained from /etc/resolv.conf. + +<sect2>ignore_list_reply_to<label +id="ignore_list_reply_to"> +<p> +Type: boolean<newline> +Default: unset + +Affects the behaviour of the <em/reply/ function when replying to +messages from mailing lists. When set, if the ``Reply-To:'' field +is set to the same value as the ``To:'' field, Mutt assumes that the +``Reply-To:'' field was set by the mailing list to automate responses +to the list, and will ignore this field. To direct a response to the +mailing list when this option is set, use the <em/list-reply/ function; +<em/group-reply/ will reply to both the sender and the list. + +<sect2>in_reply_to +<p> +Type: format string<newline> +Default: &dquot;%i; from \&dquot;%n\&dquot; on %{!%a, %b %d, %Y at %I:%M:%S%p}&dquot; + +This specifies the format of the <tt/In-Reply-To:/ header +field added when replying to a message. For a full listing of +defined escape sequences see the section on <ref id="index_format" +name="$index_format">. + +<sect2>include<label id="include"> +<p> +Type: quadoption<newline> +Default: ask-yes + +Controls whether or not a copy of the message(s) you are replying to is +included in your reply. + +<sect2>indent_string<label id="indent_string"> +<p> +Type: format string<newline> +Default: "> " + +Specifies the string to prepend to each line of text quoted in a message to +which you are replying. You are strongly encouraged not to change this +value, as it tends to agitate the more fanatical netizens. + +<sect2>ispell<label id="ispell"> +<p> +Type: string<newline> +Default: &dquot;ispell&dquot; + +How to invoke ispell (GNU's spell-checking software). + +<sect2>locale<label id="locale"> +<p> +Type: string<newline> +Default: "C" + +The locale used by <em/strftime(3)/ to format dates. Legal values are +the strings your system accepts for the locale variable <em/LC_TIME/. + +<sect2>mailcap_path<label id="mailcap_path"> +<p> +Type: string<newline> +Default: $MAILCAPS or ˜/.mailcap:/usr/local/share/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap + +This variable specifies which files to consult when attempting to display +MIME bodies not directly supported by Mutt. + +<sect2>mark_old +<p> +Type: Boolean<newline> +Default: set + +Controls whether or not Mutt makes the distinction between <em/new/ messages +and <em/old/ <bf/unread/ messages. By default, Mutt will mark new messages +as old if you exit a mailbox without reading them. The next time you start +Mutt, the messages will show up with an "O" next to them in the index menu, +indicating that they are old. In order to make Mutt treat all unread messages +as new only, you can unset this variable. + +<sect2>markers<label id="markers"> +<p> +Type: boolean<newline> +Default: set + +Controls the display of wrapped lines in the internal pager. If set, a +``+'' marker is displayed at the beginning of wrapped lines. Also see +the <ref id="smart_wrap" name="$smart_wrap"> variable. + +<sect2>mask<label id="mask"> +<p> +Type: string<newline> +Default: "^(\.\.$|[^.])" + +A regular expression used in the file browser. Files whose names don't +match this mask will not be shown. + +<sect2>mbox<label id="mbox"> +<p> +Type: String<newline> +Default: +inbox + +This specifies the folder into which read mail in your <ref id="spoolfile" +name="spoolfile"> folder will be appended. + +<sect2>mbox_type<label id="mbox_type"> +<p> +Type: String<newline> +Default: mbox + +The default mailbox type used when creating new folders. May be any of +mbox, MMDF, MH and Maildir. + +<sect2>metoo<label id="metoo"> +<p> +Type: boolean<newline> +Default: unset + +If unset, Mutt will remove your address from the list of recipients when +replying to a message. If you are replying to a message sent by you, +Mutt will also assume that you want to reply to the recipients of that +message rather than to yourself. + +<sect2>menu_scroll<label id="menu_scroll"> +<p> +Type: boolean<newline> +Default: unset + +When <em/set/, menus will be scrolled up or down one line when you attempt +to move across a screen boundary. If <em/unset/, the screen is cleared and +the next or previous page of the menu is displayed (useful for slow links to +avoid many redraws). + +<sect2>meta_key<label id="meta_key"> +<p> +Type: Boolean<newline> +Default: unset + +If set, forces Mutt to interpret keystrokes with the high bit (bit 8) set as +if the user had pressed the ESC key and whatever key remains after having +the high bit removed. For example, if the key pressed has an ASCII value of +0xf4, then this is treated as if the user had pressed ESC then ``x''. This +is because the result of removing the high bit from ``0xf4'' is ``0x74'', +which is the ASCII character ``x''. + +<sect2>mime_forward<label id="mime_forward"> +<p> +Type: boolean<newline> +Default: unset + +When set, the message you are forwarding will be attached as a separate +MIME part instead of included in the main body of the message. This is +useful for forwarding MIME messages so the receiver can properly view the +message as it was delivered to you. + +Also see <ref id="forward_decode" name="forward_decode"> and +<ref id="mime_forward_decode" +name="mime_forward_decode">. + +<sect2>mime_forward_decode<label id="mime_forward_decode"> +<p> +Type: boolean<newline> +Default: unset + +Controls the decoding of complex MIME messages into text/plain when +forwarding a message while <ref id="mime_forward" +name="mime_forward"> is <em/set/. Otherwise <ref +id="forward_decode" name="forward_decode"> is used instead. + +<sect2>move<label id="move"> +<p> +Type: quadoption<newline> +Default: ask-no + +Controls whether you will be asked to confirm moving read messages from your +spool mailbox to your <ref id="mbox" name="$mbox"> mailbox, or as a +result of a <ref id="mbox-hook" name="mbox-hook"> command. + +<sect2>message_format<label id="message_format"> +<p> +Type: string<newline> +Default: &dquot%s&dquot; + +This is the string displayed in the <ref id="attach_menu" +name="attachment"> menu for attachments of type <em>message/rfc822</em>. +For a full listing of defined escape sequences see the section on <ref +id="index_format" name="index_format">. + +<sect2>pager<label id="pager"> +<p> +Type: string<newline> +Default: builtin + +This variable specifies which pager you would like to use to view messages. +<tt/builtin/ means to use the builtin pager, otherwise this variable should +specify the pathname of the external pager you would like to use. + +<sect2>pager_context<label id="pager_context"> +<p> +Type: number<newline> +Default: 0 + +This variable controls the number of lines of context that are given when +displaying the next or previous page in the internal pager. By default, +Mutt will display the line after the last one on the screen at the top of +the next page (0 lines of context). + +<sect2>pager_format<label id="pager_format"> +<p> +Type: format string<newline> +Default: &dquot;-%S- %C/%m: %-20.20n %s&dquot; + +This variable controls the format of the one-line message ``status'' +displayed before each message in either the internal or an external +pager. The valid sequences are listed in the <ref id="index_format" +name="index_format"> section. + +<sect2>pager_index_lines<label id="pager_index_lines"> +<p> +Type: number<newline> +Default: 0 + +Determines the number of lines of a mini-index which is shown +when in the pager. The current message, unless near the top or +bottom of the folder, will be roughly one third of the way down +this mini-index, giving the reader the context of a few messages +before and after the message. This is useful, for example, to +determine how many messages remain to be read in the current thread. +One of the lines is reserved for the status bar from the index, +so a <em/pager_index_lines/ of 6 will only show 5 +lines of the actual index. A value of 0 results in no index being +shown. If the number of messages in the current folder is less than +<em/pager_index_lines/, then the index will only use as +many lines as it needs. + +<sect2>pager_stop<label id="pager_stop"> +<p> +Type: boolean<newline> +Default: unset + +When set, the internal-pager will <bf/not/ move to the next message when +you are at the end of a message and invoke the <em/next-page/ function. + +<sect2>pgp_autoencrypt<label id="pgp_autoencrypt"> +<p> +Type: boolean<newline> +Default: unset + +Setting this variable will cause Mutt to always attempt to PGP/MIME +encrypt outgoing messages. This is probably only useful in connection +to the <em/send-hook/ command. It can be overridden by use of the +<em/pgp-menu/, when encryption is not required or signing is requested +as well. + +<sect2>pgp_autosign<label id="pgp_autosign"> +<p> +Type: boolean<newline> +Default: unset + +Setting this variable will cause Mutt to always attempt to PGP/MIME sign +outgoing messages. This can be overridden by use of the <em/pgp-menu/, +when signing is not required or encryption is requested as well. + +<sect2>pgp_default_version<label id="pgp_default_version"> +<p> +Type: string<newline> +Default: pgp2 (or pgp5, if PGP 2.* is not installed) + +Set this to pgp2 (PGP 2.*), or pgp5 (PGP 5.*) depending on the +version, you are using primary. This variable is directly used, but it +is the default for the variables <ref id="pgp_receive_version" +name="$pgp_receive_version">, <ref +id="pgp_send_version" name="$pgp_send_version">, +and <ref id="pgp_key_version" +name="$pgp_key_version">. + +<sect2>pgp_encryptself<label id="pgp_encryptself"> +<p> +Type: boolean<newline> +Default: set + +If set, the PGP <em/+encrypttoself/ flag is used when encrypting messages. + +<sect2>pgp_key_version<label id="pgp_key_version"> +<p> +Type: string<newline> +Default: ``default'' + +This variable determines, which PGP version used for key ring +operations like extracting keys from messages and extracting keys from +your keyring. If you set this to default, the default defined in <ref +id="pgp_default_version" +name="$pgp_default_version"> is used. Set this to +pgp2 (PGP 2.*), or pgp5 (PGP 5.*) if you want a different PGP version +for key operations. + +<sect2>pgp_long_ids<label id="pgp_long_ids"> +<p> +Type: boolean<newline> +Default: unset + +If set, use 64 bit PGP key IDs. Unset uses the normal 32 bit Key IDs. + +<sect2>pgp_receive_version<label id="pgp_receive_version"> +<p> +Type: string<newline> +Default: ``default'' + +This variable determines, which PGP version used for decrypting +messages and verifying signatures. If you set this to default, the +default defined in <ref id="pgp_default_version" +name="$pgp_default_version"> will be used. Set +this to pgp2 (PGP 2.*), or pgp5 (PGP 5.*) if you want a different PGP +version for receiving operations. + +<sect2>pgp_replyencrypt<label id="pgp_replyencrypt"> +<p> +Type: boolean<newline> +Default: unset + +If set, automatically PGP encrypt replies to messages which are encrypted. + +<sect2>pgp_replysign<label id="pgp_replysign"> +<p> +Type: boolean<newline> +Default: unset + +If set, automatically PGP sign replies to messages which are signed. + +<bf/Note:/ this does not work on messages, that are encrypted <bf/and/ +signed! + +<sect2>pgp_send_version<label id="pgp_send_version"> +<p> +Type: string<newline> +Default: ``default'' + +This variable determines, which PGP version used for composing new +messages like encrypting and signing. If you set this to default, the +default defined in <ref id="pgp_default_version" +name="$pgp_default_version"> will be used. Set +this to pgp2 (PGP 2.*), or pgp5 (PGP 5.*) if you want a different PGP +version for sending operations. + +<sect2>pgp_sign_as<label id="pgp_sign_as"> +<p> +Type: string<newline> +Default: unset + +If you have more than one key pair, this option allows you to specify which +of your private keys to use. It is recommended that you use the keyid form +to specify your key (e.g., ``0xABCDEFGH''). + +<sect2>pgp_sign_micalg<label id="pgp_sign_micalg"> +<p> +Type: string<newline> +Default: pgp-md5 + +This variable contains the default message integrity check algorithm. +Valid values are ``pgp-md5'', ``pgp-sha1'', and ``pgp-rmd160''. If you +select a signing key using the sign as option on the compose menu, +mutt will automagically figure out the correct value to insert here, +but it does not know about the user's default key. + +So if you are using an RSA key for signing, set this variable to +``pgp-md5'', if you use a PGP 5 DSS key for signing, say ``pgp-sha1'' +here. The value of this variable will show up in the micalg parameter +of MIME headers when creating RFC 2015 signatures. + +<sect2>pgp_strict_enc<label id="pgp_strict_enc"> +<p> +Type: boolean<newline> +Default: set + +If set, Mutt will automatically encode PGP/MIME signed +messages as <em/quoted-printable/. Please note that +unsetting this variable may lead to problems with +non-verifyable PGP signatures, so only change this if you +know what you are doing. + +<sect2>pgp_timeout<label id="pgp_timeout"> +<p> +Type: number<newline> +Default: 300 + +The number of seconds after which a cached passphrase will expire if not +used. + +<sect2>pgp_v2<label id="pgp_v2"> +<p> +Type: string<newline> +Default: system dependent + +This variable allows you to override the compile time definition of +where the PGP 2.* binary resides on your system. + +<sect2>pgp_v2_language<label id="pgp_v2_language"> +<p> +Type: string<newline> +Default: en + +Sets the language, which PGP 2.* should use. If you use language.txt +from the mutt doc directory, you can try the languages +&dquot;mutt&dquot; (English) or &dquot;muttde&dquot; (German) to +reduce the noise produced by pgp. + +<sect2>pgp_v2_pubring<label id="pgp_v2_pubring"> +<p> +Type: string<newline> +Default: $PGPPATH/pubring.pgp or ˜/.pgp/pubring.pgp if +$PGPPATH isn't set. + +Points to the PGP 2.* public keyring. + +<sect2>pgp_v2_secring<label id="pgp_v2_secring"> +<p> +Type: string<newline> +Default: $PGPPATH/secring.pgp or ˜/.pgp/secring.pgp if +$PGPPATH isn't set. + +Points to the PGP 2.* secret keyring. + +<sect2>pgp_v5<label id="pgp_v5"> +<p> +Type: string<newline> +Default: system dependent + +This variable allows you to override the compile time definition of +where the PGP 5.* binary resides on your system. + +<sect2>pgp_v5_language<label id="pgp_v5_language"> +<p> +Type: string<newline> +Default: en + +Sets the language, which PGP 5.* should use. If you use language50.txt +from the mutt doc directory, you can try the languages +&dquot;mutt&dquot; (English) to reduce the noise produced by pgp. + +<sect2>pgp_v5_pubring<label id="pgp_v5_pubring"> +<p> +Type: string<newline> +Default: $PGPPATH/pubring.pkr or ˜/.pgp/pubring.pkr if +$PGPPATH isn't set. + +Points to the PGP 5.* public keyring. + +<sect2>pgp_v5_secring<label id="pgp_v5_secring"> +<p> +Type: string<newline> +Default: $PGPPATH/secring.skr or ˜/.pgp/secring.skr if +$PGPPATH isn't set. + +Points to the PGP 5.* secret keyring. + +<sect2>pipe_decode<label id="pipe_decode"> +<p> +Type: boolean<newline> +Default: unset + +Used in connection with the <em/pipe-message/ command. +When unset, Mutt will pipe the messages without any preprocessing. When +set, Mutt will weed headers and will attempt to PGP/MIME decode the +messages first. + +<sect2>pipe_sep<label id="pipe_sep"> +<p> +Type: string<newline> +Default: newline + +The separator to add between messages when piping a list of tagged messages +to an external Unix command. + +<sect2>pipe_split<label id="pipe_split"> +<p> +Type: boolean<newline> +Default: unset + +Used in connection with the <em/pipe-message/ command and the +``tag-prefix'' operator. If this variable is unset, when piping a list +of tagged messages Mutt will concatenate the messages and will pipe them +as a single folder. When set, Mutt will pipe the messages one by one. +In both cases the the messages are piped in the current sorted order, +and the <ref id="pipe_sep" name="$pipe_sep"> separator is +added after each message. + +<sect2>pop_delete<label id="pop_delete"> +<p> +Type: boolean<newline> +Default: unset + +If set, Mutt will delete successfully downloaded messages from the POP +server when using the fetch-mail function. When unset, Mutt will download +messages but also leave them on the POP server. + +<sect2>pop_host<label id="pop_host"> +<p> +Type: string<newline> +Default: none + +The name or address of your POP3 server. + +<sect2>pop_pass<label id="pop_pass"> +<p> +Type: string<newline> +Default: unset + +Specifies the password for you POP account. If unset, Mutt will prompt you +for your password when you invoke the fetch-mail function. <bf/Warning/: +you should only use this option when you are on a fairly secure machine, +because the superuser can read your muttrc even if you are the only one who +can read the file. + +<sect2>pop_port<label id="pop_port"> +<p> +Type: number<newline> +Default: 110 + +This variable specifies which port your POP server is listening on. + +<sect2>pop_user<label id="pop_user"> +<p> +Type: string<newline> +Default: login name on local system + +Your login name on the POP3 server. + +<sect2>post_indent_string<label id="post_indent_string"> +<p> +Type: format string<newline> +Default: none + +Similar to the <ref id="attribution" name="$attribution"> +variable, Mutt will append this string after the inclusion of a message +which is being replied to. + +<sect2>postpone<label id="postpone"> +<p> +Type: quadoption<newline> +Default: ask-yes + +Controls whether or not messages are saved in the <ref id="postponed" +name="$postponed"> mailbox when you elect not to send +immediately. + +<sect2>postponed<label id="postponed"> +<p> +Type: string<newline> +Default: ˜/postponed + +Mutt allows you to indefinitely <ref id="postponing_mail" name="postpone +sending a message"> which you are editing. When you choose to postpone a +message, Mutt saves it in the folder specified by this variable. Also see +the <ref id="postpone" name="$postpone"> variable. + +<sect2>print +<p> +Type: quadoption<newline> +Default: ask-no + +Controls whether or not Mutt asks for confirmation before printing. This +is useful for people (like me) who accidentally hit ``p'' often. + +<sect2>print_command<label id="print_command"> +<p> +Type: string<newline> +Default: lpr + +This specifies the command pipe that should be used to print messages. + +<sect2>prompt_after<label id="prompt_after"> +<p> +Type: boolean<newline> +Default: set + +If you use an <em/external/ <ref id="pager" name="pager">, setting this +variable will cause Mutt to prompt you for a command when the pager +exits rather than returning to the index menu. If unset, Mutt will return +to the index menu when the external pager exits. + +<sect2>query_command<label id="query_command"> +<p> +Type: string<newline> +Default: null + +This specifies the command that mutt will use to make external address +queries. The string should contain a %s, which will be substituted with +the query string the user types. See <ref id="query" name="query"> for +more information. + +<sect2>quit<label id="quit"> +<p> +Type: quadoption<newline> +Default: yes + +This variable controls whether ``quit'' and ``exit'' actually quit from mutt. +If it set to yes, they do quit, if it is set to no, they have no effect, and +if it is set to ask-yes or ask-no, you are prompted for confirmation when you +try to quit. + +<sect2>quote_regexp<label id="quote_regexp"> +<p> +Type: string<newline> +Default: "^([ \t]*[>|#:}])+" + +A regular expression used in the internal-pager to determine quoted +sections of text in the body of a message. + +<bf/Note:/ In order to use the <em/quoted/<bf/x/ patterns in the +internal pager, you need to set this to a regular expression that +matches <em/exactly/ the quote characters at the beginning of quoted +lines. + +<sect2>read_inc<label id="read_inc"> +<p> +Type: number<newline> +Default: 10 + +If set to a value greater than 0, Mutt will display which message it is +currently on when reading a mailbox. The message is printed after +<em/read_inc/ messages have been read (e.g., if set to 25, Mutt will +print a message when it reads message 25, and then again when it gets to +message 50). This variable is meant to indicate progress when reading +large mailboxes which may take some time. + +When set to 0, only a single message will appear before the reading +the mailbox. + +Also see the <ref id="write_inc" name="$write_inc"> variable. + +<sect2>read_only<label id="read_only"> +<p> +Type: boolean<newline> +Default: unset + +If set, all folders are opened in read-only mode. + +<sect2>realname<label id="realname"> +<p> +Type: string<newline> +Default: GCOS field from /etc/passwd + +This variable specifies what "real" or "personal" name should be used when +sending messages. + +<sect2>recall<label id="recall"> +<p> +Type: quadoption<newline> +Default: ask-yes + +Controls whether or not you are prompted to recall postponed messages when +composing a new message. Also see <ref id="postponed" name="postponed"> + +<sect2>record<label id="record"> +<p> +Type: string<newline> +Default: none + +This specifies the file into which your outgoing messages should be +appended. (This is meant as the primary method for saving a copy of +your messages, but another way to do this is using the <ref id="my_hdr" +name="my_hdr"> command to create a <em/Bcc:/ field with your +email address in it.) + +The value of <em/$record/ is overridden by the +<ref id="force_name" name="$force_name"> and +<ref id="save_name" name="$save_name"> variables, and the +<ref id="fcc-hook" name="fcc-hook"> command. + +<sect2>reply_regexp<label id="reply_regexp"> +<p> +Type: string<newline> +Default: "^(re|aw):[ \t]*" + +A regular expression used to recognize reply messages when threading and +replying. The default value corresponds to the English "Re:" and the +German "Aw:". + +<sect2>reply_to<label id="reply_to"> +<p> +Type: quadoption<newline> +Default: ask-yes + +If set, Mutt will ask you if you want to use the address listed in the +Reply-To: header field when replying to a message. If you answer no, it will +use the address in the From: header field instead. This option is useful for +reading a mailing list that sets the Reply-To: header field to the list address +and you want to send a private message to the author of a message. + +<sect2>resolve<label id="resolve"> +<p> +Type: boolean<newline> +Default: set + +When set, the cursor will be automatically advanced to the next (possibly +undeleted) message whenever a command that modifies the current message is +executed. + +<sect2>reverse_alias<label id="reverse_alias"> +<p> +Type: boolean<newline> +Default: unset + +This variable controls whether or not Mutt will display the "personal" name +from your aliases in the index menu if it finds an alias that matches the +message's sender. For example, if you have the following alias: + +<tscreen><verb> +alias juser abd30425@somewhere.net (Joe User) +</verb></tscreen> + +and then you receive mail which contains the following header: + +<tscreen><verb> +From: abd30425@somewhere.net +</verb></tscreen> + +It would be displayed in the index menu as ``Joe User'' instead of +``abd30425@somewhere.net.'' This is useful when the person's e-mail address +is not human friendly (like Compu$erve addresses). + +<sect2>reverse_name<label id="reverse_name"> +<p> +Type: boolean<newline> +Default: unset + +It may sometimes arrive that you receive mail to a certain machine, +move the messages to another machine, and reply to some the messages +from there. If this variable is set, the default <em/From:/ line of +the reply messages is built using the address where you received the +messages you are replying to. If the variable is unset, the <em/From:/ +line will use your address on the current machine. + +<sect2>save_address<label id="save_address"> +<p> +Type: boolean<newline> +Default: unset + +If set, mutt will take the sender's full address when choosing a +default folder for saving a mail. If <ref id="save_name" +name="save_name"> or <ref id="force_name" +name="force_name"> is set too, the selection of the fcc folder +will be changed as well. + +<sect2>save_empty<label id="save_empty"> +<p> +Type: boolean<newline> +Default: set + +When unset, mailboxes which contain no saved messages will be removed when +closed (the exception is <ref id="spoolfile" name="spoolfile"> which is +never removed). If set, mailboxes are never removed. + +<bf/Note:/ This only applies to mbox and MMDF folders, Mutt does not +delete MH and Maildir directories. + +<sect2>save_name<label id="save_name"> +<p> +Type: boolean<newline> +Default: unset + +This variable controls how copies of outgoing messages are saved. When +set, a check is made to see if a mailbox specified by the recipient +address exists (this is done by searching for a mailbox in the <ref +id="folder" name="folder"> directory with the <em/username/ part of +the recipient address). If the mailbox exists, the outgoing message +will be saved to that mailbox, otherwise the message is saved to the +<ref id="record" name="record"> mailbox. + +Also see the <ref id="force_name" name="$force_name"> variable. + +<sect2>sendmail<label id="sendmail"> +<p> +Type: string<newline> +Default: /usr/lib/sendmail -oi -oem + +Specifies the program and arguments used to deliver mail sent by Mutt. +Mutt expects that the specified program will read the message header for +recipients. + +<sect2>sendmail_wait<label id="sendmail_wait"> +<p> +Type: number<newline> +Default: 0 + +Specifies the number of seconds to wait for the <ref id="sendmail" +name="sendmail"> process to finish before giving up and putting delivery in +the background. + +Mutt interprets the value of this variable as follows: +<verb> +>0 number of seconds to wait for sendmail to finish before continuing +0 wait forever for sendmail to finish +<0 always put sendmail in the background without waiting +</verb> + +Note that if you specify a value other than 0, the output of the child +process will be put in a temporary file. If there is some error, you will +be informed as to where to find the output. + +<sect2>shell<label id="shell"> +<p> +Type: string<newline> +Default: retrieved from passwd file + +Command to use when spawning a subshell. + +<sect2>sig_dashes<label id="sig_dashes"> +<p> +Type: boolean<newline> +Default: set + +If set, a line containing ``-- '' will be inserted before your <ref +id="signature" name="signature">. It is <bf/strongly/ recommended that you +not unset this variable unless your ``signature'' contains just your name. +The reason for this is because many software packages use ``-- \n'' to +detect your signature. For example, Mutt has the ability to highlight the +signature in a different color in the builtin pager. + +<sect2>signature<label id="signature"> +<p> +Type: string<newline> +Default: ˜/.signature + +Specifies the filename of your signature, which is appended to all outgoing +messages. If the filename ends with a pipe (``|''), it is assumed that +filename is a shell command and input should be read from its stdout. + +<sect2>simple_search<label id="simple_search"> +<p> +Type: string<newline> +Default: &dquot;˜f %s | ˜s %s&dquot; + +Specifies how Mutt should expand a simple search into a real search pattern. +A simple search is one that does not contain any of the <tt/˜/ +operators. See <ref id="searching" name="searching"> for more information +on search patterns. + +For example, if you simply type <tt/joe/ at a search or limit prompt, Mutt +will automatically expand it to the value specified by this variable. For +the default value it would be: + +<tt/˜f joe | ˜s joe/ + +<sect2>smart_wrap<label id="smart_wrap"> +<p> +Type: boolean<newline> +Default: set + +Controls the display of lines longer then the screen width in the +internal pager. If set, long lines are wrapped at a word boundary. +If unset, lines are simply wrapped at the screen edge. Also see the +<ref id="markers" name="$markers"> variable. + +<sect2>sort<label id="sort"> +<p> +Type: string<newline> +Default: date-sent + +Specifies how to sort messages in the <em/index/ menu. Valid values are + +<itemize> +<item>date-sent +<item>date-received +<item>from +<item>mailbox-order (unsorted) +<item>score +<item>subject +<item>threads +<item>to +</itemize> + +You may optionally use the <tt/reverse-/ prefix to specify reverse sorting +order (example: <tt/set sort=reverse-date-sent/). + +<sect2>sort_alias<label id="sort_alias"> +<p> +Type: string<newline> +Default: alias + +Specifies how the entries in the `alias' menu are sorted. The following are +legal values: +<verb> +alias sort alphabetically by alias name +address sort alphabetically by email address +unsorted leave in order specified in .muttrc +</verb> + +<sect2>sort_aux<label id="sort_aux"> +<p> +Type: string<newline> +Default: date-sent + +When sorting by threads, this variable controls how threads are sorted in +relation to other threads, and how the branches of the thread trees are +sorted. This can be set to any value that <ref id="sort" name="sort"> can, +except <tt/threads/ (in that case, mutt will just use <tt/date-sent/). You +can also specify the <tt/last-/ prefix in addition to the <tt/reverse-/ +prefix, but <tt/last-/ must come after <tt/reverse-/. The <tt/last-/ prefix +causes messages to be sorted against its siblings by which has the last +descendant, using the rest of sort_aux as an ordering. For instance, +<tt/set sort_aux=last-date-received/ would mean that if a new message is +received in a thread, that thread becomes the last one displayed (or the +first, if you have <tt/set sort=reverse-threads/.) + +<sect2>sort_browser<label id="sort_browser"> +<p> +Type: string<newline> + +Specifies how to sort entries in the file browser. By default, the entries +are sorted alphabetically. Valid values: + +<itemize> +<item>date +<item>alpha (alphabetically) +</itemize> + +You may optionally use the <tt/reverse-/ prefix to specify reverse sorting +order (example: <tt/set sort_browser=reverse-date/). + +<sect2>spoolfile<label id="spoolfile"> +<p> +Type: string<newline> +Default: most likely /var/mail/$USER or /usr/spool/mail/$USER + +If your spool mailbox is in a non-default place where Mutt cannot find it, +you can specify its location with this variable. Mutt will automatically +set this variable to the value of the environment variable <tt/$MAIL/ +if it is not set. + +<sect2>sort_re<label id="sort_re"> +<p> +Type: boolean +Default: set + +This variable is only useful when sorting by threads with +<ref id="strict_threads" name="strict_threads"> unset. In that case, +it changes the heuristic mutt uses to thread messages by subject. With +sort_re set, mutt will only attach a message as the child of another message +by subject if the subject of the child message starts with a substring matching +the setting of <ref id="reply_regexp" name="reply_regexp">. With +sort_re unset, mutt will attach the message whether or not this is the case, +as long as the non-<ref id="reply_regexp" name="reply_regexp"> parts of +both messages are identical. + +<sect2>status_chars<label id="status_chars"> +<p> +Type: string<newline> +Default: &dquot;-*%&dquot; + +Controls the characters used by the &dquot;%r&dquot; indicator +in <ref id="status_format" name="status_format">. The first +character is used when the mailbox is unchanged. The second is used when +the mailbox has been changed, and it needs to be resynchronized. The +third is used if the mailbox is in read-only mode, or if the mailbox +will not be written when exiting that mailbox (You can toggle whether +to write changes to a mailbox with the toggle-write operation, bound by +default to &dquot;%&dquot;). + +<sect2>status_format<label id="status_format"> +<p> +Type: string<newline> +Default: &dquot;-%r-Mutt: %f [Msgs:%?M?%M/?%m%?n? New:%n?%?o? Old:%o?%?d? Del:%d?%?F? Flag:%F?%?t? Tag:%t?%?p? Post:%p?%?b? Inc:%b? %?l? %l?]---(%s/%S)-%>-(%P)---&dquot; + +Controls the format of the status line displayed in the +<em/index/ menu. This string is similar to <ref id="index_format" +name="$index_format">, but has its own set of printf()-like +sequences: + +<tscreen><verb> +%b number of mailboxes with new mail * +%d number of deleted messages * +%h local hostname +%f the full pathname of the current mailbox +%F number of flagged messages * +%l size (in bytes) of the current mailbox * +%L size (in bytes) of the messages shown (i.e., which match the current limit) * +%m the number of messages in the mailbox * +%M the number of messages shown (i.e., which match the current limit) * +%n number of new messages in the mailbox * +%o number of old unread messages +%p number of postponed messages * +%P percentage of the way through the index +%r modified/read-only/won't-write indicator, according to $status_chars +%s current sorting mode ($sort) +%S current aux sorting method ($sort_aux) +%t number of tagged messages * +%u number of unread messages * +%v Mutt version string +%V currently active limit pattern, if any * + +%>X right justify the rest of the string and pad with "X" +%|X pad to the end of the line with "X" + +* = can be optionally printed if nonzero +</verb></tscreen> + +Some of the above sequences can be used to optionally print a string if +their value is nonzero. For example, you may only want to see the number of +flagged messages if such messages exist, since zero is not particularly +meaningful. To optionally print a string based upon one of the above +sequences, the following construct is used + +<tscreen><verb> + %?<sequence_char>?<optional_string>? +</verb></tscreen> + +where <em/sequence_char/ is a character from the table above, and +<em/optional_string/ is the string you would like printed if +<em/status_char/ is nonzero. <em/optional_string/ <bf/may/ +contain other sequence as well as normal text, but you may <bf/not/ nest +optional strings. + +Here is an example illustrating how to optionally print the number of new +messages in a mailbox: + +<tscreen><verb> + %?n?%n new messages.? +</verb></tscreen> + +<sect2>status_on_top<label id="status_on_top"> +<p> +Type: boolean<newline> +Default: unset + +Setting this variable causes the <ref id="status_format" name="status +bar"> to be displayed on the first line of the screen rather than near +the bottom. + +<sect2>strict_threads<label id="strict_threads"> +<p> +Type: boolean<newline> +Default: unset + +If set, threading will only make use of the ``In-Reply-To'' and +``References'' fields when <ref id="sort" name="sorting"> by message +threads. By default, messages with the same subject are grouped together in +``pseudo threads.'' This may not always be desirable, such as in a personal +mailbox where you might have several unrelated messages with the subject +``hi'' which will get grouped together. + +<sect2>suspend<label id="suspend"> +<p> +Type: boolean<newline> +Default: set + +When <em/unset/, mutt won't stop when the user presses the +terminal's <em/susp/ key, usually ``control-Z''. This is useful +if you run mutt inside an xterm using a command like <tt/xterm +-e mutt/. + +<sect2>thorough_search<label id="thorough_search"> +<p> +Type: boolean<newline> +Default: unset + +Affects the <em/˜b/ and <em/˜h/ search operations described +in section <ref id="searching" name="Searching"> above. If set, the +headers and attachments of messages to be searched are decoded before +searching. If unset, messages are searched as they appear in the +folder. + +<sect2>tilde<label id="tilde"> +<p> +Type: boolean<newline> +Default: unset + +When set, the internal-pager will pad blank lines to the bottom of the +screen with a tilde (˜). + +<sect2>timeout<label id="timeout"> +<p> +Type: number<newline> +Default: 600 + +This variable controls the <em/number of seconds/ Mutt will wait for a key +to be pressed in the main menu before timing out and checking for new mail. +A value of zero or less will cause Mutt not to ever time out. + +<sect2>tmpdir<label id="tmpdir"> +<p> +Type: string<newline> +Default: /tmp + +This variable allows you to specify where Mutt will place its temporary +files needed for displaying and composing messages. + +<sect2>to_chars<label id="to_chars"> +<p> +Type: string<newline> +Default: &dquot; +TCF&dquot; + +Controls the character used to indicate mail addressed to you. The first +character is the one used when the mail is NOT addressed to your address +(default: space). The second is used when you are the only recipient of the +message (default: +). The third is when your address appears in the TO +header field, but you are not the only recipient of the message (default: +T). The fourth character is used when your address is specified in the CC +header field, but you are not the only recipient. The fifth character is used +to indicate mail that was sent by <em/you/. + +<sect2>use_8bitmime<label id="use_8bitmime"> +<p> +Type: boolean<newline> +Default: unset + +<bf/Warning:/ do not set this variable unless you are using a version of +sendmail which supports the <tt/-B8BITMIME/ flag (such as sendmail 8.8.x) or +you may not be able to send mail. + +When <em/set/, Mutt will invoke <ref id="sendmail" name="$sendmail"> +with the <tt/-B8BITMIME/ flag when sending 8-bit messages to enable ESMTP +negotiation. + +<sect2>use_domain<label id="use_domain"> +<p> +Type: boolean<newline> +Default: set + +When set, Mutt will qualify all local addresses (ones without the @host +portion) with the value of <ref id="hostname" name="$hostname">. If +<em/unset/, no addresses will be qualified. + +<sect2>use_from<label id="use_from"> +<p> +Type: boolean<newline> +Default: set + +When <em/set/, Mutt will generate the `From:' header field when sending +messages. If <em/unset/, no `From:' header field will be generated unless +the user explicitly sets one using the <ref id="my_hdr" +name="my_hdr"> command. + +<sect2>use_mailcap<label id="use_mailcap"> +<p> +Type: quad-option<newline> +Default: ask + +If set to ``yes'', always try to use a mailcap entry to display a MIME +part that Mutt can't understand what to do with. If ``ask'', prompt as +to whether to display as text or to use a mailcap entry. If ``no'', +always view unsupported MIME types as text. + +<bf/Note:/ For compatibility with <bf/metamail/, Mutt will also look at the +environment variable <em/MM_NOASK/. Setting this to <bf/1/ is +equivalent to setting <em/use_mailcap/ to ``yes''. Otherwise, the +value of <em/MM_NOASK/ is interpreted as a comma-separated list of type +names (without white space) for which the corresponding mailcap entries will +be used to display MIME parts without prompting the user for confirmation. + +<sect2>pgp_verify_sig<label id="pgp_verify_sig"> +<p> +Type: quad-option<newline> +Default: yes + +If ``yes'', always attempt to verify PGP/MIME signatures. If ``ask'', +ask whether or not to verify the signature. If ``no'', never attempt to +verify PGP/MIME signatures. + +<sect2>visual<label id="visual"> +<p> +Type: string<newline> +Default: $VISUAL + +Specifies the visual editor to invoke when the <em/˜v/ command is given in +the builtin editor. + +<sect2>wait_key<label id="wait_key"> +<p> +Type: boolean<newline> +Default: set + +Controls whether Mutt will ask you to press a key after +<em/shell-escape/, <em/pipe-message/, <em/pipe-entry/, +<em/print-message/, and <em/print-entry/ commands. + +It is also used when viewing attachments with <ref id="auto_view" +name="autoview">, provided that the corresponding mailcap entry has a +<em/needsterminal/ flag, and the external program is interactive. + +When set, Mutt will always ask for a key. When unset, Mutt will wait for +a key only if the external command returned a non-zero status. + +<sect2>wrap_search<label id="wrap_search"> +<p> +Type: boolean<newline> +Default: set + +Controls whether searches wrap around the end of the mailbox. + +When set, searches will wrap around the first (or last) message. When +unset, searches will not wrap. + +<sect2>write_inc<label id="write_inc"> +<p> +Type: number<newline> +Default: 10 + +When writing a mailbox, a message will be printed every +<em/write_inc/ messages to indicate progress. If set to 0, only +a single message will be displayed before writing a mailbox. + +Also see the <ref id="read_inc" name="$read_inc"> variable. + +<sect1>Functions<label id="functions"> +<p> +The following is the list of available functions listed by the mapping +in which they are available. The default key setting is given, and an +explanation of what the function does. The key bindings of these +functions can be changed with the <ref name="bind" id="bind"> +command. + +<sect2>generic +<p> + +The <em/generic/ menu is not a real menu, but specifies common functions +(such as movement) available in all menus except for <em/pager/ and +<em/editor/. Changing settings for this menu will affect the default +bindings for all menus (except as noted). + +<verb> +bottom-page L move to the bottom of the page +current-bottom not bound move current entry to bottom of page +current-middle not bound move current entry to middle of page +current-top not bound move current entry to top of page +enter-command : enter a muttrc command +exit q exit this menu +first-entry = move to the first entry +half-down ] scroll down 1/2 page +half-up [ scroll up 1/2 page +help ? this screen +jump number jump to an index number +last-entry * move to the last entry +middle-page M move to the middle of the page +next-entry j move to the next entry +next-line > scroll down one line +next-page z move to the next page +previous-entry k move to the previous entry +previous-line < scroll up one line +previous-page Z move to the previous page +refresh ^L clear and redraw the screen +search / search for a regular expression +search-next n search for next match +search-opposite not bound search for next match in opposite direction +search-reverse ESC / search backwards for a regular expression +select-entry RET select the current entry +shell-escape ! run a program in a subshell +tag-entry t toggle the tag on the current entry +tag-prefix ; apply next command to tagged entries +top-page H move to the top of the page +</verb> +<sect2>index +<p> +<verb> +bounce-message b remail a message to another user +change-folder c open a different folder +change-folder-readonly ESC c open a different folder in read only mode +clear-flag W clear a status flag from a message +copy-message C copy a message to a file/mailbox +create-alias a create an alias from a message sender +decode-copy ESC C decode a message and copy it to a file/mailbox +decode-save ESC s decode a message and save it to a file/mailbox +delete-message d delete the current entry +delete-pattern D delete messages matching a pattern +delete-subthread ESC d delete all messages in subthread +delete-thread ^D delete all messages in thread +display-address @ display full address of sender +display-headers h display message with full headers +display-message RET display a message +exit x exit without saving changes +extract-keys ^K extract PGP public keys +fetch-mail G retrieve mail from POP server +flag-message F toggle a message's 'important' flag +forget-passphrase ^F wipe PGP passphrase from memory +forward-message f forward a message with comments +group-reply g reply to all recipients +limit l show only messages matching a pattern +list-reply L reply to specified mailing list +mail m compose a new mail message +mail-key ESC k mail a PGP public key +next-new TAB jump to the next new message +next-subthread ESC n jump to the next subthread +next-thread ^N jump to the next thread +next-undeleted j move to the next undeleted message +next-unread not bound jump to the next unread message +pipe-message | pipe message/attachment to a shell command +previous-new ESC TAB jump to the previous new message +previous-page Z move to the previous page +previous-subthread ESC p jump to previous subthread +previous-thread ^P jump to previous thread +previous-undeleted k move to the last undelete message +previous-unread not bound jump to the previous unread message +print-message p print the current entry +query Q query external program for addresses +quit q save changes to mailbox and quit +read-subthread ESC r mark the current subthread as read +read-thread ^R mark the current thread as read +recall-message R recall a postponed message +reply r reply to a message +save-message s save message/attachment to a file +set-flag w set a status flag on a message +show-version V show the Mutt version number and date +show-limit ESC l show currently active limit pattern, if any +sort-mailbox o sort messages +sort-reverse O sort messages in reverse order +sync-mailbox $ save changes to mailbox +tag-pattern T tag messages matching a pattern +tag-thread ESC t tag/untag all messages in the current thread +toggle-new N toggle a message's 'new' flag +toggle-write % toggle whether the mailbox will be rewritten +undelete-message u undelete the current entry +undelete-pattern U undelete messages matching a pattern +undelete-subthread ESC u undelete all messages in subthread +undelete-thread ^U undelete all messages in thread +untag-pattern ^T untag messages matching a pattern +view-attachments v show MIME attachments +</verb> +<sect2>pager +<p> +<verb> +bottom $ jump to the bottom of the message +bounce-message b remail a message to another user +change-folder c open a different folder +change-folder-readonly ESC c open a different folder in read only mode +copy-message C copy a message to a file/mailbox +create-alias a create an alias from a message sender +decode-copy ESC C decode a message and copy it to a file/mailbox +decode-save ESC s decode a message and save it to a file/mailbox +delete-message d delete the current entry +delete-subthread ESC d delete all messages in subthread +delete-thread ^D delete all messages in thread +display-address @ display full address of sender +display-headers h display message with full headers +enter-command : enter a muttrc command +exit i return to the main-menu +extract-keys ^K extract PGP public keys +flag-message F toggle a message's 'important' flag +forget-passphrase ^F wipe PGP passphrase from memory +forward-message f forward a message with comments +group-reply g reply to all recipients +half-up not bound move up one-half page +half-down not bound move down one-half page +help ? this screen +list-reply L reply to specified mailing list +mail m compose a new mail message +mail-key ESC k mail a PGP public key +mark-as-new N toggle a message's 'new' flag +next-line RET scroll down one line +next-message J move to the next entry +next-new TAB jump to the next new message +next-page move to the next page +next-subthread ESC n jump to the next subthread +next-thread ^N jump to the next thread +next-undeleted j move to the next undeleted message +next-unread not bound jump to the next unread message +pipe-message | pipe message/attachment to a shell command +previous-line BackSpace scroll up one line +previous-message K move to the previous entry +previous-new not bound jump to the previous new message +previous-page - move to the previous page +previous-subthread ESC p jump to previous subthread +previous-thread ^P jump to previous thread +previous-undeleted k move to the last undelete message +previous-unread not bound jump to the previous unread message +print-message p print the current entry +quit Q save changes to mailbox and quit +read-subthread ESC r mark the current subthread as read +read-thread ^R mark the current thread as read +recall-message R recall a postponed message +redraw-screen ^L clear and redraw the screen +reply r reply to a message +save-message s save message/attachment to a file +search / search for a regular expression +search-next n search for next match +search-opposite not bound search for next match in opposite direction +search-reverse ESC / search backwards for a regular expression +search-toggle \ toggle search pattern coloring +shell-escape ! invoke a command in a subshell +show-version V show the Mutt version number and date +skip-quoted S skip beyond quoted text +tag-message t tag a message +toggle-quoted T toggle display of quoted text +top ^ jump to the top of the message +undelete-message u undelete the current entry +undelete-subthread ESC u undelete all messages in subthread +undelete-thread ^U undelete all messages in thread +view-attachments v show MIME attachments +</verb> +<sect2>alias +<p> +<verb> +search / search for a regular expression +search-next n search for next match +search-reverse ESC / search backwards for a regular expression +</verb> +<sect2>query +<p> +<verb> +create-alias a create an alias from a message sender +mail m compose a new mail message +query Q query external program for addresses +query-append A append new query results to current results +search / search for a regular expression +search-next n search for next match +search-opposite not bound search for next match in opposite direction +search-reverse ESC / search backwards for a regular expression +</verb> +<sect2>attach +<p> +<verb> +bounce-message b remail a message to another user +decode-copy ESC C decode a message and copy it to a file/mailbox +decode-save ESC s decode a message and save it to a file/mailbox +delete-entry d delete the current entry +display-headers h display message with full headers +extract-keys ^K extract PGP public keys +forward-message f forward a message with comments +group-reply g reply to all recipients +list-reply L reply to specified mailing list +pipe-entry | pipe message/attachment to a shell command +print-entry p print the current entry +reply r reply to a message +save-entry s save message/attachment to a file +undelete-entry u undelete the current entry +view-attach RET view attachment using mailcap entry if necessary +view-mailcap m force viewing of attachment using mailcap +view-text T view attachment as text +</verb> +<sect2>compose +<p> +<verb> +attach-file a attach a file(s) to this message +attach-key ESC k attach a PGP public key +copy-file C save message/attachment to a file +detach-file D delete the current entry +display-headers h display message with full headers +edit-bcc b edit the BCC list +edit-cc c edit the CC list +edit-description d edit attachment description +edit-encoding ^E edit attachment trasfer-encoding +edit-fcc f enter a file to save a copy of this message in +edit-from ESC f edit the from: field +edit-file ^X e edit the file to be attached +edit-headers E edit the message with headers +edit-message e edit the message +edit-mime m edit attachment using mailcap entry +edit-reply-to r edit the Reply-To field +edit-subject s edit the subject of this message +edit-to t edit the TO list +edit-type ^T edit attachment type +filter-entry F filter attachment through a shell command +forget-passphrase ^F wipe PGP passphrase from memory +ispell i run ispell on the message +new-mime n compose new attachment using mailcap entry +pgp-menu p show PGP options +pipe-entry | pipe message/attachment to a shell command +postpone-message P save this message to send later +print-entry l print the current entry +rename-file R rename/move an attached file +send-message y send the message +toggle-unlink u toggle whether to delete file after sending it +view-attach RET view attachment using mailcap entry if necessary +</verb> +<sect2>postponed +<p> +<verb> +delete-entry d delete the current entry +undelete-entry u undelete the current entry +</verb> +<sect2>browser +<p> +<verb> +change-dir c change directories +check-new TAB check mailboxes for new mail +enter-mask m enter a file mask +search / search for a regular expression +search-next n search for next match +search-reverse ESC / search backwards for a regular expression +select-new N select a new file in this directory +sort o sort messages +sort-reverse O sort messages in reverse order +</verb> +<sect2>pgp +<p> +<verb> +view-name % view the key's user id +verify-key c verify a PGP public key +</verb> +<sect2>editor +<p> +<verb> +backspace BackSpace delete the char in front of the cursor +backward-char ^B move the cursor one character to the left +bol ^A jump to the beginning of the line +buffy-cycle Space cycle among incoming mailboxes +complete TAB complete filename or alias +complete-query ^T complete address with query +delete-char ^D delete the char under the cursor +eol ^E jump to the end of the line +forward-char ^F move the cursor one character to the right +history-down not bound scroll up through the history list +history-up not bound scroll up through the history list +kill-eol ^K delete chars from cursor to end of line +kill-line ^U delete all chars on the line +kill-word ^W delete the word in front of the cursor +quote-char ^V quote the next typed key +</verb> + +<sect>Miscellany +<p> + +<sect1>Acknowledgements +<p> +Kari Hurrta +<htmlurl url="mailto:kari.hurtta@fmi.fi" name="<kari.hurtta@fmi.fi>"> +co-developed the original MIME parsing code back in the ELM-ME days. + +The following people have been very helpful to the development of Mutt: + +Francois Berjon <htmlurl url="mailto:Francois.Berjon@aar.alcatel-alsthom.fr" +name="<Francois.Berjon@aar.alcatel-alsthom.fr>">,<newline> +Aric Blumer <htmlurl url="mailto:aric@fore.com" name="<aric@fore.com>">,<newline> +John Capo <htmlurl url="mailto:jc@irbs.com" name="<jc@irbs.com>">,<newline> +Liviu Daia <htmlurl url="mailto:daia@stoilow.imar.ro" name="<daia@stoilow.imar.ro>">,<newline> +David DeSimone <htmlurl url="mailto:fox@convex.hp.com" name="<fox@convex.hp.com>">,<newline> +Nickolay N. Dudorov <htmlurl url="mailto:nnd@wint.itfs.nsk.su" name="<nnd@wint.itfs.nsk.su>">,<newline> +Michael Finken <htmlurl url="mailto:finken@conware.de" name="<finken@conware.de>">,<newline> +Sven Guckes <htmlurl url="mailto:guckes@math.fu-berlin.de" name="<guckes@math.fu-berlin.de>">,<newline> +Mark Holloman <htmlurl url="mailto:holloman@nando.net" name="<holloman@nando.net>">,<newline> +Andreas Holzmann <htmlurl url="mailto:holzmann@fmi.uni-passau.de" name="<holzmann@fmi.uni-passau.de>">,<newline> +David Jeske <htmlurl url="mailto:jeske@igcom.net" name="<jeske@igcom.net>">,<newline> +Christophe Kalt <htmlurl url="mailto:kalt@hugo.int-evry.fr" name="<kalt@hugo.int-evry.fr>">,<newline> +Felix von Leitner (a.k.a ``Fefe'') <htmlurl url="mailto:leitner@math.fu-berlin.de" name="<leitner@math.fu-berlin.de>">,<newline> +Brandon Long <htmlurl url="mailto:blong@fiction.net" name="<blong@fiction.net>">,<newline> +Lars Marowsky-Bree <htmlurl url="mailto:lmb@pointer.in-minden.de" name="<lmb@pointer.in-minden.de>">,<newline> +Thomas ``Mike'' Michlmayr <htmlurl url="mailto:mike@cosy.sbg.ac.at" name="<mike@cosy.sbg.ac.at>">,<newline> +David O'Brien <htmlurl url="mailto:obrien@Nuxi.cs.ucdavis.edu" name="<obrien@Nuxi.cs.ucdavis.edu>">,<newline> +Clint Olsen <htmlurl url="mailto:olsenc@ichips.intel.com" name="<olsenc@ichips.intel.com>">,<newline> +Park Myeong Seok <htmlurl url="mailto:pms@romance.kaist.ac.kr" name="<pms@romance.kaist.ac.kr>">,<newline> +Thomas Parmelan <htmlurl url="mailto:tom@ankh.fr.eu.org" name="<tom@ankh.fr.eu.org>">,<newline> +Ollivier Robert <htmlurl url="mailto:roberto@keltia.freenix.fr" name="<roberto@keltia.freenix.fr>">,<newline> +Thomas Roessler <htmlurl url="mailto:roessler@guug.de" name="<roessler@guug.de>">,<newline> +Allain Thivillon <htmlurl url="mailto:Allain.Thivillon@alma.fr" name="<Allain.Thivillon@alma.fr>">,<newline> +Ken Weinert <htmlurl url="mailto:kenw@ihs.com" name="<kenw@ihs.com>"> + +<sect1>About this document +<p> +This document was written in SGML, and then rendered using the +<htmlurl url="http://pobox.com/~cg/sgmltools/" name="sgml-tools"> package. + +</article> diff --git a/doc/manual.txt b/doc/manual.txt new file mode 100644 index 00000000..17cdd295 --- /dev/null +++ b/doc/manual.txt @@ -0,0 +1,4115 @@ + The Mutt E-Mail Client + by Michael Elkins <me@cs.hmc.edu> + v0.92.2, 23 April 1998 + + ``All mail clients suck. This one just sucks less.'' -me, circa 1995 + + 11.. IInnttrroodduuccttiioonn + + MMuutttt is a small but very powerful text-based MIME mail client. Mutt + is highly configurable, and is well suited to the mail power user with + advanced features like key bindings, keyboard macros, mail threading, + regular expression searches and a powerful pattern matching language + for selecting groups of messages. + + 11..11.. MMuutttt HHoommee PPaaggee + + http://www.cs.hmc.edu/~me/mutt/index.html + + 11..22.. MMaaiilliinngg LLiissttss + + To subscribe to one of the following mailing lists, send a message + with the word _s_u_b_s_c_r_i_b_e in the subject to list-name_- + _r_e_q_u_e_s_t@cs.hmc.edu. + + +o mutt-announce@cs.hmc.edu -- low traffic list for announcements + + +o mutt-users@cs.hmc.edu -- help, bug reports and feature requests + + +o mutt-dev@cs.hmc.edu -- development mailing list + + NNoottee:: all messages posted to _m_u_t_t_-_a_n_n_o_u_n_c_e are automatically forwarded + to _m_u_t_t_-_u_s_e_r_s, so you do not need to be subscribed to both lists. + + 11..33.. SSooffttwwaarree DDiissttrriibbuuttiioonn SSiitteess + + +o ftp://ftp.cs.hmc.edu/pub/me/mutt/ + + 11..44.. IIRRCC + + Visit channel _#_m_u_t_t on DALnet (www.dal.net) to chat with other people + interested in Mutt. + + 11..55.. UUSSEENNEETT + + See the newsgroup comp.mail.mutt. + + 11..66.. CCooppyyrriigghhtt + + Mutt is Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + + 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + 22.. GGeettttiinngg SSttaarrtteedd + + This section is intended as a brief overview of how to use Mutt. + There are many other features which are described elsewhere in the + manual. There is even more information available in the Mutt FAQ and + various web pages. See the Mutt Page for more details. + + The keybindings described in this section are the defaults as + distributed. Your local system administrator may have altered the + defaults for your site. You can always type ``?'' in any menu to + display the current bindings. + + The first thing you need to do is invoke mutt, simply by typing mutt + at the command line. There are various command-line options, see + either the mutt man page or the ``reference''. + + 22..11.. MMoovviinngg AArroouunndd iinn MMeennuuss + + Information is presented in menus, very similar to ELM. Here is a + table showing the common keys used to navigate menus in Mutt. + + j or Down next-entry move to the next entry + k or Up previous-entry move to the previous entry + z or PageDn page-down go to the next page + Z or PageUp page-up go to the previous page + = or Home first-entry jump to the first entry + * or End last-entry jump to the last entry + q quit exit the current menu + ? help list all keybindings for the current menu + + 22..22.. EEddiittiinngg IInnppuutt FFiieellddss + + Mutt has a builtin line editor which is used as the primary way to + input textual data such as email addresses or filenames. The keys + used to move around while editing are very similar to those of Emacs. + + ^A or <Home> bol move to the start of the line + ^B or <Left> backward-char move back one char + ^D or <Delete> delete-char delete the char under the cursor + ^E or <End> eol move to the end of the line + ^F or <Right> forward-char move forward one char + ^K kill-eol delete to the end of the line + ^U kill-line delete entire line + ^W kill-word kill the word in front of the cursor + <Up> history-up recall previous string from history + <Down> history-down recall next string from history + <BackSpace> backspace kill the char in front of the cursor + ^G n/a abort + <Tab> n/a complete filename (only when prompting for a file) + <Return> n/a finish editing + + You can remap the _e_d_i_t_o_r functions using the ``bind'' command. For + example, to make the _D_e_l_e_t_e key delete the character in front of the + cursor rather than under, you could use + + bind editor delete backspace + + 22..33.. RReeaaddiinngg MMaaiill -- TThhee IInnddeexx aanndd PPaaggeerr + + Similar to many other mail clients, there are two modes in which mail + is read in Mutt. The first is the index of messages in the mailbox, + which is called the ``index'' in Mutt. The second mode is the display + of the message contents. This is called the ``pager.'' + + The next few sections describe the functions provided in each of these + modes. + + 22..33..11.. TThhee MMeessssaaggee IInnddeexx + + c change to a different mailbox + ESC c change to a folder in read-only mode + C copy the current message to another mailbox + ESC C decode a message and copy it to a folder + ESC s decode a message and save it to a folder + D delete messages matching a pattern + d delete the current message + F mark as important + l show messages matching a pattern + N mark message as new + o change the current sort method + O reverse sort the mailbox + q save changes and exit + s save-message + t toggle the tag on a message + ESC t toggle tag on entire message thread + u undelete-message + v view-attachments + x abort changes and exit + <Return> display-message + <Tab> jump to the next new message + @ show the author's full e-mail address + $ save changes to mailbox + / search + ESC / search-reverse + ^L clear and redraw the screen + ^T tag messages matching a pattern + ^U undelete messages matching a pattern + + 22..33..11..11.. SSttaattuuss FFllaaggss + + In addition to who sent the message and the subject, a short summary + of the disposition of each message is printed beside the message + number. Zero or more of the following ``flags'' may appear, which + mean: + + D message is deleted + K contains a PGP public key + M requires mailcap to view + N message is new + O message is old + P message is PGP encrypted + r message has been replied to + S message is PGP signed + ! message is flagged + * message is tagged + + Some of the status flags can be turned on or off using + + +o sseett--ffllaagg (default: w) + + +o cclleeaarr--ffllaagg (default: W) + + Furthermore, the following flags reflect who the message is addressed + to. They can be customized with the ``$to_chars'' variable. + + + message is to you and you only + T message is to you, but also to or cc'ed to others + C message is cc'ed to you + F message is from you + + 22..33..22.. TThhee PPaaggeerr + + By default, Mutt uses its builtin pager to display the body of + messages. The pager is very similar to the Unix program _l_e_s_s though + not nearly as featureful. + + <Return> go down one line + <Space> display the next page (or next message if at the end of a message) + - go back to the previous page + n display the next message + ? show keybindings + / search for a regular expression (pattern) + \ toggle search pattern coloring + + In addition, many of the functions from the _i_n_d_e_x are available in the + pager, such as _d_e_l_e_t_e_-_m_e_s_s_a_g_e or _c_o_p_y_-_m_e_s_s_a_g_e (this is one advantage + over using an external pager to view messages). + + Also, the internal pager supports a couple other advanced features. + For one, it will accept and translate the ``standard'' nroff sequences + for bold and underline. These sequences are a series of either the + letter, backspace (^H), the letter again for bold or the letter, + backspace, ``_'' for denoting underline. Mutt will attempt to display + these in bold and underline respectively if your terminal supports + them. If not, you can use the bold and underline ``color'' objects to + specify a color or mono attribute for them. + + Additionally, the internal pager supports the ANSI escape sequences + for character attributes. Mutt translates them into the correct color + and character settings. The sequences Mutt supports are: + + ESC [ Ps;Ps;Ps;...;Ps m + where Ps = + 0 All Attributes Off + 1 Bold on + 4 Underline on + 5 Blink on + 7 Reverse video on + 3x Foreground color is x + 4x Background color is x + + Colors are + 0 black + 1 red + 2 green + 3 yellow + 4 blue + 5 magenta + 6 cyan + 7 white + + Mutt uses these attributes for handling text/enriched messages, and + they can also be used by an external ``autoview'' script for + highlighting purposes. NNoottee:: If you change the colors for your + display, for example by changing the color associated with color2 for + your xterm, then that color will be used instead of green. + + 22..33..33.. TThhrreeaaddeedd MMooddee + + When the mailbox is ``sorted'' by _t_h_r_e_a_d_s, there are a few additional + functions available in the _i_n_d_e_x and _p_a_g_e_r modes. + + ^D delete-thread delete all messages in the current thread + ^U undelete-thread undelete all messages in the current thread + ^N next-thread jump to the start of the next thread + ^P previous-thread jump to the start of the previous thread + ^R read-thread mark the current thread as read + ESC d delete-subthread delete all messages in the current subthread + ESC u undelete-subthread undelete all messages in the current subthread + ESC n next-subthread jump to the start of the next subthread + ESC p previous-subthread jump to the start of the previous subthread + ESC r read-subthread mark the current subthread as read + ESC t tag-thread toggle the tag on the current thread + + See also: ``$strict_threads''. + + 22..33..44.. MMiisscceellllaanneeoouuss FFuunnccttiioonnss + + ccrreeaattee--aalliiaass (default: a) + + Creates a new alias based upon the current message (or prompts for a + new one). Once editing is complete, an ``alias'' command is added to + the file specified by the ``$alias_file'' variable for future use. + NNoottee:: Specifying an ``$alias_file'' does not add the aliases specified + there-in, you must also ``source'' the file. + + ddiissppllaayy--hheeaaddeerrss (default: h) + + Toggles the weeding of message header fields specified by ``ignore'' + commands. + + eenntteerr--ccoommmmaanndd (default: ``:'') + + This command is used to execute any command you would normally put in + a configuration file. A common use is to check the settings of + variables, or in conjunction with ``macros'' to change settings on the + fly. + + eexxttrraacctt--kkeeyyss (default: ESC k) + + This command extracts PGP public keys from the current or tagged + message(s) and adds them to your ``$pgp_v2_pubring'' or + ``$pgp_v5_pubring'' depending on ``$pgp_key_version''. + + ffoorrggeett--ppaasssspphhrraassee (default: ^F) + + This command wipes the PGP passphrase from memory. It is useful, if + you misspelled the passphrase. + + lliisstt--rreeppllyy (default: L) + + Reply to the current or tagged message(s) by extracting any addresses + which match the addresses given by the ``lists'' command. Using this + when replying to messages posted to mailing lists help avoid duplicate + copies being sent to the author of the message you are replying to. + + ppiippee--mmeessssaaggee (default: |) + + Asks for an external Unix command and pipes the current or tagged + message(s) to it. The variables ``$pipe_decode'', ``$pipe_split'', + ``$pipe_sep'' and ``$wait_key'' control the exact behaviour of this + function. + + sshheellll--eessccaappee (default: !) + + Asks for an external Unix command and executes it. The ``$wait_key'' + can be used to control whether Mutt will wait for a key to be pressed + when the command returns (presumably to let the user read the output + of the command), based on the return status of the named command. + + ttooggggllee--qquuootteedd (default: T) + + The _p_a_g_e_r uses the ``$quote_regexp'' variable to detect quoted text + when displaying the body of the message. This function toggles the + display of the quoted material in the message. It is particularly + useful when are interested in just the response and there is a large + amount of quoted text in the way. + + sskkiipp--qquuootteedd (default: S) + + This function will go to the next line of non-quoted text which come + after a line of quoted text in the internal pager. + + 22..44.. SSeennddiinngg MMaaiill + + The following bindings are available in the _i_n_d_e_x for sending + messages. + + m compose compose a new message + r reply reply to sender + g group-reply reply to all recipients + L list-reply reply to mailing list address + f forward forward message + b bounce bounce (remail) message + ESC k mail-key mail a PGP public key to someone + + Bouncing a message sends the message as is to the recipient you + specify. Forwarding a message allows you to add comments or modify + the message you are forwarding. Bouncing a message uses the + ``sendmail'' command to send a copy of a message to recipients as if + they were original recipients of the message. See also + ``$mime_forward''. + + Mutt will then enter the _c_o_m_p_o_s_e menu and prompt you for the + recipients to place on the ``To:'' header field. Next, it will ask + you for the ``Subject:'' field for the message, providing a default if + you are replying to or forwarding a message. See also ``$askcc'', + ``$askbcc'', ``$autoedit'', and ``$fast_reply'' for changing how Mutt + asks these questions. + + Mutt will then automatically start your ``$editor'' on the message + body. If the ``$edit_headers'' variable is set, the headers will be + at the top of the message in your editor. Any messages you are + replying to will be added in sort order to the message, with + appropriate ``$attribution'', ``$indent_string'' and + ``$post_indent_string''. When forwarding a message, if the + ``$mime_forward'' variable is unset, a copy of the forwarded message + will be included. If you have specified a ``$signature'', it will be + appended to the message. + + Once you have finished editing the body of your mail message, you are + returned to the _c_o_m_p_o_s_e menu. The following options are available: + + a attach-file attach a file + ESC k attach-key attach a PGP public key + d edit-description edit description on attachment + D detach-file detach a file + T edit-to edit the To field + c edit-cc edit the Cc field + b edit-bcc edit the Bcc field + y send-message send the message + s edit-subject edit the Subject + f edit-fcc specify an ``Fcc'' mailbox + p pgp-menu select PGP options (``i'' version only) + P postpone-message postpone this message until later + q quit quit (abort) sending the message + i ispell check spelling (if available on your system) + ^F forget-passphrase whipe PGP passphrase from memory + + 22..44..11.. EEddiittiinngg tthhee mmeessssaaggee hheeaaddeerr + + When editing the header of your outgoing message, there are a couple + of special features available. + + If you specify + Fcc: _f_i_l_e_n_a_m_e + Mutt will pick up _f_i_l_e_n_a_m_e just as if you had used the _e_d_i_t_-_f_c_c + function in the _c_o_m_p_o_s_e menu. + + You can also attach files to your message by specifying + Attach: _f_i_l_e_n_a_m_e [ _d_e_s_c_r_i_p_t_i_o_n ] + where _f_i_l_e_n_a_m_e is the file to attach and _d_e_s_c_r_i_p_t_i_o_n is an optional + string to use as the description of the attached file. + + When replying to messages, if you remove the _I_n_-_R_e_p_l_y_-_T_o_: field from + the header field, Mutt will not generate a _R_e_f_e_r_e_n_c_e_s_: field, which + allows you to create a new message thread. + + If you want to use PGP, you can specify + + Pgp: [ E | S | S<id> ] + + ``E'' encrypts, ``S'' signs and ``S<id>'' signs with the given key, + setting ``$pgp_sign_as'' permanently. + + Also see ``edit_headers''. + + 22..55.. PPoossttppoonniinngg MMaaiill + + At times it is desirable to delay sending a message that you have + already begun to compose. When the _p_o_s_t_p_o_n_e_-_m_e_s_s_a_g_e function is used + in the _c_o_m_p_o_s_e menu, the body of your message and attachments are + stored in the mailbox specified by the ``$postponed'' variable. This + means that you can recall the message even if you exit Mutt and then + restart it at a later time. + + Once a message is postponed, there are several ways to resume it. + From the command line you can use the ``-p'' option, or if you _c_o_m_p_o_s_e + a new message from the _i_n_d_e_x or _p_a_g_e_r you will be prompted if + postponed messages exist. If multiple messages are currently + postponed, the _p_o_s_t_p_o_n_e_d menu will pop up and you can select which + message you would like to resume. + + NNoottee:: If you postpone a reply to a message, the reply setting of the + message is only updated when you actually finish the message and send + it. Also, you must be in the same folder with the message you replied + to for the status of the message to be updated. + + See also the ``$postpone'' quad-option. + + 33.. CCoonnffiigguurraattiioonn + + While the default configuration (or ``preferences'') make Mutt usable + right out of the box, it is often desirable to tailor Mutt to suit + your own tastes. When Mutt is first invoked, it will attempt to read + the ``system'' configuration file (defaults set by your local system + administrator), unless the ``-n'' ``command line'' option is + specified. This file is typically /usr/local/share/Muttrc or + /usr/local/lib/Muttrc. Next, it looks for a file in your home + directory named .muttrc. In this file is where you place ``commands'' + to configure Mutt. + In addition, mutt supports version specific configuration files that + are parsed instead of the default files as explained above. For + instance, if your system has a Muttrc-0.88 file in the system + configuration directory, and you are running version 0.88 of mutt, + this file will be sourced instead of the Muttrc file. The same is + true of the user configuration file, if you have a file .muttrc-0.88.6 + in your home directory, when you run mutt version 0.88.6, it will + source this file instead of the default .muttrc file. The version + number is the same which is visible using the ``-v'' ``command line'' + switch or using the show-version key (default: V) from the index menu. + + 33..11.. SSyynnttaaxx ooff IInniittiiaalliizzaattiioonn FFiilleess + + An initialization file consists of a series of ``commands'', each on + its own line. The hash mark, or pound sign (``#''), is used as a + ``comment'' character. You can use it to annotate your initialization + file. All text after the comment character to the end of the line is + ignored. For example, + + my_hdr X-Disclaimer: Why are you listening to me? # This is a comment + + Single quotes (') and double quotes (") can be used to quote strings + which contain spaces or other special characters. The difference + between the two types of quotes is similar to that of many popular + shell programs, namely that a single quote is used to specify a + literal string (one that is not interpreted for shell variables or + quoting with a backslash [see next paragraph]), while double quotes + indicate a string for which should be evaluated. For example, + backtics are evaluated inside of double quotes, but nnoott for single + quotes. + + \ quotes the next character, just as in shells such as bash and zsh. + For example, if want to put quotes ``"'' inside of a string, you can + use ``\'' to force the next character to be a literal instead of + interpreted character. + + set realname="Michael \"MuttDude\" Elkins" + + ``\\'' means to insert a literal ``\'' into the line. ``\n'' and + ``\r'' have their usual C meanings of linefeed and carriage-return, + respectively. + + A \ at the end of a line can be used to split commands over multiple + lines, provided that the split points don't appear in the middle of + command names. + + It is also possible to substitute the output of a Unix command in an + initialization file. This is accomplished by enclosing the command in + backquotes (``). For example, + + my_hdr X-Operating-System: `uname -a` + + The output of the Unix command ``uname -a'' will be substituted before + the line is parsed. Note that since initialization files are line + oriented, only the first line of output from the Unix command will be + substituted. + + For a complete list of the commands understood by mutt, see the + ``command reference''. + + 33..22.. DDeeffiinniinngg//UUssiinngg aalliiaasseess + + Usage: alias _k_e_y _a_d_d_r_e_s_s [ , _a_d_d_r_e_s_s, ... ] + + It's usually very cumbersome to remember or type out the address of + someone you are communicating with. Mutt allows you to create + ``aliases'' which map a short string to a full address. + + NNoottee:: if you want to create an alias for a group (by specifying more + than one address), you mmuusstt separate the addresses with a comma + (``,''). + + To remove an alias or aliases: + + unalias _a_d_d_r [ _a_d_d_r _._._. ] + + alias muttdude me@cs.hmc.edu (Michael Elkins) + alias theguys manny, moe, jack + + Unlike other mailers, Mutt doesn't require aliases to be defined in a + special file. The alias command can appear anywhere in a + configuration file, as long as this file is ``sourced''. + Consequently, you can have multiple alias files, or you can have all + aliases defined in your muttrc. + + On the other hand, the ``create-alias'' function can use only one + file, the one pointed to by the ``$alias_file'' variable (which is + ~/.muttrc by default). This file is not special either, in the sense + that Mutt will happily append aliases to any file, but in order for + the new aliases to take effect you need to explicitly ``source'' this + file too. + + For example: + + source /usr/local/share/Mutt.aliases + source ~/.mail_aliases + set alias_file=~/.mail_aliases + + To use aliases, you merely use the alias at any place in mutt where + mutt prompts for addresses, such as the _T_o_: or _C_c_: prompt. You can + also enter aliases in your editor at the appropriate headers if you + have the ``$edit_headers'' variable set. + + In addition, at the various address prompts, you can use the tab + character to expand a partial alias to the full alias. If there are + multiple matches, mutt will bring up a menu with the matching aliases. + In order to be presented with the full list of aliases, you must hit + tab with out a partial alias, such as at the beginning of the prompt + or after a comma denoting multiple addresses. + + In the alias menu, you can select as many aliases as you want with the + _s_e_l_e_c_t_-_e_n_t_r_y key (default: RET), and use the _e_x_i_t key (default: q) to + return to the address prompt. + + 33..33.. CChhaannggiinngg tthhee ddeeffaauulltt kkeeyy bbiinnddiinnggss + + Usage: bind _m_a_p _k_e_y _f_u_n_c_t_i_o_n + + This command allows you to change the default key bindings (operation + invoked when pressing a key). + + _m_a_p specifies in which menu the binding belongs. The currently + defined maps are: + + +o generic + + +o alias + + +o attach + + +o browser + + +o editor + + +o index + + +o compose + + +o pager + + +o pgp + + +o url + + _k_e_y is the key (or key sequence) you wish to bind. To specify a + control character, use the sequence _\_C_x, where _x is the letter of the + control character (for example, to specify control-A use ``\Ca''). + Note that the case of _x as well as _\_C is ignored, so that _\_C_A_, _\_C_a_, + _\_c_A and _\_c_a are all equivalent. An alternative form is to specify the + key as a three digit octal number prefixed with a ``\'' (for example + _\_1_7_7 is equivalent to _\_c_?). + + In addition, _k_e_y may consist of: + + \t tab + \r carriage return + \n newline + \e escape + up up arrow + down down arrow + left left arrow + right right arrow + pageup Page Up + pagedown Page Down + backspace Backspace + delete Delete + insert Insert + enter Enter + home Home + end End + f1 function key 1 + f10 function key 10 + + _k_e_y does not need to be enclosed in quotes unless it contains a space + (`` ''). + + _f_u_n_c_t_i_o_n specifies which action to take when _k_e_y is pressed. For a + complete list of functions, see the ``reference''. The special + function noop unbinds the specify key sequence. + + 33..44.. SSeettttiinngg vvaarriiaabblleess bbaasseedd uuppoonn mmaaiillbbooxx + + Usage: folder-hook [!]_p_a_t_t_e_r_n _c_o_m_m_a_n_d + + It is often desirable to change settings based on which mailbox you + are reading. The folder-hook command provides a method by which you + can execute any configuration command. _p_a_t_t_e_r_n is a regular + expression specifying in which mailboxes to execute _c_o_m_m_a_n_d before + loading. If a mailbox matches multiple folder-hook's, they are + executed in the order given in the muttrc. + + NNoottee:: if you use the ``!'' shortcut for ``$spoolfile'' at the + beginning of the pattern, you must place it inside of double or single + quotes in order to distinguish it from the logical _n_o_t operator for + the expression. + + Note that the settings are _n_o_t restored when you leave the mailbox. + For example, a command action to perform is to change the sorting + method based upon the mailbox being read: + + folder-hook mutt set sort=threads + + However, the sorting method is not restored to its previous value when + reading a different mailbox. To specify a _d_e_f_a_u_l_t command, use the + pattern ``.'': + + folder-hook . set sort=date-sent + + 33..55.. KKeeyybbooaarrdd mmaaccrrooss + + Usage: macro _m_e_n_u _k_e_y _s_e_q_u_e_n_c_e + + Macros are useful when you would like a single key to perform a series + of actions. When you press _k_e_y in menu _m_e_n_u, Mutt will behave as if + you had typed _s_e_q_u_e_n_c_e. So if you have a common sequence of commands + you type, you can create a macro to execute those commands with a + single key. + + _k_e_y and _s_e_q_u_e_n_c_e are expanded by the same rules as the ``key + bindings'', with the addition that control characters in _s_e_q_u_e_n_c_e can + also be specified as _^_x. In order to get a caret (``^'') you need to + use _^_^. + + NNoottee:: Macro definitions (if any) listed in the help screen(s), are + silently truncated at the screen width, and are not wrapped. + + 33..66.. UUssiinngg ccoolloorr aanndd mmoonnoo vviiddeeoo aattttrriibbuutteess + + Usage: color _o_b_j_e_c_t _f_o_r_e_g_r_o_u_n_d _b_a_c_k_g_r_o_u_n_d [ _r_e_g_e_x_p ] + + If your terminal supports color, you can spice up Mutt by creating + your own color scheme. To define the color of an object (type of + information), you must specify both a foreground color aanndd a + background color (it is not possible to only specify one or the + other). + + _o_b_j_e_c_t can be one of: + + +o attachment + + +o body (match _r_e_g_e_x_p in the body of messages) + + +o bold (hiliting bold patterns in the body of messages) + + +o error (error messages printed by Mutt) + + +o header (match _r_e_g_e_x_p in the message header) + + +o hdrdefault (default color of the message header in the pager) + + +o indicator (arrow or bar used to indicate the current item in a + menu) + + +o markers (the ``+'' markers at the beginning of wrapped lines in the + pager) + + +o message (informational messages) + + +o normal + + +o quoted (text matching ``$quote_regexp'' in the body of a message) + + +o quoted1, quoted2, ..., quotedNN (higher levels of quoting) + + +o search (hiliting of words in the pager) + + +o signature + + +o status (mode lines used to display info about the mailbox or + message) + + +o tilde (the ``~'' used to pad blank lines in the pager) + + +o tree (thread tree drawn in the message index and attachment menu) + + +o underline (hiliting underlined patterns in the body of messages) + + _f_o_r_e_g_r_o_u_n_d and _b_a_c_k_g_r_o_u_n_d can be one of the following: + + +o white + + +o black + + +o green + + +o magenta + + +o blue + + +o cyan + + +o yellow + + +o red + + +o default + + +o color_x + + _f_o_r_e_g_r_o_u_n_d can optionally be prefixed with the keyword bright to make + the foreground color boldfaced (e.g., brightred). + + If your terminal supports it, the special keyword _d_e_f_a_u_l_t can be used + as a transparent color. The value _b_r_i_g_h_t_d_e_f_a_u_l_t is also valid. If + Mutt is linked against the _S_-_L_a_n_g library, you also need to set the + _C_O_L_O_R_F_G_B_G environment variable to the default colors of your terminal + for this to work; for example (for Bourne-like shells): + + set COLORFGBG="green;black" + export COLORFGBG + + NNoottee:: The _S_-_L_a_n_g library requires you to use the _l_i_g_h_t_g_r_a_y and _b_r_o_w_n + keywords instead of _w_h_i_t_e and _y_e_l_l_o_w when setting this variable. + + Mutt also recognizes the keywords _c_o_l_o_r_0, _c_o_l_o_r_1, ..., _c_o_l_o_rNN--11 (NN + being the number of colors supported by your terminal). This is + useful when you remap the colors for your display (for example by + changing the color associated with _c_o_l_o_r_2 for your xterm), since color + names may then lose their normal meaning. + + If your terminal does not support color, it is still possible change + the video attributes through the use of the ``mono'' command: + + Usage: mono _<_o_b_j_e_c_t_> _<_a_t_t_r_i_b_u_t_e_> [ _r_e_g_e_x_p ] + + where _a_t_t_r_i_b_u_t_e is one of the following: + + +o none + + +o bold + + +o underline + + +o reverse + + +o standout + + 33..77.. IIggnnoorriinngg ((wweeeeddiinngg)) uunnwwaanntteedd mmeessssaaggee hheeaaddeerrss + + Usage: [un]ignore _p_a_t_t_e_r_n [ _p_a_t_t_e_r_n ... ] + + Messages often have many header fields added by automatic processing + systems, or which may not seem useful to display on the screen. This + command allows you to specify header fields which you don't normally + want to see. + + You do not need to specify the full header field name. For example, + ``ignore content-'' will ignore all header fields that begin with the + pattern ``content-''. + + To remove a previously added token from the list, use the ``unignore'' + command. Note that if you do ``ignore x-'' it is not possible to + ``unignore x-mailer,'' for example. The ``unignore'' command does nnoott + make Mutt display headers with the given pattern. + + ``unignore *'' will remove all tokens from the ignore list. + + For example: + + # Sven's draconian header weeding + ignore * + unignore from date subject to cc + unignore organization organisation x-mailer: x-newsreader: x-mailing-list: + unignore posted-to: + + 33..88.. MMaaiilliinngg lliissttss + + Usage: [un]lists _a_d_d_r_e_s_s [ _a_d_d_r_e_s_s ... ] + + Mutt has a few nice features for ``handling mailing lists''. In order + to take advantage of them, you must specify which addresses belong to + mailing lists. + + It is important to note that you should nneevveerr specify the domain name + ( the part after the ``@'') with the lists command. You should only + specify the ``mailbox'' portion of the address (the part before the + ``@''). For example, if you've subscribed to the Mutt mailing list, + you will receive mail addressed to _m_u_t_t_-_u_s_e_r_s_@_c_s_._h_m_c_._e_d_u. So, to tell + Mutt that this is a mailing list, you would add ``lists mutt-users'' + to your initialization file. + + The ``unlists'' command is to remove a token from the list of mailing- + lists. Use ``unlists *'' to remove all tokens. + + 33..99.. UUssiinngg MMuullttiippllee ssppooooll mmaaiillbbooxxeess + + Usage: mbox-hook [!]_p_a_t_t_e_r_n _m_a_i_l_b_o_x + + This command is used to move read messages from a specified mailbox to + a different mailbox automatically when you quit or change folders. + _p_a_t_t_e_r_n is a regular expression specifying the mailbox to treat as a + ``spool'' mailbox and _m_a_i_l_b_o_x specifies where mail should be saved + when read. + + Unlike some of the other _h_o_o_k commands, only the _f_i_r_s_t matching + pattern is used (it is not possible to save read mail in more than a + single mailbox). + + 33..1100.. DDeeffiinniinngg mmaaiillbbooxxeess wwhhiicchh rreecceeiivvee mmaaiill + + Usage: mailboxes [!]_f_i_l_e_n_a_m_e [ _f_i_l_e_n_a_m_e ... ] + + This command specifies folders which can receive mail and which will + be checked for new messages. By default, the main menu status bar + displays how many of these folders have new messages. + + When changing folders, pressing _s_p_a_c_e will cycle through folders with + new mail. + + Pressing TAB in the directory browser will bring up a menu showing the + files specified by the mailboxes command, and indicate which contain + new messages. Mutt will automatically enter this mode when invoked + from the command line with the -y option. + + NNoottee:: new mail is detected by comparing the last modification time to + the last access time. Utilities like biff or frm or any other program + which accesses the mailbox might cause Mutt to never detect new mail + for that mailbox if they do not properly reset the access time. + + NNoottee:: the filenames in the mailboxes command are resolved when the + command is executed, so if these names contain ``shortcut characters'' + (such as ``='' and ``!''), any variable definition that affect these + characters (like ``$folder'' and ``$spool'') should be executed before + the mailboxes command. + + 33..1111.. UUsseerr ddeeffiinneedd hheeaaddeerrss + + Usage: + my_hdr _s_t_r_i_n_g + unmy_hdr _f_i_e_l_d [ _f_i_e_l_d ... ] + + The ``my_hdr'' command allows you to create your own header fields + which will be added to every message you send. + + For example, if you would like to add an ``Organization:'' header + field to all of your outgoing messages, you can put the command + + my_hdr Organization: A Really Big Company, Anytown, USA + + in your .muttrc. + + NNoottee:: space characters are _n_o_t allowed between the keyword and the + colon (``:''). The standard for electronic mail (RFC822) says that + space is illegal there, so Mutt enforces the rule. + + If you would like to add a header field to a single message, you + should either set the ``edit_headers'' variable, or use the _e_d_i_t_- + _h_e_a_d_e_r_s function (default: ``E'') in the send-menu so that you can + edit the header of your message along with the body. + To remove user defined header fields, use the ``unmy_hdr'' command. + You may specify an asterisk (``*'') to remove all header fields, or + the fields to remove. For example, to remove all ``To'' and ``Cc'' + header fields, you could use: + + unmy_hdr to cc + + 33..1122.. DDeeffiinniinngg tthhee oorrddeerr ooff hheeaaddeerrss wwhheenn vviieewwiinngg mmeessssaaggeess + + Usage: hdr_order _h_e_a_d_e_r_1 _h_e_a_d_e_r_2 _h_e_a_d_e_r_3 + + With this command, you can specify an order in which mutt will attempt + to present headers to you when viewing messages. + + hdr_order From Date: From: To: Cc: Subject: + + 33..1133.. SSppeecciiffyy ddeeffaauulltt ssaavvee ffiilleennaammee + + Usage: save-hook [!]_r_e_g_e_x_p _f_i_l_e_n_a_m_e + + This command is used to override the default filename used when saving + messages. _f_i_l_e_n_a_m_e will be used as the default filename if the + message is _F_r_o_m_: an address matching _r_e_g_e_x_p or if you are the author + and the message is addressed _t_o_: something matching _r_e_g_e_x_p. + + See ``matching messages'' for information on the exact format of + _r_e_g_e_x_p. + + Examples: + + save-hook me@(turing\\.)?cs\\.hmc\\.edu$ +elkins + save-hook aol\\.com$ +spam + + Also see the ``fcc-save-hook'' command. + + 33..1144.. SSppeecciiffyy ddeeffaauulltt FFcccc:: mmaaiillbbooxx wwhheenn ccoommppoossiinngg + + Usage: fcc-hook [!]_r_e_g_e_x_p _m_a_i_l_b_o_x + + This command is used to save outgoing mail in a mailbox other than + ``$record''. Mutt searches the initial list of message recipients for + the first matching _r_e_g_e_x_p and uses _m_a_i_l_b_o_x as the default Fcc: + mailbox. If no match is found the message will be saved to + ``$record'' mailbox. + + See ``matching messages'' for information on the exact format of + _r_e_g_e_x_p. + + Example: fcc-hook aol.com$ +spammers + + The above will save a copy of all messages going to the aol.com domain + to the `+spammers' mailbox by default. Also see the ``fcc-save-hook'' + command. + + 33..1155.. SSppeecciiffyy ddeeffaauulltt ssaavvee ffiilleennaammee aanndd ddeeffaauulltt FFcccc:: mmaaiillbbooxx aatt oonnccee + + Usage: fcc-save-hook [!]_r_e_g_e_x_p _m_a_i_l_b_o_x + + This command is a shortcut, equivalent to doing both a ``fcc-hook'' + and a ``save-hook'' with its arguments. + + 33..1166.. CChhaannggee sseettttiinnggss bbaasseedd uuppoonn mmeessssaaggee rreecciippiieennttss + + Usage: send-hook [!]_r_e_g_e_x_p _c_o_m_m_a_n_d + + This command can be used to execute arbitrary configuration commands + based upon recipients of the message. _r_e_g_e_x_p is a regular expression + matching the desired address. _c_o_m_m_a_n_d is executed when _r_e_g_e_x_p matches + recipients of the message. When multiple matches occur, commands are + executed in the order they are specified in the muttrc. + + See ``matching messages'' for information on the exact format of + _r_e_g_e_x_p. + + Example: send-hook mutt "set mime_fwd signature=''" + + Another typical use for this command is to change the values of the + ``$attribution'', ``$signature'' and ``$locale'' variables in order to + change the language of the attributions and signatures based upon the + recipients. + + NNoottee:: the send-hook's are only executed ONCE after getting the initial + list of recipients. Adding a recipient after replying or editing the + message will NOT cause any send-hook to be executed. + + 33..1177.. AAddddiinngg kkeeyy sseeqquueenncceess ttoo tthhee kkeeyybbooaarrdd bbuuffffeerr + + Usage: push _s_t_r_i_n_g + + This command adds the named string to the keyboard buffer. You may + use it to automatically run a sequence of commands at startup, or when + entering certain folders. + + 33..1188.. MMeessssaaggee SSccoorriinngg + + Usage: score _p_a_t_t_e_r_n _v_a_l_u_e + Usage: unscore _p_a_t_t_e_r_n [ _p_a_t_t_e_r_n ... ] + + The score commands adds _v_a_l_u_e to a message's score if _p_a_t_t_e_r_n matches + it. _p_a_t_t_e_r_n is a string in the format described in the ``searching'' + section. _v_a_l_u_e is a positive or negative integer. A message's final + score is the sum total of all matching score entries. However, you + may optionally prefix _v_a_l_u_e with an equal sign (=) to cause evaluation + to stop at a particular entry if there is a match. Negative final + scores are rounded up to 0. + + The unscore command removes score entries from the list. You mmuusstt + specify the same pattern specified in the score command for it to be + removed. The pattern ``*'' is a special token which means to clear + the list of all score entries. + + 33..1199.. SSeettttiinngg vvaarriiaabblleess + + Usage: set [no|inv]_v_a_r_i_a_b_l_e[=_v_a_l_u_e] [ _v_a_r_i_a_b_l_e ... ] + Usage: toggle _v_a_r_i_a_b_l_e [_v_a_r_i_a_b_l_e ... ] + Usage: unset _v_a_r_i_a_b_l_e [_v_a_r_i_a_b_l_e ... ] + Usage: reset _v_a_r_i_a_b_l_e [_v_a_r_i_a_b_l_e ... ] + + This command is used to set (and unset) ``configuration variables''. + There are four basic types of variables: boolean, number, string and + quadoption. _b_o_o_l_e_a_n variables can be _s_e_t (true) or _u_n_s_e_t (false). + _n_u_m_b_e_r variables can be assigned a positive integer value. + + _s_t_r_i_n_g variables consist of any number of printable characters. + _s_t_r_i_n_g_s must be enclosed in quotes if they contain spaces or tabs. + You may also use the ``C'' escape sequences \\nn and \\tt for newline and + tab, respectively. + + _q_u_a_d_o_p_t_i_o_n variables are used to control whether or not to be prompted + for certain actions, or to specify a default action. A value of _y_e_s + will cause the action to be carried out automatically as if you had + answered yes to the question. Similarly, a value of _n_o will cause the + the action to be carried out as if you had answered ``no.'' A value + of _a_s_k_-_y_e_s will cause a prompt with a default answer of ``yes'' and + _a_s_k_-_n_o will provide a default answer of ``no.'' + + Prefixing a variable with ``no'' will unset it. Example: set + noaskbcc. + + For _b_o_o_l_e_a_n variables, you may optionally prefix the variable name + with inv to toggle the value (on or off). This is useful when writing + macros. Example: set invsmart_wrap. + + The toggle command automatically prepends the inv prefix to all + specified variables. + + The unset command automatically prepends the no prefix to all + specified variables. + + Using the enter-command function in the _i_n_d_e_x menu, you can query the + value of a variable by prefixing the name of the variable with a + question mark: + + set ?allow_8bit + + The question mark is actually only required for boolean variables. + + The reset command resets all given variables to the compile time + defaults (hopefully mentioned in this manual). If you use the command + set and prefix the variable with ``&'' this has the same behavior as + the reset command. + + With the reset command there exists the special variable ``all'', + which allows you to reset all variables to their system defaults. + + 33..2200.. RReeaaddiinngg iinniittiiaalliizzaattiioonn ccoommmmaannddss ffrroomm aannootthheerr ffiillee + + Usage: source _f_i_l_e_n_a_m_e + + This command allows the inclusion of initialization commands from + other files. For example, I place all of my aliases in + ~/.mail_aliases so that I can make my ~/.muttrc readable and keep my + aliases private. + + If the filename begins with a tilde (``~''), it will be expanded to + the path of your home directory. + + 44.. AAddvvaanncceedd UUssaaggee + + 44..11.. SSeeaarrcchhiinngg aanndd RReegguullaarr EExxpprreessssiioonnss + + All text patterns for searching and matching in Mutt must be specified + as regular expressions (regexp) in the ``POSIX extended'' syntax + (which is more or less the syntax used by egrep and GNU awk). For + your convenience, we have included below a brief description of this + syntax. + + The search is case sensitive if the pattern contains at least one + upper case letter, and case insensitive otherwise. Note that ``\'' + must be quoted if used for a regular expression in an initialization + command: ``\\''. For more information, see the section on + ``searching'' below. + + 44..11..11.. RReegguullaarr EExxpprreessssiioonnss + + A regular expression is a pattern that describes a set of strings. + Regular expressions are constructed analogously to arithmetic + expressions, by using various operators to combine smaller + expressions. + + The fundamental building blocks are the regular expressions that match + a single character. Most characters, including all letters and + digits, are regular expressions that match themselves. Any + metacharacter with special meaning may be quoted by preceding it with + a backslash. + + The period ``.'' matches any single character. The caret ``^'' and + the dollar sign ``$'' are metacharacters that respectively match the + empty string at the beginning and end of a line. + + A list of characters enclosed by ``['' and ``]'' matches any single + character in that list; if the first character of the list is a caret + ``^'' then it matches any character nnoott in the list. For example, the + regular expression [[00112233445566778899]] matches any single digit. A range of + ASCII characters may be specified by giving the first and last + characters, separated by a hyphen ``-''. Most metacharacters lose + their special meaning inside lists. To include a literal ``]'' place + it first in the list. Similarly, to include a literal ``^'' place it + anywhere but first. Finally, to include a literal hyphen ``-'' place + it last. + + Certain named classes of characters are predefined. Character classes + consist of ``[:'', a keyword denoting the class, and ``:]''. The + following classes are defined by the POSIX standard: + + [[::aallnnuumm::]] + Alphanumeric characters. + + [[::aallpphhaa::]] + Alphabetic characters. + + [[::bbllaannkk::]] + Space or tab characters. + + [[::ccnnttrrll::]] + Control characters. + + [[::ddiiggiitt::]] + Numeric characters. + + [[::ggrraapphh::]] + Characters that are both printable and visible. (A space is + printable, but not visible, while an ``a'' is both.) + + [[::lloowweerr::]] + Lower-case alphabetic characters. + + [[::pprriinntt::]] + Printable characters (characters that are not control + characters.) + + [[::ppuunncctt::]] + Punctuation characters (characters that are not letter, digits, + control characters, or space characters). + + [[::ssppaaccee::]] + Space characters (such as space, tab and formfeed, to name a + few). + + [[::uuppppeerr::]] + Upper-case alphabetic characters. + + [[::xxddiiggiitt::]] + Characters that are hexadecimal digits. + + A character class is only valid in a regular expression inside the + brackets of a character list. Note that the brackets in these class + names are part of the symbolic names, and must be included in addition + to the brackets delimiting the bracket list. For example, [[[[::ddiiggiitt::]]]] + is equivalent to [[00--99]]. + + Two additional special sequences can appear in character lists. These + apply to non-ASCII character sets, which can have single symbols + (called collating elements) that are represented with more than one + character, as well as several characters that are equivalent for + collating or sorting purposes: + + CCoollllaattiinngg SSyymmbboollss + A collating symbols is a multi-character collating element + enclosed in ``[.'' and ``.]''. For example, if ``ch'' is a + collating element, then [[[[..cchh..]]]] is a regexp that matches this + collating element, while [[cchh]] is a regexp that matches either + ``c'' or ``h''. + + EEqquuiivvaalleennccee CCllaasssseess + An equivalence class is a locale-specific name for a list of + characters that are equivalent. The name is enclosed in ``[='' + and ``=]''. For example, the name ``e'' might be used to + represent all of ``e'' ``e'' and ``e''. In this case, [[[[==ee==]]]] + is a regexp that matches any of ``e'', ``e'' and ``e''. + + A regular expression matching a single character may be followed by + one of several repetition operators: + + ?? The preceding item is optional and matched at most once. + + ** The preceding item will be matched zero or more times. + + ++ The preceding item will be matched one or more times. + + {{nn}} + The preceding item is matched exactly _n times. + + {{nn,,}} + The preceding item is matched _n or more times. + + {{,,mm}} + The preceding item is matched at most _m times. + + {{nn,,mm}} + The preceding item is matched at least _n times, but no more than + _m times. + + Two regular expressions may be concatenated; the resulting regular + expression matches any string formed by concatenating two substrings + that respectively match the concatenated subexpressions. + + Two regular expressions may be joined by the infix operator ``|''; the + resulting regular expression matches any string matching either + subexpression. + + Repetition takes precedence over concatenation, which in turn takes + precedence over alternation. A whole subexpression may be enclosed in + parentheses to override these precedence rules. + + NNoottee:: If you compile Mutt with the GNU _r_x package, the following + operators may also be used in regular expressions: + + \\yy Matches the empty string at either the beginning or the end of a + word. + + \\BB Matches the empty string within a word. + + \\<< Matches the empty string at the beginning of a word. + + \\>> Matches the empty string at the end of a word. + + \\ww Matches any word-constituent character (letter, digit, or + underscore). + + \\WW Matches any character that is not word-constituent. + + \\`` Matches the empty string at the beginning of a buffer (string). + + \\'' Matches the empty string at the end of a buffer. + + Please note however that these operators are not defined by POSIX, so + they may or may not be available in stock libraries on various + systems. + + 44..11..22.. SSeeaarrcchhiinngg + + Many of Mutt's commands allow you to specify a pattern to match + (limit, tag-pattern, delete-pattern, etc.). There are several ways to + select messages: + + ~A all messages + ~b PATTERN messages which contain PATTERN in the message body + ~c USER messages carbon-copied to USER + ~C PATTERN message is either to: or cc: PATTERN + ~D deleted messages + ~d [MIN]-[MAX] messages with ``date-sent'' in a Date range + ~E expired messages + ~e PATTERN message which contains PATTERN in the ``Sender'' field + ~F flagged messages + ~f USER messages originating from USER + ~h PATTERN messages which contain PATTERN in the message header + ~i ID message which match ID in the ``Message-ID'' field + ~L PATTERN message is either originated or recieved by PATTERN + ~l message is addressed to a known mailing list + ~m [MIN]-[MAX] message in the range MIN to MAX + ~n [MIN]-[MAX] messages with a score in the range MIN to MAX + ~N new messages + ~O old messages + ~p message is addressed to you (consults $alternates) + ~P message is from you (consults $alternates) + ~Q messages which have been replied to + ~R read messages + ~r [MIN]-[MAX] messages with ``date-received'' in a Date range + ~S superseded messages + ~s SUBJECT messages having SUBJECT in the ``Subject'' field. + ~T tagged messages + ~t USER messages addressed to USER + ~U unread messages + ~x PATTERN messages which contain PATTERN in the `References' field + + Where PATTERN, USER, ID, and SUBJECT are ``regular expressions''. + + 44..11..33.. CCoommpplleexx SSeeaarrcchheess + + Logical AND is performed by specifying more than one criterion. For + example: + + ~t mutt ~f elkins + + would select messages which contain the word ``mutt'' in the list of + recipients aanndd that have the word ``elkins'' in the ``From'' header + field. + + Mutt also recognizes the following operators to create more complex + search patterns: + + +o ! -- logical NOT operator + + +o | -- logical OR operator + + +o () -- logical grouping operator + + Here is an example illustrating a complex search pattern. This + pattern will select all messages which do not contain ``mutt'' in the + ``To'' or ``Cc'' field and which are from ``elkins''. + !(~t mutt|~c mutt) ~f elkins + + 44..11..44.. SSeeaarrcchhiinngg bbyy DDaattee + + Mutt supports two types of dates, _a_b_s_o_l_u_t_e and _r_e_l_a_t_i_v_e. + + AAbbssoolluuttee. Dates mmuusstt be in DD/MM/YY format (month and year are + optional, defaulting to the current month and year). An example of a + valid range of dates is: + + Limit to messages matching: ~d 20/1/95-31/10 + + If you omit the minimum (first) date, and just specify ``-DD/MM/YY'', + all messages _b_e_f_o_r_e the given date will be selected. If you omit the + maximum (second) date, and specify ``DD/MM/YY-'', all messages _a_f_t_e_r + the given date will be selected. If you specify a single date with no + dash (``-''), only messages sent on the given date will be selected. + + RReellaattiivvee. This type of date is relative to the current date, and may + be specified as: + + +o >_o_f_f_s_e_t (messages older than _o_f_f_s_e_t units) + + +o <_o_f_f_s_e_t (messages newer than _o_f_f_s_e_t units) + + +o =_o_f_f_s_e_t (messages exactly _o_f_f_s_e_t units old) + + _o_f_f_s_e_t is specified as a positive number with one of the following + units: + + y years + m months + w weeks + d days + + Example: to select messages less than 1 month old, you would use + + Limit to messages matching: ~d <1m + + NNoottee:: all dates used when searching are relative to the llooccaall time + zone, so unless you change the setting of your ``$header_format'' to + include a %[...] format, these are nnoott the dates shown in the main + index. + + 44..22.. UUssiinngg TTaaggss + + Sometimes it is desirable to perform an operation on a group of + messages all at once rather than one at a time. An example might be + to save messages to a mailing list to a separate folder, or to delete + all messages with a given subject. To tag all messages matching a + pattern, use the tag-pattern function, which is bound to ``control-T'' + by default. Or you can select individual messages by hand using the + ``tag-message'' function, which is bound to ``t'' by default. See + ``searching'' for Mutt's searching syntax. + + Once you have tagged the desired messages, you can use the ``tag- + prefix'' operator, which is the ``;'' (semicolon) key by default. + When the ``tag-prefix'' operator is used, the nneexxtt operation will be + applied to all tagged messages if that operation can be used in that + manner. If the ``$auto_tag'' variable is set, the next operation + applies to the tagged messages automatically, without requiring the + ``tag-prefix''. + + 44..33.. UUssiinngg HHooookkss + + A _h_o_o_k is a concept borrowed from the EMACS editor which allows you to + execute arbitrary commands before performing some operation. For + example, you may wish to tailor your configuration based upon which + mailbox you are reading, or to whom you are sending mail. In the Mutt + world, a _h_o_o_k consists of a ``regular expression'' along with a + configuration option/command. See + + +o ``folder-hook'' + + +o ``send-hook'' + + +o ``save-hook'' + + +o ``mbox-hook'' + + +o ``fcc-hook'' + + +o ``fcc-save-hook'' + + for specific details on each type of _h_o_o_k available. + + 44..33..11.. MMeessssaaggee MMaattcchhiinngg iinn HHooookkss + + Hooks that act upon messages (send-hook, save-hook, fcc-hook) are + evaluated in a slightly different manner. For the other types of + hooks, a ``regular expression''. But in dealing with messages a finer + grain of control is needed for matching since for different purposes + you want to match different criteria. + + Mutt allows the use of the ``search pattern'' language for matching + messages in hook commands. This works in exactly the same way as it + would when _l_i_m_i_t_i_n_g or _s_e_a_r_c_h_i_n_g the mailbox, except that you are + restricted to those operators which match information from the + envelope of the message (i.e. from, to, cc, date, subject, etc.). + + For example, if you wanted to set your return address based upon + sending mail to a specific address, you could do something like: + + send-hook '~t ^me@cs\.hmc\.edu$' 'my_hdr From: Mutt User <user@host>' + + which would execute the given command when sending mail to + _m_e_@_c_s_._h_m_c_._e_d_u. + + However, it is not required that you write the pattern to match using + the full searching language. You can still specify a simple _r_e_g_u_l_a_r + _e_x_p_r_e_s_s_i_o_n like the other hooks, in which case Mutt will translate + your pattern into the full language, using the translation specified + by the ``$dfault_hook'' variable. The pattern is translated at the + time the hook is declared, so the value of ``$dfault_hook'' that is in + effect at that time will be used. + + 44..44.. EExxtteerrnnaall AAddddrreessss QQuueerriieess + + Mutt supports connecting to external directory databases such as LDAP, + ph/qi, bbdb, or NIS through a wrapper script which connects to mutt + using a simple interface. Using the ``$query_command'' variable, you + specify the wrapper command to use. For example: + + set query_command = "mutt_ldap_query.pl '%s'" + + The wrapper script should accept the query on the command-line. It + should return a one line message, than each matching response on a + single line, each line containing a tab separated address then name + then some other optional information. On error, or if there are no + matching addresses, return a non-zero exit code and a one line error + message. + + An example multiple response output: + + Searching database ... 20 entries ... 3 matching: + me@cs.hmc.edu Michael Elkins mutt dude + blong@fiction.net Brandon Long mutt and more + roessler@guug.de Thomas Roessler mutt pgp + + There are two mechanisms for accessing the query function of mutt. + One is to do a query from the index menu using the query function + (default: Q). This will prompt for a query, then bring up the query + menu which will list the matching responses. From the query menu, you + can select addresses to create aliases, or to mail. You can tag + multiple messages to mail, start a new query, or have a new query + appended to the current responses. + + The other mechanism for accessing the query function is for address + completion, similar to the alias completion. In any prompt for + address entry, you can use the complete-query function (default: ^T) + to run a query based on the current address you have typed. Like + aliases, mutt will look for what you have typed back to the last space + or comma. If there is a single response for that query, mutt will + expand the address in place. If there are multiple responses, mutt + will activate the query menu. At the query menu, you can select one + or more addresses to be added to the prompt. + + 44..55.. MMaaiillbbooxx FFoorrmmaattss + + Mutt supports reading and writing of four different mailbox formats: + mbox, MMDF, MH and Maildir. The mailbox type is autodetected, so + there is no need to use a flag for different mailbox types. When + creating new mailboxes, Mutt uses the default specified with the + ``$mbox_type'' variable. + + mmbbooxx. This is the most widely used mailbox format for UNIX. All + messages are stored in a single file. Each message has a line of the + form: + + From me@cs.hmc.edu Fri, 11 Apr 1997 11:44:56 PST + + to denote the start of a new message (this is often referred to as the + ``From_'' line). + + MMMMDDFF. This is a variant of the _m_b_o_x format. Each message is + surrounded by lines containing ``^A^A^A^A'' (four control-A's). + + MMHH. A radical departure from _m_b_o_x and _M_M_D_F, a mailbox consists of a + directory and each message is stored in a separate file. The filename + indicates the message number (however, this is may not correspond to + the message number Mutt displays). Deleted messages are renamed with a + comma (,) prepended to the filename. NNoottee:: Mutt detects this type of + mailbox by looking for either .mh_sequences or .xmhcache (needed to + distinguish normal directories from MH mailboxes). Mutt does not + update these files, yet. + + MMaaiillddiirr. The newest of the mailbox formats, used by the Qmail MTA (a + replacement for sendmail). Similar to _M_H, except that it adds three + subdirectories of the mailbox: _t_m_p, _n_e_w and _c_u_r. Filenames for the + messages are chosen in such a way they are unique, even when two + programs are writing the mailbox over NFS, which means that no file + locking is needed. + + 44..66.. MMaaiillbbooxx SShhoorrttccuuttss + + There are a number of built in shortcuts which refer to specific + mailboxes. These shortcuts can be used anywhere you are prompted for + a file or mailbox path. + + +o ! -- refers to your ``$spool'' (incoming) mailbox + + +o > -- refers to your ``$mbox'' file + + +o < -- refers to your ``$record'' file + + +o - -- refers to the file you've last visited + + +o ~ -- refers to your home directory + + +o = or + -- refers to your ``$folder'' directory + + 44..77.. HHaannddlliinngg MMaaiilliinngg LLiissttss + + Mutt has a few configuration options that make dealing with large + amounts of mail easier. The first thing you must do is to let Mutt + know what addresses you consider to be mailing lists (technically this + does not have to be a mailing list, but that is what it is most often + used for). This is accomplished through the use of the ``lists'' + command in your muttrc. + + Now that Mutt knows what your mailing lists are, it can do several + things, the first of which is the ability to show the list name in the + _i_n_d_e_x menu display. This is useful to distinguish between personal + and list mail in the same mailbox. In the ``$header_format'' + variable, the escape ``%L'' will return the string ``To <list>'' when + ``list'' appears in the ``To'' field, and ``Cc <list>'' when it + appears in the ``Cc'' field (otherwise it returns the name of the + author). + + Often times the ``To'' and ``Cc'' fields in mailing list messages tend + to get quite large. Most people do not bother to remove the author of + the message they are reply to from the list, resulting in two or more + copies being sent to that person. The ``list-reply'' function, which + by default is bound to ``L'' in the _i_n_d_e_x menu and _p_a_g_e_r, helps reduce + the clutter by only replying to the mailing list addresses instead of + all recipients. + + The other method some mailing list admins use is to generate a + ``Reply-To'' field which points back to the mailing list address + rather than the author of the message. This can create problems when + trying to reply directly to the author in private, since most mail + clients will automatically reply to the address given in the ``Reply- + To'' field. Mutt uses the ``$reply_to'' variable to help decide which + address to use. If set, you will be prompted as to whether or not you + would like to use the address given in the ``Reply-To'' field, or + reply directly to the address given in the ``From'' field. When + unset, the ``Reply-To'' field will be used when present. + + Lastly, Mutt has the ability to ``sort'' the mailbox into ``threads''. + A thread is a group of messages which all relate to the same subject. + This is usually organized into a tree-like structure where a message + and all of its replies are represented graphically. If you've ever + used a threaded news client, this is the same concept. It makes + dealing with large volume mailing lists easier because you can easily + delete uninteresting threads and quickly find topics of value. + + 44..88.. DDeelliivveerryy SSttaattuuss NNoottiiffiiccaattiioonn ((DDSSNN)) SSuuppppoorrtt + + RFC1894 defines a set of MIME content types for relaying information + about the status of electronic mail messages. These can be thought of + as ``return receipts.'' Berkeley sendmail 8.8.x currently has some + command line options in which the mail client can make requests as to + what type of status messages should be returned. + + To support this, there are two variables. ``$dsn_notify'' is used to + request receipts for different results (such as failed message, + message delivered, etc.). ``$dsn_return'' requests how much of your + message should be returned with the receipt (headers or full message). + Refer to the man page on sendmail for more details on DSN. + + 44..99.. PPOOPP33 SSuuppppoorrtt ((OOPPTTIIOONNAALL)) + + If Mutt was compiled with POP3 support (by running the _c_o_n_f_i_g_u_r_e + script with the _-_-_e_n_a_b_l_e_-_p_o_p flag), it has the ability to fetch your + mail from a remote server for local browsing. When you invoke the + _f_e_t_c_h_-_m_a_i_l function (default: G), Mutt attempts to connect to + ``pop_host'' and authenticate by logging in as ``pop_user''. After + the connection is established, you will be prompted for your password + on the remote system. + + Once you have been authenticated, Mutt will fetch all your new mail + and place it in the local ``spoolfile''. After this point, Mutt runs + exactly as if the mail had always been local. + NNoottee:: The POP3 support is there only for convenience, and it's rather + limited. If you need more functionality you should consider using a + specialized program, such as fetchmail + + 55.. MMuutttt''ss MMIIMMEE SSuuppppoorrtt + + Quite a bit of effort has been made to make Mutt the premier text-mode + MIME MUA. Every effort has been made to provide the functionality + that the discerning MIME user requires, and the conformance to the + standards wherever possible. When configuring Mutt for MIME, there + are two extra types of configuration files which Mutt uses. One is + the mime.types file, which contains the mapping of file extensions to + IANA MIME types. The other is the mailcap file, which specifies the + external commands to use for handling specific MIME types. + + 55..11.. UUssiinngg MMIIMMEE iinn MMuutttt + + There are three areas/menus in Mutt which deal with MIME, they are the + pager (while viewing a message), the attachment menu and the compose + menu. + + 55..11..11.. VViieewwiinngg MMIIMMEE mmeessssaaggeess iinn tthhee ppaaggeerr + + When you select a message from the index and view it in the pager, + Mutt decodes the message to a text representation. Mutt internally + supports a number of MIME types, including text/plain, text/enriched, + message/rfc822, and message/news. In addition, the export controlled + version of Mutt recognizes a variety of PGP MIME types, including + PGP/MIME and application/pgp. + + Mutt will denote attachments with a couple lines describing them. + These lines are of the form: + + [-- Attachment #1: Description --] + [-- Type: text/plain, Encoding: 7bit, Size: 10000 --] + + Where the Description is the description or filename given for the + attachment, and the Encoding is one of 7bit/8bit/quoted-print- + able/base64/binary. + + If Mutt cannot deal with a MIME type, it will display a message like: + + [-- image/gif is unsupported (use 'v' to view this part) --] + + 55..11..22.. TThhee AAttttaacchhmmeenntt MMeennuu + + The default binding for view-attachments is `v', which displays the + attachment menu for a message. The attachment menu displays a list of + the attachments in a message. From the attachment menu, you can save, + print, pipe, delete, and view attachments. You can apply these + operations to a group of attachments at once, by tagging the + attachments and by using the ``tag-prefix'' operator. You can also + reply to the current message from this menu, and only the current + attachment (or the attachments tagged) will be quoted in your reply. + You can view attachments as text, or view them using the mailcap + viewer definition. See the help on the attachment menu for more + information. + + 55..11..33.. TThhee CCoommppoossee MMeennuu + + The compose menu is the menu you see before you send a message. It + allows you to edit the recipient list, the subject, and other aspects + of your message. It also contains a list of the attachments of your + message, including the main body. From this menu, you can print, + copy, filter, pipe, edit, compose, review, and rename an attachment or + a list of tagged attachments. You can also modifying the attachment + information, notably the type, encoding and description. + + Attachments appear as follows: + + - 1 [text/plain, 7bit, 1K] /tmp/mutt-euler-8082-0 <no description> + 2 [applica/x-gunzip, base64, 422K] ~/src/mutt-0.85.tar.gz <no description> + + The '-' denotes that Mutt will delete the file after sending the + message. It can be toggled with the toggle-unlink command (default: + u). The next field is the MIME content-type, and can be changed with + the edit-type command (default: ^T). The next field is the encoding + for the attachment, which allows a binary message to be encoded for + transmission on 7bit links. It can be changed with the edit-encoding + command (default: ^E). The next field is the size of the attachment, + rounded to kilobytes or megabytes. The next field is the filename, + which can be changed with the rename-file command (default: R). The + final field is the description of the attachment, and can be changed + with the edit-description command (default: d). + + 55..22.. MMIIMMEE TTyyppee ccoonnffiigguurraattiioonn wwiitthh mime.types + + When you add an attachment to your mail message, Mutt searches your + personal mime.types file at ${HOME}/.mime.types, and then the system + mime.types file at SHAREDIR/mime.types. SHAREDIR is defined at + compilation time, and can be determined by typing mutt -v from the + command line. + + The mime.types file consist of lines containing a MIME type and a + space separated list of extensions. For example: + + application/postscript ps eps + application/pgp pgp + audio/x-aiff aif aifc aiff + + A sample mime.types file comes with the Mutt distribution, and should + contain most of the MIME types you are likely to use. + + If Mutt can not determine the mime type by the extension of the file + you attach, it will look at the file. If the file is free of binary + information, Mutt will assume that the file is plain text, and mark it + as text/plain. If the file contains binary information, then Mutt + will mark it as application/octect-stream. You can change the MIME + type that Mutt assigns to an attachment by using the edit-type command + from the compose menu (default: ^T). When typing in the MIME type, + Mutt requires that major type be one of the 5 types: application, + text, image, video, or audio. If you attempt to use a different major + type, Mutt will abort the change. + + 55..33.. MMIIMMEE VViieewweerr ccoonnffiigguurraattiioonn wwiitthh mailcap + + Mutt supports RFC 1524 MIME Configuration, in particular the Unix + specific format specified in Appendix A of RFC 1524. This file format + is commonly referred to as the mailcap format. Many MIME compliant + programs utilize the mailcap format, allowing you to specify handling + for all MIME types in one place for all programs. Programs known to + use this format include Netscape, XMosaic, lynx and metamail. + + In order to handle various MIME types that Mutt can not handle + internally, Mutt parses a series of external configuration files to + find an external handler. The default search string for these files + is a colon delimited list set to + + ${HOME}/.mailcap:SHAREDIR/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap + + where $HOME is your home directory and SHAREDIR is the shared direc- + tory defined at compile time (visible from mutt -v). + + In particular, the metamail distribution will install a mailcap file, + usually as /usr/local/etc/mailcap, which contains some baseline + entries. + + 55..33..11.. TThhee BBaassiiccss ooff tthhee mmaaiillccaapp ffiillee + + A mailcap file consists of a series of lines which are comments, + blank, or definitions. + + A comment line consists of a # character followed by anything you + want. + + A blank line is blank. + + A definition line consists of a content type, a view command, and any + number of optional fields. Each field of a definition line is divided + by a semicolon ';' character. + + The content type is specified in the MIME standard type/subtype + method. For example, text/plain, text/html, image/gif, etc. In + addition, the mailcap format includes two formats for wildcards, one + using the special '*' subtype, the other is the implicit wild, where + you only include the major type. For example, image/*, or video, will + match all image types and video types, respectively. + + The view command is a Unix command for viewing the type specified. + There are two different types of commands supported. The default is to + send the body of the MIME message to the command on stdin. You can + change this behaviour by using %s as a parameter to your view command. + This will cause Mutt to save the body of the MIME message to a + temporary file, and then call the view command with the %s replaced by + the name of the temporary file. In both cases, Mutt will turn over the + terminal to the view program until the program quits, at which time + Mutt will remove the temporary file if it exists. + + So, in the simplest form, you can send a text/plain message to the + external pager more on stdin: + + text/plain; more + + Or, you could send the message as a file: + + text/plain; more %s + + Perhaps you would like to use lynx to interactively view a text/html + message: + + text/html; lynx "%s" + + In this case, lynx does not support viewing a file from stdin, so you + must use the %s syntax. NNoottee:: _S_o_m_e _o_l_d_e_r _v_e_r_s_i_o_n_s _o_f _l_y_n_x _c_o_n_t_a_i_n _a + _b_u_g _w_h_e_r_e _t_h_e_y _w_i_l_l _c_h_e_c_k _t_h_e _m_a_i_l_c_a_p _f_i_l_e _f_o_r _a _v_i_e_w_e_r _f_o_r _t_e_x_t_/_h_t_m_l_. + _T_h_e_y _w_i_l_l _f_i_n_d _t_h_e _l_i_n_e _w_h_i_c_h _c_a_l_l_s _l_y_n_x_, _a_n_d _r_u_n _i_t_. _T_h_i_s _c_a_u_s_e_s + _l_y_n_x _t_o _c_o_n_t_i_n_u_o_u_s_l_y _s_p_a_w_n _i_t_s_e_l_f _t_o _v_i_e_w _t_h_e _o_b_j_e_c_t_. + + On the other hand, maybe you don't want to use lynx interactively, you + just want to have it convert the text/html to text/plain, then you can + use: + + text/html; lynx -dump "%s" | more + + Perhaps you wish to use lynx to view text/html files, and a pager on + all other text formats, then you would use the following: + + text/html; lynx "%s" + text/*; more + + This is the simplest form of a mailcap file. + + 55..33..22.. AAddvvaanncceedd mmaaiillccaapp UUssaaggee + + 55..33..22..11.. OOppttiioonnaall FFiieellddss + + In addition to the required content-type and view command fields, you + can add semi-colon ';' separated fields to set flags and other + options. Mutt recognizes the following optional fields: + + ccooppiioouussoouuttppuutt + This flag tells Mutt that the command passes possibly large + amounts of text on stdout. This causes Mutt to invoke a pager + (either the internal pager or the external pager defined by the + pager variable) on the output of the view command. Without this + flag, Mutt assumes that the command is interactive. One could + use this to replace the pipe to more in the lynx -dump example + in the Basic section: + + text/html; lynx -dump %s ; copiousoutput + + This will cause lynx to format the text/html output as text/plain + and Mutt will use your standard pager to display the results. + + nneeeeddsstteerrmmiinnaall + Mutt uses this flag when viewing attachments with ``autoview'', + in order to decide whether it should honor the setting of the + ``$wait_key'' variable or not. When an attachment is viewed + using an interactive program, and the corresponding mailcap + entry has a _n_e_e_d_s_t_e_r_m_i_n_a_l flag, Mutt will use ``$wait_key'' and + the exit status of the program to decide if it will ask you to + press a key after the external program has exited. In all other + situations it will not prompt you for a key. + + ccoommppoossee==<<ccoommmmaanndd>> + This flag specifies the command to use to create a new + attachment of a specific MIME type. Mutt supports this from the + compose menu. + + ccoommppoosseettyyppeedd==<<ccoommmmaanndd>> + This flag specifies the command to use to create a new + attachment of a specific MIME type. This command differs from + the compose command in that mutt will expect standard MIME + headers on the data. This can be used to specify parameters, + filename, description, etc. for a new attachment. Mutt + supports this from the compose menu. + + pprriinntt==<<ccoommmmaanndd>> + This flag specifies the command to use to print a specific MIME + type. Mutt supports this from the attachment and compose menus. + + eeddiitt==<<ccoommmmaanndd>> + This flag specifies the command to use to edit a specific MIME + type. Mutt supports this from the compose menu, and also uses + it to compose new attachments. Mutt will default to the defined + editor for text attachments. + + nnaammeetteemmppllaattee==<<tteemmppllaattee>> + This field specifies the format for the file denoted by %s in + the command fields. Certain programs will require a certain + file extension, for instance, to correctly view a file. For + instance, lynx will only interpret a file as text/html if the + file ends in .html. So, you would specify lynx as a text/html + viewer with a line in the mailcap file like: + + text/html; lynx %s; nametemplate=%s.html + + tteesstt==<<ccoommmmaanndd>> + This field specifies a command to run to test whether this + mailcap entry should be used. The command is defined with the + command expansion rules defined in the next section. If the + command returns 0, then the test passed, and Mutt uses this + entry. If the command returns non-zero, then the test failed, + and Mutt continues searching for the right entry. NNoottee:: _t_h_e + _c_o_n_t_e_n_t_-_t_y_p_e _m_u_s_t _m_a_t_c_h _b_e_f_o_r_e _M_u_t_t _p_e_r_f_o_r_m_s _t_h_e _t_e_s_t_. For + example: + + text/html; netscape -remote 'openURL(%s)' ; test=RunningX + text/html; lynx %s + + In this example, Mutt will run the program RunningX which will + return 0 if the X Window manager is running, and non-zero if it + isn't. If RunningX returns 0, then Mutt will call netscape to dis- + play the text/html object. If RunningX doesn't return 0, then Mutt + will go on to the next entry and use lynx to display the text/html + object. + + 55..33..22..22.. SSeeaarrcchh OOrrddeerr + + When searching for an entry in the mailcap file, Mutt will search for + the most useful entry for its purpose. For instance, if you are + attempting to print an image/gif, and you have the following entries + in your mailcap file, Mutt will search for an entry with the print + command: + + image/*; xv %s + image/gif; ; print= anytopnm %s | pnmtops | lpr; \ + nametemplate=%s.gif + + Mutt will skip the image/* entry and use the image/gif entry with the + print command. + + In addition, you can use this with ``Autoview'' to denote two commands + for viewing an attachment, one to be viewed automatically, the other + to be viewed interactively from the attachment menu. In addition, you + can then use the test feature to determine which viewer to use + interactively depending on your environment. + + text/html; netscape -remote 'openURL(%s)' ; test=RunningX + text/html; lynx %s; nametemplate=%s.html + text/html; lynx -dump %s; nametemplate=%s.html; copiousoutput + + For ``Autoview'', Mutt will choose the third entry because of the + copiousoutput tag. For interactive viewing, Mutt will run the program + RunningX to determine if it should use the first entry. If the pro- + gram returns non-zero, Mutt will use the second entry for interactive + viewing. + + 55..33..22..33.. CCoommmmaanndd EExxppaannssiioonn + + The various commands defined in the mailcap files are passed to the + /bin/sh shell using the system() function. Before the command is + passed to /bin/sh -c, it is parsed to expand various special + parameters with information from Mutt. The keywords Mutt expands are: + + %%ss As seen in the basic mailcap section, this variable is expanded + to a filename specified by the calling program. This file + contains the body of the message to view/print/edit or where the + composing program should place the results of composition. In + addition, the use of this keyword causes Mutt to not pass the + body of the message to the view/print/edit program on stdin. + + %%tt Mutt will expand %t to the text representation of the content + type of the message in the same form as the first parameter of + the mailcap definition line, ie text/html or image/gif. + + %%{{<<ppaarraammeetteerr>>}} + Mutt will expand this to the value of the specified parameter + from the Content-Type: line of the mail message. For instance, + if Your mail message contains: + + Content-Type: text/plain; charset=iso-8859-1 + + then Mutt will expand %{charset} to iso-8859-1. The default meta- + mail mailcap file uses this feature to test the charset to spawn an + xterm using the right charset to view the message. + + \\%% This will be replaced by a % + + Mutt does not currently support the %F and %n keywords specified in + RFC 1524. The main purpose of these parameters is for multipart mes- + sages, which is handled internally by Mutt. + + 55..33..33.. EExxaammppllee mmaaiillccaapp ffiilleess + + This mailcap file is fairly simple and standard: + + ______________________________________________________________________ + # I'm always running X :) + video/*; xanim %s > /dev/null + image/*; xv %s > /dev/null + + # I'm always running netscape (if my computer had more memory, maybe) + text/html; netscape -remote 'openURL(%s)' + ______________________________________________________________________ + + This mailcap file shows quite a number of examples: + + ______________________________________________________________________ + # Use xanim to view all videos Xanim produces a header on startup, + # send that to /dev/null so I don't see it + video/*; xanim %s > /dev/null + + # Send html to a running netscape by remote + text/html; netscape -remote 'openURL(%s)'; test=RunningNetscape + + # If I'm not running netscape but I am running X, start netscape on the + # object + text/html; netscape %s; test=RunningX + + # Else use lynx to view it as text + text/html; lynx %s + + # This version would convert the text/html to text/plain + text/html; lynx -dump %s; copiousoutput + + # enriched.sh converts text/enriched to text/html and then uses + # lynx -dump to convert it to text/plain + text/enriched; enriched.sh ; copiousoutput + + # I use enscript to print text in two columns to a page + text/*; more %s; print=enscript -2Gr %s + + # Netscape adds a flag to tell itself to view jpegs internally + image/jpeg;xv %s; x-mozilla-flags=internal + + # Use xv to view images if I'm running X + # In addition, this uses the \ to extend the line and set my editor + # for images + image/*;xv %s; test=RunningX; \ + edit=xpaint %s + + # Convert images to text using the netpbm tools + image/*; (anytopnm %s | pnmscale -xysize 80 46 | ppmtopgm | pgmtopbm | + pbmtoascii -1x2 ) 2>&1 ; copiousoutput + + # Send excel spreadsheets to my NT box + application/ms-excel; open.pl %s + ______________________________________________________________________ + + 55..44.. MMIIMMEE AAuuttoovviieeww + + In addition to explicitly telling Mutt to view an attachment with the + MIME viewer defined in the mailcap file, Mutt has support for + automatically viewing MIME attachments while in the pager. + + To work, you must define a viewer in the mailcap file which uses the + copiousoutput option to denote that it is non-interactive. Usually, + you also use the entry to convert the attachment to a text + representation which you can view in the pager. + + You then use the auto_view muttrc command to list the content-types + that you wish to view automatically. + + For instance, if you set auto_view to: + + auto_view text/html text/enriched application/x-gunzip application/postscript image/gif application/x-tar-gz + + Mutt could use the following mailcap entries to automatically view + attachments of these types. + + text/html; lynx -dump %s; copiousoutput; nametemplate=%s.html + text/enriched; enriched.sh ; copiousoutput + image/*; anytopnm %s | pnmscale -xsize 80 -ysize 50 | ppmtopgm | pgmtopbm | pbmtoascii ; copiousoutput + application/x-gunzip; gzcat; copiousoutput + application/x-tar-gz; gunzip -c %s | tar -tf - ; copiousoutput + application/postscript; ps2ascii %s; copiousoutput + + 55..55.. MMIIMMEE MMuullttiippaarrtt//AAlltteerrnnaattiivvee + + Mutt has some heuristics for determining which attachment of a + multipart/alternative type to display. First, mutt will check the + alternative_order list to determine if one of the available types is + preferred. The alternative_order list consists of a number of + mimetypes in order, including support for implicit and explicit + wildcards, for example: + + alternative_order text/enriched text/plain text application/postscript image/* + + Next, mutt will check if any of the types have a defined + ``auto_view'', and use that. Failing that, Mutt will look for any + text type. As a last attempt, mutt will look for any type it knows + how to handle. + + 66.. RReeffeerreennccee + + 66..11.. CCoommmmaanndd lliinnee ooppttiioonnss + + Running mutt with no arguments will make Mutt attempt to read your + spool mailbox. However, it is possible to read other mailboxes and to + send messages from the command line as well. + + -a attach a file to a message + -c specify a carbon-copy (Cc) address + -e specify a config command to be run after initilization files are read + -F specify an alternate file to read initialization commands + -f specify a mailbox to load + -h print help on command line options + -H specify a draft file from which to read a header and body + -i specify a file to include in a message composition + -n do not read the system Muttrc + -m specify a default mailbox type + -p recall a postponed message + -R open mailbox in read-only mode + -s specify a subject (enclose in quotes if it contains spaces) + -v show version number and compile-time definitions + -x simulate the mailx(1) compose mode + -y show a menu containing the files specified by the mailboxes command + -z exit immediately if there are no messages in the mailbox + -Z open the first folder with new message,exit immediately if none + + To read messages in a mailbox + + mutt [ -nz ] [ -F _m_u_t_t_r_c ] [ -m _t_y_p_e ] [ -f _m_a_i_l_b_o_x ] + + To compose a new message + + mutt [ -n ] [ -F _m_u_t_t_r_c ] [ -a _f_i_l_e ] [ -c _a_d_d_r_e_s_s ] [ -i _f_i_l_e_n_a_m_e ] [ + -s _s_u_b_j_e_c_t ] _a_d_d_r_e_s_s [ _a_d_d_r_e_s_s ... ] + + Mutt also supports a ``batch'' mode to send prepared messages. Simply + redirect input from the file you wish to send. For example, + + mutt -s "data set for run #2" professor@bigschool.edu < ~/run2.dat + + This command will send a message to ``professor@bigschool.edu'' with a + subject of ``data set for run #2''. In the body of the message will + be the contents of the file ``~/run2.dat''. + + 66..22.. CCoonnffiigguurraattiioonn CCoommmmaannddss + + The following are the commands understood by mutt. + + +o ``alias'' _k_e_y _a_d_d_r_e_s_s [ , _a_d_d_r_e_s_s, ... ] + + +o ``unalias'' _k_e_y _a_d_d_r_e_s_s [ , _a_d_d_r_e_s_s, ... ] + + +o ``alternative_order'' _m_i_m_e_t_y_p_e [ _m_i_m_e_t_y_p_e ... ] + + +o ``auto_view'' _m_i_m_e_t_y_p_e [ _m_i_m_e_t_y_p_e ... ] + + +o ``bind'' _m_a_p _k_e_y _f_u_n_c_t_i_o_n + + +o ``color'' _o_b_j_e_c_t _f_o_r_e_g_r_o_u_n_d _b_a_c_k_g_r_o_u_n_d [ _r_e_g_e_x_p ] + + +o ``folder-hook'' _p_a_t_t_e_r_n _c_o_m_m_a_n_d + + +o ``ignore'' _p_a_t_t_e_r_n [ _p_a_t_t_e_r_n ... ] + + +o ``unignore'' _p_a_t_t_e_r_n [ _p_a_t_t_e_r_n ... ] + + +o ``hdr_order'' _h_e_a_d_e_r [ _h_e_a_d_e_r ... ] + + +o ``lists'' _a_d_d_r_e_s_s [ _a_d_d_r_e_s_s ... ] + + +o ``unlists'' _a_d_d_r_e_s_s [ _a_d_d_r_e_s_s ... ] + + +o ``macro'' _m_e_n_u _k_e_y _s_e_q_u_e_n_c_e + + +o ``mailboxes'' _f_i_l_e_n_a_m_e [ _f_i_l_e_n_a_m_e ... ] + + +o ``mono'' _o_b_j_e_c_t _a_t_t_r_i_b_u_t_e [ _r_e_g_e_x_p ] + + +o ``mbox-hook'' _p_a_t_t_e_r_n _m_a_i_l_b_o_x + + +o ``my_hdr'' _s_t_r_i_n_g + + +o ``unmy_hdr'' _f_i_e_l_d [ _f_i_e_l_d ... ] + + +o ``push'' _s_t_r_i_n_g + + +o ``save-hook'' _r_e_g_e_x_p _f_i_l_e_n_a_m_e + + +o ``send-hook'' _r_e_g_e_x_p _c_o_m_m_a_n_d + + +o ``set'' [no|inv]_v_a_r_i_a_b_l_e[=_v_a_l_u_e] [ _v_a_r_i_a_b_l_e ... ] + + +o ``toggle'' _v_a_r_i_a_b_l_e [_v_a_r_i_a_b_l_e ... ] + + +o ``unset'' _v_a_r_i_a_b_l_e [_v_a_r_i_a_b_l_e ... ] + + +o ``source'' _f_i_l_e_n_a_m_e + + 66..33.. CCoonnffiigguurraattiioonn vvaarriiaabblleess + + 66..33..11.. aabboorrtt__nnoossuubbjjeecctt + + Type: quadoption + Default: ask-yes + + If set to _y_e_s, when composing messages and no subject is given at the + subject prompt, composition will be aborted. If set to _n_o, composing + messages with no subject given at the subject prompt will never be + aborted. + + 66..33..22.. aabboorrtt__uunnmmooddiiffiieedd + + Type: quadoption + Default: yes + + If set to _y_e_s, composition will automatically abort after editing the + message body if no changes are made to the file (this check only + happens after the _f_i_r_s_t edit of the file). When set to _n_o, + composition will never be aborted. + + 66..33..33.. aalliiaass__ffiillee + + Type: string + Default: ~/.muttrc + + The default file in which to save aliases created by the ``create- + alias'' function. + + NNoottee:: Mutt will not automatically source this file; you must + explicitly use the ``source'' command for it to be executed. + + 66..33..44.. aalliiaass__ffoorrmmaatt + + Type: string + Default: "%2n %t %-10a %r" + + Specifies the format of the data displayed for the `alias' menu. The + following printf(3)-style sequences are available. + + %a alias name + %n index number + %r address which alias expands to + %t character which indicates if the alias is tagged for inclusion (*/ ) + + 66..33..55.. aallllooww__88bbiitt + + Type: boolean + Default: set + + Controls whether 8-bit data is converted to 7-bit using either Quoted- + Printable or Base64 encoding when sending mail. + + 66..33..66.. aalltteerrnnaatteess + + Type: string + Default: none + + A regexp that allows you to specify _a_l_t_e_r_n_a_t_e addresses where you + receive mail. This affects Mutt's idea about messages from you and + addressed to you. + + 66..33..77.. aarrrrooww__ccuurrssoorr + + Type: boolean + Default: unset + + When set, an arrow (``->'') will be used to indicate the current entry + in menus instead of hiliting the whole line. On slow network or modem + links this will make response faster because there is less that has to + be redrawn on the screen when moving to the next or previous entries + in the menu. + + 66..33..88.. aasscciiii__cchhaarrss + + Type: boolean + Default: unset + + If set, Mutt will use plain ASCII characters when displaying thread + and attachment trees, instead of the default _A_C_S characters. + + 66..33..99.. aasskkbbcccc + + Type: boolean + Default: unset + + If set, Mutt will prompt you for blind-carbon-copy (Bcc) recipients + before editing an outgoing message. + + 66..33..1100.. aasskkcccc + + Type: boolean + Default: unset + + If set, Mutt will prompt you for carbon-copy (Cc) recipients before + editing the body of an outgoing message. + + 66..33..1111.. aattttaacchh__sseepp + + Type: string + Default: newline + + The separator to add between attachments when piping or saving a list + of tagged attachments to an external Unix command. + + 66..33..1122.. aattttaacchh__sspplliitt + + Type: boolean + Default: set + + Used in connection with the _p_i_p_e_-_e_n_t_r_y and _s_a_v_e_-_e_n_t_r_y commands and the + ``tag-prefix'' operator in the ``attachment'' menu. If this variable + is unset, when piping or saving a list of tagged attachments Mutt will + concatenate the attachments and will pipe or save them as a single + file. The ``attach_sep'' separator will be added after each message. + When set, Mutt will pipe or save the messages one by one. In both + cases the the messages are processed in the displayed order. + + 66..33..1133.. aattttrriibbuuttiioonn + + Type: format string + Default: "On %d, %n wrote:" + + This is the string that will precede a message which has been included + in a reply. For a full listing of defined escape sequences see the + section on ``$header_format''. + + 66..33..1144.. aauuttooeeddiitt + + Type: boolean + Default: unset + + When set, Mutt will skip the initial send-menu and allow you to + immediately begin editing the body of your message when replying to + another message. The send-menu may still be accessed once you have + finished editing the body of your message. + + If the ``$edit_headers'' variable is also set, the initial prompts in + the send-menu are always skipped, even when composing a new message. + + 66..33..1155.. aauuttoo__ttaagg + + Type: boolean + Default: unset + + When set, functions in the _i_n_d_e_x menu which affect a message will be + applied to all tagged messages (if there are any). When unset, you + must first use the tag-prefix function (default: ";") to make the next + function apply to all tagged messages. + + 66..33..1166.. bbeeeepp + + Type: boolean + Default: set + + When this variable is set, mutt will beep when an error occurs. + + 66..33..1177.. bbeeeepp__nneeww + + Type boolean + Default: unset + + When this variable is set, mutt will beep whenever it prints a message + notifying you of new mail. This is independent of the setting of the + ``beep'' variable. + + 66..33..1188.. cchhaarrsseett + + Type: string + Default: iso-8859-1 + + Character set your terminal uses to display and enter textual data. + This information is required to properly label outgoing messages which + contain 8-bit characters so that receiving parties can display your + messages in the correct character set. + + 66..33..1199.. cchheecckk__nneeww + + Type: boolean + Default: set + + NNoottee:: this option only affects _m_a_i_l_d_i_r and _M_H style mailboxes. + + When _s_e_t, Mutt will check for new mail delivered while the mailbox is + open. Especially with MH mailboxes, this operation can take quite + some time since it involves scanning the directory and checking each + file to see if it has already been looked at. If _c_h_e_c_k___n_e_w is _u_n_s_e_t, + no check for new mail is performed while the mailbox is open. + + 66..33..2200.. ccoonnffiirrmmaappppeenndd + + Type: boolean + Default: set + + When set, Mutt will prompt for confirmation when appending messages to + an existing mailbox. + + 66..33..2211.. ccoonnffiirrmmccrreeaattee + + Type: boolean + Default: set + + When set, Mutt will prompt for confirmation when saving messages to a + mailbox which does not yet exist before creating it. + + 66..33..2222.. ccooppyy + + Type: quadoption + Default: yes + + This variable controls whether or not copies of your outgoing messages + will be saved for later references. Also see ``record'', + ``save_name'', ``force_name'' and ``fcc-hook''. + + 66..33..2233.. ddaattee__ffoorrmmaatt + + Type: string + Default: "!%a, %b %d, %Y at %I:%M:%S%p %Z" + + This variable controls the format of the date printed by the ``%d'' + sequence in ``$header_format''. This is passed to the _s_t_r_f_t_i_m_e call + to process the date. See the man page for _s_t_r_f_t_i_m_e_(_3_) for the proper + syntax. + + Unless the first character in the string is a bang (``!''), the month + and week day names are expanded according to the locale specified in + the variable ``locale''. If the first character in the string is a + bang, the bang is discarded, and the month and week day names in the + rest of the string are expanded in the _C locale (that is in US + English). + + 66..33..2244.. ddeeffaauulltt__hhooookk + + Type: string + Default: "~f %s | (~P (~c %s | ~t %s))" + + This variable controls how send-hooks, save-hooks, and fcc-hooks will + be interpreted if they are specified with only a simple regexp, + instead of a matching pattern. The hooks are expanded when they are + declared, so a hook will be interpreted according to the value of this + variable at the time the hook is declared. The default value matches + if the message is either from a user matching the regular expression + given, or if it is from you (if the from address matches + ``alternates'') and is to or cc'ed to a user matching the given + regular expression. + + 66..33..2255.. ddeelleettee + + Type: quadoption + Default: ask-yes + + Controls whether or not messages are really deleted when closing or + synchronizing a mailbox. If set to _y_e_s, messages marked for deleting + will automatically be purged without prompting. If set to _n_o, + messages marked for deletion will be kept in the mailbox. + + 66..33..2266.. ddeelleettee__ffoorrmmaatt + + Type: string + Default: "[-- Attachment from %u deleted on %<%D> --]" + + This variable controls the format of the message used to replace an + attachment when the attachment is deleted. It uses the same format + sequences as the ``$header_format'' variable. + + 66..33..2277.. ddssnn__nnoottiiffyy + + Type: string + Default: none + + NNoottee:: you should not enable this unless you are using Sendmail 8.8.x + or greater. + + This variable sets the request for when notification is returned. The + string consists of a comma separated list (no spaces!) of one or more + of the following: _n_e_v_e_r, to never request notification, _f_a_i_l_u_r_e, to + request notification on transmission failure, _d_e_l_a_y, to be notified of + message delays, _s_u_c_c_e_s_s, to be notified of successful transmission. + + Example: set dsn_notify="failure,delay" + + 66..33..2288.. ddssnn__rreettuurrnn + + Type: string Default: none + + NNoottee:: you should not enable this unless you are using Sendmail 8.8.x + or greater. + + This variable controls how much of your message is returned in DSN + messages. It may be set to either _h_d_r_s to return just the message + header, or _f_u_l_l to return the full message. + + Example: set dsn_return=hdrs + + 66..33..2299.. eeddiitt__hheeaaddeerrss + + Type: boolean + Default: unset + + This option allows you to edit the header of your outgoing messages + along with the body of your message. + + 66..33..3300.. eeddiittoorr + + Type: String + Default: value of environment variable $VISUAL, $EDITOR, or "vi" + + This variable specifies which editor to use when composing messages. + + 66..33..3311.. eessccaappee + + Type: string + Default: ~ + + Escape character to use for functions in the builtin editor. + + 66..33..3322.. ffaasstt__rreeppllyy + + Type: boolean + Default: unset + + When set, the initial prompt for recipients and subject are skipped + when replying to messages, and the initial prompt for subject is + skipped when forwarding messages. + + NNoottee:: this variable has no effect when the ``$autoedit'' variable is + set. + + 66..33..3333.. ffcccc__aattttaacchh + + Type: boolean + Default: set + + This variable controls whether or not attachments on outgoing messages + are saved along with the main body of your message. + + 66..33..3344.. ffoollddeerr + + Type: String + Default: ~/Mail + + Specifies the default location of your mailboxes. A `+' or `=' at the + beginning of a pathname will be expanded to the value of this + variable. Note that if you change this variable from the default + value you need to make sure that the assignment occurs _b_e_f_o_r_e you use + `+' or `=' for any other variables since expansion takes place during + the `set' command. + + 66..33..3355.. ffoolllloowwuupp__ttoo + + Type: boolean + Default: set + + Controls whether or not the _M_a_i_l_-_F_o_l_l_o_w_u_p_-_T_o header field is generated + when sending mail. When _s_e_t, Mutt will generate this field when you + are replying to a known mailing ``lists''. + + The purpose of this field is to prevent you from receiving duplicate + copies of replies to messages which you send by specifying that you + will receive a copy of the message if it is addressed to the mailing + list (and thus there is no need to also include your address in a + group reply). + + 66..33..3366.. ffoorrccee__nnaammee + + Type: boolean + Default: unset + + This variable is similar to ``$save_name'', except that Mutt will + store a copy of your outgoing message by the username of the address + you are sending to even if that mailbox does not exist. + + Also see the ``$record'' variable. + + 66..33..3377.. ffoorrwwaarrdd__ddeeccooddee + + Type: boolean + Default: unset + + Controls the decoding of complex MIME messages into text/plain when + forwarding a message. If ``mime_forward'' is _u_n_s_e_t, then the message + header is also RFC2047 decoded (this cannot be done when forwarding a + message as a message/rfc822 attachment because it would violate the + MIME spec, which states that you must only use US-ASCII in the + header). + + 66..33..3388.. ffoorrwwaarrdd__ffoorrmmaatt + + Type: format string + Default: "[%a: %s]" + + This variable controls the default subject when forwarding a message. + It uses the same format sequences as the ``$header_format'' variable. + + 66..33..3399.. ffoorrwwaarrdd__qquuoottee + + Type: boolean + Default: unset + + When _s_e_t forwarded messages included in the main body of the message + (when ``mime_forward'' is _u_n_s_e_t) will be quoted using + ``indent_string''. + + 66..33..4400.. hheeaaddeerr__ffoorrmmaatt + + Type: format string + Default: "%4C %Z %{%b %d} %-15.15L (%4l) %s" + + This variable allows you to customize the message index display to + your personal taste. + + ``Format strings'' are similar to the strings used in the ``C'' + function printf to format output (see the man page for more detail). + The following sequences are defined in Mutt: + + %a address of the author + %b filename of the original message folder (think mailBox) + %B the list to which the letter was sent, or else the folder + name (%b). + %c number of characters (bytes) in the message + %C current message number + %d date and time of the message in the format specified by + ``date_format'' + %f entire From: line (address + real name) + %F author name, or recipient name if the message is from you + %i message-id of the current message + %l number of lines in the message + %L list-from function + %m total number of message in the mailbox + %N message score + %n author's real name (or address if missing) + %O (_O_riginal save folder) Where mutt would formerly have + stashed the message: list name or recipient name if no list + %s subject of the message + %S status of the message (N/D/d/!/*/r) + %t `to:' field (recipients) + %T the appropriate character from the $to_chars string + %u user (login) name of the author + %Z message status flags + + %{fmt} the date and time of the message is converted to sender's + time zone, and ``fmt'' is expanded by the system call + ``strftime''; a leading bang disables locales + %[fmt] the date and time of the message is converted to the local + time zone, and ``fmt'' is expanded by the system call + ``strftime''; a leading bang disables locales + %(fmt) the local date and time when the message was received. + ``fmt'' is expanded by the system call ``strftime''; + a leading bang disables locales + %<fmt> the current local time. ``fmt'' is expanded by the system + call ``strftime''; a leading bang disables locales. + + %>X right justify the rest of the string and pad with character "X" + %|X pad to the end of the line with character "X" + + See also: ``$to_chars''. + + 66..33..4411.. hhddrrss + + Type: boolean + Default: set + + When unset, the header fields normally added by the ``my_hdr'' command + are not created. This variable _m_u_s_t be unset before composing a new + message or replying in order to take effect. If set, the user defined + header fields are added to every new message. + + 66..33..4422.. hheeaaddeerr + + Type: boolean + Default: unset + + When set, this variable causes Mutt to include the _f_u_l_l header of the + message you are replying to into the edit buffer. + + 66..33..4433.. hheellpp + + Type: boolean + Default: set + + When set, help lines describing the bindings for the major functions + provided by each menu are displayed on the first line of the screen. + + NNoottee:: The binding will not be displayed correctly if the function is + bound to a sequence rather than a single keystroke. Also, the help + line may not be updated if a binding is changed while Mutt is running. + Since this variable is primarily aimed at new users, neither of these + should present a major problem. + + 66..33..4444.. hhiissttoorryy + + Type: number + Default: 10 + + This variable controls the size (in number of strings remembered) of + the string history buffer. The buffer is cleared each time the + variable is set. + + 66..33..4455.. hhoossttnnaammee + + Type: string + Default: varies + + Specifies the hostname to use after the ``@'' in local e-mail + addresses. This overrides the compile time definition obtained from + /etc/resolv.conf. + + 66..33..4466.. iiggnnoorree__lliisstt__rreeppllyy__ttoo + + Type: boolean + Default: unset + + Affects the behaviour of the _r_e_p_l_y function when replying to messages + from mailing lists. When set, if the ``Reply-To:'' field is set to + the same value as the ``To:'' field, Mutt assumes that the ``Reply- + To:'' field was set by the mailing list to automate responses to the + list, and will ignore this field. To direct a response to the mailing + list when this option is set, use the _l_i_s_t_-_r_e_p_l_y function; _g_r_o_u_p_-_r_e_p_l_y + will reply to both the sender and the list. + + 66..33..4477.. iinn__rreeppllyy__ttoo + + Type: format string + Default: "%i; from \"%n\" on %{!%a, %b %d, %Y at %I:%M:%S%p}" + + This specifies the format of the In-Reply-To: header field added when + replying to a message. For a full listing of defined escape sequences + see the section on ``$header_format''. + + 66..33..4488.. iinncclluuddee + + Type: quadoption + Default: ask-yes + + Controls whether or not a copy of the message(s) you are replying to + is included in your reply. + + 66..33..4499.. iinnddeenntt__ssttrriinngg + + Type: format string + Default: "> " + + Specifies the string to prepend to each line of text quoted in a + message to which you are replying. You are strongly encouraged not to + change this value, as it tends to agitate the more fanatical netizens. + + 66..33..5500.. iissppeellll + + Type: string + Default: "ispell" + + How to invoke ispell (GNU's spell-checking software). + + 66..33..5511.. llooccaallee + + Type: string + Default: "C" + + The locale used by _s_t_r_f_t_i_m_e_(_3_) to format dates. Legal values are the + strings your system accepts for the locale variable _L_C___T_I_M_E. + + 66..33..5522.. mmaaiillccaapp__ppaatthh + + Type: string + Default: $MAILCAPS or + ~/.mailcap:/usr/local/share/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap + + This variable specifies which files to consult when attempting to + display MIME bodies not directly supported by Mutt. + + 66..33..5533.. mmaarrkk__oolldd + + Type: Boolean + Default: set + + Controls whether or not Mutt makes the distinction between _n_e_w + messages and _o_l_d uunnrreeaadd messages. By default, Mutt will mark new + messages as old if you exit a mailbox without reading them. The next + time you start Mutt, the messages will show up with an "O" next to + them in the index menu, indicating that they are old. In order to + make Mutt treat all unread messages as new only, you can unset this + variable. + + 66..33..5544.. mmaarrkkeerrss + + Type: boolean + Default: set + + Controls the display of wrapped lines in the internal pager. If set, a + ``+'' marker is displayed at the beginning of wrapped lines. Also see + the ``$smart_wrap'' variable. + + 66..33..5555.. mmaasskk + + Type: string + Default: "^(\.\.$|[^.])" + + A regular expression used in the file browser. Files whose names don't + match this mask will not be shown. + + 66..33..5566.. mmbbooxx + + Type: String + Default: +inbox + + This specifies the folder into which read mail in your ``spoolfile'' + folder will be appended. + + 66..33..5577.. mmbbooxx__ttyyppee + + Type: String + Default: mbox + + The default mailbox type used when creating new folders. May be any of + mbox, MMDF, MH and Maildir. + + 66..33..5588.. mmeettoooo + + Type: boolean + Default: unset + + If unset, Mutt will remove your address from the list of recipients + when replying to a message. If you are replying to a message sent by + you, Mutt will also assume that you want to reply to the recipients of + that message rather than to yourself. + + 66..33..5599.. mmeennuu__ssccrroollll + + Type: boolean + Default: unset + + When _s_e_t, menus will be scrolled up or down one line when you attempt + to move across a screen boundary. If _u_n_s_e_t, the screen is cleared and + the next or previous page of the menu is displayed (useful for slow + links to avoid many redraws). + + 66..33..6600.. mmeettaa__kkeeyy + + Type: Boolean + Default: unset + + If set, forces Mutt to interpret keystrokes with the high bit (bit 8) + set as if the user had pressed the ESC key and whatever key remains + after having the high bit removed. For example, if the key pressed + has an ASCII value of 0xf4, then this is treated as if the user had + pressed ESC then ``x''. This is because the result of removing the + high bit from ``0xf4'' is ``0x74'', which is the ASCII character + ``x''. + 66..33..6611.. mmiimmee__ffoorrwwaarrdd + + Type: boolean + Default: unset + + When set, the message you are forwarding will be attached as a + separate MIME part instead of included in the main body of the + message. This is useful for forwarding MIME messages so the receiver + can properly view the message as it was delivered to you. + + Also see ``forward_decode''. + + 66..33..6622.. mmoovvee + + Type: quadoption + Default: ask-no + + Controls whether you will be asked to confirm moving read messages + from your spool mailbox to your ``$mbox'' mailbox, or as a result of a + ``mbox-hook'' command. + + 66..33..6633.. mmeessssaaggee__ffoorrmmaatt + + Type: string + Default: "%s" + + This is the string displayed in the ``attachment'' menu for + attachments of type _m_e_s_s_a_g_e_/_r_f_c_8_2_2. For a full listing of defined + escape sequences see the section on ``header_format''. + + 66..33..6644.. ppaaggeerr + + Type: string + Default: builtin + + This variable specifies which pager you would like to use to view + messages. builtin means to use the builtin pager, otherwise this + variable should specify the pathname of the external pager you would + like to use. + + 66..33..6655.. ppaaggeerr__ccoonntteexxtt + + Type: number + Default: 0 + + This variable controls the number of lines of context that are given + when displaying the next or previous page in the internal pager. By + default, Mutt will display the line after the last one on the screen + at the top of the next page (0 lines of context). + + 66..33..6666.. ppaaggeerr__ffoorrmmaatt + + Type: format string + Default: "-%S- %C/%m: %-20.20n %s" + + This variable controls the format of the one-line message ``status'' + displayed before each message in either the internal or an external + pager. The valid sequences are listed in the ``header_format'' + section. + + 66..33..6677.. ppaaggeerr__iinnddeexx__lliinneess + + Type: number + Default: 0 + + Determines the number of lines of a mini-index which is shown when in + the pager. The current message, unless near the top or bottom of the + folder, will be roughly one third of the way down this mini-index, + giving the reader the context of a few messages before and after the + message. This is useful, for example, to determine how many messages + remain to be read in the current thread. One of the lines is reserved + for the status bar from the index, so a _p_a_g_e_r___i_n_d_e_x___l_i_n_e_s of 6 will + only show 5 lines of the actual index. A value of 0 results in no + index being shown. If the number of messages in the current folder is + less than _p_a_g_e_r___i_n_d_e_x___l_i_n_e_s, then the index will only use as many + lines as it needs. + + 66..33..6688.. ppaaggeerr__ssttoopp + + Type: boolean + Default: unset + + When set, the internal-pager will nnoott move to the next message when + you are at the end of a message and invoke the _n_e_x_t_-_p_a_g_e function. + + 66..33..6699.. ppggpp__aauuttooeennccrryypptt + + Type: boolean + Default: unset + + Setting this variable will cause Mutt to always attempt to PGP/MIME + encrypt outgoing messages. This is probably only useful in connection + to the _s_e_n_d_-_h_o_o_k command. It can be overridden by use of the _p_g_p_- + _m_e_n_u, when encryption is not required or signing is requested as well. + + 66..33..7700.. ppggpp__aauuttoossiiggnn + + Type: boolean + Default: unset + + Setting this variable will cause Mutt to always attempt to PGP/MIME + sign outgoing messages. This can be overridden by use of the _p_g_p_- + _m_e_n_u, when signing is not required or encryption is requested as well. + + 66..33..7711.. ppggpp__ddeeffaauulltt__vveerrssiioonn + + Type: string + Default: pgp2 (or pgp5, if PGP 2.* is not installed) + + Set this to pgp2 (PGP 2.*), or pgp5 (PGP 5.*) depending on the + version, you are using primary. This variable is directly used, but it + is the default for the variables ``$pgp_receive_version'', + ``$pgp_send_version'', and ``$pgp_key_version''. + + 66..33..7722.. ppggpp__eennccrryyppttsseellff + + Type: boolean + Default: set + + If set, the PGP _+_e_n_c_r_y_p_t_t_o_s_e_l_f flag is used when encrypting messages. + + 66..33..7733.. ppggpp__kkeeyy__vveerrssiioonn + + Type: string + Default: ``default'' + + This variable determines, which PGP version used for key ring + operations like extracting keys from messages and extracting keys from + your keyring. If you set this to default, the default defined in + ``$pgp_default_version'' is used. Set this to pgp2 (PGP 2.*), or pgp5 + (PGP 5.*) if you want a different PGP version for key operations. + + 66..33..7744.. ppggpp__lloonngg__iiddss + + Type: boolean + Default: unset + + If set, use 64 bit PGP key IDs. Unset uses the normal 32 bit Key IDs. + + 66..33..7755.. ppggpp__rreecceeiivvee__vveerrssiioonn + + Type: string + Default: ``default'' + + This variable determines, which PGP version used for decrypting + messages and verifying signatures. If you set this to default, the + default defined in ``$pgp_default_version'' will be used. Set this to + pgp2 (PGP 2.*), or pgp5 (PGP 5.*) if you want a different PGP version + for receiving operations. + + 66..33..7766.. ppggpp__rreeppllyyeennccrryypptt + + Type: boolean + Default: unset + + If set, automatically PGP encrypt replies to messages which are + encrypted. + + 66..33..7777.. ppggpp__rreeppllyyssiiggnn + + Type: boolean + Default: unset + + If set, automatically PGP sign replies to messages which are signed. + + NNoottee:: this does not work on messages, that are encrypted aanndd signed! + + 66..33..7788.. ppggpp__sseenndd__vveerrssiioonn + + Type: string + Default: ``default'' + + This variable determines, which PGP version used for composing new + messages like encrypting and signing. If you set this to default, the + default defined in ``$pgp_default_version'' will be used. Set this to + pgp2 (PGP 2.*), or pgp5 (PGP 5.*) if you want a different PGP version + for sending operations. + + 66..33..7799.. ppggpp__ssiiggnn__aass + + Type: string + Default: unset + + If you have more than one key pair, this option allows you to specify + which of your private keys to use. It is recommended that you use the + keyid form to specify your key (e.g., ``0xABCDEFGH''). + + 66..33..8800.. ppggpp__ssiiggnn__mmiiccaallgg + + Type: string + Default: pgp-md5 + + This variable contains the default message integrity check algorithm. + Valid values are ``pgp-md5'', ``pgp-sha1'', and ``pgp-rmd160''. If you + select a signing key using the sign as option on the compose menu, + mutt will automagically figure out the correct value to insert here, + but it does not know about the user's default key. + + So if you are using an RSA key for signing, set this variable to + ``pgp-md5'', if you use a PGP 5 DSS key for signing, say ``pgp-sha1'' + here. The value of this variable will show up in the micalg parameter + of MIME headers when creating RFC 2015 signatures. + + 66..33..8811.. ppggpp__ssttrriicctt__eenncc + + Type: boolean + Default: set + + If set, Mutt will automatically encode PGP/MIME signed messages as + _q_u_o_t_e_d_-_p_r_i_n_t_a_b_l_e. Please note that unsetting this variable may lead + to problems with non-verifyable PGP signatures, so only change this if + you know what you are doing. + + 66..33..8822.. ppggpp__ttiimmeeoouutt + + Type: number + Default: 300 + + The number of seconds after which a cached passphrase will expire if + not used. + + 66..33..8833.. ppggpp__vv22 + + Type: string + Default: system dependent + + This variable allows you to override the compile time definition of + where the PGP 2.* binary resides on your system. + + 66..33..8844.. ppggpp__vv22__llaanngguuaaggee + + Type: string + Default: en + + Sets the language, which PGP 2.* should use. If you use language.txt + from the mutt doc directory, you can try the languages "mutt" + (English) or "muttde" (German) to reduce the noise produced by pgp. + + 66..33..8855.. ppggpp__vv22__ppuubbrriinngg + + Type: string + Default: $PGPPATH/pubring.pgp or ~/.pgp/pubring.pgp if $PGPPATH isn't + set. + + Points to the PGP 2.* public keyring. + + 66..33..8866.. ppggpp__vv22__sseeccrriinngg + + Type: string + Default: $PGPPATH/secring.pgp or ~/.pgp/secring.pgp if $PGPPATH isn't + set. + + Points to the PGP 2.* secret keyring. + + 66..33..8877.. ppggpp__vv55 + + Type: string + Default: system dependent + + This variable allows you to override the compile time definition of + where the PGP 5.* binary resides on your system. + + 66..33..8888.. ppggpp__vv55__llaanngguuaaggee + + Type: string + Default: en + + Sets the language, which PGP 5.* should use. If you use language50.txt + from the mutt doc directory, you can try the languages "mutt" + (English) to reduce the noise produced by pgp. + + 66..33..8899.. ppggpp__vv55__ppuubbrriinngg + + Type: string + Default: $PGPPATH/pubring.pkr or ~/.pgp/pubring.pkr if $PGPPATH isn't + set. + + Points to the PGP 5.* public keyring. + + 66..33..9900.. ppggpp__vv55__sseeccrriinngg + + Type: string + Default: $PGPPATH/secring.skr or ~/.pgp/secring.skr if $PGPPATH isn't + set. + + Points to the PGP 5.* secret keyring. + + 66..33..9911.. ppiippee__ddeeccooddee + + Type: boolean + Default: unset + + Used in connection with the _p_i_p_e_-_m_e_s_s_a_g_e command. When unset, Mutt + will pipe the messages without any preprocessing. When set, Mutt will + weed headers and will attempt to PGP/MIME decode the messages first. + + 66..33..9922.. ppiippee__sseepp + + Type: string + Default: newline + + The separator to add between messages when piping a list of tagged + messages to an external Unix command. + + 66..33..9933.. ppiippee__sspplliitt + + Type: boolean + Default: unset + + Used in connection with the _p_i_p_e_-_m_e_s_s_a_g_e command and the ``tag- + prefix'' operator. If this variable is unset, when piping a list of + tagged messages Mutt will concatenate the messages and will pipe them + as a single folder. When set, Mutt will pipe the messages one by one. + In both cases the the messages are piped in the current sorted order, + and the ``$pipe_sep'' separator is added after each message. + + 66..33..9944.. ppoopp__ddeelleettee + + Type: boolean + Default: unset + + If set, Mutt will delete successfully downloaded messages from the POP + server when using the fetch-mail function. When unset, Mutt will + download messages but also leave them on the POP server. + + 66..33..9955.. ppoopp__hhoosstt + + Type: string + Default: none + + The name or address of your POP3 server. + + 66..33..9966.. ppoopp__ppaassss + + Type: string + Default: unset + + Specifies the password for you POP account. If unset, Mutt will + prompt you for your password when you invoke the fetch-mail function. + WWaarrnniinngg: you should only use this option when you are on a fairly + secure machine, because the superuser can read your muttrc even if you + are the only one who can read the file. + + 66..33..9977.. ppoopp__ppoorrtt + + Type: number + Default: 110 + + This variable specifies which port your POP server is listening on. + + 66..33..9988.. ppoopp__uusseerr + + Type: string + Default: login name on local system + + Your login name on the POP3 server. + + 66..33..9999.. ppoosstt__iinnddeenntt__ssttrriinngg + + Type: format string + Default: none + + Similar to the ``$attribution'' variable, Mutt will append this string + after the inclusion of a message which is being replied to. + + 66..33..110000.. ppoossttppoonnee + + Type: quadoption + Default: ask-yes + + Controls whether or not messages are saved in the ``$postponed'' + mailbox when you elect not to send immediately. + + 66..33..110011.. ppoossttppoonneedd + + Type: string + Default: ~/postponed + + Mutt allows you to indefinitely ``postpone sending a message'' which + you are editing. When you choose to postpone a message, Mutt saves it + in the folder specified by this variable. Also see the ``$postpone'' + variable. + + 66..33..110022.. pprriinntt + + Type: quadoption + Default: ask-no + + Controls whether or not Mutt asks for confirmation before printing. + This is useful for people (like me) who accidentally hit ``p'' often. + + 66..33..110033.. pprriinntt__ccoommmmaanndd + + Type: string + Default: lpr + + This specifies the command pipe that should be used to print messages. + + 66..33..110044.. pprroommpptt__aafftteerr + + Type: boolean + Default: set + + If you use an _e_x_t_e_r_n_a_l ``pager'', setting this variable will cause + Mutt to prompt you for a command when the pager exits rather than + returning to the index menu. If unset, Mutt will return to the index + menu when the external pager exits. + + 66..33..110055.. qquueerryy__ccoommmmaanndd + + Type: string + Default: null + + This specifies the command that mutt will use to make external address + queries. The string should contain a %s, which will be substituted + with the query string the user types. See ``query'' for more + information. + + 66..33..110066.. qquuoottee__rreeggeexxpp + + Type: string + Default: "^([ \t]*[>|#:}])+" + + A regular expression used in the internal-pager to determine quoted + sections of text in the body of a message. + + NNoottee:: In order to use the _q_u_o_t_e_dxx patterns in the internal pager, you + need to set this to a regular expression that matches _e_x_a_c_t_l_y the + quote characters at the beginning of quoted lines. + + 66..33..110077.. rreeaadd__iinncc + + Type: number + Default: 10 + + If set to a value greater than 0, Mutt will display which message it + is currently on when reading a mailbox. The message is printed after + _r_e_a_d___i_n_c messages have been read (e.g., if set to 25, Mutt will print + a message when it reads message 25, and then again when it gets to + message 50). This variable is meant to indicate progress when reading + large mailboxes which may take some time. + + When set to 0, only a single message will appear before the reading + the mailbox. + + Also see the ``$write_inc'' variable. + + 66..33..110088.. rreeaadd__oonnllyy + + Type: boolean + Default: unset + + If set, all folders are opened in read-only mode. + + 66..33..110099.. rreeaallnnaammee + + Type: string + Default: GCOS field from /etc/passwd + + This variable specifies what "real" or "personal" name should be used + when sending messages. + + 66..33..111100.. rreeccaallll + + Type: quadoption + Default: ask-yes + + Controls whether or not you are prompted to recall postponed messages + when composing a new message. Also see ``postponed'' + + 66..33..111111.. rreeccoorrdd + + Type: string + Default: none + + This specifies the file into which your outgoing messages should be + appended. (This is meant as the primary method for saving a copy of + your messages, but another way to do this is using the ``my_hdr'' + command to create a _B_c_c_: field with your email address in it.) + + The value of _$_r_e_c_o_r_d is overridden by the ``$force_name'' and + ``$save_name'' variables, and the ``fcc-hook'' command. + + 66..33..111122.. rreeppllyy__rreeggeexxpp + + Type: string + Default: "^(re|aw):[ \t]*" + + A regular expression used to recognize reply messages when threading + and replying. The default value corresponds to the English "Re:" and + the German "Aw:". + + 66..33..111133.. rreeppllyy__ttoo + + Type: quadoption + Default: ask-yes + + If set, Mutt will ask you if you want to use the address listed in the + Reply-To: header field when replying to a message. If you answer no, + it will use the address in the From: header field instead. This + option is useful for reading a mailing list that sets the Reply-To: + header field to the list address and you want to send a private + message to the author of a message. + + 66..33..111144.. rreessoollvvee + + Type: boolean + Default: set + + When set, the cursor will be automatically advanced to the next + (possibly undeleted) message whenever a command that modifies the + current message is executed. + + 66..33..111155.. rreevveerrssee__aalliiaass + + Type: boolean + Default: unset + + This variable controls whether or not Mutt will display the "personal" + name from your aliases in the index menu if it finds an alias that + matches the message's sender. For example, if you have the following + alias: + + alias juser abd30425@somewhere.net (Joe User) + + and then you receive mail which contains the following header: + + From: abd30425@somewhere.net + + It would be displayed in the index menu as ``Joe User'' instead of + ``abd30425@somewhere.net.'' This is useful when the person's e-mail + address is not human friendly (like Compu$erve addresses). + + 66..33..111166.. rreevveerrssee__nnaammee + + Type: boolean + Default: unset + + It may sometimes arrive that you receive mail to a certain machine, + move the messages to another machine, and reply to some the messages + from there. If this variable is set, the default _F_r_o_m_: line of the + reply messages is built using the address where you received the + messages you are replying to. If the variable is unset, the _F_r_o_m_: + line will use your address on the current machine. + + 66..33..111177.. ssaavvee__aaddddrreessss + + Type: boolean + Default: unset + + If set, mutt will take the sender's full address when choosing a + default folder for saving a mail. If ``save_name'' or ``force_name'' + is set too, the selection of the fcc folder will be changed as well. + + 66..33..111188.. ssaavvee__eemmppttyy + + Type: boolean + Default: set + + When unset, mailboxes which contain no saved messages will be removed + when closed (the exception is ``spoolfile'' which is never removed). + If set, mailboxes are never removed. + + NNoottee:: This only applies to mbox and MMDF folders, Mutt does not delete + MH and Maildir directories. + + 66..33..111199.. ssaavvee__nnaammee + + Type: boolean + Default: unset + + This variable controls how copies of outgoing messages are saved. + When set, a check is made to see if a mailbox specified by the + recipient address exists (this is done by searching for a mailbox in + the ``folder'' directory with the _u_s_e_r_n_a_m_e part of the recipient + address). If the mailbox exists, the outgoing message will be saved + to that mailbox, otherwise the message is saved to the ``record'' + mailbox. + + Also see the ``$force_name'' variable. + + 66..33..112200.. sseennddmmaaiill + + Type: string + Default: /usr/lib/sendmail -oi -oem + Specifies the program and arguments used to deliver mail sent by Mutt. + Mutt expects that the specified program will read the message header + for recipients. + + 66..33..112211.. sseennddmmaaiill__wwaaiitt + + Type: number + Default: 0 + + Specifies the number of seconds to wait for the ``sendmail'' process + to finish before giving up and putting delivery in the background. + + Mutt interprets the value of this variable as follows: + + >0 number of seconds to wait for sendmail to finish before continuing + 0 wait forever for sendmail to finish + <0 always put sendmail in the background without waiting + + Note that if you specify a value other than 0, the output of the child + process will be put in a temporary file. If there is some error, you + will be informed as to where to find the output. + + 66..33..112222.. sshheellll + + Type: string + Default: retrieved from passwd file + + Command to use when spawning a subshell. + + 66..33..112233.. ssiigg__ddaasshheess + + Type: boolean + Default: set + + If set, a line containing ``-- '' will be inserted before your + ``signature''. It is ssttrroonnggllyy recommended that you not unset this + variable unless your ``signature'' contains just your name. The + reason for this is because many software packages use ``-- \n'' to + detect your signature. For example, Mutt has the ability to highlight + the signature in a different color in the builtin pager. + + 66..33..112244.. ssiiggnnaattuurree + + Type: string + Default: ~/.signature + + Specifies the filename of your signature, which is appended to all + outgoing messages. If the filename ends with a pipe (``|''), it is + assumed that filename is a shell command and input should be read from + its stdout. + + 66..33..112255.. ssiimmppllee__sseeaarrcchh + + Type: string + Default: "~f %s | ~s %s" + + Specifies how Mutt should expand a simple search into a real search + pattern. A simple search is one that does not contain any of the ~ + operators. See ``searching'' for more information on search patterns. + For example, if you simply type joe at a search or limit prompt, Mutt + will automatically expand it to the value specified by this variable. + For the default value it would be: + + ~f joe | ~s joe + + 66..33..112266.. ssmmaarrtt__wwrraapp + + Type: boolean + Default: set + + Controls the display of lines longer then the screen width in the + internal pager. If set, long lines are wrapped at a word boundary. If + unset, lines are simply wrapped at the screen edge. Also see the + ``$markers'' variable. + + 66..33..112277.. ssoorrtt + + Type: string + Default: date-sent + + Specifies how to sort messages in the _i_n_d_e_x menu. Valid values are + + +o date-sent + + +o date-received + + +o from + + +o mailbox-order (unsorted) + + +o score + + +o subject + + +o threads + + +o to + + You may optionally use the reverse- prefix to specify reverse sorting + order (example: set sort=reverse-date-sent). + + 66..33..112288.. ssoorrtt__aalliiaass + + Type: string + Default: alias + + Specifies how the entries in the `alias' menu are sorted. The + following are legal values: + + alias sort alphabetically by alias name + address sort alphabetically by email address + unsorted leave in order specified in .muttrc + + 66..33..112299.. ssoorrtt__aauuxx + + Type: string + Default: date-sent + + When sorting by threads, this variable controls how threads are sorted + in relation to other threads, and how the branches of the thread trees + are sorted. This can be set to any value that ``sort'' can, except + threads (in that case, mutt will just use date-sent). You can also + specify the last- prefix in addition to the reverse- prefix, but last- + must come after reverse-. The last- prefix causes messages to be + sorted against its siblings by which has the last descendant, using + the rest of sort_aux as an ordering. For instance, set sort_aux=last- + date-received would mean that if a new message is received in a + thread, that thread becomes the last one displayed (or the first, if + you have set sort=reverse-threads.) + + 66..33..113300.. ssoorrtt__bbrroowwsseerr + + Type: string + + Specifies how to sort entries in the file browser. By default, the + entries are sorted alphabetically. Valid values: + + +o date + + +o alpha (alphabetically) + + You may optionally use the reverse- prefix to specify reverse sorting + order (example: set sort_browser=reverse-date). + + 66..33..113311.. ssppoooollffiillee + + Type: string + Default: most likely /var/mail/$USER or /usr/spool/mail/$USER + + If your spool mailbox is in a non-default place where Mutt cannot find + it, you can specify its location with this variable. Mutt will + automatically set this variable to the value of the environment + variable $MAIL if it is not set. + + 66..33..113322.. ssoorrtt__rree + + Type: boolean Default: set + + This variable is only useful when sorting by threads with + ``strict_threads'' unset. In that case, it changes the heuristic mutt + uses to thread messages by subject. With sort_re set, mutt will only + attach a message as the child of another message by subject if the + subject of the child message starts with a substring matching the + setting of ``reply_regexp''. With sort_re unset, mutt will attach the + message whether or not this is the case, as long as the + non-``reply_regexp'' parts of both messages are identical. + + 66..33..113333.. ssttaattuuss__cchhaarrss + + Type: string + Default: "-*%" + + Controls the characters used by the "%r" indicator in + ``status_format''. The first character is used when the mailbox is + unchanged. The second is used when the mailbox has been changed, and + it needs to be resynchronized. The third is used if the mailbox is in + read-only mode, or if the mailbox will not be written when exiting + that mailbox (You can toggle whether to write changes to a mailbox + with the toggle-write operation, bound by default to "%"). + 66..33..113344.. ssttaattuuss__ffoorrmmaatt + + Type: string + Default: "-%r-Mutt: %f [Msgs:%?M?%M/?%m%?n? New:%n?%?o? Old:%o?%?d? + Del:%d?%?F? Flag:%F?%?t? Tag:%t?%?p? Post:%p?%?b? Inc:%b? %?l? + %l?]---(%s/%S)-%>-(%P)---" + + Controls the format of the status line displayed in the _i_n_d_e_x menu. + This string is similar to ``$header_format'', but has its own set of + printf()-like sequences: + + %b number of mailboxes with new mail * + %d number of deleted messages * + %h local hostname + %f the full pathname of the current mailbox + %F number of flagged messages * + %l size (in bytes) of the current mailbox * + %L size (in bytes) of the messages shown (i.e., which match the current limit) * + %m the number of messages in the mailbox * + %M the number of messages shown (i.e., which match the current limit) * + %n number of new messages in the mailbox * + %o number of old unread messages + %p number of postponed messages * + %P percentage of the way through the index + %r modified/read-only/won't-write indicator, according to $status_chars + %s current sorting mode ($sort) + %S current aux sorting method ($sort_aux) + %t number of tagged messages * + %u number of unread messages * + %v Mutt version string + + %>X right justify the rest of the string and pad with "X" + %|X pad to the end of the line with "X" + + * = can be optionally printed if nonzero + + Some of the above sequences can be used to optionally print a string + if their value is nonzero. For example, you may only want to see the + number of flagged messages if such messages exist, since zero is not + particularly meaningful. To optionally print a string based upon one + of the above sequences, the following construct is used + + %?<sequence_char>?<optional_string>? + + where _s_e_q_u_e_n_c_e___c_h_a_r is a character from the table above, and + _o_p_t_i_o_n_a_l___s_t_r_i_n_g is the string you would like printed if _s_t_a_t_u_s___c_h_a_r is + nonzero. _o_p_t_i_o_n_a_l___s_t_r_i_n_g mmaayy contain other sequence as well as normal + text, but you may nnoott nest optional strings. + + Here is an example illustrating how to optionally print the number of + new messages in a mailbox: + + %?n?%n new messages.? + + 66..33..113355.. ssttaattuuss__oonn__ttoopp + + Type: boolean + Default: unset + + Setting this variable causes the ``status bar'' to be displayed on the + first line of the screen rather than near the bottom. + + 66..33..113366.. ssttrriicctt__tthhrreeaaddss + + Type: boolean + Default: unset + + If set, threading will only make use of the ``In-Reply-To'' and + ``References'' fields when ``sorting'' by message threads. By + default, messages with the same subject are grouped together in + ``pseudo threads.'' This may not always be desirable, such as in a + personal mailbox where you might have several unrelated messages with + the subject ``hi'' which will get grouped together. + + 66..33..113377.. ssuussppeenndd + + Type: boolean + Default: set + + When _u_n_s_e_t, mutt won't stop when the user presses the terminal's _s_u_s_p + key, usually ``control-Z''. This is useful if you run mutt inside an + xterm using a command like xterm -e mutt. + + 66..33..113388.. tthhoorroouugghh__sseeaarrcchh + + Type: boolean + Default: unset + + Affects the _~_b and _~_h search operations described in section + ``Searching'' above. If set, the headers and attachments of messages + to be searched are decoded before searching. If unset, messages are + searched as they appear in the folder. + + 66..33..113399.. ttiillddee + + Type: boolean + Default: unset + + When set, the internal-pager will pad blank lines to the bottom of the + screen with a tilde (~). + + 66..33..114400.. ttiimmeeoouutt + + Type: number + Default: 600 + + This variable controls the _n_u_m_b_e_r _o_f _s_e_c_o_n_d_s Mutt will wait for a key + to be pressed in the main menu before timing out and checking for new + mail. A value of zero or less will cause Mutt not to ever time out. + + 66..33..114411.. ttmmppddiirr + + Type: string + Default: /tmp + This variable allows you to specify where Mutt will place its + temporary files needed for displaying and composing messages. + + 66..33..114422.. ttoo__cchhaarrss + + Type: string + Default: " +TCF" + + Controls the character used to indicate mail addressed to you. The + first character is the one used when the mail is NOT addressed to your + address (default: space). The second is used when you are the only + recipient of the message (default: +). The third is when your address + appears in the TO header field, but you are not the only recipient of + the message (default: T). The fourth character is used when your + address is specified in the CC header field, but you are not the only + recipient. The fifth character is used to indicate mail that was sent + by _y_o_u. + + 66..33..114433.. uussee__88bbiittmmiimmee + + Type: boolean + Default: unset + + WWaarrnniinngg:: do not set this variable unless you are using a version of + sendmail which supports the -B8BITMIME flag (such as sendmail 8.8.x) + or you may not be able to send mail. + + When _s_e_t, Mutt will invoke ``$sendmail'' with the -B8BITMIME flag when + sending 8-bit messages to enable ESMTP negotiation. + + 66..33..114444.. uussee__ddoommaaiinn + + Type: boolean + Default: set + + When set, Mutt will qualify all local addresses (ones without the + @host portion) with the value of ``$hostname''. If _u_n_s_e_t, no + addresses will be qualified. + + 66..33..114455.. uussee__ffrroomm + + Type: boolean + Default: set + + When _s_e_t, Mutt will generate the `From:' header field when sending + messages. If _u_n_s_e_t, no `From:' header field will be generated unless + the user explicitly sets one using the ``my_hdr'' command. + + 66..33..114466.. uussee__mmaaiillccaapp + + Type: quad-option + Default: ask + + If set to ``yes'', always try to use a mailcap entry to display a MIME + part that Mutt can't understand what to do with. If ``ask'', prompt + as to whether to display as text or to use a mailcap entry. If + ``no'', always view unsupported MIME types as text. + + NNoottee:: For compatibility with mmeettaammaaiill, Mutt will also look at the + environment variable _M_M___N_O_A_S_K. Setting this to 11 is equivalent to + setting _u_s_e___m_a_i_l_c_a_p to ``yes''. Otherwise, the value of _M_M___N_O_A_S_K is + interpreted as a comma-separated list of type names (without white + space) for which the corresponding mailcap entries will be used to + display MIME parts without prompting the user for confirmation. + + 66..33..114477.. ppggpp__vveerriiffyy__ssiigg + + Type: quad-option + Default: yes + + If ``yes'', always attempt to verify PGP/MIME signatures. If ``ask'', + ask whether or not to verify the signature. If ``no'', never attempt + to verify PGP/MIME signatures. + + 66..33..114488.. vviissuuaall + + Type: string + Default: $VISUAL + + Specifies the visual editor to invoke when the _~_v command is given in + the builtin editor. + + 66..33..114499.. wwaaiitt__kkeeyy + + Type: boolean + Default: set + + Controls whether Mutt will ask you to press a key after _s_h_e_l_l_-_e_s_c_a_p_e, + _p_i_p_e_-_m_e_s_s_a_g_e, _p_i_p_e_-_e_n_t_r_y, _p_r_i_n_t_-_m_e_s_s_a_g_e, and _p_r_i_n_t_-_e_n_t_r_y commands. + + It is also used when viewing attachments with ``autoview'', provided + that the corresponding mailcap entry has a _n_e_e_d_s_t_e_r_m_i_n_a_l flag, and the + external program is interactive. + + When set, Mutt will always ask for a key. When unset, Mutt will wait + for a key only if the external command returned a non-zero status. + + 66..33..115500.. wwrriittee__iinncc + + Type: number + Default: 10 + + When writing a mailbox, a message will be printed every _w_r_i_t_e___i_n_c + messages to indicate progress. If set to 0, only a single message + will be displayed before writing a mailbox. + + Also see the ``$read_inc'' variable. + + 66..44.. FFuunnccttiioonnss + + The following is the list of available functions listed by the mapping + in which they are available. The default key setting is given, and an + explanation of what the function does. The key bindings of these + functions can be changed with the ``bind'' command. + + 66..44..11.. ggeenneerriicc + + The _g_e_n_e_r_i_c menu is not a real menu, but specifies common functions + (such as movement) available in all menus except for _p_a_g_e_r and _e_d_i_t_o_r. + Changing settings for this menu will affect the default bindings for + all menus (except as noted). + + bottom-page L move to the bottom of the page + current-bottom not bound move current entry to bottom of page + current-middle not bound move current entry to middle of page + current-top not bound move current entry to top of page + enter-command : enter a muttrc command + exit q exit this menu + first-entry = move to the first entry + half-down ] scroll down 1/2 page + half-up [ scroll up 1/2 page + help ? this screen + jump number jump to an index number + last-entry * move to the last entry + middle-page M move to the middle of the page + next-entry j move to the next entry + next-line > scroll down one line + next-page z move to the next page + previous-entry k move to the previous entry + previous-line < scroll up one line + previous-page Z move to the previous page + refresh ^L clear and redraw the screen + search / search for a regular expression + search-next n search for next match + search-opposite not bound search for next match in opposite direction + search-reverse ESC / search backwards for a regular expression + select-entry RET select the current entry + shell-escape ! run a program in a subshell + tag-entry t toggle the tag on the current entry + tag-prefix ; apply next command to tagged entries + top-page H move to the top of the page + + 66..44..22.. iinnddeexx + + bounce-message b remail a message to another user + change-folder c open a different folder + change-folder-readonly ESC c open a different folder in read only mode + clear-flag W clear a status flag from a message + copy-message C copy a message to a file/mailbox + create-alias a create an alias from a message sender + decode-copy ESC C decode a message and copy it to a file/mailbox + decode-save ESC s decode a message and save it to a file/mailbox + delete-message d delete the current entry + delete-pattern D delete messages matching a pattern + delete-subthread ESC d delete all messages in subthread + delete-thread ^D delete all messages in thread + display-address @ display full address of sender + display-headers h display message with full headers + display-message RET display a message + exit x exit without saving changes + extract-keys ^K extract PGP public keys + fetch-mail G retrieve mail from POP server + flag-message F toggle a message's 'important' flag + forget-passphrase ^F wipe PGP passphrase from memory + forward-message f forward a message with comments + group-reply g reply to all recipients + limit l show only messages matching a pattern + list-reply L reply to specified mailing list + mail m compose a new mail message + mail-key ESC k mail a PGP public key + next-new TAB jump to the next new message + next-subthread ESC n jump to the next subthread + next-thread ^N jump to the next thread + next-undeleted j move to the next undeleted message + next-unread not bound jump to the next unread message + pipe-message | pipe message/attachment to a shell command + previous-new ESC TAB jump to the previous new message + previous-page Z move to the previous page + previous-subthread ESC p jump to previous subthread + previous-thread ^P jump to previous thread + previous-undeleted k move to the last undelete message + previous-unread not bound jump to the previous unread message + print-message p print the current entry + query Q query external program for addresses + quit q save changes to mailbox and quit + read-subthread ESC r mark the current subthread as read + read-thread ^R mark the current thread as read + recall-message R recall a postponed message + reply r reply to a message + save-message s save message/attachment to a file + set-flag w set a status flag on a message + show-version V show the Mutt version number and date + sort-mailbox o sort messages + sort-reverse O sort messages in reverse order + sync-mailbox $ save changes to mailbox + tag-pattern T tag messages matching a pattern + tag-thread ESC t tag/untag all messages in the current thread + toggle-new N toggle a message's 'new' flag + toggle-write % toggle whether the mailbox will be rewritten + undelete-message u undelete the current entry + undelete-pattern U undelete messages matching a pattern + undelete-subthread ESC u undelete all messages in subthread + undelete-thread ^U undelete all messages in thread + untag-pattern ^T untag messages matching a pattern + view-attachments v show MIME attachments + + 66..44..33.. ppaaggeerr + + bottom $ jump to the bottom of the message + bounce-message b remail a message to another user + change-folder c open a different folder + change-folder-readonly ESC c open a different folder in read only mode + copy-message C copy a message to a file/mailbox + create-alias a create an alias from a message sender + decode-copy ESC C decode a message and copy it to a file/mailbox + decode-save ESC s decode a message and save it to a file/mailbox + delete-message d delete the current entry + delete-subthread ESC d delete all messages in subthread + delete-thread ^D delete all messages in thread + display-address @ display full address of sender + display-headers h display message with full headers + enter-command : enter a muttrc command + exit i return to the main-menu + extract-keys ^K extract PGP public keys + flag-message F toggle a message's 'important' flag + forget-passphrase ^F wipe PGP passphrase from memory + forward-message f forward a message with comments + group-reply g reply to all recipients + half-up not bound move up one-half page + half-down not bound move down one-half page + help ? this screen + list-reply L reply to specified mailing list + mail m compose a new mail message + mail-key ESC k mail a PGP public key + mark-as-new N toggle a message's 'new' flag + next-line RET scroll down one line + next-message J move to the next entry + next-new TAB jump to the next new message + next-page move to the next page + next-subthread ESC n jump to the next subthread + next-thread ^N jump to the next thread + next-undeleted j move to the next undeleted message + next-unread not bound jump to the next unread message + pipe-message | pipe message/attachment to a shell command + previous-line BackSpace scroll up one line + previous-message K move to the previous entry + previous-new not bound jump to the previous new message + previous-page - move to the previous page + previous-subthread ESC p jump to previous subthread + previous-thread ^P jump to previous thread + previous-undeleted k move to the last undelete message + previous-unread not bound jump to the previous unread message + print-message p print the current entry + quit Q save changes to mailbox and quit + read-subthread ESC r mark the current subthread as read + read-thread ^R mark the current thread as read + recall-message R recall a postponed message + redraw-screen ^L clear and redraw the screen + reply r reply to a message + save-message s save message/attachment to a file + search / search for a regular expression + search-next n search for next match + search-opposite not bound search for next match in opposite direction + search-reverse ESC / search backwards for a regular expression + search-toggle \ toggle search pattern coloring + shell-escape ! invoke a command in a subshell + show-version V show the Mutt version number and date + skip-quoted S skip beyond quoted text + tag-message t tag a message + toggle-quoted T toggle display of quoted text + top ^ jump to the top of the message + undelete-message u undelete the current entry + undelete-subthread ESC u undelete all messages in subthread + undelete-thread ^U undelete all messages in thread + view-attachments v show MIME attachments + + 66..44..44.. aalliiaass + + search / search for a regular expression + search-next n search for next match + search-reverse ESC / search backwards for a regular expression + + 66..44..55.. qquueerryy + + create-alias a create an alias from a message sender + mail m compose a new mail message + query Q query external program for addresses + query-append A append new query results to current results + search / search for a regular expression + search-next n search for next match + search-opposite not bound search for next match in opposite direction + search-reverse ESC / search backwards for a regular expression + + 66..44..66.. aattttaacchh + + bounce-message b remail a message to another user + decode-copy ESC C decode a message and copy it to a file/mailbox + decode-save ESC s decode a message and save it to a file/mailbox + delete-entry d delete the current entry + display-headers h display message with full headers + extract-keys ^K extract PGP public keys + forward-message f forward a message with comments + group-reply g reply to all recipients + list-reply L reply to specified mailing list + pipe-entry | pipe message/attachment to a shell command + print-entry p print the current entry + reply r reply to a message + save-entry s save message/attachment to a file + undelete-entry u undelete the current entry + view-attach RET view attachment using mailcap entry if necessary + view-mailcap m force viewing of attachment using mailcap + view-text T view attachment as text + + 66..44..77.. ccoommppoossee + + attach-file a attach a file(s) to this message + attach-key ESC k attach a PGP public key + copy-file C save message/attachment to a file + detach-file D delete the current entry + display-headers h display message with full headers + edit-bcc b edit the BCC list + edit-cc c edit the CC list + edit-description d edit attachment description + edit-encoding ^E edit attachment trasfer-encoding + edit-fcc f enter a file to save a copy of this message in + edit-from ESC f edit the from: field + edit-file ^X e edit the file to be attached + edit-headers E edit the message with headers + edit-message e edit the message + edit-mime m edit attachment using mailcap entry + edit-reply-to r edit the Reply-To field + edit-subject s edit the subject of this message + edit-to t edit the TO list + edit-type ^T edit attachment type + filter-entry F filter attachment through a shell command + forget-passphrase ^F wipe PGP passphrase from memory + ispell i run ispell on the message + new-mime n compose new attachment using mailcap entry + pgp-menu p show PGP options + pipe-entry | pipe message/attachment to a shell command + postpone-message P save this message to send later + print-entry l print the current entry + rename-file R rename/move an attached file + send-message y send the message + toggle-unlink u toggle whether to delete file after sending it + view-attach RET view attachment using mailcap entry if necessary + + 66..44..88.. ppoossttppoonneedd + + delete-entry d delete the current entry + undelete-entry u undelete the current entry + + 66..44..99.. bbrroowwsseerr + + change-dir c change directories + check-new TAB check mailboxes for new mail + enter-mask m enter a file mask + search / search for a regular expression + search-next n search for next match + search-reverse ESC / search backwards for a regular expression + select-new N select a new file in this directory + sort o sort messages + sort-reverse O sort messages in reverse order + + 66..44..1100.. ppggpp + + view-name % view the key's user id + verify-key c verify a PGP public key + + 66..44..1111.. eeddiittoorr + + backspace BackSpace delete the char in front of the cursor + backward-char ^B move the cursor one character to the left + bol ^A jump to the beginning of the line + buffy-cycle Space cycle among incoming mailboxes + complete TAB complete filename or alias + complete-query ^T complete address with query + delete-char ^D delete the char under the cursor + eol ^E jump to the end of the line + forward-char ^F move the cursor one character to the right + history-down not bound scroll up through the history list + history-up not bound scroll up through the history list + kill-eol ^K delete chars from cursor to end of line + kill-line ^U delete all chars on the line + kill-word ^W delete the word in front of the cursor + quote-char ^V quote the next typed key + + 77.. MMiisscceellllaannyy + + 77..11.. AAcckknnoowwlleeddggeemmeennttss + + Kari Hurrta <kari.hurtta@fmi.fi> co-developed the original MIME + parsing code back in the ELM-ME days. + + The following people have been very helpful to the development of + Mutt: + + Francois Berjon <Francois.Berjon@aar.alcatel-alsthom.fr>, + Aric Blumer <aric@fore.com>, + John Capo <jc@irbs.com>, + Liviu Daia <daia@stoilow.imar.ro>, + David DeSimone <fox@convex.hp.com>, + Nickolay N. Dudorov <nnd@wint.itfs.nsk.su>, + Michael Finken <finken@conware.de>, + Sven Guckes <guckes@math.fu-berlin.de>, + Mark Holloman <holloman@nando.net>, + Andreas Holzmann <holzmann@fmi.uni-passau.de>, + David Jeske <jeske@igcom.net>, + Christophe Kalt <kalt@hugo.int-evry.fr>, + Felix von Leitner (a.k.a ``Fefe'') <leitner@math.fu-berlin.de>, + Brandon Long <blong@fiction.net>, + Lars Marowsky-Bree <lmb@pointer.in-minden.de>, + Thomas ``Mike'' Michlmayr <mike@cosy.sbg.ac.at>, + David O'Brien <obrien@Nuxi.cs.ucdavis.edu>, + Clint Olsen <olsenc@ichips.intel.com>, + Park Myeong Seok <pms@romance.kaist.ac.kr>, + Thomas Parmelan <tom@ankh.fr.eu.org>, + Ollivier Robert <roberto@keltia.freenix.fr>, + Thomas Roessler <roessler@guug.de>, + Allain Thivillon <Allain.Thivillon@alma.fr>, + Ken Weinert <kenw@ihs.com> + + 77..22.. AAbboouutt tthhiiss ddooccuummeenntt + + This document was written in SGML, and then rendered using the sgml- + tools package. + diff --git a/doc/mutt.man b/doc/mutt.man new file mode 100644 index 00000000..7fcc3ced --- /dev/null +++ b/doc/mutt.man @@ -0,0 +1,259 @@ +.if n .ds Q \&" +.if t .ds Q `` +.if n .ds U \&" +.if t .ds U '' +.TH "Mutt" 1 +.tr \& +.nr bi 0 +.nr ll 0 +.nr el 0 +.de DS +.. +.de DE +.. +.de Pp +.ie \\n(ll>0 \{\ +.ie \\n(bi=1 \{\ +.nr bi 0 +.if \\n(t\\n(ll=0 \{.IP \\(bu\} +.if \\n(t\\n(ll=1 \{.IP \\n+(e\\n(el.\} +.\} +.el .sp +.\} +.el \{\ +.ie \\nh=1 \{\ +.LP +.nr h 0 +.\} +.el .PP +.\} +.. +.SH NAME + +.Pp +mutt - The Mutt Mail User Agent +.Pp +.SH SYNOPSIS + +.Pp +mutt [ -hnpRvxyzZ ] +[ -a \fIfile\fP ] +[ -b \fIaddress\fP ] +[ -c \fIaddress\fP ] +[ -e \fIcommand\fP ] +[ -f \fImailbox\fP ] +[ -F \fImuttrc\fP ] +[ -H \fIdraftfile\fP ] +[ -i \fIinclude\fP ] +[ -m \fItype\fP ] +[ -s \fIsubject\fP ] +[ \fIaddress\fP ... ] +.Pp +.SH DESCRIPTION + +.Pp +Mutt is a small but very powerful text based program for reading electronic +mail under unix operating systems, including support color terminals, MIME, +and a threaded sorting mode. +.Pp +.SH OPTIONS + +.Pp +.nr ll +1 +.nr t\n(ll 2 +.if \n(ll>1 .RS +.IP "-a \fIfile\fP" +.nr bi 1 +.Pp +Attach a file to your message using MIME. +.IP "-b \fIaddress\fP" +.nr bi 1 +.Pp +Specify a blind-carbon-copy (BCC) recipient +.IP "-c \fIaddress\fP" +.nr bi 1 +.Pp +Specify a carbon-copy (CC) recipient +.IP "-e \fIcommand\fP" +.nr bi 1 +.Pp +Specify a configuration command to be run after processing of initialization +files. +.IP "-f \fImailbox\fP" +.nr bi 1 +.Pp +Specify which mailbox to load. +.IP "-F \fImuttrc\fP" +.nr bi 1 +.Pp +Specify an initialization file to read instead of ~/.muttrc +.IP "-h" +.nr bi 1 +.Pp +Display help. +.IP "-H \fIdraft\fP" +.nr bi 1 +.Pp +Specify a draft file which contains header and body to use to send a +message. +.IP "-i \fIinclude\fP" +.nr bi 1 +.Pp +Specify a file to include into the body of a message. +.IP "-m \fItype\fP " +.nr bi 1 +.Pp +specify a default mailbox type +.IP "-n" +.nr bi 1 +.Pp +Causes Mutt to bypass the system configuration file. +.IP "-p" +.nr bi 1 +.Pp +Resume a postponed message. +.IP "-R" +.nr bi 1 +.Pp +Open a mailbox in \fIread-only\fP mode. +.IP "-s \fIsubject\fP" +.nr bi 1 +.Pp +Specify the subject of the message. +.IP "-v" +.nr bi 1 +.Pp +Display the Mutt version number and compile-time definitions. +.IP "-x" +.nr bi 1 +.Pp +Emulate the mailx compose mode. +.IP "-y" +.nr bi 1 +.Pp +Start Mutt with a listing of all mailboxes specified by the \fImailboxes\fP +command. +.IP "-z" +.nr bi 1 +.Pp +When used with -f, causes Mutt not to start if there are no messages in the +mailbox. +.IP "-Z" +.nr bi 1 +.Pp +Causes Mutt to open the first mailbox specified by the \fImailboxes\fP +command which contains new mail. +.if \n(ll>1 .RE +.nr ll -1 +.Pp +.SH ENVIRONMENT + +.Pp +.nr ll +1 +.nr t\n(ll 2 +.if \n(ll>1 .RS +.IP "EDITOR" +.nr bi 1 +.Pp +Editor to invoke when composing a message. +.IP "HOME" +.nr bi 1 +.Pp +Full path of the user's home directory. +.IP "MAIL" +.nr bi 1 +.Pp +Full path of the user's spool mailbox. +.IP "MAILCAPS" +.nr bi 1 +.Pp +Path to search for mailcap files. +.IP "MM_NOASK" +.nr bi 1 +.Pp +If this variable is set, mailcap are always used without prompting first. +.IP "PGPPATH" +.nr bi 1 +.Pp +Directory in which the user's PGP public keyring can be found. +.IP "TMPDIR" +.nr bi 1 +.Pp +Directory in which temporary files are created. +.IP "REPLYTO" +.nr bi 1 +.Pp +Default Reply-To address. +.IP "VISUAL" +.nr bi 1 +.Pp +Editor to invoke when the ~v command is given in the builtin editor. +.if \n(ll>1 .RE +.nr ll -1 +.Pp +.SH FILES + +.Pp +.nr ll +1 +.nr t\n(ll 2 +.if \n(ll>1 .RS +.IP "~/.muttrc" +.nr bi 1 +.Pp +User configuration file. +.IP "/usr/local/share/Muttrc" +.nr bi 1 +.Pp +System-wide configuration file. +.IP "/tmp/muttXXXXXX" +.nr bi 1 +.Pp +Temporary files created by Mutt. +.IP "~/.mailcap" +.nr bi 1 +.Pp +User definition for handling non-text MIME types. +.IP "/usr/local/share/mailcap" +.nr bi 1 +.Pp +System definition for handing non-text MIME types. +.IP "~/.mime.types" +.nr bi 1 +.Pp +User's personal mapping between MIME types and file extensions. +.IP "/usr/local/share/mime.types" +.nr bi 1 +.Pp +System mapping between MIME types and file extensions. +.Pp +.if \n(ll>1 .RE +.nr ll -1 +.Pp +.SH BUGS + +.Pp +suspend/resume while editing a file with an external editor does not work +under SunOS 4.x if you use the curses lib in /usr/5lib. It \fIdoes\fP work +with the S-Lang library, however. +.Pp +Resizing the screen while using an external pager causes Mutt to go haywire +on some systems. +.Pp +suspend/resume does not work under Ultrix. +.Pp +The help line for the index menu is not updated if you change the bindings +for one of the functions listed while Mutt is running. +.Pp +.SH SEE ALSO + +.Pp +curses(3), ncurses(3), sendmail(1), smail(1), mailcap(5) +.Pp +Mutt Home Page: http://www.cs.hmc.edu/~me/mutt/ +.Pp +.SH AUTHOR + +.Pp +Michael Elkins <me@cs.hmc.edu> +.Pp +http://www.cs.hmc.edu/~me \ No newline at end of file diff --git a/doc/mutt.sgml b/doc/mutt.sgml new file mode 100644 index 00000000..169fe056 --- /dev/null +++ b/doc/mutt.sgml @@ -0,0 +1,181 @@ +<!doctype linuxdoc system> + +<manpage title="Mutt" sectnum="1"> + +<sect1>NAME +<p> +mutt - The Mutt Mail User Agent + +<sect1>SYNOPSIS +<p> +mutt [ -hnpRvxyzZ ] +[ -a <em/file/ ] +[ -b <em/address/ ] +[ -c <em/address/ ] +[ -e <em/command/ ] +[ -f <em/mailbox/ ] +[ -F <em/muttrc/ ] +[ -H <em/draftfile/ ] +[ -i <em/include/ ] +[ -m <em/type/ ] +[ -s <em/subject/ ] +&lsqb <em/address/ ... ] + +<sect1>DESCRIPTION +<p> +Mutt is a small but very powerful text based program for reading electronic +mail under unix operating systems, including support color terminals, MIME, +and a threaded sorting mode. + +<sect1>OPTIONS +<p> +<descrip> +<tag>-a <em/file/ +<p> +Attach a file to your message using MIME. +<tag>-b <em/address/ +<p> +Specify a blind-carbon-copy (BCC) recipient +<tag>-c <em/address/ +<p> +Specify a carbon-copy (CC) recipient +<tag>-e <em/command/ +<p> +Specify a configuration command to be run after processing of initialization +files. +<tag>-f <em/mailbox/ +<p> +Specify which mailbox to load. +<tag>-F <em/muttrc/ +<p> +Specify an initialization file to read instead of ˜/.muttrc +<tag>-h +<p> +Display help. +<tag>-H <em/draft/ +<p> +Specify a draft file which contains header and body to use to send a +message. +<tag>-i <em/include/ +<p> +Specify a file to include into the body of a message. +<tag>-m <em/type/ +<p> +specify a default mailbox type +<tag>-n +<p> +Causes Mutt to bypass the system configuration file. +<tag>-p +<p> +Resume a postponed message. +<tag>-R +<p> +Open a mailbox in <em/read-only/ mode. +<tag>-s <em/subject/ +<p> +Specify the subject of the message. +<tag>-v +<p> +Display the Mutt version number and compile-time definitions. +<tag>-x +<p> +Emulate the mailx compose mode. +<tag>-y +<p> +Start Mutt with a listing of all mailboxes specified by the <em/mailboxes/ +command. +<tag>-z +<p> +When used with -f, causes Mutt not to start if there are no messages in the +mailbox. +<tag>-Z +<p> +Causes Mutt to open the first mailbox specified by the <em/mailboxes/ +command which contains new mail. +</descrip> + +<sect1>ENVIRONMENT +<p> +<descrip> +<tag>EDITOR +<p> +Editor to invoke when composing a message. +<tag>HOME +<p> +Full path of the user's home directory. +<tag>MAIL +<p> +Full path of the user's spool mailbox. +<tag>MAILCAPS +<p> +Path to search for mailcap files. +<tag>MM_NOASK +<p> +If this variable is set, mailcap are always used without prompting first. +<tag>PGPPATH +<p> +Directory in which the user's PGP public keyring can be found. +<tag>TMPDIR +<p> +Directory in which temporary files are created. +<tag>REPLYTO +<p> +Default Reply-To address. +<tag>VISUAL +<p> +Editor to invoke when the ˜v command is given in the builtin editor. +</descrip> + +<sect1>FILES +<p> +<descrip> +<tag>˜/.muttrc +<p> +User configuration file. +<tag>/usr/local/share/Muttrc +<p> +System-wide configuration file. +<tag>/tmp/muttXXXXXX +<p> +Temporary files created by Mutt. +<tag>˜/.mailcap +<p> +User definition for handling non-text MIME types. +<tag>/usr/local/share/mailcap +<p> +System definition for handing non-text MIME types. +<tag>˜/.mime.types +<p> +User's personal mapping between MIME types and file extensions. +<tag>/usr/local/share/mime.types +<p> +System mapping between MIME types and file extensions. +<p> +</descrip> + +<sect1>BUGS +<p> +suspend/resume while editing a file with an external editor does not work +under SunOS 4.x if you use the curses lib in /usr/5lib. It <em/does/ work +with the S-Lang library, however. +<p> +Resizing the screen while using an external pager causes Mutt to go haywire +on some systems. +<p> +suspend/resume does not work under Ultrix. +<p> +The help line for the index menu is not updated if you change the bindings +for one of the functions listed while Mutt is running. + +<sect1>SEE ALSO +<p> +curses(3), ncurses(3), sendmail(1), smail(1), mailcap(5) +<p> +Mutt Home Page: http://www.cs.hmc.edu/˜me/mutt/ + +<sect1>AUTHOR +<p> +Michael Elkins <me@cs.hmc.edu> +<p> +http://www.cs.hmc.edu/˜me +</manpage> diff --git a/doc/pgp-Notes.txt b/doc/pgp-Notes.txt new file mode 100644 index 00000000..fc064f89 --- /dev/null +++ b/doc/pgp-Notes.txt @@ -0,0 +1,162 @@ + Some notes on Mutt's PGP integration + + 1997-12-04, tlr <roessler@guug.de> + + Last updated: 1998-03-11, tlr + + +While encryption, verification and signing of messages are +done by an externally invoked PGP binary, the key +selection process is handled by mutt itself. The public +key ring (2.6 or 5.0 format) is parsed; PGP's cached trust +parameters are evaluated and used to select the proper +numerical key IDs for a message's recipients. These key +IDs are then passed to the external PGP binary on the +command line. + + +Recent Changes +-------------- + +$pgp_pubring, $pgp_language, $pgp_secring, and $pgp are gone. +They have been replaced by the following variables: + + pgp_v2_language pgp_v5_language + pgp_v2_pubring pgp_v5_pubring + pgp_v2_secring pgp_v5_secring + pgp_v2 pgp_v5 + +For all of these variables, we use "reasonable" defaults. +This includes a fix for the outstanding "pkr/skr" problem +for people using pgp 5. + +$pgp_version has been split up into a bunch of variables: + + pgp_default_version + pgp_send_version + pgp_receive_version + pgp_key_version + +The latter three may be set to the value "default" (which +is the default ;-); in this case, the value of +$pgp_default_version will be used instead. + +$pgp_send_version is the version of pgp used for composing +new messages. $pgp_receive version is used for decrypting +messages and verifying signatures. $pgp_key_version is the +one which is used for key ring operations (extracting keys +from messages, extracting keys from your public key ring). + +Valid values for _all_ variables include "pgp5", "pgp3", +"pgp2"; "pgp3" and "pgp5" are equivalent. "g10" has been +removed for now: The program has changed it's name to +GNUPG; Support for that program will be included soon. +Support will be added as soon as the current state of the +code turns out to be stable. + + +A new variable named $pgp_sign_micalg has been introduced. +It contains the default message integrity check algorithm. +Valid values are "pgp-md5", "pgp-sha1", and "pgp-rmd160". +If you select a signing key using the "sign as" option on +the compose menu, mutt will automagically figure out the +correct value to insert here, but it does not know about +the user's default key. + +So if you are using an RSA key for signing, set this +variable to "pgp-md5", if you use a PGP 5 DSS key for +signing, say "pgp-sha1" here. The value of this variable +will show up in the "micalg" parameter of MIME headers +when creating RFC 2015 signatures. + + + +Frequently Asked Questions and Tips +----------------------------------- + +Q: "How do it get PGP 5 support working?" + +It should work out of the box - just put the following +into your ~/.muttrc: + + set pgp_default_version=pgp5 + + +Q: "People are sending PGP messages which mutt doesn't + recognize. What can I do?" + +Add the following lines to your ~/.procmailrc (you are +using procmail, aren't you?): + +------------------------------ + + ## + ## PGP + ## + + :0 H + * ^Content-Type: text + { + :0 fBw + * ^-----BEGIN PGP MESSAGE----- + | formail -I "Content-Type: application/pgp; format=text; x-action=encryptsign" + + :0 fBw + * ^-----BEGIN PGP SIGNED MESSAGE----- + | formail -I "Content-Type: application/pgp; format=text; x-action=sign" + } + + ## + ## Add a "Content-Type: application/pgp" header so Mutt will know the + ## mail is encrypted. + ## + + :0 fBw + * ^-----BEGIN PGP MESSAGE----- + | formail -a "Content-Type: application/pgp; format=text; x-action=encryptsign" + + ## + ## Add a "Content-Type: application/pgp" header so Mutt will know the + ## mail is signed. + ## + + :0 fBw + * ^-----BEGIN PGP SIGNED MESSAGE----- + | formail -a "Content-Type: application/pgp; format=text; x-action=sign" + +------------------------------ + + +Q: "I don't like that PGP/MIME stuff, but want to use the + old way of PGP-signing my mails. Can't you include + that with mutt?" + +No. Application/pgp is not really suited to a world with +MIME, non-textual body parts and similar things. Anyway, +if you really want to generate these old-style +attachments, include the following macro in your ~/.muttrc +(line breaks for readibility, this is actually one line): + + macro compose S "Fpgp +verbose=0 -fast + +clearsig=on\ny^T^Uapplication/pgp; format=text; + x-action=sign\n" + + + +Q: "I don't like all the ^Gs and various other verbosity + PGP is presenting me with." + +Roland Rosenfeld <roland@spinnaker.rhein.de> has found a +quite elegant solution to this problem: PGP has some +pretty good foreign language support. So we just +introduce a language called "mutt" which contains empty +strings for the messages we don't want to see. To use +this, copy either language.txt or language50.txt +(depending on what PGP version you are using) to your +$PGPPATH and add the following line to your muttrc: + + set pgp_language="mutt" + +For PGP 2.6, a German version called "muttde" is available +as well. + diff --git a/doc/style-guide b/doc/style-guide new file mode 100644 index 00000000..1ac44ab6 --- /dev/null +++ b/doc/style-guide @@ -0,0 +1,18 @@ +Mutt Programming Style Guide +============================ + +This information is meant for those of you who are hacking on Mutt and +submitting patches to me. If you follow these guidelines, it will make it +much easier for me to integrate patches. + +- global functions should have the prefix "mutt_". All other functions + should be declared "static". + +- avoid global vars where possible. If one is required, try to contain it + to a single source file and declare it "static". Global vars should have + the first letter of each word capitilized, and no underscores should be + used (e.g., MailGid, LastFolder, MailDir). + +- re-use code as much as possible. There are a lot of "library" functions. + One of the biggest causes of bloat in ELM and PINE is the tremendous + duplication of code... Help keep Mutt small! diff --git a/edit.c b/edit.c new file mode 100644 index 00000000..3f02f0bf --- /dev/null +++ b/edit.c @@ -0,0 +1,458 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Close approximation of the mailx(1) builtin editor for sending mail. */ + +#include "mutt.h" +#include "mutt_curses.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> + +/* + * SLcurses_waddnstr() can't take a "const char *", so this is only + * declared "static" (sigh) + */ +static char EditorHelp[] = "\ +~~ insert a line begining with a single ~\n\ +~b users add users to the Bcc: field\n\ +~c users add users to the Cc: field\n\ +~f messages include messages\n\ +~F messages same as ~f, except also include headers\n\ +~h edit the message header\n\ +~m messages include and quote messages\n\ +~M messages same as ~m, except include headers\n\ +~p print the message\n\ +~q write file and quit editor\n\ +~r file read a file into the editor\n\ +~t users add users to the To: field\n\ +~u recall the previous line\n\ +~v edit message with the $visual editor\n\ +~w file write message to file\n\ +~x abort changes and quit editor\n\ +~? this message\n\ +. on a line by itself ends input\n"; + +static char ** +be_snarf_data (FILE *f, char **buf, int *bufmax, int *buflen, int offset, + int bytes, int prefix) +{ + char tmp[HUGE_STRING]; + char *p = tmp; + int tmplen = sizeof (tmp); + + tmp[sizeof (tmp) - 1] = 0; + if (prefix) + { + strfcpy (tmp, Prefix, sizeof (tmp)); + tmplen = strlen (tmp); + p = tmp + tmplen; + tmplen = sizeof (tmp) - tmplen; + } + + fseek (f, offset, 0); + while (bytes > 0) + { + if (fgets (p, tmplen - 1, f) == NULL) break; + bytes -= strlen (p); + if (*bufmax == *buflen) + safe_realloc ((void **)&buf, sizeof (char *) * (*bufmax += 25)); + buf[(*buflen)++] = safe_strdup (tmp); + } + if (buf) buf[*buflen] = NULL; + return (buf); +} + +static char ** +be_snarf_file (const char *path, char **buf, int *max, int *len, int verbose) +{ + FILE *f; + char tmp[LONG_STRING]; + struct stat sb; + + if ((f = fopen (path, "r"))) + { + fstat (fileno (f), &sb); + buf = be_snarf_data (f, buf, max, len, 0, sb.st_size, 0); + if (verbose) + { + snprintf(tmp, sizeof(tmp), "\"%s\" %d bytes\n", path, sb.st_size); + addstr(tmp); + } + fclose (f); + } + else + { + snprintf(tmp, sizeof(tmp), "%s: %s\n", path, strerror(errno)); + addstr(tmp); + } + return (buf); +} + +static int be_barf_file (const char *path, char **buf, int buflen) +{ + FILE *f; + int i; + + if ((f = fopen (path, "w")) == NULL) + { + addstr (strerror (errno)); + addch ('\n'); + return (-1); + } + for (i = 0; i < buflen; i++) fputs (buf[i], f); + if (fclose (f) == 0) return 0; + printw ("fclose: %s\n", strerror (errno)); + return (-1); +} + +static void be_free_memory (char **buf, int buflen) +{ + while (buflen-- > 0) + free (buf[buflen]); + if (buf) + free (buf); +} + +static char ** +be_include_messages (char *msg, char **buf, int *bufmax, int *buflen, + int pfx, int inc_hdrs) +{ + int offset, bytes, n; + char tmp[LONG_STRING]; + + while ((msg = strtok (msg, " ,")) != NULL) + { + n = atoi (msg); + if (n > 0 && n <= Context->msgcount) + { + n--; + + /* add the attribution */ + if (Attribution) + { + mutt_make_string (tmp, sizeof (tmp) - 1, Attribution, Context->hdrs[n]); + strcat (tmp, "\n"); + } + + if (*bufmax == *buflen) + safe_realloc ((void **) &buf, sizeof (char *) * (*bufmax += 25)); + buf[(*buflen)++] = safe_strdup (tmp); + + bytes = Context->hdrs[n]->content->length; + if (inc_hdrs) + { + offset = Context->hdrs[n]->offset; + bytes += Context->hdrs[n]->content->offset - offset; + } + else + offset = Context->hdrs[n]->content->offset; + buf = be_snarf_data (Context->fp, buf, bufmax, buflen, offset, bytes, + pfx); + + if (*bufmax == *buflen) + safe_realloc ((void **)&buf, sizeof (char *) * (*bufmax += 25)); + buf[(*buflen)++] = safe_strdup ("\n"); + } + else + printw ("%d: invalid message number.\n", n); + msg = NULL; + } + return (buf); +} + +static void be_print_header (ENVELOPE *env) +{ + char tmp[HUGE_STRING]; + + if (env->to) + { + addstr ("To: "); + tmp[0] = 0; + rfc822_write_address (tmp, sizeof (tmp), env->to); + addstr (tmp); + addch ('\n'); + } + if (env->cc) + { + addstr ("Cc: "); + tmp[0] = 0; + rfc822_write_address (tmp, sizeof (tmp), env->cc); + addstr (tmp); + addch ('\n'); + } + if (env->bcc) + { + addstr ("Bcc: "); + tmp[0] = 0; + rfc822_write_address (tmp, sizeof (tmp), env->bcc); + addstr (tmp); + addch ('\n'); + } + if (env->subject) + { + addstr ("Subject: "); + addstr (env->subject); + addch ('\n'); + } + addch ('\n'); +} + +/* args: + * force override the $ask* vars (used for the ~h command) + */ +static void be_edit_header (ENVELOPE *e, int force) +{ + char tmp[HUGE_STRING]; + + move (LINES-1, 0); + + addstr ("To: "); + tmp[0] = 0; + rfc822_write_address (tmp, sizeof (tmp), e->to); + if (!e->to || force) + { + if (mutt_enter_string ((unsigned char *) tmp, sizeof (tmp), LINES-1, 4, 0) == 0) + { + rfc822_free_address (&e->to); + e->to = mutt_parse_adrlist (e->to, tmp); + e->to = mutt_expand_aliases (e->to); + tmp[0] = 0; + rfc822_write_address (tmp, sizeof (tmp), e->to); + mvaddstr (LINES - 1, 4, tmp); + } + } + else + { + addstr (tmp); + } + addch ('\n'); + + if (!e->subject || force) + { + addstr ("Subject: "); + strfcpy (tmp, e->subject ? e->subject: "", sizeof (tmp)); + if (mutt_enter_string ((unsigned char *) tmp, sizeof (tmp), LINES-1, 9, 0) == 0) + { + safe_free ((void **) &e->subject); + e->subject = safe_strdup (tmp); + } + addch ('\n'); + } + + if ((!e->cc && option (OPTASKCC)) || force) + { + addstr ("Cc: "); + tmp[0] = 0; + rfc822_write_address (tmp, sizeof (tmp), e->cc); + if (mutt_enter_string ((unsigned char *) tmp, sizeof (tmp), LINES-1, 4, 0) == 0) + { + rfc822_free_address (&e->cc); + e->cc = mutt_parse_adrlist (e->cc, tmp); + e->cc = mutt_expand_aliases (e->cc); + tmp[0] = 0; + rfc822_write_address (tmp, sizeof (tmp), e->cc); + mvaddstr (LINES - 1, 4, tmp); + } + addch ('\n'); + } + + if (option (OPTASKBCC) || force) + { + addstr ("Bcc: "); + tmp[0] = 0; + rfc822_write_address (tmp, sizeof (tmp), e->bcc); + if (mutt_enter_string ((unsigned char *) tmp, sizeof (tmp), LINES-1, 5, 0) == 0) + { + rfc822_free_address (&e->bcc); + e->bcc = mutt_parse_adrlist (e->bcc, tmp); + e->bcc = mutt_expand_aliases (e->bcc); + tmp[0] = 0; + rfc822_write_address (tmp, sizeof (tmp), e->bcc); + mvaddstr (LINES - 1, 5, tmp); + } + addch ('\n'); + } +} + +int mutt_builtin_editor (const char *path, HEADER *msg, HEADER *cur) +{ + char **buf = NULL; + int bufmax = 0, buflen = 0; + char tmp[LONG_STRING]; + int abort = 0; + int done = 0; + int i; + char *p; + + scrollok (stdscr, TRUE); + + be_edit_header (msg->env, 0); + + addstr ("(End message with a . on a line by itself)\n"); + + buf = be_snarf_file (path, buf, &bufmax, &buflen, 0); + + tmp[0] = 0; + while (!done) + { + if (mutt_enter_string ((unsigned char *) tmp, sizeof (tmp), LINES-1, 0, 0) == -1) + { + tmp[0] = 0; + continue; + } + addch ('\n'); + + if (tmp[0] == EscChar[0] && tmp[1] != EscChar[0]) + { + /* remove trailing whitespace from the line */ + p = tmp + strlen (tmp) - 1; + while (p >= tmp && ISSPACE (*p)) + *p-- = 0; + + p = tmp + 2; + SKIPWS (p); + + switch (tmp[1]) + { + case '?': + addstr (EditorHelp); + break; + case 'b': + msg->env->bcc = mutt_parse_adrlist (msg->env->bcc, p); + msg->env->bcc = mutt_expand_aliases (msg->env->bcc); + break; + case 'c': + msg->env->cc = mutt_parse_adrlist (msg->env->cc, p); + msg->env->cc = mutt_expand_aliases (msg->env->cc); + break; + case 'h': + be_edit_header (msg->env, 1); + break; + case 'F': + case 'f': + case 'm': + case 'M': + if (Context) + { + if (!*p && cur) + { + /* include the current message */ + p = tmp + strlen (tmp) + 1; + snprintf (tmp + strlen (tmp), sizeof (tmp) - strlen (tmp), " %d", + cur->msgno + 1); + } + buf = be_include_messages (p, buf, &bufmax, &buflen, + (tolower (tmp[1]) == 'm'), + (isupper (tmp[1]))); + } + else + addstr ("No mailbox.\n"); + break; + case 'p': + addstr ("-----\n"); + addstr ("Message contains:\n"); + be_print_header (msg->env); + for (i = 0; i < buflen; i++) + addstr (buf[i]); + addstr ("(continue)\n"); + break; + case 'q': + done = 1; + break; + case 'r': + if (*p) + buf = be_snarf_file (p, buf, &bufmax, &buflen, 1); + else + addstr ("missing filename.\n"); + break; + case 's': + safe_free ((void **) &msg->env->subject); + msg->env->subject = safe_strdup (p); + break; + case 't': + msg->env->to = rfc822_parse_adrlist (msg->env->to, p); + msg->env->to = mutt_expand_aliases (msg->env->to); + break; + case 'u': + if (buflen) + { + buflen--; + strfcpy (tmp, buf[buflen], sizeof (tmp)); + tmp[strlen (tmp)-1] = 0; + free (buf[buflen]); + buf[buflen] = NULL; + continue; + } + else + addstr ("No lines in message.\n"); + break; + + case 'e': + case 'v': + if (be_barf_file (path, buf, buflen) == 0) + { + be_free_memory (buf, buflen); + buf = NULL; + bufmax = buflen = 0; + + if (option (OPTEDITHDRS)) + mutt_edit_headers (Visual, path, msg, NULL, 0); + else + mutt_edit_file (Visual, path); + + buf = be_snarf_file (path, buf, &bufmax, &buflen, 0); + + addstr ("(continue)\n"); + } + break; + case 'w': + be_barf_file (*p ? p : path, buf, buflen); + break; + case 'x': + abort = 1; + done = 1; + break; + default: + printw ("%s: unknown editor command (~? for help)\n", tmp); + break; + } + } + else if (strcmp (".", tmp) == 0) + done = 1; + else + { + strcat (tmp, "\n"); + if (buflen == bufmax) + safe_realloc ((void **)&buf, sizeof (char *) * (bufmax += 25)); + buf[buflen++] = safe_strdup (tmp[1] == '~' ? tmp + 1 : tmp); + } + + tmp[0] = 0; + } + + if (!abort) be_barf_file (path, buf, buflen); + be_free_memory (buf, buflen); + + return (abort ? -1 : 0); +} diff --git a/enter.c b/enter.c new file mode 100644 index 00000000..a7981ccc --- /dev/null +++ b/enter.c @@ -0,0 +1,525 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_menu.h" +#include "mutt_curses.h" +#include "keymap.h" + +#include <termios.h> +#include <sys/types.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> + +/* macro to print control chars in reverse-video */ +#define ADDCH(x) addch (IsPrint (x) ? x : (ColorDefs[MT_COLOR_MARKERS] | (x + '@'))) + +/* global vars used for the string-history routines */ +static char **Hist = NULL; +static short HistCur = 0; +static short HistLast = 0; + +void mutt_init_history (void) +{ + int i; + static int OldSize = 0; + + if (Hist) + { + for (i = 0 ; i < OldSize ; i ++) + safe_free ((void **) &Hist[i]); + safe_free ((void **) &Hist); + } + + if (HistSize) + Hist = safe_calloc (HistSize, sizeof (char *)); + HistCur = 0; + HistLast = 0; + OldSize = HistSize; +} + +static void sh_add (char *s) +{ + int prev; + + if (!HistSize) + return; /* disabled */ + + if (*s) + { + prev = HistLast - 1; + if (prev < 0) prev = HistSize - 1; + if (!Hist[prev] || strcmp (Hist[prev], s) != 0) + { + safe_free ((void **) &Hist[HistLast]); + Hist[HistLast++] = safe_strdup (s); + if (HistLast > HistSize - 1) + HistLast = 0; + } + } + HistCur = HistLast; /* reset to the last entry */ +} + +static char *sh_next (void) +{ + int next; + + if (!HistSize) + return (""); /* disabled */ + + next = HistCur + 1; + if (next > HistLast - 1) + next = 0; + if (Hist[next]) + HistCur = next; + return (Hist[HistCur] ? Hist[HistCur] : ""); +} + +static char *sh_prev (void) +{ + int prev; + + if (!HistSize) + return (""); /* disabled */ + + prev = HistCur - 1; + if (prev < 0) + { + prev = HistLast - 1; + if (prev < 0) + { + prev = HistSize - 1; + while (prev > 0 && Hist[prev] == NULL) + prev--; + } + } + if (Hist[prev]) + HistCur = prev; + return (Hist[HistCur] ? Hist[HistCur] : ""); +} + +/* redraw flags for mutt_enter_string() */ +enum +{ + M_REDRAW_INIT = 1, /* recalculate lengths */ + M_REDRAW_LINE, /* redraw entire line */ + M_REDRAW_EOL, /* redraw from current position to eol */ + M_REDRAW_PREV_EOL /* redraw from curpos-1 to eol */ +}; + +/* Returns: + * 1 need to redraw the screen and call me again + * 0 if input was given + * -1 if abort. + * + */ +int mutt_enter_string (unsigned char *buf, size_t buflen, int y, int x, + int flags) +{ + int curpos = 0; /* the location of the cursor */ + int lastchar = 0; /* offset of the last char in the string */ + int begin = 0; /* first character displayed on the line */ + int ch; /* last typed character */ + int width = COLS - x - 1; /* width of field */ + int redraw = M_REDRAW_INIT; /* when/what to redraw */ + int pass = (flags == M_PASS); + int first = 1; + int j; + char tempbuf[_POSIX_PATH_MAX] = ""; + + FOREVER + { + if (redraw) + { + if (redraw == M_REDRAW_INIT) + { + /* full redraw */ + lastchar = curpos = strlen ((char *) buf); + begin = lastchar - width; + } + if (begin < 0) + begin = 0; + switch (redraw) + { + case M_REDRAW_PREV_EOL: + j = curpos - 1; + break; + case M_REDRAW_EOL: + j = curpos; + break; + default: + j = begin; + } + move (y, x + j - begin); + for (; j < lastchar && j < begin + width; j++) + ADDCH (buf[j]); + clrtoeol (); + if (redraw != M_REDRAW_INIT) + move (y, x + curpos - begin); + redraw = 0; + } + mutt_refresh (); + + /* first look to see if a keypress is an editor operation. km_dokey() + * returns 0 if there is no entry in the keymap, so restore the last + * keypress and continue normally. + */ + if ((ch = km_dokey (MENU_EDITOR)) == -1) + { + buf[curpos] = 0; + return (-1); + } + + if (ch != 0) + { + first = 0; /* make sure not to clear the buffer */ + switch (ch) + { + case OP_EDITOR_HISTORY_UP: + if (!pass) + { + strfcpy ((char *) buf, sh_prev (), buflen); + redraw = M_REDRAW_INIT; + } + break; + case OP_EDITOR_HISTORY_DOWN: + if (!pass) + { + strfcpy ((char *) buf, sh_next (), buflen); + redraw = M_REDRAW_INIT; + } + break; + case OP_EDITOR_BACKSPACE: + if (curpos == 0) + { + BEEP (); + break; + } + for (j = curpos ; j < lastchar ; j++) + buf[j - 1] = buf[j]; + curpos--; + lastchar--; + if (!pass) + { + if (curpos > begin) + { + if (lastchar == curpos) + { + move (y, x + curpos - begin); + delch (); + } + else + redraw = M_REDRAW_EOL; + } + else + { + begin -= width / 2; + redraw = M_REDRAW_LINE; + } + } + break; + case OP_EDITOR_BOL: + /* reposition the cursor at the begininning of the line */ + curpos = 0; + if (!pass) + { + if (begin) + { + /* the first char is not displayed, so readjust */ + begin = 0; + redraw = M_REDRAW_LINE; + } + else + move (y, x); + } + break; + case OP_EDITOR_EOL: + curpos = lastchar; + if (!pass) + { + if (lastchar < begin + width) + move (y, x + lastchar - begin); + else + { + begin = lastchar - width / 2; + redraw = M_REDRAW_LINE; + } + } + break; + case OP_EDITOR_KILL_LINE: + lastchar = curpos = 0; + if (!pass) + { + begin = 0; + redraw = M_REDRAW_LINE; + } + break; + case OP_EDITOR_KILL_EOL: + lastchar = curpos; + if (!pass) + clrtoeol (); + break; + case OP_EDITOR_BACKWARD_CHAR: + if (curpos == 0) + { + BEEP (); + } + else + { + curpos--; + if (!pass) + { + if (curpos < begin) + { + begin -= width / 2; + redraw = M_REDRAW_LINE; + } + else + move (y, x + curpos - begin); + } + } + break; + case OP_EDITOR_FORWARD_CHAR: + if (curpos == lastchar) + { + BEEP (); + } + else + { + curpos++; + if (!pass) + { + if (curpos >= begin + width) + { + begin = curpos - width / 2; + redraw = M_REDRAW_LINE; + } + else + move (y, x + curpos - begin); + } + } + break; + case OP_EDITOR_DELETE_CHAR: + if (curpos != lastchar) + { + for (j = curpos; j < lastchar; j++) + buf[j] = buf[j + 1]; + lastchar--; + if (!pass) + redraw = M_REDRAW_EOL; + } + else + BEEP (); + break; + case OP_EDITOR_KILL_WORD: + /* delete to begining of word */ + if (curpos != 0) + { + j = curpos; + while (j > 0 && ISSPACE (buf[j - 1])) + j--; + if (j > 0) + { + if (isalnum (buf[j - 1])) + { + for (j--; j > 0 && isalnum (buf[j - 1]); j--) + ; + } + else + j--; + } + ch = j; /* save current position */ + while (curpos < lastchar) + buf[j++] = buf[curpos++]; + lastchar = j; + curpos = ch; /* restore current position */ + /* update screen */ + if (!pass) + { + if (curpos < begin) + { + begin = curpos - width / 2; + redraw = M_REDRAW_LINE; + } + else + redraw = M_REDRAW_EOL; + } + } + break; + case OP_EDITOR_BUFFY_CYCLE: + if (flags & M_EFILE) + { + first = 1; /* clear input if user types a real key later */ + buf[curpos] = 0; + mutt_buffy ((char *) buf); + redraw = M_REDRAW_INIT; + break; + } + else if (!(flags & M_FILE)) + goto self_insert; + /* fall through to completion routine (M_FILE) */ + + case OP_EDITOR_COMPLETE: + if (flags & M_CMD) + { + buf[curpos] = 0; + for (j = curpos - 1; j >= 0 && buf[j] != ' '; j--); + if (strcmp (tempbuf, (char *) buf) == 0) + { + mutt_select_file ((char *) buf + j + 1, buflen - j - 1, 0); + set_option (OPTNEEDREDRAW); + return (1); + } + if (mutt_complete ((char *) buf + j + 1) == 0) + strfcpy (tempbuf, (char *) buf + j + 1, sizeof (tempbuf)); + else + BEEP (); + redraw = M_REDRAW_INIT; + } + else if (flags & M_ALIAS) + { + /* invoke the alias-menu to get more addresses */ + buf[curpos] = 0; + if (curpos) + { + for (j = curpos - 1 ; j >= 0 && buf[j] != ' ' && buf[j] != ',' ; j--); + if (mutt_alias_complete ((char *) buf + j + 1, buflen - j - 1)) + { + redraw = M_REDRAW_INIT; + continue; + } + } + else + mutt_alias_menu ((char *) buf, buflen, Aliases); + return (1); + } + else if (flags & (M_FILE | M_EFILE)) + { + buf[curpos] = 0; + + /* see if the path has changed from the last time */ + if (strcmp (tempbuf, (char *) buf) == 0) + { + mutt_select_file ((char *) buf, buflen, 0); + set_option (OPTNEEDREDRAW); + if (buf[0]) + { + mutt_pretty_mailbox ((char *) buf); + sh_add ((char *) buf); + return (0); + } + return (-1); + } + + if (mutt_complete ((char *) buf) == 0) + strfcpy (tempbuf, (char *) buf, sizeof (tempbuf)); + else + BEEP (); /* let the user know that nothing matched */ + redraw = M_REDRAW_INIT; + } + else + goto self_insert; + break; + + case OP_EDITOR_COMPLETE_QUERY: + if (flags & M_ALIAS) + { + /* invoke the query-menu to get more addresses */ + buf[curpos] = 0; + if (curpos) + { + for (j = curpos - 1 ; j >= 0 && buf[j] != ' ' && buf[j] != ',' ; j--); + mutt_query_complete ((char *) buf + j + 1, buflen - j - 1); + } + else + mutt_query_menu ((char *) buf, buflen); + return (1); + } + else + goto self_insert; + + case OP_EDITOR_QUOTE_CHAR: + ADDCH (LastKey); + LastKey = mutt_getch (); + move (y, x + curpos - begin); + goto self_insert; + + default: + BEEP (); + } + } + else + { + +self_insert: + + /* use the raw keypress */ + ch = LastKey; + + if (first && (flags & M_CLEAR)) + { + first = 0; + if (IsPrint (ch)) + { + mutt_ungetch (ch); + buf[0] = 0; + redraw = M_REDRAW_INIT; + continue; + } + } + + if (CI_is_return (ch)) + { + buf[lastchar] = 0; + if (!pass) + sh_add ((char *) buf); + return (0); + } + else if ((ch < ' ' || IsPrint (ch)) && (lastchar + 1 < buflen)) + { + for (j = lastchar; j > curpos; j--) + buf[j] = buf[j - 1]; + buf[curpos++] = ch; + lastchar++; + + if (!pass) + { + if (curpos >= begin + width) + { + begin = curpos - width / 2; + redraw = M_REDRAW_LINE; + } + else if (curpos == lastchar) + ADDCH (ch); + else + redraw = M_REDRAW_PREV_EOL; + } + } + else + { + mutt_flushinp (); + BEEP (); + } + } + } + /* not reached */ +} diff --git a/filter.c b/filter.c new file mode 100644 index 00000000..0685f69b --- /dev/null +++ b/filter.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins. + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" + +#include <unistd.h> +#include <stdlib.h> +#include <sys/wait.h> + +/* Invokes a commmand on a pipe and optionally connects its stdin and stdout + * to the specified handles. + */ +pid_t +mutt_create_filter_fd (const char *cmd, FILE **in, FILE **out, FILE **err, + int fdin, int fdout, int fderr) +{ + int pin[2], pout[2], perr[2], thepid; + + if (in) + { + *in = 0; + if (pipe (pin) == -1) + return (-1); + } + + if (out) + { + *out = 0; + if (pipe (pout) == -1) + { + if (in) + { + close (pin[0]); + close (pin[1]); + } + return (-1); + } + } + + if (err) + { + *err = 0; + if (pipe (perr) == -1) + { + if (in) + { + close (pin[0]); + close (pin[1]); + } + if (out) + { + close (pout[0]); + close (pout[1]); + } + return (-1); + } + } + + mutt_block_signals_system (); + + if ((thepid = fork ()) == 0) + { + mutt_unblock_signals_system (0); + + if (in) + { + close (pin[1]); + dup2 (pin[0], 0); + close (pin[0]); + } + else if (fdin != -1) + { + dup2 (fdin, 0); + close (fdin); + } + + if (out) + { + close (pout[0]); + dup2 (pout[1], 1); + close (pout[1]); + } + else if (fdout != -1) + { + dup2 (fdout, 1); + close (fdout); + } + + if (err) + { + close (perr[0]); + dup2 (perr[1], 2); + close (perr[1]); + } + else if (fderr != -1) + { + dup2 (fderr, 2); + close (fderr); + } + + execl (EXECSHELL, "sh", "-c", cmd, NULL); + _exit (127); + } + else if (thepid == -1) + { + mutt_unblock_signals_system (1); + + if (in) + { + close (pin[0]); + close (pin[1]); + } + + if (out) + { + close (pout[0]); + close (pout[1]); + } + + if (err) + { + close (perr[0]); + close (perr[1]); + } + + return (-1); + } + + if (out) + { + close (pout[1]); + *out = fdopen (pout[0], "r"); + } + + if (in) + { + close (pin[0]); + *in = fdopen (pin[1], "w"); + } + + if (err) + { + close (perr[1]); + *err = fdopen (perr[0], "r"); + } + + return (thepid); +} + +pid_t mutt_create_filter (const char *s, FILE **in, FILE **out, FILE **err) +{ + return (mutt_create_filter_fd (s, in, out, err, -1, -1, -1)); +} + +int mutt_wait_filter (pid_t pid) +{ + int rc; + + waitpid (pid, &rc, 0); + mutt_unblock_signals_system (1); + rc = WIFEXITED (rc) ? WEXITSTATUS (rc) : -1; + + return rc; +} diff --git a/flags.c b/flags.c new file mode 100644 index 00000000..20fc650e --- /dev/null +++ b/flags.c @@ -0,0 +1,303 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "sort.h" + +void mutt_set_flag (CONTEXT *ctx, HEADER *h, int flag, int bf) +{ + int changed = h->changed; + int deleted = ctx->deleted; + int tagged = ctx->tagged; + + if (ctx->readonly && flag != M_TAG) + return; /* don't modify anything if we are read-only */ + + switch (flag) + { + case M_DELETE: + if (bf) + { + if (!h->deleted) + { + h->deleted = 1; + ctx->deleted++; + } + } + else if (h->deleted) + { + h->deleted = 0; + ctx->deleted--; + } + break; + + case M_NEW: + if (bf) + { + if (h->read || h->old) + { + h->old = 0; + ctx->new++; + if (h->read) + { + h->read = 0; + ctx->unread++; + } + h->changed = 1; + ctx->changed = 1; + } + } + else if (!h->read) + { + if (!h->old) + ctx->new--; + h->read = 1; + ctx->unread--; + h->changed = 1; + ctx->changed = 1; + } + break; + + case M_OLD: + if (bf) + { + if (!h->old) + { + h->old = 1; + if (!h->read) + ctx->new--; + h->changed = 1; + ctx->changed = 1; + } + } + else if (h->old) + { + h->old = 0; + if (!h->read) + ctx->new++; + h->changed = 1; + ctx->changed = 1; + } + break; + + case M_READ: + if (bf) + { + if (!h->read) + { + h->read = 1; + ctx->unread--; + if (!h->old) + ctx->new--; + h->changed = 1; + ctx->changed = 1; + } + } + else if (h->read) + { + h->read = 0; + ctx->unread++; + if (!h->old) + ctx->new++; + h->changed = 1; + ctx->changed = 1; + } + break; + + case M_REPLIED: + if (bf) + { + if (!h->replied) + { + h->replied = 1; + if (!h->read) + { + h->read = 1; + ctx->unread--; + if (!h->old) + ctx->new--; + } + h->changed = 1; + ctx->changed = 1; + } + } + else if (h->replied) + { + h->replied = 0; + h->changed = 1; + ctx->changed = 1; + } + break; + + case M_FLAG: + if (bf) + { + if (!h->flagged) + { + h->flagged = bf; + ctx->flagged++; + h->changed = 1; + ctx->changed = 1; + } + } + else if (h->flagged) + { + h->flagged = 0; + ctx->flagged--; + h->changed = 1; + ctx->changed = 1; + } + break; + + case M_TAG: + if (bf) + { + if (!h->tagged) + { + h->tagged = 1; + ctx->tagged++; + } + } + else if (h->tagged) + { + h->tagged = 0; + ctx->tagged--; + } + break; + } + + mutt_set_header_color(ctx, h); + + /* if the message status has changed, we need to invalidate the cached + * search results so that any future search will match the current status + * of this message and not what it was at the time it was last searched. + */ + if (h->searched && (changed != h->changed || deleted != ctx->deleted || tagged != ctx->tagged)) + h->searched = 0; +} + +void mutt_tag_set_flag (int flag, int bf) +{ + int j; + + for (j = 0; j < Context->vcount; j++) + if (Context->hdrs[Context->v2r[j]]->tagged) + mutt_set_flag (Context, Context->hdrs[Context->v2r[j]], flag, bf); +} + +int mutt_thread_set_flag (HEADER *cur, int flag, int bf, int subthread) +{ + HEADER *start; + + if ((Sort & SORT_MASK) != SORT_THREADS) + { + mutt_error ("Threading is not enabled."); + return (-1); + } + + if (!subthread) + while (cur->parent) + cur = cur->parent; + start = cur; + + mutt_set_flag (Context, cur, flag, bf); + if ((cur = cur->child) == NULL) + return (0); + FOREVER + { + mutt_set_flag (Context, cur, flag, bf); + if (cur->child) + cur = cur->child; + else if (cur->next) + cur = cur->next; + else + { + while (!cur->next) + { + cur = cur->parent; + if (cur == start) + return (0); + } + cur = cur->next; + } + } + /* not reached */ +} + +int mutt_change_flag (HEADER *h, int bf) +{ + int i, flag; + + mvprintw (LINES - 1, 0, "%s flag? (D/N/O/r/*/!): ", bf ? "Set" : "Clear"); + clrtoeol (); + + if ((i = mutt_getch ()) == ERR) + { + CLEARLINE (LINES-1); + return (-1); + } + + CLEARLINE (LINES-1); + + switch (i) + { + case 'd': + case 'D': + flag = M_DELETE; + break; + + case 'N': + case 'n': + flag = M_NEW; + break; + + case 'o': + case 'O': + if (h) + mutt_set_flag (Context, h, M_READ, !bf); + else + mutt_tag_set_flag (M_READ, !bf); + flag = M_OLD; + break; + + case 'r': + case 'R': + flag = M_REPLIED; + break; + + case '*': + flag = M_TAG; + break; + + case '!': + flag = M_FLAG; + break; + + default: + BEEP (); + return (-1); + } + + if (h) + mutt_set_flag (Context, h, flag, bf); + else + mutt_tag_set_flag (flag, bf); + + return 0; +} diff --git a/from.c b/from.c new file mode 100644 index 00000000..4279f9a8 --- /dev/null +++ b/from.c @@ -0,0 +1,187 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" + +#include <ctype.h> +#include <string.h> + +static const char *next_word (const char *s) +{ + while (*s && !ISSPACE (*s)) + s++; + SKIPWS (s); + return s; +} + +int mutt_check_month (const char *s) +{ + int i; + + for (i = 0; i < 12; i++) + if (strncasecmp (s, Months[i], 3) == 0) + return (i); + return (-1); /* error */ +} + +static int is_day_name (const char *s) +{ + int i; + + if (!ISSPACE (*(s+3))) + return 0; + for (i=0; i<7; i++) + if (strncasecmp (s, Weekdays[i], 3) == 0) + return 1; + return 0; +} + +/* + * A valid message separator looks like: + * + * From [ <return-path> ] <weekday> <month> <day> <time> [ <timezone> ] <year> + */ + +time_t is_from (const char *s, char *path, size_t pathlen) +{ + struct tm tm; + int yr; + + *path = 0; + + if (strncmp ("From ", s, 5) != 0) + return 0; + + s = next_word (s); /* skip over the From part. */ + if (!*s) + return 0; + + dprint (3, (debugfile, "\nis_from(): parsing: %s", s)); + + if (!is_day_name (s)) + { + const char *p; + size_t len; + + /* looks like we got the return-path, so extract it */ + if (*s == '"') + { + /* sometimes we see bogus addresses like + * From "/foo/bar baz/"@dumbdar.com Sat Nov 22 15:29:32 PST 1997 + */ + p = s; + p++; /* skip over the quote */ + do + { + if (!(p = strpbrk (p, "\\\""))) + return 0; + if (*p == '\\') + p += 2; + } + while (*p != '"'); + while (*p && !ISSPACE (*p)) + p++; + } + else + { + if ((p = strchr (s, ' ')) == NULL) + return 0; + } + len = (size_t) (p - s); + if (len + 1 > pathlen) + len = pathlen - 1; + memcpy (path, s, len); + path[len] = 0; + + s = p + 1; + SKIPWS (s); + if (!*s) + return 0; + + if (!is_day_name (s)) + { + dprint(1, (debugfile, "is_from(): expected weekday, got: %s\n", s)); + return 0; + } + } + + s = next_word (s); + if (!*s) return 0; + + /* do a quick check to make sure that this isn't really the day of the week. + * this could happen when receiving mail from a local user whose login name + * is the same as a three-letter abbreviation of the day of the week. + */ + if (is_day_name (s)) + { + s = next_word (s); + if (!*s) return 0; + } + + /* now we should be on the month. */ + if ((tm.tm_mon = mutt_check_month (s)) < 0) return 0; + + /* day */ + s = next_word (s); + if (!*s) return 0; + if (sscanf (s, "%d", &tm.tm_mday) != 1) return 0; + + /* time */ + s = next_word (s); + if (!*s) return 0; + + /* Accept either HH:MM or HH:MM:SS */ + if (sscanf (s, "%d:%d:%d", &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 3); + else if (sscanf (s, "%d:%d", &tm.tm_hour, &tm.tm_min) == 2) + tm.tm_sec = 0; + else + return 0; + + s = next_word (s); + if (!*s) return 0; + + /* timezone? */ + if (isalpha (*s) || *s == '+' || *s == '-') + { + s = next_word (s); + if (!*s) return 0; + + /* + * some places have two timezone fields after the time, e.g. + * From xxxx@yyyyyyy.fr Wed Aug 2 00:39:12 MET DST 1995 + */ + if (isalpha (*s)) + { + s = next_word (s); + if (!*s) return 0; + } + } + + /* year */ + if (sscanf (s, "%d", &yr) != 1) return 0; + tm.tm_year = yr > 1900 ? yr - 1900 : yr; + + dprint (3,(debugfile, "is_from(): month=%d, day=%d, hr=%d, min=%d, sec=%d, yr=%d.\n", + tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_year)); + + tm.tm_isdst = 0; + tm.tm_yday = 0; + tm.tm_wday = 0; + + return (mutt_mktime (&tm, 0)); +} diff --git a/functions.h b/functions.h new file mode 100644 index 00000000..f8a1af17 --- /dev/null +++ b/functions.h @@ -0,0 +1,407 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * This file contains the structures needed to parse ``bind'' commands, as + * well as the default bindings for each menu. + * + * Notes: + * + * - If you want to bind \n or \r, use M_ENTER_S so that it will work + * correctly under both ncurses and S-Lang + * + * - If you need to bind a control char, use the octal value because the \cX + * construct does not work at this level. + * + */ + +struct binding_t OpGeneric[] = { + { "top-page", OP_TOP_PAGE, "H" }, + { "next-entry", OP_NEXT_ENTRY, "j" }, + { "previous-entry", OP_PREV_ENTRY, "k" }, + { "bottom-page", OP_BOTTOM_PAGE, "L" }, + { "refresh", OP_REDRAW, "\014" }, + { "middle-page", OP_MIDDLE_PAGE, "M" }, + { "search-next", OP_SEARCH_NEXT, "n" }, + { "exit", OP_EXIT, "q" }, + { "tag-entry", OP_TAG, "t" }, + { "next-page", OP_NEXT_PAGE, "z" }, + { "previous-page", OP_PREV_PAGE, "Z" }, + { "last-entry", OP_LAST_ENTRY, "*" }, + { "first-entry", OP_FIRST_ENTRY, "=" }, + { "enter-command", OP_ENTER_COMMAND, ":" }, + { "next-line", OP_NEXT_LINE, ">" }, + { "previous-line", OP_PREV_LINE, "<" }, + { "half-up", OP_HALF_UP, "[" }, + { "half-down", OP_HALF_DOWN, "]" }, + { "help", OP_HELP, "?" }, + { "tag-prefix", OP_TAG_PREFIX, ";" }, + { "shell-escape", OP_SHELL_ESCAPE, "!" }, + { "select-entry", OP_GENERIC_SELECT_ENTRY,M_ENTER_S }, + { "search", OP_SEARCH, "/" }, + { "search-reverse", OP_SEARCH_REVERSE, "\033/" }, + { "search-opposite", OP_SEARCH_OPPOSITE, NULL }, + { "jump", OP_JUMP, NULL }, + { "current-top", OP_CURRENT_TOP, NULL }, + { "current-middle", OP_CURRENT_MIDDLE, NULL }, + { "current-bottom", OP_CURRENT_BOTTOM, NULL }, + { NULL, 0, NULL } +}; + +struct binding_t OpMain[] = { + { "create-alias", OP_CREATE_ALIAS, "a" }, + { "bounce-message", OP_BOUNCE_MESSAGE, "b" }, + { "change-folder", OP_MAIN_CHANGE_FOLDER, "c" }, + { "change-folder-readonly", OP_MAIN_CHANGE_FOLDER_READONLY, "\033c" }, + { "copy-message", OP_COPY_MESSAGE, "C" }, + { "decode-copy", OP_DECODE_COPY, "\033C" }, + { "decode-save", OP_DECODE_SAVE, "\033s" }, + { "delete-message", OP_DELETE, "d" }, + { "delete-pattern", OP_MAIN_DELETE_PATTERN, "D" }, + { "delete-thread", OP_DELETE_THREAD, "\004" }, + { "delete-subthread", OP_DELETE_SUBTHREAD, "\033d" }, + { "forward-message", OP_FORWARD_MESSAGE, "f" }, + { "flag-message", OP_FLAG_MESSAGE, "F" }, + { "group-reply", OP_GROUP_REPLY, "g" }, +#ifdef USE_POP + { "fetch-mail", OP_MAIN_FETCH_MAIL, "G" }, +#endif + { "display-headers", OP_DISPLAY_HEADERS, "h" }, + { "next-undeleted", OP_MAIN_NEXT_UNDELETED, "j" }, + { "previous-undeleted", OP_MAIN_PREV_UNDELETED, "k" }, + { "limit", OP_MAIN_LIMIT, "l" }, + { "list-reply", OP_LIST_REPLY, "L" }, + { "mail", OP_MAIL, "m" }, + { "toggle-new", OP_TOGGLE_NEW, "N" }, + { "toggle-write", OP_TOGGLE_WRITE, "%" }, + { "next-thread", OP_MAIN_NEXT_THREAD, "\016" }, + { "next-subthread", OP_MAIN_NEXT_SUBTHREAD, "\033n" }, + { "query", OP_QUERY, "Q" }, + { "quit", OP_QUIT, "q" }, + { "reply", OP_REPLY, "r" }, + { "show-limit", OP_MAIN_SHOW_LIMIT, "\033l" }, + { "sort-mailbox", OP_SORT, "o" }, + { "sort-reverse", OP_SORT_REVERSE, "O" }, + { "print-message", OP_PRINT, "p" }, + { "previous-thread", OP_MAIN_PREV_THREAD, "\020" }, + { "previous-subthread", OP_MAIN_PREV_SUBTHREAD, "\033p" }, + { "recall-message", OP_RECALL_MESSAGE, "R" }, + { "read-thread", OP_MAIN_READ_THREAD, "\022" }, + { "read-subthread", OP_MAIN_READ_SUBTHREAD, "\033r" }, + { "save-message", OP_SAVE, "s" }, + { "tag-pattern", OP_MAIN_TAG_PATTERN, "T" }, + { "tag-subthread", OP_TAG_SUBTHREAD, NULL }, + { "tag-thread", OP_TAG_THREAD, "\033t" }, + { "untag-pattern", OP_MAIN_UNTAG_PATTERN, "\024" }, + { "undelete-message", OP_UNDELETE, "u" }, + { "undelete-pattern", OP_MAIN_UNDELETE_PATTERN, "U"}, + { "undelete-subthread", OP_UNDELETE_SUBTHREAD, "\033u" }, + { "undelete-thread", OP_UNDELETE_THREAD, "\025" }, + { "view-attachments", OP_VIEW_ATTACHMENTS, "v" }, + { "show-version", OP_VERSION, "V" }, + { "set-flag", OP_MAIN_SET_FLAG, "w" }, + { "clear-flag", OP_MAIN_CLEAR_FLAG, "W" }, + { "display-message", OP_DISPLAY_MESSAGE, M_ENTER_S }, + { "sync-mailbox", OP_MAIN_SYNC_FOLDER, "$" }, + { "display-address", OP_DISPLAY_ADDRESS, "@" }, + { "pipe-message", OP_PIPE, "|" }, + { "next-new", OP_MAIN_NEXT_NEW, "\t" }, + { "previous-new", OP_MAIN_PREV_NEW, "\033\t" }, + { "next-unread", OP_MAIN_NEXT_UNREAD, NULL }, + { "previous-unread", OP_MAIN_PREV_UNREAD, NULL }, + + + +#ifdef _PGPPATH + { "extract-keys", OP_EXTRACT_KEYS, "\013" }, + { "forget-passphrase", OP_FORGET_PASSPHRASE, "\006" }, + { "mail-key", OP_MAIL_KEY, "\033k" }, +#endif + + + + { NULL, 0, NULL } +}; + +struct binding_t OpPager[] = { + { "create-alias", OP_CREATE_ALIAS, "a" }, + { "bounce-message", OP_BOUNCE_MESSAGE, "b" }, + { "change-folder", OP_MAIN_CHANGE_FOLDER, "c" }, + { "change-folder-readonly", OP_MAIN_CHANGE_FOLDER_READONLY, "\033c" }, + { "copy-message", OP_COPY_MESSAGE, "C" }, + { "decode-copy", OP_DECODE_COPY, "\033C" }, + { "delete-message", OP_DELETE, "d" }, + { "delete-thread", OP_DELETE_THREAD, "\004" }, + { "delete-subthread", OP_DELETE_SUBTHREAD, "\033d" }, + { "forward-message", OP_FORWARD_MESSAGE, "f" }, + { "flag-message", OP_FLAG_MESSAGE, "F" }, + { "group-reply", OP_GROUP_REPLY, "g" }, + { "display-headers", OP_DISPLAY_HEADERS, "h" }, + { "exit", OP_PAGER_EXIT, "i" }, + { "next-undeleted", OP_MAIN_NEXT_UNDELETED, "j" }, + { "next-entry", OP_NEXT_ENTRY, "J" }, + { "previous-undeleted",OP_MAIN_PREV_UNDELETED, "k" }, + { "previous-entry", OP_PREV_ENTRY, "K" }, + { "list-reply", OP_LIST_REPLY, "L" }, + { "redraw-screen", OP_REDRAW, "\014" }, + { "mail", OP_MAIL, "m" }, + { "mark-as-new", OP_TOGGLE_NEW, "N" }, + { "search-next", OP_SEARCH_NEXT, "n" }, + { "next-thread", OP_MAIN_NEXT_THREAD, "\016" }, + { "next-subthread", OP_MAIN_NEXT_SUBTHREAD, "\033n" }, + { "print-message", OP_PRINT, "p" }, + { "previous-thread", OP_MAIN_PREV_THREAD, "\020" }, + { "previous-subthread",OP_MAIN_PREV_SUBTHREAD, "\033p" }, + { "quit", OP_QUIT, "Q" }, + { "reply", OP_REPLY, "r" }, + { "recall-message", OP_RECALL_MESSAGE, "R" }, + { "read-thread", OP_MAIN_READ_THREAD, "\022" }, + { "read-subthread", OP_MAIN_READ_SUBTHREAD, "\033r" }, + { "save-message", OP_SAVE, "s" }, + { "skip-quoted", OP_PAGER_SKIP_QUOTED, "S" }, + { "decode-save", OP_DECODE_SAVE, "\033s" }, + { "tag-message", OP_TAG, "t" }, + { "toggle-quoted", OP_PAGER_HIDE_QUOTED, "T" }, + { "undelete-message", OP_UNDELETE, "u" }, + { "undelete-subthread",OP_UNDELETE_SUBTHREAD, "\033u" }, + { "undelete-thread", OP_UNDELETE_THREAD, "\025" }, + { "view-attachments", OP_VIEW_ATTACHMENTS, "v" }, + { "show-version", OP_VERSION, "V" }, + { "search-toggle", OP_SEARCH_TOGGLE, "\\" }, + { "display-address", OP_DISPLAY_ADDRESS, "@" }, + { "next-new", OP_MAIN_NEXT_NEW, "\t" }, + { "pipe-message", OP_PIPE, "|" }, + { "help", OP_HELP, "?" }, + { "next-page", OP_NEXT_PAGE, " " }, + { "previous-page", OP_PREV_PAGE, "-" }, + { "top", OP_PAGER_TOP, "^" }, + { "bottom", OP_PAGER_BOTTOM, "$" }, + { "shell-escape", OP_SHELL_ESCAPE, "!" }, + { "enter-command", OP_ENTER_COMMAND, ":" }, + { "search", OP_SEARCH, "/" }, + { "search-reverse", OP_SEARCH_REVERSE, "\033/" }, + { "search-opposite", OP_SEARCH_OPPOSITE, NULL }, + { "next-line", OP_NEXT_LINE, M_ENTER_S }, + { "jump", OP_JUMP, NULL }, + { "next-unread", OP_MAIN_NEXT_UNREAD, NULL }, + { "previous-new", OP_MAIN_PREV_NEW, NULL }, + { "previous-unread", OP_MAIN_PREV_UNREAD, NULL }, + { "half-up", OP_HALF_UP, NULL }, + { "half-down", OP_HALF_DOWN, NULL }, + { "previous-line", OP_PREV_LINE, NULL }, + + + + + + + + + + + + +#ifdef _PGPPATH + { "extract-keys", OP_EXTRACT_KEYS, "\013" }, + { "forget-passphrase",OP_FORGET_PASSPHRASE, "\006" }, + { "mail-key", OP_MAIL_KEY, "\033k" }, +#endif + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { NULL, 0, NULL } +}; + +struct binding_t OpAttach[] = { + { "bounce-message", OP_BOUNCE_MESSAGE, "b" }, + { "display-headers", OP_DISPLAY_HEADERS, "h" }, + { "print-entry", OP_PRINT, "p" }, + { "save-entry", OP_SAVE, "s" }, + { "pipe-entry", OP_PIPE, "|" }, + { "view-mailcap", OP_ATTACH_VIEW_MAILCAP, "m" }, + { "reply", OP_REPLY, "r" }, + { "group-reply", OP_GROUP_REPLY, "g" }, + { "list-reply", OP_LIST_REPLY, "L" }, + { "forward-message", OP_FORWARD_MESSAGE, "f" }, + { "view-text", OP_ATTACH_VIEW_TEXT, "T" }, + { "view-attach", OP_VIEW_ATTACH, M_ENTER_S }, + { "delete-entry", OP_DELETE, "d" }, + { "undelete-entry", OP_UNDELETE, "u" }, + + + +#ifdef _PGPPATH + { "extract-keys", OP_EXTRACT_KEYS, "\013" }, +#endif + + + + { NULL, 0, NULL } +}; + +struct binding_t OpCompose[] = { + { "attach-file", OP_COMPOSE_ATTACH_FILE, "a" }, + { "edit-bcc", OP_COMPOSE_EDIT_BCC, "b" }, + { "edit-cc", OP_COMPOSE_EDIT_CC, "c" }, + { "copy-file", OP_SAVE, "C" }, + { "detach-file", OP_DELETE, "D" }, + { "display-headers", OP_DISPLAY_HEADERS, "h" }, + { "edit-description", OP_COMPOSE_EDIT_DESCRIPTION, "d" }, + { "edit-message", OP_COMPOSE_EDIT_MESSAGE, "e" }, + { "edit-headers", OP_COMPOSE_EDIT_HEADERS, "E" }, + { "edit-file", OP_COMPOSE_EDIT_FILE, "\030e" }, + { "edit-encoding", OP_COMPOSE_EDIT_ENCODING, "\005" }, + { "edit-from", OP_COMPOSE_EDIT_FROM, "\033f" }, + { "edit-fcc", OP_COMPOSE_EDIT_FCC, "f" }, + { "filter-entry", OP_FILTER, "F" }, + { "ispell", OP_COMPOSE_ISPELL, "i" }, + { "print-entry", OP_PRINT, "l" }, + { "redraw-screen", OP_REDRAW, "\014" }, + { "edit-mime", OP_COMPOSE_EDIT_MIME, "m" }, + { "new-mime", OP_COMPOSE_NEW_MIME, "n" }, + { "postpone-message", OP_COMPOSE_POSTPONE_MESSAGE, "P" }, + { "edit-reply-to", OP_COMPOSE_EDIT_REPLY_TO, "r" }, + { "rename-file", OP_COMPOSE_RENAME_FILE, "R" }, + { "edit-subject", OP_COMPOSE_EDIT_SUBJECT, "s" }, + { "edit-to", OP_COMPOSE_EDIT_TO, "t" }, + { "edit-type", OP_COMPOSE_EDIT_TYPE, "\024" }, + { "toggle-unlink", OP_COMPOSE_TOGGLE_UNLINK, "u" }, + { "view-attach", OP_VIEW_ATTACH, M_ENTER_S }, + { "send-message", OP_COMPOSE_SEND_MESSAGE, "y" }, + { "pipe-entry", OP_PIPE, "|" }, + +#ifdef _PGPPATH + { "attach-key", OP_COMPOSE_ATTACH_KEY, "\033k" }, + { "forget-passphrase",OP_FORGET_PASSPHRASE, "\006" }, + { "pgp-menu", OP_COMPOSE_PGP_MENU, "p" }, +#endif + + { NULL, 0, NULL } +}; + +struct binding_t OpPost[] = { + { "delete-entry", OP_DELETE, "d" }, + { "undelete-entry", OP_UNDELETE, "u" }, + { NULL, 0, NULL } +}; + +/* The file browser */ +struct binding_t OpBrowser[] = { + { "change-dir", OP_CHANGE_DIRECTORY, "c" }, + { "enter-mask", OP_ENTER_MASK, "m" }, + { "sort", OP_SORT, "o" }, + { "sort-reverse", OP_SORT_REVERSE, "O" }, + { "select-new", OP_BROWSER_NEW_FILE, "N" }, + { "check-new", OP_CHECK_NEW, "\t" }, + { NULL, 0, NULL } +}; + +/* External Query Menu */ +struct binding_t OpQuery[] = { + { "create-alias", OP_CREATE_ALIAS, "a" }, + { "search", OP_SEARCH, "/" }, + { "search-reverse", OP_SEARCH_REVERSE, "\033/" }, + { "search-opposite", OP_SEARCH_OPPOSITE, NULL }, + { "mail", OP_MAIL, "m" }, + { "query", OP_QUERY, "Q" }, + { "query-append", OP_QUERY_APPEND, "A" }, + { NULL, 0, NULL } +}; + +struct binding_t OpEditor[] = { + { "bol", OP_EDITOR_BOL, "\001" }, + { "backward-char", OP_EDITOR_BACKWARD_CHAR, "\002" }, + { "delete-char", OP_EDITOR_DELETE_CHAR, "\004" }, + { "eol", OP_EDITOR_EOL, "\005" }, + { "forward-char", OP_EDITOR_FORWARD_CHAR, "\006" }, + { "backspace", OP_EDITOR_BACKSPACE, "\010" }, + { "kill-eol", OP_EDITOR_KILL_EOL, "\013" }, + { "kill-line", OP_EDITOR_KILL_LINE, "\025" }, + { "quote-char", OP_EDITOR_QUOTE_CHAR, "\026" }, + { "kill-word", OP_EDITOR_KILL_WORD, "\027" }, + { "complete", OP_EDITOR_COMPLETE, "\t" }, + { "complete-query", OP_EDITOR_COMPLETE_QUERY, "\024" }, + { "buffy-cycle", OP_EDITOR_BUFFY_CYCLE, " " }, + { "history-up", OP_EDITOR_HISTORY_UP, NULL }, + { "history-down", OP_EDITOR_HISTORY_DOWN, NULL }, + { NULL, 0, NULL } +}; + + + +#ifdef _PGPPATH +struct binding_t OpPgp[] = { + { "verify-key", OP_VERIFY_KEY, "c" }, + { "view-name", OP_VIEW_ID, "%" }, + { "search-next", OP_SEARCH_NEXT, "n" }, + { "search", OP_SEARCH, "/" }, + { "search-reverse", OP_SEARCH_REVERSE, "\033/" }, + { NULL, 0, NULL } +}; +#endif /* _PGPPATH */ diff --git a/gen_defs b/gen_defs new file mode 100755 index 00000000..926c3dad --- /dev/null +++ b/gen_defs @@ -0,0 +1,31 @@ +#!/bin/sh + +echo '/* Automatically generated by gen_defs. Do not edit! */' +echo '' + +for mode in help defs; do + case $mode in + help) + echo "#ifdef HELP_C" + echo "const char *HelpStrings[] = {" + expr='s;^[^ ]* *\(.*\); \1,;' + ;; + *) + echo "enum {" + expr='s;^\([^ ]*\).*; \1,;' + ;; + esac + for i in $*; do + sed -e "$expr" < $i + done + if test $mode = help; then + echo ' NULL' + else + echo ' OP_MAX' + fi + echo "};" + if test $mode = help; then + echo "#endif /* MAIN_C */" + echo '' + fi +done diff --git a/getdomain.c b/getdomain.c new file mode 100644 index 00000000..70c8e88f --- /dev/null +++ b/getdomain.c @@ -0,0 +1,54 @@ +#include <stdio.h> +#include <ctype.h> +#include <string.h> + +#include "mutt.h" + +#ifndef STDC_HEADERS +int fclose (); +#endif + +/* poor man's version of getdomainname() for systems where it does not return + * return the DNS domain, but the NIS domain. + */ + +int getdnsdomainname (char *s, size_t l) +{ + FILE *f; + char tmp[1024]; + char *p = NULL; + + if ((f = fopen ("/etc/resolv.conf", "r")) == NULL) return (-1); + + tmp[sizeof (tmp) - 1] = 0; + + l--; /* save room for the terminal \0 */ + + while (fgets (tmp, sizeof (tmp) - 1, f) != NULL) + { + p = tmp; + while (ISSPACE (*p)) p++; + if (strncmp ("domain", p, 6) == 0 || strncmp ("search", p, 6) == 0) + { + p += 6; + while (ISSPACE (*p)) p++; + + if (*p) + { + while (*p && !ISSPACE (*p) && l > 0) + { + *s++ = *p++; + l--; + } + if (*(s-1) == '.') s--; + *s = 0; + + fclose (f); + return (0); + } + } + } + + fclose (f); + return (-1); +} diff --git a/globals.h b/globals.h new file mode 100644 index 00000000..8413a7a6 --- /dev/null +++ b/globals.h @@ -0,0 +1,144 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +WHERE void (*mutt_error) (const char *, ...); + +WHERE CONTEXT *Context; + +WHERE char Errorbuf[SHORT_STRING]; + +WHERE char *AliasFile; +WHERE char *AliasFmt; +WHERE char *Attribution; +WHERE char *Charset; +WHERE char *DefaultHook; +WHERE char *DateFmt; +WHERE char *DeleteFmt; +WHERE char *DsnNotify; +WHERE char *DsnReturn; +WHERE char *Editor; +WHERE char *EmptyTo; +WHERE char *EscChar; +WHERE char *FolderFormat; +WHERE char *ForwFmt; +WHERE char *Fqdn; +WHERE char *HdrFmt; +WHERE char *Homedir; +WHERE char *Hostname; +WHERE char *InReplyTo; +WHERE char *Inbox; +WHERE char *Ispell; +WHERE char *Locale; +WHERE char *MailcapPath; +WHERE char *Maildir; +WHERE char *MsgFmt; +WHERE char *Muttrc INITVAL (NULL); +WHERE char *Outbox; +WHERE char *Pager; +WHERE char *PagerFmt; +WHERE char *PipeSep; +#ifdef USE_POP +WHERE char *PopHost; +WHERE char *PopPass; +WHERE char *PopUser; +#endif +WHERE char *PostIndentString; +WHERE char *Postponed; +WHERE char *Prefix; +WHERE char *PrintCmd; +WHERE char *QueryCmd; +WHERE char *Realname; +WHERE char *Sendmail; +WHERE char *Shell; +WHERE char *Signature; +WHERE char *SimpleSearch; +WHERE char *Spoolfile; +WHERE char *StChars; +WHERE char *Status; +WHERE char *Tempdir; +WHERE char *Tochars; +WHERE char *Username; +WHERE char *Visual; + +WHERE char *LastFolder; + +WHERE LIST *AutoViewList INITVAL(0); +WHERE LIST *AlternativeOrderList INITVAL(0); +WHERE LIST *HeaderOrderList INITVAL(0); +WHERE LIST *Ignore INITVAL(0); +WHERE LIST *UnIgnore INITVAL(0); +WHERE LIST *MailLists INITVAL(0); + +/* bit vector for boolean variables */ +#ifdef MAIN_C +unsigned char Options[(OPTMAX + 7)/8]; +#else +extern unsigned char Options[]; +#endif + +/* bit vector for the yes/no/ask variable type */ +WHERE unsigned long QuadOptions INITVAL (0); + +WHERE unsigned short Counter INITVAL (0); + +WHERE short HistSize; +WHERE short PagerContext; +WHERE short PagerIndexLines; +WHERE short PopPort; +WHERE short ReadInc; +WHERE short SendmailWait; +WHERE short Timeout; +WHERE short WriteInc; + +/* vector to store received signals */ +WHERE short Signals INITVAL (0); + +WHERE ALIAS *Aliases INITVAL (0); +WHERE LIST *UserHeader INITVAL (0); + +#ifdef DEBUG +WHERE FILE *debugfile INITVAL (0); +WHERE int debuglevel INITVAL (0); +#endif + +#ifdef USE_SETGID +WHERE gid_t MailGid; +WHERE gid_t UserGid; +#endif /* USE_SETGID */ + +#ifdef MAIN_C +const char *Weekdays[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; +const char *Months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "ERR" }; + +const char *BodyTypes[] = { "x-unknown", "audio", "application", "image", "message", "multipart", "text", "video" }; +const char *BodyEncodings[] = { "x-unknown", "7bit", "8bit", "quoted-printable", "base64", "binary" }; +#else +extern const char *Weekdays[]; +extern const char *Months[]; +#endif + +#ifdef MAIN_C +/* so that global vars get included */ +#include "mx.h" +#include "mutt_regex.h" +#include "buffy.h" +#include "sort.h" +#ifdef _PGPPATH +#include "pgp.h" +#endif +#endif /* MAIN_C */ diff --git a/handler.c b/handler.c new file mode 100644 index 00000000..15a0ca99 --- /dev/null +++ b/handler.c @@ -0,0 +1,1289 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <sys/wait.h> +#include <sys/stat.h> + +#include "mutt.h" +#include "mutt_curses.h" +#include "rfc1524.h" +#include "keymap.h" +#include "mime.h" +#include "copy.h" + + + +#ifdef _PGPPATH +#include "pgp.h" +#endif + + + +typedef void handler_f (BODY *, STATE *); +typedef handler_f *handler_t; + +int Index_hex[128] = { + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1, -1,-1,-1,-1, + -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1 +}; + +int Index_64[128] = { + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, + 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, + -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 +}; + +void mutt_decode_xbit (STATE *s, long len, int istext) +{ + int linelen; + char buffer[LONG_STRING]; + + if (istext) + { + while (len > 0) + { + if (fgets (buffer, LONG_STRING, s->fpin) == 0) return; + linelen = strlen (buffer); + len -= linelen; + if (linelen >= 2 && buffer[linelen-2] == '\r') + { + buffer[linelen-2] = '\n'; + buffer[linelen-1] = 0; + } + if (s->prefix) state_puts (s->prefix, s); + state_puts (buffer, s); + } + } + else + mutt_copy_bytes (s->fpin, s->fpout, len); +} + +void mutt_decode_quoted (STATE *s, long len, int istext) +{ + char *c, buffer[LONG_STRING]; + int ch, soft = 0; + + while (len > 0) + { + if (fgets (buffer, LONG_STRING, s->fpin) == NULL) + { + dprint (1, (debugfile, "mutt_decode_quoted: unexpected EOF.\n")); + state_puts ("[-- Error: unexpected end of file! --]\n", s); + break; + } + c = buffer; + len -= strlen (buffer); + if (s->prefix && !soft) state_puts (s->prefix, s); + soft = 0; + while (*c) + { + if (*c == '=') + { + if (c[1] == '\n' || c[1] == '\r' || c[1] == ' ' || c[1] == '\t') + { + /* Skip whitespace at the end of the line since MIME does not + * allow for it + */ + soft = 1; + break; + } + ch = hexval ((int) c[1]) << 4; + ch |= hexval ((int) c[2]); + state_putc (ch, s); + c += 3; + } + else if (istext && c[0] == '\r' && c[1] == '\n') + { + state_putc ('\n', s); + break; + } + else + { + state_putc (*c, s); + c++; + } + } + } +} + +void mutt_decode_base64 (STATE *s, long len, int istext) +{ + char buf[5]; + int c1, c2, c3, c4, ch, cr = 0, i; + + buf[4] = 0; + + if (s->prefix) state_puts (s->prefix, s); + + while (len > 0) + { + for (i = 0 ; i < 4 && len > 0 ; len--) + { + if ((ch = fgetc (s->fpin)) == EOF) + return; + if (!ISSPACE (ch)) + buf[i++] = ch; + } + if (i != 4) + return; /* didn't get a multiple of four chars! */ + + c1 = base64val ((int) buf[0]); + c2 = base64val ((int) buf[1]); + ch = (c1 << 2) | (c2 >> 4); + + if (cr && ch != '\n') state_putc ('\r', s); + cr = 0; + + if (istext && ch == '\r') + cr = 1; + else + { + state_putc (ch, s); + if (ch == '\n' && s->prefix) state_puts (s->prefix, s); + } + + if (buf[2] == '=') + break; + c3 = base64val ((int) buf[2]); + ch = ((c2 & 0xf) << 4) | (c3 >> 2); + + if (cr && ch != '\n') + state_putc ('\r', s); + cr = 0; + + if (istext && ch == '\r') + cr = 1; + else + { + state_putc (ch, s); + if (ch == '\n' && s->prefix) + state_puts (s->prefix, s); + } + + if (buf[3] == '=') break; + c4 = base64val ((int) buf[3]); + ch = ((c3 & 0x3) << 6) | c4; + + if (cr && ch != '\n') + state_putc ('\r', s); + cr = 0; + + if (istext && ch == '\r') + cr = 1; + else + { + state_putc (ch, s); + if (ch == '\n' && s->prefix) + state_puts (s->prefix, s); + } + } +} + +/* ---------------------------------------------------------------------------- + * A (not so) minimal implementation of RFC1563. + */ + +#define IndentSize (4) + +enum { RICH_PARAM=0, RICH_BOLD, RICH_UNDERLINE, RICH_ITALIC, RICH_NOFILL, + RICH_INDENT, RICH_INDENT_RIGHT, RICH_EXCERPT, RICH_CENTER, RICH_FLUSHLEFT, + RICH_FLUSHRIGHT, RICH_COLOR, RICH_LAST_TAG }; + +static struct { + const char *tag_name; + int index; +} EnrichedTags[] = { + { "param", RICH_PARAM }, + { "bold", RICH_BOLD }, + { "italic", RICH_ITALIC }, + { "underline", RICH_UNDERLINE }, + { "nofill", RICH_NOFILL }, + { "excerpt", RICH_EXCERPT }, + { "indent", RICH_INDENT }, + { "indentright", RICH_INDENT_RIGHT }, + { "center", RICH_CENTER }, + { "flushleft", RICH_FLUSHLEFT }, + { "flushright", RICH_FLUSHRIGHT }, + { "flushboth", RICH_FLUSHLEFT }, + { "color", RICH_COLOR }, + { "x-color", RICH_COLOR }, + { NULL, -1 } +}; + +struct enriched_state +{ + char *buffer; + char *line; + char *param; + size_t buff_len; + size_t line_len; + size_t line_used; + size_t line_max; + size_t indent_len; + size_t word_len; + size_t buff_used; + size_t param_len; + int tag_level[RICH_LAST_TAG]; + int WrapMargin; + STATE *s; +}; + +static void enriched_wrap (struct enriched_state *stte) +{ + int x; + int extra; + + if (stte->line_len) + { + if (stte->tag_level[RICH_CENTER] || stte->tag_level[RICH_FLUSHRIGHT]) + { + /* Strip trailing white space */ + size_t y = stte->line_used - 1; + + while (y && ISSPACE (stte->line[y])) + { + stte->line[y] = '\0'; + y--; + stte->line_used--; + stte->line_len--; + } + if (stte->tag_level[RICH_CENTER]) + { + /* Strip leading whitespace */ + y = 0; + + while (stte->line[y] && ISSPACE (stte->line[y])) + y++; + if (y) + { + size_t z; + + for (z = y ; z <= stte->line_used; z++) + { + stte->line[z - y] = stte->line[z]; + } + + stte->line_len -= y; + stte->line_used -= y; + } + } + } + + extra = stte->WrapMargin - stte->line_len - stte->indent_len - + (stte->tag_level[RICH_INDENT_RIGHT] * IndentSize); + if (extra > 0) + { + if (stte->tag_level[RICH_CENTER]) + { + x = extra / 2; + while (x) + { + state_putc (' ', stte->s); + x--; + } + } + else if (stte->tag_level[RICH_FLUSHRIGHT]) + { + x = extra-1; + while (x) + { + state_putc (' ', stte->s); + x--; + } + } + } + state_puts (stte->line, stte->s); + } + + state_putc ('\n', stte->s); + stte->line[0] = '\0'; + stte->line_len = 0; + stte->line_used = 0; + stte->indent_len = 0; + if (stte->s->prefix) + { + state_puts (stte->s->prefix, stte->s); + stte->indent_len += strlen (stte->s->prefix); + } + + if (stte->tag_level[RICH_EXCERPT]) + { + x = stte->tag_level[RICH_EXCERPT]; + while (x) + { + if (stte->s->prefix) + { + state_puts (stte->s->prefix, stte->s); + stte->indent_len += strlen (stte->s->prefix); + } + else + { + state_puts ("> ", stte->s); + stte->indent_len += strlen ("> "); + } + x--; + } + } + else + stte->indent_len = 0; + if (stte->tag_level[RICH_INDENT]) + { + x = stte->tag_level[RICH_INDENT] * IndentSize; + stte->indent_len += x; + while (x) + { + state_putc (' ', stte->s); + x--; + } + } +} + +static void enriched_flush (struct enriched_state *stte, int wrap) +{ + if (!stte->tag_level[RICH_NOFILL] && (stte->line_len + stte->word_len > + (stte->WrapMargin - (stte->tag_level[RICH_INDENT_RIGHT] * IndentSize) - + stte->indent_len))) + enriched_wrap (stte); + + if (stte->buff_used) + { + stte->buffer[stte->buff_used] = '\0'; + stte->line_used += stte->buff_used; + if (stte->line_used > stte->line_max) + { + stte->line_max = stte->line_used; + safe_realloc ((void **) &stte->line, stte->line_max + 1); + } + strcat (stte->line, stte->buffer); + stte->line_len += stte->word_len; + stte->word_len = 0; + stte->buff_used = 0; + } + if (wrap) + enriched_wrap(stte); +} + + +static void enriched_putc (int c, struct enriched_state *stte) +{ + if (stte->tag_level[RICH_PARAM]) + { + if (stte->tag_level[RICH_COLOR]) + { + stte->param[stte->param_len++] = c; + } + return; /* nothing to do */ + } + + /* see if more space is needed (plus extra for possible rich characters) */ + if (stte->buff_len < stte->buff_used + 3) + { + stte->buff_len += LONG_STRING; + safe_realloc ((void **) &stte->buffer, stte->buff_len + 1); + } + + if ((!stte->tag_level[RICH_NOFILL] && ISSPACE (c)) || c == '\0' ) + { + if (c == '\t') + stte->word_len += 8 - (stte->line_len + stte->word_len) % 8; + else + stte->word_len++; + + stte->buffer[stte->buff_used++] = c; + enriched_flush (stte, 0); + } + else + { + if (stte->s->flags & M_DISPLAY) + { + if (stte->tag_level[RICH_BOLD]) + { + stte->buffer[stte->buff_used++] = c; + stte->buffer[stte->buff_used++] = '\010'; + stte->buffer[stte->buff_used++] = c; + } + else if (stte->tag_level[RICH_UNDERLINE]) + { + + stte->buffer[stte->buff_used++] = '_'; + stte->buffer[stte->buff_used++] = '\010'; + stte->buffer[stte->buff_used++] = c; + } + else if (stte->tag_level[RICH_ITALIC]) + { + stte->buffer[stte->buff_used++] = c; + stte->buffer[stte->buff_used++] = '\010'; + stte->buffer[stte->buff_used++] = '_'; + } + else + { + stte->buffer[stte->buff_used++] = c; + } + } + else + { + stte->buffer[stte->buff_used++] = c; + } + stte->word_len++; + } +} + +static void enriched_puts (char *s, struct enriched_state *stte) +{ + char *c; + + if (stte->buff_len < stte->buff_used + strlen(s)) + { + stte->buff_len += LONG_STRING; + safe_realloc ((void **) &stte->buffer, stte->buff_len + 1); + } + c = s; + while (*c) + { + stte->buffer[stte->buff_used++] = *c; + c++; + } +} + +static void enriched_set_flags (const char *tag, struct enriched_state *stte) +{ + const char *tagptr = tag; + int i, j; + + if (*tagptr == '/') + tagptr++; + + for (i = 0, j = -1; EnrichedTags[i].tag_name; i++) + if (strcasecmp (EnrichedTags[i].tag_name,tagptr) == 0) + { + j = EnrichedTags[i].index; + break; + } + + if (j != -1) + { + if (j == RICH_CENTER || j == RICH_FLUSHLEFT || j == RICH_FLUSHRIGHT) + enriched_flush (stte, 1); + + if (*tag == '/') + { + if (stte->tag_level[j]) /* make sure not to go negative */ + stte->tag_level[j]--; + if ((stte->s->flags & M_DISPLAY) && j == RICH_PARAM && stte->tag_level[RICH_COLOR]) + { + stte->param[stte->param_len] = '\0'; + if (!strcasecmp(stte->param, "black")) + { + enriched_puts("\033[30m", stte); + } + else if (!strcasecmp(stte->param, "red")) + { + enriched_puts("\033[31m", stte); + } + else if (!strcasecmp(stte->param, "green")) + { + enriched_puts("\033[32m", stte); + } + else if (!strcasecmp(stte->param, "yellow")) + { + enriched_puts("\033[33m", stte); + } + else if (!strcasecmp(stte->param, "blue")) + { + enriched_puts("\033[34m", stte); + } + else if (!strcasecmp(stte->param, "magenta")) + { + enriched_puts("\033[35m", stte); + } + else if (!strcasecmp(stte->param, "cyan")) + { + enriched_puts("\033[36m", stte); + } + else if (!strcasecmp(stte->param, "white")) + { + enriched_puts("\033[37m", stte); + } + stte->param_len = 0; + stte->param[0] = '\0'; + } + if ((stte->s->flags & M_DISPLAY) && j == RICH_COLOR) + { + enriched_puts("\033[0m", stte); + } + } + else + stte->tag_level[j]++; + + if (j == RICH_EXCERPT) + enriched_flush(stte, 1); + } +} + +void text_enriched_handler (BODY *a, STATE *s) +{ + enum { + TEXT, LANGLE, TAG, BOGUS_TAG, NEWLINE, ST_EOF, DONE + } state = TEXT; + + long bytes = a->length; + struct enriched_state stte; + int c = 0; + int tag_len = 0; + char tag[LONG_STRING + 1]; + + memset (&stte, 0, sizeof (stte)); + stte.s = s; + stte.WrapMargin = ((s->flags & M_DISPLAY) ? (COLS-4) : ((COLS-4)<72)?(COLS-4):72); + stte.line_max = stte.WrapMargin * 4; + stte.line = (char *) safe_calloc (1, stte.line_max + 1); + stte.param = (char *) safe_calloc (1, STRING); + + if (s->prefix) + { + state_puts (s->prefix, s); + stte.indent_len += strlen (s->prefix); + } + + while (state != DONE) + { + if (state != ST_EOF) + { + if (!bytes || (c = fgetc (s->fpin)) == EOF) + state = ST_EOF; + else + bytes--; + } + + switch (state) + { + case TEXT : + switch (c) + { + case '<' : + state = LANGLE; + break; + + case '\n' : + if (stte.tag_level[RICH_NOFILL]) + { + enriched_flush (&stte, 1); + } + else + { + enriched_putc (' ', &stte); + state = NEWLINE; + } + break; + + default: + enriched_putc (c, &stte); + } + break; + + case LANGLE : + if (c == '<') + { + enriched_putc (c, &stte); + state = TEXT; + break; + } + else + { + tag_len = 0; + state = TAG; + } + /* Yes, fall through (it wasn't a <<, so this char is first in TAG) */ + case TAG : + if (c == '>') + { + tag[tag_len] = '\0'; + enriched_set_flags (tag, &stte); + state = TEXT; + } + else if (tag_len < LONG_STRING) /* ignore overly long tags */ + tag[tag_len++] = c; + else + state = BOGUS_TAG; + break; + + case BOGUS_TAG : + if (c == '>') + state = TEXT; + break; + + case NEWLINE : + if (c == '\n') + enriched_flush (&stte, 1); + else + { + ungetc (c, s->fpin); + bytes++; + state = TEXT; + } + break; + + case ST_EOF : + enriched_putc ('\0', &stte); + state = DONE; + break; + + case DONE: /* not reached, but gcc complains if this is absent */ + break; + } + } + + state_putc ('\n', s); /* add a final newline */ + + if (stte.buffer) + free (stte.buffer); + free (stte.line); + free (stte.param); +} + +#define TXTPLAIN 1 +#define TXTENRICHED 2 +#define TXTHTML 3 + +void alternative_handler (BODY *a, STATE *s) +{ + BODY *choice = NULL; + BODY *b; + LIST *t; + char buf[STRING]; + int type = 0; + + /* First, search list of prefered types */ + t = AlternativeOrderList; + while (t && !choice) + { + if (a && a->parts) + b = a->parts; + else + b = a; + while (b && !choice) + { + int i; + + /* catch base only matches */ + i = strlen (t->data) - 1; + if (!strchr(t->data, '/') || + (i > 0 && t->data[i-1] == '/' && t->data[i] == '*')) + { + if (!strcasecmp(t->data, TYPE(b->type))) + { + choice = b; + } + } + else + { + snprintf (buf, sizeof (buf), "%s/%s", TYPE (b->type), b->subtype); + if (!strcasecmp(t->data, buf)) + { + choice = b; + } + } + b = b->next; + } + t = t->next; + } + /* Next, look for an autoviewable type */ + if (a && a->parts) + b = a->parts; + else + b = a; + while (b && !choice) + { + snprintf (buf, sizeof (buf), "%s/%s", TYPE (b->type), b->subtype); + if (mutt_is_autoview (buf)) + { + rfc1524_entry *entry = rfc1524_new_entry (); + + if (rfc1524_mailcap_lookup (b, buf, entry, M_AUTOVIEW)) + { + choice = b; + } + rfc1524_free_entry (&entry); + } + b = b->next; + } + + /* Then, look for a text entry */ + if (!choice) + { + if (a && a->parts) + b = a->parts; + else + b = a; + while (b) + { + if (b->type == TYPETEXT) + { + if (strcasecmp ("plain", b->subtype) == 0) + { + choice = b; + type = TXTPLAIN; + } + else if (strcasecmp ("enriched", b->subtype) == 0) + { + if (type == 0 || type > TXTENRICHED) + { + choice = b; + type = TXTENRICHED; + } + } + else if (strcasecmp ("html", b->subtype) == 0) + { + if (type == 0) + { + choice = b; + type = TXTHTML; + } + } + } + b = b->next; + } + } + /* Finally, look for other possibilities */ + if (!choice) + { + if (a && a->parts) + b = a->parts; + else + b = a; + while (b && !choice) + { + if (mutt_can_decode (b)) + choice = b; + b = b->next; + } + } + if (choice) + { + if (s->flags & M_DISPLAY && !option (OPTWEED)) + { + fseek (s->fpin, choice->hdr_offset, 0); + mutt_copy_bytes(s->fpin, s->fpout, choice->offset-choice->hdr_offset); + } + mutt_body_handler (choice, s); + } + else if (s->flags & M_DISPLAY) + { + /* didn't find anything that we could display! */ + state_puts("[-- Error: Could not display any parts of Multipart/Alternative! --]\n", s); + } +} + +/* handles message/rfc822 body parts */ +void message_handler (BODY *a, STATE *s) +{ + struct stat st; + BODY *b; + long off_start; + + off_start = ftell (s->fpin); + if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE) + { + fstat (fileno (s->fpin), &st); + b = mutt_new_body (); + b->length = (long) st.st_size; + b->parts = mutt_parse_messageRFC822 (s->fpin, b); + } + else + b = a; + + if (b->parts) + { + mutt_copy_hdr (s->fpin, s->fpout, off_start, b->parts->offset, + (((s->flags & M_DISPLAY) && option (OPTWEED)) ? (CH_WEED | CH_REORDER) : 0) | + (s->prefix ? CH_PREFIX : 0) | CH_DECODE | CH_FROM, s->prefix); + + if (s->prefix) + state_puts (s->prefix, s); + state_putc ('\n', s); + + mutt_body_handler (b->parts, s); + } + + if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE) + mutt_free_body (&b); +} + +/* returns 1 if decoding the attachment will produce output */ +int mutt_can_decode (BODY *a) +{ + char type[STRING]; + + snprintf (type, sizeof (type), "%s/%s", TYPE (a->type), a->subtype); + if (mutt_is_autoview (type)) + return (rfc1524_mailcap_lookup (a, type, NULL, M_AUTOVIEW)); + else if (a->type == TYPETEXT) + return (1); + else if (a->type == TYPEMESSAGE) + return (1); + else if (a->type == TYPEMULTIPART) + { + + + +#ifdef _PGPPATH + if (strcasecmp (a->subtype, "signed") == 0 || + strcasecmp (a->subtype, "encrypted") == 0) + return (1); + else +#endif + + + + { + BODY *p; + + for (p = a->parts; p; p = p->next) + { + if (mutt_can_decode (p)) + return (1); + } + } + } + + + +#ifdef _PGPPATH + else if (a->type == TYPEAPPLICATION) + { + if (strcasecmp (a->subtype, "pgp") == 0 || + strcasecmp (a->subtype, "x-pgp-message") == 0 || + strcasecmp (a->subtype, "pgp-signed") == 0 || + strcasecmp (a->subtype, "pgp-keys") == 0) + return (1); + } +#endif + + + + return (0); +} + +void multipart_handler (BODY *a, STATE *s) +{ + BODY *b, *p; + char buffer[STRING]; + char length[5]; + struct stat st; + int count; + + if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE) + { + fstat (fileno (s->fpin), &st); + b = mutt_new_body (); + b->length = (long) st.st_size; + b->parts = mutt_parse_multipart (s->fpin, + mutt_get_parameter ("boundary", a->parameter), + (long) st.st_size, strcasecmp ("digest", a->subtype) == 0); + } + else + b = a; + + for (p = b->parts, count = 1; p; p = p->next, count++) + { + if (s->flags & M_DISPLAY) + { + snprintf (buffer, sizeof (buffer), "[-- Attachment #%d", count); + state_puts (buffer, s); + if (p->description || p->filename || p->form_name) + { + state_puts (": ", s); + state_puts (p->description ? p->description : + p->filename ? p->filename : p->form_name, s); + } + state_puts (" --]\n", s); + + mutt_pretty_size (length, sizeof (length), p->length); + + snprintf (buffer, sizeof (buffer), + "[-- Type: %s/%s, Encoding: %s, Size: %s --]\n", + TYPE (p->type), p->subtype, ENCODING (p->encoding), length); + state_puts (buffer, s); + if (!option (OPTWEED)) + { + fseek (s->fpin, p->hdr_offset, 0); + mutt_copy_bytes(s->fpin, s->fpout, p->offset-p->hdr_offset); + } + else + state_putc ('\n', s); + } + else + { + if (p->description && mutt_can_decode (p)) + { + state_puts ("Content-Description: ", s); + state_puts (p->description, s); + state_putc ('\n', s); + } + if (p->form_name) + { + state_puts (p->form_name, s); + state_puts (": \n", s); + } + } + mutt_body_handler (p, s); + state_putc ('\n', s); + } + + if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE) + mutt_free_body (&b); +} + +void autoview_handler (BODY *a, STATE *s) +{ + rfc1524_entry *entry = rfc1524_new_entry (); + char buffer[LONG_STRING]; + char type[STRING]; + char command[LONG_STRING]; + char tempfile[_POSIX_PATH_MAX] = ""; + FILE *fpin = NULL; + FILE *fpout = NULL; + FILE *fperr = NULL; + int piped = FALSE; + pid_t thepid; + + snprintf (type, sizeof (type), "%s/%s", TYPE (a->type), a->subtype); + rfc1524_mailcap_lookup (a, type, entry, M_AUTOVIEW); + + rfc1524_expand_filename (entry->nametemplate, a->filename, tempfile, sizeof (tempfile)); + + if (entry->command) + { + strfcpy (command, entry->command, sizeof (command)); + + /* rfc1524_expand_command returns 0 if the file is required */ + piped = rfc1524_expand_command (a, tempfile, type, command, sizeof (command)); + + if (s->flags & M_DISPLAY) + { + char mesg[STRING]; + + snprintf (mesg, sizeof (buffer), "[-- Autoview using %s --]\n", command); + state_puts (mesg, s); + mutt_message("Invoking autoview command: %s",command); + } + + if (piped) + { + thepid = mutt_create_filter (command, &fpin, &fpout, &fperr); + mutt_copy_bytes (s->fpin, fpin, a->length); + fclose (fpin); + } + else + { + if ((fpin = safe_fopen (tempfile, "w")) == NULL) + { + mutt_perror ("fopen"); + rfc1524_free_entry (&entry); + return; + } + + mutt_copy_bytes (s->fpin, fpin, a->length); + fclose (fpin); + thepid = mutt_create_filter (command, NULL, &fpout, &fperr); + } + + if (s->prefix) + { + while (fgets (buffer, sizeof(buffer), fpout) != NULL) + { + state_puts (s->prefix, s); + state_puts (buffer, s); + } + /* check for data on stderr */ + if (fgets (buffer, sizeof(buffer), fperr)) + { + if (s->flags & M_DISPLAY) + { + char mesg[STRING]; + + snprintf (mesg, sizeof (buffer), "[-- Autoview stderr of %s --]\n", + command); + state_puts (mesg, s); + } + state_puts (s->prefix, s); + state_puts (buffer, s); + while (fgets (buffer, sizeof(buffer), fperr) != NULL) + { + state_puts (s->prefix, s); + state_puts (buffer, s); + } + } + } + else + { + mutt_copy_stream (fpout, s->fpout); + /* Check for stderr messages */ + if (fgets (buffer, sizeof(buffer), fperr)) + { + if (s->flags & M_DISPLAY) + { + char mesg[STRING]; + + snprintf (mesg, sizeof (buffer), "[-- Autoview stderr of %s --]\n", + command); + state_puts (mesg, s); + } + state_puts (buffer, s); + mutt_copy_stream (fperr, s->fpout); + } + } + + fclose (fpout); + fclose (fperr); + mutt_wait_filter (thepid); + mutt_unlink (tempfile); + if (s->flags & M_DISPLAY) + mutt_clear_error (); + } + rfc1524_free_entry (&entry); +} + +void mutt_decode_attachment (BODY *b, STATE *s) +{ + fseek (s->fpin, b->offset, 0); + switch (b->encoding) + { + case ENCQUOTEDPRINTABLE: + mutt_decode_quoted (s, b->length, mutt_is_text_type (b->type, b->subtype)); + break; + case ENCBASE64: + mutt_decode_base64 (s, b->length, mutt_is_text_type (b->type, b->subtype)); + break; + default: + mutt_decode_xbit (s, b->length, mutt_is_text_type (b->type, b->subtype)); + break; + } +} + +void mutt_body_handler (BODY *b, STATE *s) +{ + int decode = 0; + int plaintext = 0; + FILE *fp = NULL; + char tempfile[_POSIX_PATH_MAX]; + handler_t handler = NULL; + long tmpoffset = 0; + size_t tmplength = 0; + char type[STRING]; + + /* first determine which handler to use to process this part */ + + snprintf (type, sizeof (type), "%s/%s", TYPE (b->type), b->subtype); + if (mutt_is_autoview (type)) + { + rfc1524_entry *entry = rfc1524_new_entry (); + + if (rfc1524_mailcap_lookup (b, type, entry, M_AUTOVIEW)) + handler = autoview_handler; + rfc1524_free_entry (&entry); + } + else if (b->type == TYPETEXT) + { + if (strcasecmp ("plain", b->subtype) == 0) + { + /* avoid copying this part twice since removing the transfer-encoding is + * the only operation needed. + */ + plaintext = 1; + } + else if (strcasecmp ("enriched", b->subtype) == 0) + handler = text_enriched_handler; + else if (strcasecmp ("rfc822-headers", b->subtype) == 0) + plaintext = 1; + } + else if (b->type == TYPEMESSAGE) + { + if (!strcasecmp ("rfc822", b->subtype) || !strcasecmp ("news", b->subtype)) + handler = message_handler; + else if (!strcasecmp ("delivery-status", b->subtype)) + plaintext = 1; + } + else if (b->type == TYPEMULTIPART) + { + + + +#ifdef _PGPPATH + char *p; +#endif /* _PGPPATH */ + + + + if (strcasecmp ("alternative", b->subtype) == 0) + handler = alternative_handler; + + + +#ifdef _PGPPATH + else if (strcasecmp ("signed", b->subtype) == 0) + { + p = mutt_get_parameter ("protocol", b->parameter); + + if (!p) + mutt_error ("Error: Multipart/Signed has no protocol."); + else if (strcasecmp ("application/pgp-signature", p) == 0) + { + if (s->flags & M_VERIFY) + handler = pgp_signed_handler; + } + } + else if (strcasecmp ("encrypted", b->subtype) == 0) + { + p = mutt_get_parameter ("protocol", b->parameter); + + if (!p) + mutt_error ("Error: Multipart/Encrypted has no protocol parameter!"); + else if (strcasecmp ("application/pgp-encrypted", p) == 0) + handler = pgp_encrypted_handler; + } +#endif /* _PGPPATH */ + + + + if (!handler) + handler = multipart_handler; + } + + + +#ifdef _PGPPATH + else if (b->type == TYPEAPPLICATION) + { + if (strcasecmp ("pgp", b->subtype) == 0 || + strcasecmp ("x-pgp-message", b->subtype) == 0 || + strcasecmp ("pgp-signed", b->subtype) == 0 || + strcasecmp ("pgp-keys", b->subtype) == 0) + + handler = application_pgp_handler; + } +#endif /* _PGPPATH */ + + + + if (plaintext || handler) + { + fseek (s->fpin, b->offset, 0); + + /* see if we need to decode this part before processing it */ + if (b->encoding == ENCBASE64 || b->encoding == ENCQUOTEDPRINTABLE || + (s->prefix && plaintext)) + { + int origType = b->type; + char *savePrefix = NULL; + + if (!plaintext) + { + /* decode to a tempfile, saving the original destination */ + fp = s->fpout; + mutt_mktemp (tempfile); + if ((s->fpout = safe_fopen (tempfile, "w")) == NULL) + { + mutt_error ("Unable to open temporary file!"); + return; + } + /* decoding the attachment changes the size and offset, so save a copy + * of the "real" values now, and restore them after processing + */ + tmplength = b->length; + tmpoffset = b->offset; + + /* if we are decoding binary bodies, we don't want to prefix each + * line with the prefix or else the data will get corrupted. + */ + savePrefix = s->prefix; + s->prefix = NULL; + + decode = 1; + } + else + b->type = TYPETEXT; + + mutt_decode_attachment (b, s); + + if (decode) + { + b->length = ftell (s->fpout); + b->offset = 0; + fclose (s->fpout); + + /* restore final destination and substitute the tempfile for input */ + s->fpout = fp; + fp = s->fpin; + s->fpin = fopen (tempfile, "r"); + unlink (tempfile); + + /* restore the prefix */ + s->prefix = savePrefix; + } + + b->type = origType; + } + else if (plaintext) + mutt_copy_bytes (s->fpin, s->fpout, b->length); + + /* process the (decoded) body part */ + if (handler) + { + handler (b, s); + + if (decode) + { + b->length = tmplength; + b->offset = tmpoffset; + + /* restore the original source stream */ + fclose (s->fpin); + s->fpin = fp; + } + } + } + else if (s->flags & M_DISPLAY) + { + fprintf (s->fpout, "[-- %s/%s is unsupported ", TYPE (b->type), b->subtype); + if (!option (OPTVIEWATTACH)) + { + if (km_expand_key (type, sizeof(type), + km_find_func (MENU_PAGER, OP_VIEW_ATTACHMENTS))) + fprintf (s->fpout, "(use '%s' to view this part)", type); + else + fputs ("(need 'view-attachments' bound to key!)", s->fpout); + } + fputs (" --]\n", s->fpout); + } +} diff --git a/hash.c b/hash.c new file mode 100644 index 00000000..f670eb4a --- /dev/null +++ b/hash.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "mutt.h" + +int hash_string (const unsigned char *s, int n) +{ + int h = 0; + while (*s) + h += *s++; + return (h % n); +} + +HASH *hash_create (int nelem) +{ + HASH *table = safe_malloc (sizeof (HASH)); + table->nelem = nelem; + table->table = safe_calloc (nelem, sizeof (struct hash_elem *)); + return table; +} + +/* table hash table to update + key key to hash on + data data to associate with `key' + allow_dup if nonzero, duplicate keys are allowed in the table */ +int hash_insert (HASH * table, const char *key, void *data, int allow_dup) +{ + struct hash_elem *ptr = malloc (sizeof (struct hash_elem)); + int h = hash_string ((unsigned char *) key, table->nelem); + + ptr->key = key; + ptr->data = data; + + if (allow_dup) + { + ptr->next = table->table[h]; + table->table[h] = ptr; + } + else + { + struct hash_elem *tmp, *last; + int r; + + for (tmp = table->table[h], last = NULL; tmp; last = tmp, tmp = tmp->next) + { + r = strcmp (tmp->key, key); + if (r == 0) + { + free (ptr); + return (-1); + } + if (r > 0) + break; + } + if (last) + last->next = ptr; + else + table->table[h] = ptr; + ptr->next = tmp; + } + return h; +} + +void *hash_find_hash (const HASH * table, int hash, const char *key) +{ + struct hash_elem *ptr = table->table[hash]; + for (; ptr; ptr = ptr->next) + { + if (strcmp (key, ptr->key) == 0) + return (ptr->data); + } + return NULL; +} + +void hash_delete_hash (HASH * table, int hash, const char *key, const void *data, + void (*destroy) (void *)) +{ + struct hash_elem *ptr = table->table[hash]; + struct hash_elem *last = NULL; + for (; ptr; last = ptr, ptr = ptr->next) + { + /* if `data' is given, look for a matching ->data member. this is + required for the case where we have multiple entries with the same + key */ + if (data == ptr->data || (!data && strcmp (ptr->key, key) == 0)) + { + if (last) + last->next = ptr->next; + else + table->table[hash] = ptr->next; + if (destroy) + destroy (ptr->data); + memset (ptr, 0, sizeof (struct hash_elem)); + free (ptr); + ptr = 0; + return; + } + } +} + +/* ptr pointer to the hash table to be freed + destroy() function to call to free the ->data member (optional) */ +void hash_destroy (HASH **ptr, void (*destroy) (void *)) +{ + int i; + HASH *pptr = *ptr; + struct hash_elem *elem, *tmp; + + for (i = 0 ; i < pptr->nelem; i++) + { + for (elem = pptr->table[i]; elem; ) + { + tmp = elem; + elem = elem->next; + if (destroy) + destroy (tmp->data); + safe_free ((void **) &tmp); + } + } + safe_free ((void **) &pptr->table); + safe_free ((void **) ptr); +} diff --git a/hash.h b/hash.h new file mode 100644 index 00000000..a0757c6b --- /dev/null +++ b/hash.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +struct hash_elem +{ + const char *key; + void *data; + struct hash_elem *next; +}; + +typedef struct +{ + int nelem; + struct hash_elem **table; +} +HASH; + +#define hash_find(table, key) hash_find_hash(table, hash_string ((unsigned char *)key, table->nelem), key) + +#define hash_delete(table,key,data,destroy) hash_delete_hash(table, hash_string ((unsigned char *)key, table->nelem), key, data, destroy) + +HASH *hash_create (int nelem); +int hash_string (const unsigned char *s, int n); +int hash_insert (HASH * table, const char *key, void *data, int allow_dup); +void *hash_find_hash (const HASH * table, int hash, const char *key); +void hash_delete_hash (HASH * table, int hash, const char *key, const void *data, + void (*destroy) (void *)); +void hash_destroy (HASH ** hash, void (*destroy) (void *)); diff --git a/hdrline.c b/hdrline.c new file mode 100644 index 00000000..1d43bc19 --- /dev/null +++ b/hdrline.c @@ -0,0 +1,481 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" + + + +#ifdef _PGPPATH +#include "pgp.h" +#endif + + + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <locale.h> + +int mutt_is_mail_list (ADDRESS *addr) +{ + LIST *p; + + if (addr->mailbox) + { + for (p = MailLists; p; p = p->next) + if (strncasecmp (addr->mailbox, p->data, strlen (p->data)) == 0) + return 1; + } + return 0; +} + +static int +check_for_mailing_list (ADDRESS *adr, char *pfx, char *buf, int buflen) +{ + for (; adr; adr = adr->next) + { + if (mutt_is_mail_list (adr)) + { + if (pfx && buf && buflen) + snprintf (buf, buflen, "%s%s", pfx, mutt_get_name (adr)); + return 1; + } + } + return 0; +} + +static int first_mailing_list (char *buf, size_t buflen, ADDRESS *a) +{ + for (; a; a = a->next) + { + if (mutt_is_mail_list (a)) + { + mutt_save_path (buf, buflen, a); + return 1; + } + } + return 0; +} + +static void make_from (ENVELOPE *hdr, char *buf, size_t len, int do_lists) +{ + int me; + + me = mutt_addr_is_user (hdr->from); + + if (do_lists || me) + { + if (check_for_mailing_list (hdr->to, "To ", buf, len)) + return; + if (check_for_mailing_list (hdr->cc, "Cc ", buf, len)) + return; + } + + if (me && hdr->to) + snprintf (buf, len, "To %s", mutt_get_name (hdr->to)); + else if (me && hdr->cc) + snprintf (buf, len, "Cc %s", mutt_get_name (hdr->cc)); + else if (hdr->from) + strfcpy (buf, mutt_get_name (hdr->from), len); + else + *buf = 0; +} + +int mutt_user_is_recipient (ADDRESS *a) +{ + for (; a; a = a->next) + if (mutt_addr_is_user (a)) + return 1; + return 0; +} + +/* Return values: + * 0: user is not in list + * 1: user is unique recipient + * 2: user is in the TO list + * 3: user is in the CC list + * 4: user is originator + */ +static int user_is_recipient (ENVELOPE *hdr) +{ + if (mutt_addr_is_user (hdr->from)) + return 4; + + if (mutt_user_is_recipient (hdr->to)) + { + if (hdr->to->next || hdr->cc) + return 2; /* non-unique recipient */ + else + return 1; /* unique recipient */ + } + + if (mutt_user_is_recipient (hdr->cc)) + return 3; + + return (0); +} + +/* %a = address of author + * %b = filename of the originating folder + * %B = the list to which the letter was sent + * %c = size of message in bytes + * %C = current message number + * %d = date and time of message (using strftime) + * %f = entire from line + * %F = like %n, unless from self + * %i = message-id + * %l = number of lines in the message + * %L = like %F, except `lists' are displayed first + * %m = number of messages in the mailbox + * %n = name of author + * %N = score + * %s = subject + * %S = short message status (e.g., N/O/D/!/r/-) + * %t = `to:' field (recipients) + * %T = $to_chars + * %u = user (login) name of author + * %Z = status flags */ + +static const char * +hdr_format_str (char *dest, + size_t destlen, + char op, + const char *src, + const char *prefix, + const char *ifstring, + const char *elsestring, + unsigned long data, + format_flag flags) +{ + HEADER *hdr = (HEADER *) data; + char fmt[SHORT_STRING], buf2[SHORT_STRING], ch, *p; + int do_locales, i; + int optional = (flags & M_FORMAT_OPTIONAL); + size_t len; + + dest[0] = 0; + switch (op) + { + case 'a': + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + snprintf (dest, destlen, fmt, hdr->env->from->mailbox); + break; + + case 'B': + if (!first_mailing_list (dest, destlen, hdr->env->to) && + !first_mailing_list (dest, destlen, hdr->env->cc)) + dest[0] = 0; + break; + + case 'b': + if ((p = strrchr (Context->path, '/'))) + strncpy (dest, p + 1, destlen); + else + strncpy (dest, Context->path, destlen); + break; + + case 'c': + mutt_pretty_size (buf2, sizeof (buf2), (long) hdr->content->length); + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + snprintf (dest, destlen, fmt, buf2); + break; + + case 'C': + snprintf (fmt, sizeof (fmt), "%%%sd", prefix); + snprintf (dest, destlen, fmt, hdr->msgno + 1); + break; + + case 'd': + case '{': + case '[': + case '(': + case '<': + + /* preprocess $date_format to handle %Z */ + { + const char *cp; + struct tm *tm; + time_t T; + + p = dest; + + cp = (op == 'd') ? (NONULL (DateFmt)) : src; + if (*cp == '!') + { + do_locales = 0; + cp++; + } + else + do_locales = 1; + + len = destlen - 1; + while (len > 0 && ((op == 'd' && *cp) || + (op == '{' && *cp != '}') || + (op == '[' && *cp != ']') || + (op == '(' && *cp != ')') || + (op == '<' && *cp != '>'))) + { + if (*cp == '%') + { + cp++; + if (*cp == 'Z' && (op == 'd' || op == '{')) + { + if (len >= 5) + { + sprintf (p, "%c%02d%02d", hdr->zoccident ? '-' : '+', + hdr->zhours, hdr->zminutes); + p += 5; + len -= 5; + } + else + break; /* not enough space left */ + } + else + { + if (len >= 2) + { + *p++ = '%'; + *p++ = *cp; + len -= 2; + } + else + break; /* not enough space */ + } + cp++; + } + else + { + *p++ = *cp++; + len--; + } + } + *p = 0; + + if (do_locales && Locale) + setlocale (LC_TIME, Locale); + + if (op == '[') + tm = localtime (&hdr->date_sent); + else if (op == '(') + tm = localtime (&hdr->received); + else if (op == '<') + { + T = time (NULL); + tm = localtime (&T); + } + else + { + /* restore sender's time zone */ + T = hdr->date_sent; + if (hdr->zoccident) + T -= (hdr->zhours * 3600 + hdr->zminutes * 60); + else + T += (hdr->zhours * 3600 + hdr->zminutes * 60); + tm = gmtime (&T); + } + + strftime (buf2, sizeof (buf2), dest, tm); + + if (do_locales) + setlocale (LC_TIME, "C"); + + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + snprintf (dest, destlen, fmt, buf2); + if (len > 0 && op != 'd') + src = cp + 1; + } + break; + + case 'f': + buf2[0] = 0; + rfc822_write_address (buf2, sizeof (buf2), hdr->env->from); + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + snprintf (dest, destlen, fmt, buf2); + break; + + case 'F': + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + make_from (hdr->env, buf2, sizeof (buf2), 0); + snprintf (dest, destlen, fmt, buf2); + break; + + case 'i': + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + snprintf (dest, destlen, fmt, hdr->env->message_id ? hdr->env->message_id : "<no.id>"); + break; + + case 'l': + snprintf (fmt, sizeof (fmt), "%%%sd", prefix); + snprintf (dest, destlen, fmt, (int) hdr->lines); + break; + + case 'L': + if (!optional) + { + make_from (hdr->env, buf2, sizeof (buf2), 1); + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + snprintf (dest, destlen, fmt, buf2); + } + else if (!check_for_mailing_list (hdr->env->to, NULL, NULL, 0) && + !check_for_mailing_list (hdr->env->cc, NULL, NULL, 0)) + { + optional = 0; + } + break; + + case 'm': + snprintf (fmt, sizeof (fmt), "%%%sd", prefix); + snprintf (dest, destlen, fmt, Context->msgcount); + break; + + case 'n': + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + snprintf (dest, destlen, fmt, mutt_get_name (hdr->env->from)); + break; + + case 'N': + snprintf (fmt, sizeof (fmt), "%%%sd", prefix); + snprintf (dest, destlen, fmt, hdr->score); + break; + + case 's': + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + if (flags & M_FORMAT_TREE) + { + if (flags & M_FORMAT_FORCESUBJ) + { + snprintf (buf2, sizeof (buf2), "%s%s", hdr->tree, + hdr->env->subject ? hdr->env->subject : ""); + snprintf (dest, destlen, fmt, buf2); + } + else + snprintf (dest, destlen, fmt, hdr->tree); + } + else + { + snprintf (dest, destlen, fmt, hdr->env->subject ? hdr->env->subject : ""); + } + break; + + case 'S': + if (hdr->deleted) + ch = 'D'; + else if (hdr->attach_del) + ch = 'd'; + else if (hdr->tagged) + ch = '*'; + else if (hdr->flagged) + ch = '!'; + else if (hdr->replied) + ch = 'r'; + else if (hdr->read && (Context->msgnotreadyet != hdr->msgno)) + ch = '-'; + else if (hdr->old) + ch = 'O'; + else + ch = 'N'; + + /* FOO - this is probably unsafe, but we are not likely to have such + a short string passed into this routine */ + *dest = ch; + *(dest + 1) = 0; + break; + + case 't': + buf2[0] = 0; + if (!check_for_mailing_list (hdr->env->to, "To ", buf2, sizeof (buf2)) && + !check_for_mailing_list (hdr->env->cc, "Cc ", buf2, sizeof (buf2))) + { + if (hdr->env->to) + snprintf (buf2, sizeof (buf2), "To %s", mutt_get_name (hdr->env->to)); + else if (hdr->env->cc) + snprintf (buf2, sizeof (buf2), "Cc %s", mutt_get_name (hdr->env->cc)); + } + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + snprintf (dest, destlen, fmt, buf2); + break; + + case 'T': + snprintf (fmt, sizeof (fmt), "%%%sc", prefix); + snprintf (dest, destlen, fmt, + (Tochars && ((i = user_is_recipient (hdr->env))) < strlen (Tochars)) ? Tochars[i] : ' '); + break; + + case 'u': + if (hdr->env->from && hdr->env->from->mailbox) + { + strfcpy (buf2, hdr->env->from->mailbox, sizeof (buf2)); + if ((p = strpbrk (buf2, "%@"))) + *p = 0; + } + else + buf2[0] = 0; + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + snprintf (dest, destlen, fmt, buf2); + break; + + case 'Z': + if (hdr->mailcap) + ch = 'M'; + + + +#ifdef _PGPPATH + else if (hdr->pgp & PGPENCRYPT) + ch = 'P'; + else if (hdr->pgp & PGPSIGN) + ch = 'S'; + else if (hdr->pgp & PGPKEY) + ch = 'K'; +#endif + + + + else + ch = ' '; + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + snprintf (buf2, sizeof (buf2), + "%c%c%c", + (hdr->read && (Context->msgnotreadyet != hdr->msgno)) + ? (hdr->replied ? 'r' : ' ') : (hdr->old ? 'O' : 'N'), + hdr->deleted ? 'D' : (hdr->attach_del ? 'd' : ch), + hdr->tagged ? '*' : + (hdr->flagged ? '!' : + (Tochars && ((i = user_is_recipient (hdr->env)) < strlen (Tochars)) ? Tochars[user_is_recipient (hdr->env)] : ' '))); + snprintf (dest, destlen, fmt, buf2); + break; + + default: + snprintf (dest, destlen, "%%%s%c", prefix, op); + break; + } + + if (optional) + mutt_FormatString (dest, destlen, ifstring, hdr_format_str, (unsigned long) hdr, flags); + else if (flags & M_FORMAT_OPTIONAL) + mutt_FormatString (dest, destlen, elsestring, hdr_format_str, (unsigned long) hdr, flags); + + return (src); +} + +void +_mutt_make_string (char *dest, size_t destlen, const char *s, HEADER *hdr, format_flag flags) +{ + mutt_FormatString (dest, destlen, s, hdr_format_str, (unsigned long) hdr, flags); +} diff --git a/headers.c b/headers.c new file mode 100644 index 00000000..b718eaf1 --- /dev/null +++ b/headers.c @@ -0,0 +1,269 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" + + + +#ifdef _PGPPATH +#include "pgp.h" +#endif + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#include <sys/stat.h> +#include <string.h> +#include <ctype.h> + +void mutt_edit_headers (const char *editor, + const char *body, + HEADER *msg, + char *fcc, + size_t fcclen) +{ + char path[_POSIX_PATH_MAX]; /* tempfile used to edit headers + body */ + char buffer[LONG_STRING]; + char *p; + FILE *ifp, *ofp; + int i, keep; + int in_reply_to = 0; /* did we see the in-reply-to field ? */ + ENVELOPE *n; + time_t mtime; + struct stat st; + LIST *cur, *last = NULL, *tmp; + + mutt_mktemp (path); + if ((ofp = safe_fopen (path, "w")) == NULL) + { + mutt_perror (path); + return; + } + + mutt_write_rfc822_header (ofp, msg->env, NULL, 1); + fputc ('\n', ofp); /* tie off the header. */ + + /* now copy the body of the message. */ + if ((ifp = fopen (body, "r")) == NULL) + { + mutt_perror (body); + return; + } + + mutt_copy_stream (ifp, ofp); + + fclose (ifp); + fclose (ofp); + + if (stat (path, &st) == -1) + { + mutt_perror (path); + return; + } + + mtime = st.st_mtime; + mutt_edit_file (editor, path); + stat (path, &st); + if (mtime == st.st_mtime) + { + dprint (1, (debugfile, "ci_edit_headers(): temp file was not modified.\n")); + /* the file has not changed! */ + mutt_unlink (path); + return; + } + + mutt_unlink (body); + mutt_free_list (&msg->env->userhdrs); + + /* Read the temp file back in */ + ifp = fopen (path, "r"); + ofp = safe_fopen (body, "w"); + n = mutt_read_rfc822_header (ifp, NULL); + while ((i = fread (buffer, 1, sizeof (buffer), ifp)) > 0) + fwrite (buffer, 1, i, ofp); + fclose (ofp); + fclose (ifp); + mutt_unlink (path); + + /* restore old info. */ + n->references = msg->env->references; + msg->env->references = NULL; + mutt_free_envelope (&msg->env); + msg->env = n; + + msg->env->to = mutt_expand_aliases (msg->env->to); + msg->env->cc = mutt_expand_aliases (msg->env->cc); + msg->env->bcc = mutt_expand_aliases (msg->env->bcc); + msg->env->reply_to = mutt_expand_aliases (msg->env->reply_to); + msg->env->mail_followup_to = mutt_expand_aliases (msg->env->mail_followup_to); + + /* search through the user defined headers added to see if either a * fcc: + or attach-file: field was specified. */ + cur = msg->env->userhdrs; + while (cur) + { + keep = 1; + + /* keep track of whether or not we see the in-reply-to field. if we did + * not, remove the references: field later so that we can generate a new + * message based upon this one. + */ + if (strncasecmp ("in-reply-to:", cur->data, 12) == 0) + in_reply_to = 1; + else if (fcc && strncasecmp ("fcc:", cur->data, 4) == 0) + { + p = cur->data + 4; + SKIPWS (p); + if (*p) + { + strfcpy (fcc, p, fcclen); + mutt_pretty_mailbox (fcc); + } + keep = 0; + } + else if (strncasecmp ("attach:", cur->data, 7) == 0) + { + BODY *body; + BODY *parts; + char *q; + + p = cur->data + 7; + SKIPWS (p); + if (*p) + { + if ((q = strpbrk (p, " \t"))) + { + mutt_substrcpy (path, p, q, sizeof (path)); + SKIPWS (q); + } + else + strfcpy (path, p, sizeof (path)); + mutt_expand_path (path, sizeof (path)); + if ((body = mutt_make_attach (path))) + { + body->description = safe_strdup (q); + for (parts = msg->content; parts->next; parts = parts->next) ; + parts->next = body; + } + else + { + mutt_pretty_mailbox (path); + mutt_error ("%s: unable to attach file", path); + } + } + keep = 0; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#ifdef _PGPPATH + else if (strncasecmp ("pgp:", cur->data, 4) == 0) + { + msg->pgp = mutt_parse_pgp_hdr (cur->data + 4, 0); + keep = 0; + } +#endif + + + + + + + + + if (keep) + { + last = cur; + cur = cur->next; + } + else + { + if (last) + last->next = cur->next; + else + msg->env->userhdrs = cur->next; + tmp = cur; + cur = cur->next; + tmp->next = NULL; + mutt_free_list (&tmp); + } + } + + if (!in_reply_to) + mutt_free_list (&msg->env->references); +} diff --git a/help.c b/help.c new file mode 100644 index 00000000..fa79b4ce --- /dev/null +++ b/help.c @@ -0,0 +1,205 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define HELP_C + +#include "mutt.h" +#include "mutt_curses.h" +#include "keymap.h" +#include "pager.h" +#include "mapping.h" + +#include <ctype.h> +#include <string.h> + +static struct binding_t *help_lookupFunction (int op, int menu) +{ + int i; + struct binding_t *map; + + if (menu != MENU_PAGER) + { + /* first look in the generic map for the function */ + for (i = 0; OpGeneric[i].name; i++) + if (OpGeneric[i].op == op) + return (&OpGeneric[i]); + } + + if ((map = km_get_table(menu))) + { + for (i = 0; map[i].name; i++) + if (map[i].op == op) + return (&map[i]); + } + + return (NULL); +} + +void mutt_make_help (char *d, size_t dlen, char *txt, int menu, int op) +{ + char buf[SHORT_STRING]; + + if (km_expand_key (buf, sizeof (buf), km_find_func (menu, op)) || + km_expand_key (buf, sizeof (buf), km_find_func (MENU_GENERIC, op))) + snprintf (d, dlen, "%s:%s", buf, txt); + else + d[0] = 0; +} + +char * +mutt_compile_help (char *buf, size_t buflen, int menu, struct mapping_t *items) +{ + int i; + size_t len; + char *pbuf = buf; + + for (i = 0; items[i].name && buflen > 2; i++) + { + if (i) + { + *pbuf++ = ' '; + *pbuf++ = ' '; + buflen -= 2; + } + mutt_make_help (pbuf, buflen, items[i].name, menu, items[i].value); + len = strlen (pbuf); + pbuf += len; + buflen -= len; + } + return buf; +} + +static void print_macro (FILE *f, const char *macro) +{ + int i; + + for (i = 0; *macro && i < COLS - 34; macro++, i++) + { + switch (*macro) + { + case '\033': + fputs ("\\e", f); + i++; + break; + case '\n': + fputs ("\\n", f); + i++; + break; + case '\r': + fputs ("\\r", f); + i++; + break; + case '\t': + fputs ("\\t", f); + i++; + break; + default: + fputc (*macro, f); + break; + } + } +} + +static void dump_menu (FILE *f, int menu) +{ + struct keymap_t *map; + struct binding_t *b; + char buf[SHORT_STRING]; + + /* browse through the keymap table */ + for (map = Keymaps[menu]; map; map = map->next) + { + km_expand_key (buf, sizeof (buf), map); + + if (map->op == OP_MACRO) + { + fprintf (f, "%s\t%-20s\t", buf, "macro"); + print_macro (f, map->macro); + fputc ('\n', f); + } + else if (map->op != OP_NULL) + { + b = help_lookupFunction (map->op, menu); + fprintf (f, "%s\t%-20s\t%s\n", buf, + b ? b->name : "UNKNOWN", + b ? HelpStrings[b->op] : "ERROR: please report this bug"); + } + } +} + +static int is_bound (struct keymap_t *map, int op) +{ + for (; map; map = map->next) + if (map->op == op) + return 1; + return 0; +} + +static void dump_unbound (FILE *f, + struct binding_t *funcs, + struct keymap_t *map, + struct keymap_t *aux) +{ + int i; + + for (i = 0; funcs[i].name; i++) + { + if (! is_bound (map, funcs[i].op) && + (!aux || ! is_bound (aux, funcs[i].op))) + fprintf (f, "%s\t\t%s\n", funcs[i].name, HelpStrings[funcs[i].op]); + } +} + +void mutt_help (int menu) +{ + char t[_POSIX_PATH_MAX]; + char buf[SHORT_STRING]; + char *desc; + FILE *f; + struct binding_t *funcs; + + mutt_mktemp (t); + if ((f = safe_fopen (t, "w")) == NULL) + { + mutt_perror (t); + return; + } + + funcs = km_get_table (menu); + desc = mutt_getnamebyvalue (menu, Menus); + if (!desc) + desc = "<UNKNOWN>"; + + dump_menu (f, menu); + if (menu != MENU_EDITOR && menu != MENU_PAGER) + { + fputs ("\nGeneric bindings:\n\n", f); + dump_menu (f, MENU_GENERIC); + } + + fputs ("\nUnbound functions:\n\n", f); + if (funcs) + dump_unbound (f, funcs, Keymaps[menu], NULL); + if (menu != MENU_PAGER) + dump_unbound (f, OpGeneric, Keymaps[MENU_GENERIC], Keymaps[menu]); + + fclose (f); + + snprintf (buf, sizeof (buf), "Help for %s", desc); + mutt_do_pager (buf, t, 0, NULL); +} diff --git a/hook.c b/hook.c new file mode 100644 index 00000000..b50af1fc --- /dev/null +++ b/hook.c @@ -0,0 +1,308 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" + +#include <limits.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <unistd.h> + +typedef struct hook +{ + int type; /* hook type */ + REGEXP rx; /* regular expression */ + char *command; /* filename, command or pattern to execute */ + pattern_t *pattern; /* used for fcc,save,send-hook */ + struct hook *next; +} HOOK; + +static HOOK *Hooks = NULL; + +int mutt_parse_hook (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + HOOK *ptr; + BUFFER command, pattern; + int rc, not = 0; + regex_t *rx = NULL; + pattern_t *pat = NULL; + char path[_POSIX_PATH_MAX]; + + memset (&pattern, 0, sizeof (pattern)); + memset (&command, 0, sizeof (command)); + + if (*s->dptr == '!') + { + s->dptr++; + SKIPWS (s->dptr); + not = 1; + } + + mutt_extract_token (&pattern, s, 0); + + if (!MoreArgs (s)) + { + strfcpy (err->data, "too few arguments", err->dsize); + goto error; + } + + mutt_extract_token (&command, s, (data & (M_FOLDERHOOK | M_SENDHOOK)) ? M_TOKEN_SPACE : 0); + + if (!command.data) + { + strfcpy (err->data, "too few arguments", err->dsize); + goto error; + } + + if (MoreArgs (s)) + { + strfcpy (err->data, "too many arguments", err->dsize); + goto error; + } + + if (data & (M_FOLDERHOOK | M_MBOXHOOK)) + { + strfcpy (path, pattern.data, sizeof (path)); + mutt_expand_path (path, sizeof (path)); + FREE (&pattern.data); + memset (&pattern, 0, sizeof (pattern)); + pattern.data = safe_strdup (path); + } + else if (DefaultHook) + { + char tmp[HUGE_STRING]; + + strfcpy (tmp, pattern.data, sizeof (tmp)); + mutt_check_simple (tmp, sizeof (tmp), DefaultHook); + FREE (&pattern.data); + memset (&pattern, 0, sizeof (pattern)); + pattern.data = safe_strdup (tmp); + } + + if (data & (M_MBOXHOOK | M_SAVEHOOK | M_FCCHOOK)) + { + strfcpy (path, command.data, sizeof (path)); + mutt_expand_path (path, sizeof (path)); + FREE (&command.data); + memset (&command, 0, sizeof (command)); + command.data = safe_strdup (path); + } + + /* check to make sure that a matching hook doesn't already exist */ + for (ptr = Hooks; ptr; ptr = ptr->next) + { + if (ptr->type == data && + ptr->rx.not == not && + !strcmp (pattern.data, ptr->rx.pattern)) + { + if (data & (M_FOLDERHOOK | M_SENDHOOK)) + { + /* folder-hook and send-hook allow multiple commands with the same + pattern, so if we've already seen this pattern/command pair, just + ignore it instead of creating a duplicate */ + if (!strcmp (ptr->command, command.data)) + { + FREE (&command.data); + FREE (&pattern.data); + return 0; + } + } + else + { + /* other hooks only allow one command per pattern, so update the + entry with the new command. this currently does not change the + order of execution of the hooks, which i think is desirable since + a common action to perform is to change the default (.) entry + based upon some other information. */ + FREE (&ptr->command); + ptr->command = command.data; + FREE (&pattern.data); + return 0; + } + } + if (!ptr->next) + break; + } + + if (data & (M_SENDHOOK | M_SAVEHOOK | M_FCCHOOK)) + { + if ((pat = mutt_pattern_comp (pattern.data, (data & M_SENDHOOK) ? M_FULL_MSG : 0, err)) == NULL) + goto error; + } + else + { + rx = safe_malloc (sizeof (regex_t)); + if ((rc = REGCOMP (rx, pattern.data, 0)) != 0) + { + regerror (rc, rx, err->data, err->dsize); + regfree (rx); + safe_free ((void **) &rx); + goto error; + } + } + + if (ptr) + { + ptr->next = safe_calloc (1, sizeof (HOOK)); + ptr = ptr->next; + } + else + Hooks = ptr = safe_calloc (1, sizeof (HOOK)); + ptr->type = data; + ptr->command = command.data; + ptr->pattern = pat; + ptr->rx.pattern = pattern.data; + ptr->rx.rx = rx; + ptr->rx.not = not; + return 0; + +error: + FREE (&pattern.data); + FREE (&command.data); + return (-1); +} + +void mutt_folder_hook (char *path) +{ + HOOK *tmp = Hooks; + BUFFER err, token; + char buf[STRING]; + + err.data = buf; + err.dsize = sizeof (buf); + memset (&token, 0, sizeof (token)); + for (; tmp; tmp = tmp->next) + if (tmp->type & M_FOLDERHOOK) + { + if ((regexec (tmp->rx.rx, path, 0, NULL, 0) == 0) ^ tmp->rx.not) + { + if (mutt_parse_rc_line (tmp->command, &token, &err) == -1) + { + mutt_error ("%s", err.data); + FREE (&token.data); + sleep (1); /* pause a moment to let the user see the error */ + return; + } + } + } + FREE (&token.data); +} + +char *mutt_find_hook (int type, const char *pat) +{ + HOOK *tmp = Hooks; + + for (; tmp; tmp = tmp->next) + if (tmp->type & type) + { + if (regexec (tmp->rx.rx, pat, 0, NULL, 0) == 0) + return (tmp->command); + } + return (NULL); +} + +void mutt_send_hook (HEADER *hdr) +{ + BUFFER err, token; + HOOK *hook; + char buf[STRING]; + + err.data = buf; + err.dsize = sizeof (buf); + memset (&token, 0, sizeof (token)); + for (hook = Hooks; hook; hook = hook->next) + if (hook->type & M_SENDHOOK) + if ((mutt_pattern_exec (hook->pattern, 0, NULL, hdr) > 0) ^ hook->rx.not) + if (mutt_parse_rc_line (hook->command, &token, &err) != 0) + { + FREE (&token.data); + mutt_error ("%s", err.data); + sleep (1); + return; + } + FREE (&token.data); +} + +static int +mutt_addr_hook (char *path, size_t pathlen, int type, CONTEXT *ctx, HEADER *hdr) +{ + HOOK *hook; + + /* determine if a matching hook exists */ + for (hook = Hooks; hook; hook = hook->next) + if (hook->type & type) + if ((mutt_pattern_exec (hook->pattern, 0, ctx, hdr) > 0) ^ hook->rx.not) + { + mutt_make_string (path, pathlen, hook->command, hdr); + return 0; + } + + return -1; +} + +void mutt_default_save (char *path, size_t pathlen, HEADER *hdr) +{ + *path = 0; + if (mutt_addr_hook (path, pathlen, M_SAVEHOOK, Context, hdr) != 0) + { + char tmp[_POSIX_PATH_MAX]; + ADDRESS *adr; + ENVELOPE *env = hdr->env; + int fromMe = mutt_addr_is_user (env->from); + + if (!fromMe && env->reply_to && env->reply_to->mailbox) + adr = env->reply_to; + else if (!fromMe && env->from && env->from->mailbox) + adr = env->from; + else if (env->to && env->to->mailbox) + adr = env->to; + else if (env->cc && env->cc->mailbox) + adr = env->cc; + else + adr = NULL; + if (adr) + { + mutt_safe_path (tmp, sizeof (tmp), adr); + snprintf (path, pathlen, "=%s", tmp); + } + } +} + +void mutt_select_fcc (char *path, size_t pathlen, HEADER *hdr) +{ + ADDRESS *adr; + char buf[_POSIX_PATH_MAX]; + ENVELOPE *env = hdr->env; + + if (mutt_addr_hook (path, pathlen, M_FCCHOOK, NULL, hdr) != 0) + { + if ((option (OPTSAVENAME) || option (OPTFORCENAME)) && + (env->to || env->cc || env->bcc)) + { + adr = env->to ? env->to : (env->cc ? env->cc : env->bcc); + mutt_safe_path (buf, sizeof (buf), adr); + snprintf (path, pathlen, "%s/%s", NONULL (Maildir), buf); + if (!option (OPTFORCENAME) && access (path, W_OK) != 0) + strfcpy (path, NONULL (Outbox), pathlen); + } + else + strfcpy (path, NONULL (Outbox), pathlen); + } + mutt_pretty_mailbox (path); +} diff --git a/imap.c b/imap.c new file mode 100644 index 00000000..022d72d1 --- /dev/null +++ b/imap.c @@ -0,0 +1,918 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "mx.h" +#include "mailbox.h" + +#include <unistd.h> +#include <netinet/in.h> +#include <netdb.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> + +/* Minimal support for IMAP 4rev1 */ + +#define IMAP_PORT 143 + +#define SEQLEN 5 + +/* number of entries in the hash table */ +#define IMAP_CACHE_LEN 10 + +enum +{ + IMAP_FATAL = 1, + IMAP_NEW_MAIL, + IMAP_BYE +}; + +typedef struct +{ + int index; + char *path; +} IMAP_CACHE; + +typedef struct +{ + short status; + unsigned short sequence; + unsigned short newMailCount; + short xxx; + IMAP_CACHE cache[IMAP_CACHE_LEN]; +} IMAP_DATA; + +static char ImapUser[SHORT_STRING] = { 0 }; +static char ImapPass[SHORT_STRING] = { 0 }; + +static int imap_read_line (char *buf, size_t buflen, int fd) +{ + char ch; + int i; + + for (i = 0; i < buflen; i++) + { + if (read (fd, &ch, 1) != 1) + return (-1); + if (ch == '\n') + break; + buf[i] = ch; + } + buf[i-1] = 0; + return (i + 1); +} + +static int imap_read_line_d (char *buf, size_t buflen, int fd) +{ + int r = imap_read_line (buf, buflen, fd); + dprint (1,(debugfile,"imap_read_line_d():%s\n", buf)); + return r; +} + +static void imap_make_sequence (char *buf, size_t buflen, CONTEXT *ctx) +{ + snprintf (buf, buflen, "a%04d", ((IMAP_DATA *) ctx->data)->sequence++); +} + +static int imap_write (int fd, const char *buf) +{ + dprint (1,(debugfile,"imap_write():%s", buf)); + return (write (fd, buf, strlen (buf))); +} + +static void imap_error (const char *where, const char *msg) +{ + dprint (1, (debugfile, "imap_error(): unexpected response in %s: %s\n", where, msg)); +} + +/* date is of the form: DD-MMM-YYYY HH:MM:SS +ZZzz */ +static time_t imap_parse_date (char *s) +{ + struct tm t; + time_t tz; + + t.tm_mday = (s[0] - '0') * 10 + (s[1] - '0'); + s += 2; + if (*s != '-') + return 0; + s++; + t.tm_mon = mutt_check_month (s); + s += 3; + if (*s != '-') + return 0; + s++; + t.tm_year = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0') - 1900; + s += 4; + if (*s != ' ') + return 0; + s++; + + /* time */ + t.tm_hour = (s[0] - '0') * 10 + (s[1] - '0'); + s += 2; + if (*s != ':') + return 0; + s++; + t.tm_min = (s[0] - '0') * 10 + (s[1] - '0'); + s += 2; + if (*s != ':') + return 0; + s++; + t.tm_sec = (s[0] - '0') * 10 + (s[1] - '0'); + s += 2; + if (*s != ' ') + return 0; + s++; + + /* timezone */ + tz = ((s[1] - '0') * 10 + (s[2] - '0')) * 3600 + + ((s[3] - '0') * 10 + (s[4] - '0')) * 60; + if (s[0] == '+') + tz = -tz; + + return (mutt_mktime (&t, 0) + tz); +} + +static int imap_parse_fetch (HEADER *h, char *s) +{ + char tmp[SHORT_STRING]; + char *ptmp; + int state = 0; + + if (!s) + return (-1); + + h->read = 0; + h->old = 0; + + while (*s) + { + SKIPWS (s); + + switch (state) + { + case 0: + if (strncasecmp ("FLAGS", s, 5) == 0) + { + s += 5; + SKIPWS (s); + if (*s != '(') + { + dprint (1, (debugfile, "imap_parse_fetch(): bogus FLAGS entry: %s\n", s)); + return (-1); /* parse error */ + } + s++; + state = 1; + } + else if (strncasecmp ("INTERNALDATE", s, 12) == 0) + { + s += 12; + SKIPWS (s); + if (*s != '\"') + { + dprint (1, (debugfile, "imap_parse_fetch(): bogus INTERNALDATE entry: %s\n", s)); + return (-1); + } + s++; + ptmp = tmp; + while (*s && *s != '\"') + *ptmp++ = *s++; + if (*s != '\"') + return (-1); + s++; /* skip past the trailing " */ + *ptmp = 0; + h->received = imap_parse_date (tmp); + } + else if (strncasecmp ("RFC822.SIZE", s, 11) == 0) + { + s += 11; + SKIPWS (s); + ptmp = tmp; + while (isdigit (*s)) + *ptmp++ = *s++; + *ptmp = 0; + } + else if (*s == ')') + s++; /* end of request */ + else + { + /* got something i don't understand */ + imap_error ("imap_parse_fetch()", s); + return (-1); + } + break; + case 1: /* flags */ + if (*s == ')') + { + s++; + state = 0; + } + else if (strncasecmp ("\\deleted", s, 8) == 0) + { + s += 8; + h->deleted = 1; + } + else if (strncasecmp ("\\flagged", s, 8) == 0) + { + s += 8; + h->flagged = 1; + } + else if (strncasecmp ("\\answered", s, 9) == 0) + { + s += 9; + h->replied = 1; + } + else if (strncasecmp ("\\seen", s, 5) == 0) + { + s += 5; + h->read = 1; + } + else + { + while (*s && !ISSPACE (*s) && *s != ')') + s++; + } + break; + } + } + return 0; +} + +static int imap_read_bytes (FILE *fp, int fd, long bytes) +{ + long pos; + long len; + char buf[LONG_STRING]; + + for (pos = 0; pos < bytes; ) + { + len = imap_read_line (buf, sizeof (buf), fd); + if (len < 0) + return (-1); + pos += len; + fputs (buf, fp); + fputs ("\n", fp); + } + + return 0; +} + +/* returns 1 if the command result was OK, or 0 if NO or BAD */ +static int imap_code (const char *s) +{ + s += SEQLEN; + SKIPWS (s); + return (strncasecmp ("OK", s, 2) == 0); +} + +static char *imap_next_word (char *s) +{ + while (*s && !ISSPACE (*s)) + s++; + SKIPWS (s); + return s; +} + +static int imap_handle_untagged (CONTEXT *ctx, char *s) +{ + char *pn; + int count; + + s = imap_next_word (s); + + if (isdigit (*s)) + { + pn = s; + s = imap_next_word (s); + + if (strncasecmp ("EXISTS", s, 6) == 0) + { + /* new mail arrived */ + count = atoi (pn); + + if (count <= ctx->msgcount) + { + /* something is wrong because the server reported fewer messages + * than we previously saw + */ + mutt_error ("Fatal error. Message count is out of sync!"); + ((IMAP_DATA *) ctx->data)->status = IMAP_FATAL; + mx_fastclose_mailbox (ctx); + return (-1); + } + else + { + ((IMAP_DATA *) ctx->data)->status = IMAP_NEW_MAIL; + ((IMAP_DATA *) ctx->data)->newMailCount = count; + } + } + } + else if (strncasecmp ("BYE", s, 3) == 0) + { + /* server shut down our connection */ + s += 3; + SKIPWS (s); + mutt_error (s); + ((IMAP_DATA *) ctx->data)->status = IMAP_BYE; + mx_fastclose_mailbox (ctx); + return (-1); + } + else + { + dprint (1, (debugfile, "imap_unhandle_untagged(): unhandled request: %s\n", + s)); + } + + return 0; +} + +static int imap_read_header (CONTEXT *ctx, int msgno) +{ + char buf[LONG_STRING]; + FILE *fp; + char tempfile[_POSIX_PATH_MAX]; + char seq[8]; + char *pc; + char *pn; + long bytes; + + ctx->hdrs[ctx->msgcount]->index = ctx->msgcount; + + mutt_mktemp (tempfile); + if (!(fp = safe_fopen (tempfile, "w+"))) + { + return (-1); + } + + imap_make_sequence (seq, sizeof (seq), ctx); + snprintf (buf, sizeof (buf), "%s FETCH %d RFC822.HEADER\r\n", seq, msgno + 1); + imap_write (ctx->fd, buf); + + do + { + if (imap_read_line (buf, sizeof (buf), ctx->fd) < 0) + { + return (-1); + } + + if (buf[0] == '*') + { + pc = buf; + pc = imap_next_word (pc); + pc = imap_next_word (pc); + if (strncasecmp ("FETCH", pc, 5) == 0) + { + if (!(pc = strchr (pc, '{'))) + { + imap_error ("imap_read_header()", buf); + return (-1); + } + pc++; + pn = pc; + while (isdigit (*pc)) + pc++; + *pc = 0; + bytes = atoi (pn); + + imap_read_bytes (fp, ctx->fd, bytes); + } + else if (imap_handle_untagged (ctx, buf) != 0) + return (-1); + } + } + while (strncmp (seq, buf, SEQLEN) != 0); + + rewind (fp); + ctx->hdrs[msgno]->env = mutt_read_rfc822_header (fp, ctx->hdrs[msgno]); + + fclose (fp); + unlink (tempfile); + + /* get the status of this message */ + imap_make_sequence (seq, sizeof (seq), ctx); + snprintf (buf, sizeof (buf), "%s FETCH %d FAST\r\n", seq, msgno + 1); + imap_write (ctx->fd, buf); + do + { + if (imap_read_line_d (buf, sizeof (buf), ctx->fd) < 0) + break; + if (buf[0] == '*') + { + pc = buf; + pc = imap_next_word (pc); + pc = imap_next_word (pc); + if (strncasecmp ("FETCH", pc, 5) == 0) + { + if (!(pc = strchr (pc, '('))) + { + imap_error ("imap_read_header()", buf); + return (-1); + } + if (imap_parse_fetch (ctx->hdrs[msgno], pc + 1) != 0) + return (-1); + } + else if (imap_handle_untagged (ctx, buf) != 0) + return (-1); + } + } + while (strncmp (seq, buf, SEQLEN) != 0) + ; + + return 0; +} + +static int imap_exec (char *buf, size_t buflen, + CONTEXT *ctx, const char *seq, const char *cmd) +{ + int count; + + imap_write (ctx->fd, cmd); + + do + { + if (imap_read_line_d (buf, buflen, ctx->fd) < 0) + return (-1); + + if (buf[0] == '*' && imap_handle_untagged (ctx, buf) != 0) + return (-1); + } + while (strncmp (buf, seq, SEQLEN) != 0); + + if (((IMAP_DATA *) ctx->data)->status == IMAP_NEW_MAIL) + { + /* read new mail messages */ + + dprint (1, (debugfile, "imap_exec(): new mail detected\n")); + mutt_message ("Fetching headers for new mail..."); + + ((IMAP_DATA *) ctx->data)->status = 0; + + count = ((IMAP_DATA *) ctx->data)->newMailCount; + while (count > ctx->hdrmax) + mx_alloc_memory (ctx); + + while (ctx->msgcount < count) + { + ctx->hdrs[ctx->msgcount] = mutt_new_header (); + imap_read_header (ctx, ctx->msgcount); + mx_update_context (ctx); /* incremements ->msgcount */ + + /* check to make sure that new mail hasn't arrived in the middle of + * checking for new mail (sigh) + */ + if (((IMAP_DATA *) ctx->data)->status == IMAP_NEW_MAIL) + { + count = ((IMAP_DATA *) ctx->data)->newMailCount; + while (count > ctx->hdrmax) + mx_alloc_memory (ctx); + ((IMAP_DATA *) ctx->data)->status = 0; + } + } + + mutt_clear_error (); + } + + if (!imap_code (buf)) + { + char *pc; + + dprint (1, (debugfile, "imap_exec(): command failed: %s\n", buf)); + pc = buf + SEQLEN; + SKIPWS (pc); + pc = imap_next_word (pc); + mutt_error (pc); + sleep (1); + return (-1); + } + + return 0; +} + +int imap_open_mailbox (CONTEXT *ctx) +{ + struct sockaddr_in sin; + struct hostent *he; + char buf[LONG_STRING]; + char bufout[LONG_STRING]; + char host[SHORT_STRING]; + char mailbox[_POSIX_PATH_MAX]; + char seq[16]; + int count = 0; + int n; + char *pc; + + pc = ctx->path; + if (*pc != '{') + return (-1); + pc++; + n = 0; + while (*pc && *pc != '}') + host[n++] = *pc++; + host[n] = 0; + if (!*pc) + return (-1); + pc++; + strfcpy (mailbox, pc, sizeof (mailbox)); + + if (!ImapUser[0]) + strfcpy (ImapUser, Username, sizeof (ImapUser)); + if (mutt_get_field ("IMAP Username: ", ImapUser, sizeof (ImapUser), 0) != 0 || + !ImapUser[0]) + { + ImapUser[0] = 0; + return (-1); + } + + snprintf (buf, sizeof (buf), "Password for %s@%s: ", ImapUser, host); + ImapPass[0] = 0; + if (mutt_get_field (buf, ImapPass, sizeof (ImapPass), M_PASS) != 0 || + !ImapPass[0]) + { + return (-1); + } + + memset (&sin, 0, sizeof (sin)); + sin.sin_port = htons (IMAP_PORT); + sin.sin_family = AF_INET; + if ((he = gethostbyname (host)) == NULL) + { + mutt_perror (host); + return (-1); + } + memcpy (&sin.sin_addr, he->h_addr_list[0], he->h_length); + + if ((ctx->fd = socket (AF_INET, SOCK_STREAM, IPPROTO_IP)) < 0) + { + mutt_perror ("socket"); + return (-1); + } + + mutt_message ("Connecting to %s...", host); + + if (connect (ctx->fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) + { + mutt_perror ("connect"); + close (ctx->fd); + } + + if (imap_read_line_d (buf, sizeof (buf), ctx->fd) < 0) + { + close (ctx->fd); + return (-1); + } + + if (strncmp ("* OK", buf, 4) != 0) + { + imap_error ("imap_open_mailbox()", buf); + close (ctx->fd); + return (-1); + } + + /* create IMAP-specific state struct */ + ctx->data = safe_malloc (sizeof (IMAP_DATA)); + memset (ctx->data, 0, sizeof (IMAP_DATA)); + + mutt_message ("Logging in..."); + imap_make_sequence (seq, sizeof (seq), ctx); + snprintf (buf, sizeof (buf), "%s LOGIN %s %s\r\n", seq, ImapUser, ImapPass); + if (imap_exec (buf, sizeof (buf), ctx, seq, buf) != 0) + { + imap_error ("imap_open_mailbox()", buf); + return (-1); + } + + mutt_message ("Selecting %s...", mailbox); + imap_make_sequence (seq, sizeof (seq), ctx); + snprintf (bufout, sizeof (bufout), "%s SELECT %s\r\n", seq, mailbox); + imap_write (ctx->fd, bufout); + + do + { + if (imap_read_line_d (buf, sizeof (buf), ctx->fd) < 0) + break; + + if (buf[0] == '*') + { + pc = buf + 2; + + if (isdigit (*pc)) + { + char *pn = pc; + + while (*pc && isdigit (*pc)) + pc++; + *pc++ = 0; + n = atoi (pn); + SKIPWS (pc); + if (strncasecmp ("EXISTS", pc, 6) == 0) + count = n; + } + else if (imap_handle_untagged (ctx, buf) != 0) + return (-1); + } + } + while (strncmp (seq, buf, strlen (seq)) != 0); + + mutt_message ("Fetching message headers..."); + ctx->hdrmax = count; + ctx->hdrs = safe_malloc (count * sizeof (HEADER *)); + ctx->v2r = safe_malloc (count * sizeof (int)); + for (ctx->msgcount = 0; ctx->msgcount < count; ) + { + ctx->hdrs[ctx->msgcount] = mutt_new_header (); + + /* `count' can get modified if new mail arrives while fetching the + * header for this message + */ + if (imap_read_header (ctx, ctx->msgcount) != 0) + { + mx_fastclose_mailbox (ctx); + return (-1); + } + mx_update_context (ctx); /* increments ->msgcount */ + + /* in case we get new mail while fetching the headers */ + if (((IMAP_DATA *) ctx->data)->status == IMAP_NEW_MAIL) + { + count = ((IMAP_DATA *) ctx->data)->newMailCount; + while (count > ctx->hdrmax) + mx_alloc_memory (ctx); + ((IMAP_DATA *) ctx->data)->status = 0; + } + } + + return 0; +} + +int imap_fetch_message (MESSAGE *msg, CONTEXT *ctx, int msgno) +{ + char seq[8]; + char buf[LONG_STRING]; + char path[_POSIX_PATH_MAX]; + char *pc; + char *pn; + long bytes; + IMAP_CACHE *cache; + + /* see if we already have the message in our cache */ + cache = &((IMAP_DATA *) ctx->data)->cache[ctx->hdrs[msgno]->index % IMAP_CACHE_LEN]; + + if (cache->path) + { + if (cache->index == ctx->hdrs[msgno]->index) + { + /* yes, so just return a pointer to the message */ + if (!(msg->fp = fopen (cache->path, "r"))) + { + mutt_perror (cache->path); + return (-1); + } + return 0; + } + else + { + /* clear the previous entry */ + unlink (cache->path); + free (cache->path); + } + } + + mutt_message ("Fetching message..."); + + cache->index = ctx->hdrs[msgno]->index; + mutt_mktemp (path); + cache->path = safe_strdup (path); + if (!(msg->fp = safe_fopen (path, "w+"))) + { + safe_free ((void **) &cache->path); + return (-1); + } + + imap_make_sequence (seq, sizeof (seq), ctx); + snprintf (buf, sizeof (buf), "%s FETCH %d RFC822\r\n", seq, + ctx->hdrs[msgno]->index + 1); + imap_write (ctx->fd, buf); + do + { + if (imap_read_line (buf, sizeof (buf), ctx->fd) < 0) + { + return (-1); + } + + if (buf[0] == '*') + { + pc = buf; + pc = imap_next_word (pc); + pc = imap_next_word (pc); + if (strncasecmp ("FETCH", pc, 5) == 0) + { + if (!(pc = strchr (buf, '{'))) + { + imap_error ("imap_fetch_message()", buf); + return (-1); + } + pc++; + pn = pc; + while (isdigit (*pc)) + pc++; + *pc = 0; + bytes = atoi (pn); + imap_read_bytes (msg->fp, ctx->fd, bytes); + } + else if (imap_handle_untagged (ctx, buf) != 0) + return (-1); + } + } + while (strncmp (buf, seq, SEQLEN) != 0) + ; + + if (!imap_code (buf)) + { + return (-1); + } + + return 0; +} + +int imap_close_connection (CONTEXT *ctx) +{ + char buf[LONG_STRING]; + char seq[8]; + + /* if the server didn't shut down on us, close the connection gracefully */ + if (((IMAP_DATA *) ctx->data)->status != IMAP_BYE) + { + mutt_message ("Closing connection to IMAP server..."); + imap_make_sequence (seq, sizeof (seq), ctx); + snprintf (buf, sizeof (buf), "%s LOGOUT\r\n", seq); + imap_write (ctx->fd, buf); + do + { + if (imap_read_line_d (buf, sizeof (buf), ctx->fd) < 0) + break; + } + while (strncmp (seq, buf, SEQLEN) != 0); + mutt_clear_error (); + } + close (ctx->fd); + return 0; +} + +static int make_delete_list (char *list, size_t listlen, CONTEXT *ctx) +{ + int first = -1, last = -1; + int n; + char tmp[LONG_STRING]; + + *list = 0; + for (n=0; n<ctx->msgcount; n++) + { + if (ctx->hdrs[n]->deleted) + { + if (first < 0) + { + first = n; + last = n; + } + else if (last != n - 1) + { + if (first != last) + snprintf (tmp, sizeof (tmp), "%d:%d", first + 1, last + 1); + else + snprintf (tmp, sizeof (tmp), "%d", first + 1); + if (list[0]) + strcat (list, ","); + strcat (list, tmp); + first = last = n; + } + else + last = n; + } + } + + if (first >= 0) + { + if (first != last) + snprintf (tmp, sizeof (tmp), "%d:%d", first + 1, last + 1); + else + snprintf (tmp, sizeof (tmp), "%d", first + 1); + if (list[0]) + strcat (list, ","); + strcat (list, tmp); + } + + return 0; +} + +int imap_sync_mailbox (CONTEXT *ctx) +{ + char seq[8]; + char buf[LONG_STRING]; + char tmp[LONG_STRING]; + int n; + + /* save status changes */ + mutt_message ("Saving message status flags..."); + for (n = 0; n < ctx->msgcount; n++) + { + if (!ctx->hdrs[n]->deleted && ctx->hdrs[n]->changed) + { + if (ctx->hdrs[n]->read) + strcat (tmp, "\\Seen "); + if (ctx->hdrs[n]->flagged) + strcat (tmp, "\\Flagged "); + if (ctx->hdrs[n]->replied) + strcat (tmp, "\\Answered"); + mutt_remove_trailing_ws (tmp); + + imap_make_sequence (seq, sizeof (seq), ctx); + snprintf (buf, sizeof (buf), "%s STORE %d FLAGS.SILENT (%s)\r\n", seq, tmp); + if (imap_exec (buf, sizeof (buf), ctx, seq, buf) != 0) + { + imap_error ("imap_sync_mailbox()", buf); + return (-1); + } + } + } + + mutt_message ("Marking messages as deleted..."); + make_delete_list (tmp, sizeof (tmp), ctx); + imap_make_sequence (seq, sizeof (seq), ctx); + snprintf (buf, sizeof (buf), "%s STORE %s +FLAGS.SILENT (\\Deleted)\r\n", seq, tmp); + if (imap_exec (buf, sizeof (buf), ctx, seq, buf) != 0) + { + imap_error ("imap_sync_mailbox()", buf); + return (-1); + } + + return 0; +} + +void imap_fastclose_mailbox (CONTEXT *ctx) +{ + int i; + + imap_close_connection (ctx); + for (i = 0; i < IMAP_CACHE_LEN; i++) + { + if (((IMAP_DATA *) ctx->data)->cache[i].path) + { + unlink (((IMAP_DATA *) ctx->data)->cache[i].path); + safe_free ((void **) &((IMAP_DATA *) ctx->data)->cache[i].path); + } + } + safe_free ((void **) &ctx->data); +} + +/* commit changes and terminate connection */ +int imap_close_mailbox (CONTEXT *ctx) +{ + char seq[8]; + char buf[LONG_STRING]; + + /* tell the server to commit changes */ + mutt_message ("Closing mailbox..."); + imap_make_sequence (seq, sizeof (seq), ctx); + snprintf (buf, sizeof (buf), "%s CLOSE\r\n", seq); + if (imap_exec (buf, sizeof (buf), ctx, seq, buf) != 0) + { + imap_error ("imap_close_mailbox()", buf); + return (-1); + } + return 0; +} + +/* use the NOOP command to poll for new mail */ +int imap_check_mailbox (CONTEXT *ctx, int *index_hint) +{ + char seq[8]; + char buf[LONG_STRING]; + int msgcount = ctx->msgcount; + + imap_make_sequence (seq, sizeof (seq), ctx); + snprintf (buf, sizeof (buf), "%s NOOP\r\n", seq); + if (imap_exec (buf, sizeof (buf), ctx, seq, buf) != 0) + { + imap_error ("imap_check_mailbox()", buf); + return (-1); + } + + return (msgcount != ctx->msgcount); +} diff --git a/init.c b/init.c new file mode 100644 index 00000000..ddcb531e --- /dev/null +++ b/init.c @@ -0,0 +1,1437 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "mutt_regex.h" + + + +#ifdef _PGPPATH +#include "pgp.h" +#endif + + + +#include "mx.h" +#include "init.h" +#include "mailbox.h" + +#include <pwd.h> +#include <ctype.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/utsname.h> +#include <errno.h> +#include <sys/wait.h> + +#define toggle_quadoption(opt) QuadOptions ^= (1 << (2 * opt)) + +void set_quadoption (int opt, int flag) +{ + QuadOptions &= ~(0x3 << (2 * opt)); /* first clear the bits */ + QuadOptions |= (flag & 0x3) << (2 * opt); /* now set them */ +} + +int quadoption (int opt) +{ + return ((QuadOptions >> (opt * 2)) & 0x3); +} + +int query_quadoption (int opt, const char *prompt) +{ + int v = quadoption (opt); + + switch (v) + { + case M_YES: + case M_NO: + return (v); + + default: + v = mutt_yesorno (prompt, (v == M_ASKYES)); + CLEARLINE (LINES - 1); + return (v); + } + + /* not reached */ +} + +/* given the variable ``s'', return the index into the rc_vars array which + matches, or -1 if the variable is not found. */ +int mutt_option_index (char *s) +{ + int i; + + for (i = 0; MuttVars[i].option; i++) + if (strcmp (s, MuttVars[i].option) == 0) + return (MuttVars[i].type == DT_SYN ? mutt_option_index ((char *) MuttVars[i].data) : i); + return (-1); +} + +static void add_char (BUFFER *buf, char ch) +{ + size_t offset; + + if (buf->dptr >= buf->data + buf->dsize) + { + offset = buf->dptr - buf->data; + buf->dsize += 4; + safe_realloc ((void **) &buf->data, buf->dsize); + buf->dptr = buf->data + offset; + } + *buf->dptr++ = ch; +} + +static void add_str (BUFFER *buf, const char *s) +{ + size_t slen = strlen (s); + size_t offset; + + if (buf->dptr + slen > buf->data + buf->dsize) + { + offset = buf->dptr - buf->data; + buf->dsize += slen; + safe_realloc ((void **) &buf->data, buf->dsize); + buf->dptr = buf->data + offset; + } + memcpy (buf->dptr, s, slen); + buf->dptr += slen; +} + +int mutt_extract_token (BUFFER *dest, BUFFER *tok, int flags) +{ + char ch; + char qc = 0; /* quote char */ + char *pc; + + /* reset the destination pointer to the beginning of the buffer */ + dest->dptr = dest->data; + + SKIPWS (tok->dptr); + while ((ch = *tok->dptr)) + { + if (!qc) + { + if ((ISSPACE (ch) && !(flags & M_TOKEN_SPACE)) || + (ch == '#' && !(flags & M_TOKEN_COMMENT)) || + (ch == '=' && (flags & M_TOKEN_EQUAL)) || + (ch == ';' && !(flags & M_TOKEN_SEMICOLON)) || + ((flags & M_TOKEN_PATTERN) && strchr ("~!|", ch))) + break; + } + + tok->dptr++; + + if (ch == qc) + qc = 0; /* end of quote */ + else if (!qc && (ch == '\'' || ch == '"') && !(flags & M_TOKEN_QUOTE)) + qc = ch; + else if (ch == '\\' && qc != '\'') + { + switch (ch = *tok->dptr++) + { + case 'c': + case 'C': + add_char (dest, (toupper (*tok->dptr) - '@') & 0x7f); + tok->dptr++; + break; + case 'r': + add_char (dest, '\r'); + break; + case 'n': + add_char (dest, '\n'); + break; + case 't': + add_char (dest, '\t'); + break; + case 'f': + add_char (dest, '\f'); + break; + case 'e': + add_char (dest, '\033'); + break; + default: + if (isdigit (ch) && + isdigit (*tok->dptr) && + isdigit (*(tok->dptr + 1))) + { + + add_char (dest, (ch << 6) + (*tok->dptr << 3) + *(tok->dptr + 1) - 3504); + tok->dptr += 2; + } + else + add_char (dest, ch); + } + } + else if (ch == '^' && (flags & M_TOKEN_CONDENSE)) + { + ch = *tok->dptr++; + if (ch == '^') + add_char (dest, ch); + else if (ch == '[') + add_char (dest, '\033'); + else if (isalpha (ch)) + add_char (dest, toupper (ch) - '@'); + else + { + add_char (dest, '^'); + add_char (dest, ch); + } + } + else if (ch == '`' && (!qc || qc == '"')) + { + FILE *fp; + pid_t pid; + char *cmd, *ptr; + size_t expnlen; + BUFFER expn; + int line = 0; + + pc = tok->dptr; + do { + if ((pc = strpbrk (pc, "\\`"))) + { + /* skip any quoted chars */ + if (*pc == '\\') + pc += 2; + } + } while (pc && *pc != '`'); + if (!pc) + { + dprint (1, (debugfile, "mutt_get_token: mismatched backtics\n")); + return (-1); + } + cmd = mutt_substrdup (tok->dptr, pc); + if ((pid = mutt_create_filter (cmd, NULL, &fp, NULL)) < 0) + { + dprint (1, (debugfile, "mutt_get_token: unable to fork command: %s", cmd)); + return (-1); + } + FREE (&cmd); + + tok->dptr = pc + 1; + + /* read line */ + memset (&expn, 0, sizeof (expn)); + expn.data = mutt_read_line (NULL, &expn.dsize, fp, &line); + fclose (fp); + mutt_wait_filter (pid); + + /* if we got output, make a new string consiting of the shell ouptput + plus whatever else was left on the original line */ + if (expn.data) + { + expnlen = strlen (expn.data); + tok->dsize = expnlen + strlen (tok->dptr) + 1; + ptr = safe_malloc (tok->dsize); + memcpy (ptr, expn.data, expnlen); + strcpy (ptr + expnlen, tok->dptr); + if (tok->destroy) + FREE (&tok->data); + tok->data = ptr; + tok->dptr = ptr; + tok->destroy = 1; /* mark that the caller should destroy this data */ + ptr = NULL; + FREE (&expn.data); + } + } + else if (ch == '$' && (!qc || qc == '"') && (*tok->dptr == '{' || isalpha (*tok->dptr))) + { + char *env, *var; + + if (*tok->dptr == '{') + { + tok->dptr++; + if ((pc = strchr (tok->dptr, '}'))) + { + var = mutt_substrdup (tok->dptr, pc); + tok->dptr = pc + 1; + } + } + else + { + for (pc = tok->dptr; isalpha (*pc) || *pc == '_'; pc++) + ; + var = mutt_substrdup (tok->dptr, pc); + tok->dptr = pc; + } + if ((env = getenv (var))) + add_str (dest, env); + FREE (&var); + } + else + add_char (dest, ch); + } + add_char (dest, 0); /* terminate the string */ + SKIPWS (tok->dptr); + return 0; +} + +void mutt_add_to_list (LIST **list, BUFFER *inp) +{ + LIST *t, *last = NULL; + BUFFER buf; + + memset (&buf, 0, sizeof (buf)); + do + { + mutt_extract_token (&buf, inp, 0); + + /* check to make sure the item is not already on this list */ + for (last = *list; last; last = last->next) + { + if (strcasecmp (buf.data, last->data) == 0) + { + /* already on the list, so just ignore it */ + last = NULL; + break; + } + if (!last->next) + break; + } + + if (!*list || last) + { + t = (LIST *) safe_calloc (1, sizeof (LIST)); + t->data = buf.data; + memset (&buf, 0, sizeof (buf)); + if (last) + { + last->next = t; + last = last->next; + } + else + *list = last = t; + } + } + while (MoreArgs (inp)); + FREE (&buf.data); +} + +static void remove_from_list (LIST **l, BUFFER *inp) +{ + LIST *p, *last = NULL; + BUFFER buf; + + memset (&buf, 0, sizeof (buf)); + do + { + mutt_extract_token (&buf, inp, 0); + + if (strcmp ("*", buf.data) == 0) + mutt_free_list (l); /* ``unCMD *'' means delete all current entries */ + else + { + p = *l; + last = NULL; + while (p) + { + if (strcasecmp (buf.data, p->data) == 0) + { + safe_free ((void **) &p->data); + if (last) + last->next = p->next; + else + (*l) = p->next; + safe_free ((void **) &p); + } + else + { + last = p; + p = p->next; + } + } + } + } + while (MoreArgs (inp)); + FREE (&buf.data); +} + +static int parse_unignore (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + mutt_add_to_list (&UnIgnore, s); + remove_from_list (&Ignore, s); + return 0; +} + +static int parse_ignore (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + mutt_add_to_list (&Ignore, s); + remove_from_list (&UnIgnore, s); + return 0; +} + +static int parse_list (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + mutt_add_to_list ((LIST **) data, s); + return 0; +} + +static int parse_unlist (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + remove_from_list ((LIST **) data, s); + return 0; +} + +static int parse_unalias (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + ALIAS *tmp, *last = NULL; + + do + { + mutt_extract_token (buf, s, 0); + + tmp = Aliases; + for (tmp = Aliases; tmp; tmp = tmp->next) + { + if (strcasecmp (buf->data, tmp->name) == 0) + { + if (last) + last->next = tmp->next; + else + Aliases = tmp->next; + tmp->next = NULL; + mutt_free_alias (&tmp); + break; + } + last = tmp; + } + } + while (MoreArgs (s)); + return 0; +} + +static int parse_alias (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + ALIAS *tmp = Aliases; + ALIAS *last = NULL; + char *p; + size_t len; + + if ((p = strpbrk (s->dptr, " \t")) == NULL) + { + strfcpy (err->data, "alias: no address", err->dsize); + return (-1); + } + + len = p - s->dptr; + + /* check to see if an alias with this name already exists */ + for (; tmp; tmp = tmp->next) + { + if (!strncasecmp (tmp->name, s->dptr, len) && *(tmp->name + len) == 0) + break; + last = tmp; + } + + if (!tmp) + { + /* create a new alias */ + tmp = (ALIAS *) safe_calloc (1, sizeof (ALIAS)); + tmp->name = safe_malloc (len + 1); + memcpy (tmp->name, s->dptr, len); + tmp->name[len] = 0; + } + else + { + /* override the previous value */ + rfc822_free_address (&tmp->addr); + } + s->dptr = p; + + mutt_extract_token (buf, s, M_TOKEN_QUOTE | M_TOKEN_SPACE); + tmp->addr = mutt_parse_adrlist (tmp->addr, buf->data); + if (last) + last->next = tmp; + else + Aliases = tmp; + return 0; +} + +static int +parse_unmy_hdr (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + LIST *last = NULL; + LIST *tmp = UserHeader; + LIST *ptr; + size_t l; + + do + { + mutt_extract_token (buf, s, 0); + if (strcmp ("*", buf->data) == 0) + mutt_free_list (&UserHeader); + else + { + tmp = UserHeader; + last = NULL; + + l = strlen (buf->data); + if (buf->data[l - 1] == ':') + l--; + + while (tmp) + { + if (strncasecmp (buf->data, tmp->data, l) == 0 && tmp->data[l] == ':') + { + ptr = tmp; + if (last) + last->next = tmp->next; + else + UserHeader = tmp->next; + tmp = tmp->next; + ptr->next = NULL; + mutt_free_list (&ptr); + } + else + { + last = tmp; + tmp = tmp->next; + } + } + } + } + while (MoreArgs (s)); + return 0; +} + +static int parse_my_hdr (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + LIST *tmp; + size_t keylen; + char *p; + + mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE); + if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') + { + strfcpy (err->data, "invalid header field", err->dsize); + return (-1); + } + keylen = p - buf->data + 1; + p++; + SKIPWS (p); + if (!*p) + { + snprintf (err->data, err->dsize, "ignoring empty header field: %s", buf->data); + return (-1); + } + + if (UserHeader) + { + for (tmp = UserHeader; ; tmp = tmp->next) + { + /* see if there is already a field by this name */ + if (strncasecmp (buf->data, tmp->data, keylen) == 0) + { + /* replace the old value */ + safe_free ((void **) &tmp->data); + tmp->data = buf->data; + memset (buf, 0, sizeof (BUFFER)); + return 0; + } + if (!tmp->next) + break; + } + tmp->next = mutt_new_list (); + tmp = tmp->next; + } + else + { + tmp = mutt_new_list (); + UserHeader = tmp; + } + tmp->data = buf->data; + memset (buf, 0, sizeof (BUFFER)); + return 0; +} + +static int +parse_sort (short *val, const char *s, const struct mapping_t *map, BUFFER *err) +{ + int i, flags = 0; + + if (strncmp ("reverse-", s, 8) == 0) + { + s += 8; + flags = SORT_REVERSE; + } + + if (strncmp ("last-", s, 5) == 0) + { + s += 5; + flags |= SORT_LAST; + } + + if ((i = mutt_getvaluebyname (s, map)) == -1) + { + snprintf (err->data, err->dsize, "%s: unknown sorting method", s); + return (-1); + } + + *val = i | flags; + + return 0; +} + +static void mutt_restore_default (struct option_t *p) +{ + switch (p->type & DT_MASK) + { + case DT_STR: + if (p->init) + { + FREE (p->data); + *((char **) p->data) = safe_strdup ((char *) p->init); + } + break; + case DT_PATH: + if (p->init) + { + char path[_POSIX_PATH_MAX]; + + FREE (p->data); + strfcpy (path, (char *) p->init, sizeof (path)); + mutt_expand_path (path, sizeof (path)); + *((char **) p->data) = safe_strdup (path); + } + break; + case DT_BOOL: + if (p->init) + set_option (p->data); + else + unset_option (p->data); + break; + case DT_QUAD: + set_quadoption (p->data, p->init); + break; + case DT_NUM: + case DT_SORT: + case DT_MAGIC: + *((short *) p->data) = p->init; + break; + case DT_RX: + { + REGEXP *pp = (REGEXP *) p->data; + FREE (&pp->pattern); + if (p->init) + { + if (pp->rx) + regfree (pp->rx); + else + pp->rx = safe_calloc (1, sizeof (regex_t)); + pp->pattern = safe_strdup ((char *) p->init); + if (REGCOMP (pp->rx, pp->pattern, mutt_which_case (pp->pattern)) != 0) + { + fprintf (stderr, "mutt_restore_default: error in regexp: %s\n", + pp->pattern); + } + } + } + break; + } + if (p->flags & R_INDEX) + set_option (OPTFORCEREDRAWINDEX); + if (p->flags & R_PAGER) + set_option (OPTFORCEREDRAWPAGER); + if (p->flags & R_RESORT_SUB) + set_option (OPTSORTSUBTHREADS); + if (p->flags & R_RESORT) + set_option (OPTNEEDRESORT); +} + +static int parse_set (BUFFER *tmp, BUFFER *s, unsigned long data, BUFFER *err) +{ + int idx, query, unset, inv, reset, r = 0; + char *p, scratch[_POSIX_PATH_MAX]; + + while (MoreArgs (s)) + { + /* reset state variables */ + query = 0; + unset = data & M_SET_UNSET; + inv = data & M_SET_INV; + reset = data & M_SET_RESET; + + if (*s->dptr == '?') + { + query = 1; + s->dptr++; + } + else if (strncmp ("no", s->dptr, 2) == 0) + { + s->dptr += 2; + unset = !unset; + } + else if (strncmp ("inv", s->dptr, 3) == 0) + { + s->dptr += 3; + inv = !inv; + } + else if (*s->dptr == '&') + { + reset = 1; + s->dptr++; + } + + /* get the variable name */ + mutt_extract_token (tmp, s, M_TOKEN_EQUAL); + + if ((idx = mutt_option_index (tmp->data)) == -1 && + !(reset && !strcmp ("all", tmp->data))) + { + snprintf (err->data, err->dsize, "%s: unknown variable", tmp->data); + return (-1); + } + SKIPWS (s->dptr); + + if (reset) + { + if (query || unset || inv) + { + snprintf (err->data, err->dsize, "prefix is illegal with reset"); + return (-1); + } + + if (s && *s->dptr == '=') + { + snprintf (err->data, err->dsize, "value is illegal with reset"); + return (-1); + } + + if (!strcmp ("all", tmp->data)) + { + for (idx = 0; MuttVars[idx].option; idx++) + mutt_restore_default (&MuttVars[idx]); + return 0; + } + else + mutt_restore_default (&MuttVars[idx]); + } + else if (DTYPE (MuttVars[idx].type) == DT_BOOL) + { + if (s && *s->dptr == '=') + { + snprintf (err->data, err->dsize, "%s is a boolean var!", tmp->data); + return (-1); + } + + if (query) + { + snprintf (err->data, err->dsize, "%s is %sset", tmp->data, + option (MuttVars[idx].data) ? "" : "un"); + return 0; + } + + if (unset) + unset_option (MuttVars[idx].data); + else if (inv) + toggle_option (MuttVars[idx].data); + else + set_option (MuttVars[idx].data); + } + else if (DTYPE (MuttVars[idx].type) == DT_STR || + DTYPE (MuttVars[idx].type) == DT_PATH) + { + if (query || *s->dptr != '=') + { + /* user requested the value of this variable */ + snprintf (err->data, err->dsize, "%s=\"%s\"", MuttVars[idx].option, + NONULL (*((char **) MuttVars[idx].data))); + break; + } + + s->dptr++; + + /* copy the value of the string */ + FREE (MuttVars[idx].data); + mutt_extract_token (tmp, s, 0); + if (MuttVars[idx].type == DT_PATH) + { + strfcpy (scratch, tmp->data, sizeof (scratch)); + mutt_expand_path (scratch, sizeof (scratch)); + *((char **) MuttVars[idx].data) = safe_strdup (scratch); + } + else + { + *((char **) MuttVars[idx].data) = safe_strdup (tmp->data); + } + } + else if (DTYPE(MuttVars[idx].type) == DT_RX) + { + REGEXP *ptr = (REGEXP *) MuttVars[idx].data; + regex_t *rx; + int e, flags = 0; + + if (query || *s->dptr != '=') + { + /* user requested the value of this variable */ + snprintf (err->data, err->dsize, "%s=\"%s\"", MuttVars[idx].option, + NONULL (ptr->pattern)); + break; + } + + s->dptr++; + + /* copy the value of the string */ + mutt_extract_token (tmp, s, 0); + + if (!ptr->pattern || strcmp (ptr->pattern, tmp->data) != 0) + { + /* $alternates is case-insensitive, + $mask is case-sensitive */ + if (strcmp (MuttVars[idx].option, "alternates") == 0) + flags |= REG_ICASE; + else if (strcmp (MuttVars[idx].option, "mask") != 0) + flags |= mutt_which_case (tmp->data); + + rx = (regex_t *) safe_malloc (sizeof (regex_t)); + if ((e = REGCOMP (rx, tmp->data, flags)) != 0) + { + regerror (e, rx, err->data, err->dsize); + regfree (rx); + FREE (&rx); + break; + } + + /* get here only if everything went smootly */ + if (ptr->pattern) + { + FREE (&ptr->pattern); + regfree ((regex_t *) ptr->rx); + FREE (&ptr->rx); + } + + ptr->pattern = safe_strdup (tmp->data); + ptr->rx = rx; + + /* $reply_regexp requires special treatment */ + if (Context && Context->msgcount && + strcmp (MuttVars[idx].option, "reply_regexp") == 0) + { + regmatch_t pmatch[1]; + int i; + +#define CUR_ENV Context->hdrs[i]->env + for (i = 0; i < Context->msgcount; i++) + { + if (CUR_ENV && CUR_ENV->subject) + { + CUR_ENV->real_subj = (regexec (ReplyRegexp.rx, + CUR_ENV->subject, 1, pmatch, 0)) ? + CUR_ENV->subject : + CUR_ENV->subject + pmatch[0].rm_eo; + } + } +#undef CUR_ENV + } + } + } + else if (DTYPE(MuttVars[idx].type) == DT_MAGIC) + { + if (query || *s->dptr != '=') + { + switch (DefaultMagic) + { + case M_MBOX: + p = "mbox"; + break; + case M_MMDF: + p = "MMDF"; + break; + case M_MH: + p = "MH"; + break; + case M_MAILDIR: + p = "Maildir"; + break; + default: + p = "unknown"; + break; + } + snprintf (err->data, err->dsize, "%s=%s", MuttVars[idx].option, p); + break; + } + + s->dptr++; + + /* copy the value of the string */ + mutt_extract_token (tmp, s, 0); + if (mx_set_magic (tmp->data)) + { + snprintf (err->data, err->dsize, "%s: invalid mailbox type", tmp->data); + r = -1; + break; + } + } + else if (DTYPE(MuttVars[idx].type) == DT_NUM) + { + short *ptr = (short *) MuttVars[idx].data; + + if (query || *s->dptr != '=') + { + /* user requested the value of this variable */ + snprintf (err->data, err->dsize, "%s=%d", MuttVars[idx].option, *ptr); + break; + } + + s->dptr++; + + mutt_extract_token (tmp, s, 0); + *ptr = (short) atoi (tmp->data); + + /* these ones need a sanity check */ + if (strcmp (MuttVars[idx].option, "history") == 0) + { + if (*ptr < 0) + *ptr = 0; + mutt_init_history (); + } + else if (strcmp (MuttVars[idx].option, "pager_index_lines") == 0) + { + if (*ptr < 0) + *ptr = 0; + } + } + else if (DTYPE (MuttVars[idx].type) == DT_QUAD) + { + if (query) + { + char *vals[] = { "no", "yes", "ask-no", "ask-yes" }; + + snprintf (err->data, err->dsize, "%s=%s", MuttVars[idx].option, + vals [ quadoption (MuttVars[idx].data) ]); + break; + } + + if (*s->dptr == '=') + { + s->dptr++; + mutt_extract_token (tmp, s, 0); + if (strcasecmp ("yes", tmp->data) == 0) + set_quadoption (MuttVars[idx].data, M_YES); + else if (strcasecmp ("no", tmp->data) == 0) + set_quadoption (MuttVars[idx].data, M_NO); + else if (strcasecmp ("ask-yes", tmp->data) == 0) + set_quadoption (MuttVars[idx].data, M_ASKYES); + else if (strcasecmp ("ask-no", tmp->data) == 0) + set_quadoption (MuttVars[idx].data, M_ASKNO); + else + { + snprintf (err->data, err->dsize, "%s: invalid value.", tmp->data); + r = -1; + break; + } + } + else + { + if (inv) + toggle_quadoption (MuttVars[idx].data); + else if (unset) + set_quadoption (MuttVars[idx].data, M_NO); + else + set_quadoption (MuttVars[idx].data, M_YES); + } + } + else if (DTYPE (MuttVars[idx].type) == DT_SORT) + { + const struct mapping_t *map; + + switch (MuttVars[idx].type & DT_SUBTYPE_MASK) + { + case DT_SORT_ALIAS: + map = SortAliasMethods; + break; + case DT_SORT_BROWSER: + map = SortBrowserMethods; + break; + default: + map = SortMethods; + break; + } + + if (query || *s->dptr != '=') + { + p = mutt_getnamebyvalue (*((short *) MuttVars[idx].data) & SORT_MASK, map); + + snprintf (err->data, err->dsize, "%s=%s%s%s", MuttVars[idx].option, + (*((short *) MuttVars[idx].data) & SORT_REVERSE) ? "reverse-" : "", + (*((short *) MuttVars[idx].data) & SORT_LAST) ? "last-" : "", + p); + return 0; + } + s->dptr++; + mutt_extract_token (tmp, s , 0); + + if (parse_sort ((short *) MuttVars[idx].data, tmp->data, map, err) == -1) + break; + } + else + { + snprintf (err->data, err->dsize, "%s: unknown type", MuttVars[idx].option); + r = -1; + break; + } + + if (MuttVars[idx].flags & R_INDEX) + set_option (OPTFORCEREDRAWINDEX); + if (MuttVars[idx].flags & R_PAGER) + set_option (OPTFORCEREDRAWPAGER); + if (MuttVars[idx].flags & R_RESORT_SUB) + set_option (OPTSORTSUBTHREADS); + if (MuttVars[idx].flags & R_RESORT) + set_option (OPTNEEDRESORT); + } + return (r); +} + +void mutt_nocurses_error (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fputc ('\n', stderr); +} + +/* reads the specified initialization file. returns -1 if errors were found + so that we can pause to let the user know... */ +static int source_rc (const char *rcfile, BUFFER *err) +{ + FILE *f; + int line = 0, rc = 0; + BUFFER token; + char *linebuf = NULL; + size_t buflen; + pid_t pid; + + if ((f = mutt_open_read (rcfile, &pid)) == NULL) + { + snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno)); + return (-1); + } + + memset (&token, 0, sizeof (token)); + while ((linebuf = mutt_read_line (linebuf, &buflen, f, &line)) != NULL) + { + if (mutt_parse_rc_line (linebuf, &token, err) == -1) + { + mutt_error ("Error in %s, line %d: %s", rcfile, line, err->data); + rc = -1; + } + } + FREE (&token.data); + safe_free ((void **) &linebuf); + fclose (f); + if (pid != -1) + mutt_wait_filter (pid); + if (rc) + snprintf (err->data, err->dsize, "source: errors in %s", rcfile); + return (rc); +} + +static int parse_source (BUFFER *tmp, BUFFER *s, unsigned long data, BUFFER *err) +{ + char path[_POSIX_PATH_MAX]; + + if (mutt_extract_token (tmp, s, 0) != 0) + { + snprintf (err->data, err->dsize, "source: error at %s", s->dptr); + return (-1); + } + if (MoreArgs (s)) + { + strfcpy (err->data, "source: too many arguments", err->dsize); + return (-1); + } + strfcpy (path, tmp->data, sizeof (path)); + mutt_expand_path (path, sizeof (path)); + return (source_rc (path, err)); +} + +/* line command to execute + + token scratch buffer to be used by parser. caller should free + token->data when finished. the reason for this variable is + to avoid having to allocate and deallocate a lot of memory + if we are parsing many lines. the caller can pass in the + memory to use, which avoids having to create new space for + every call to this function. + + err where to write error messages */ +int mutt_parse_rc_line (/* const */ char *line, BUFFER *token, BUFFER *err) +{ + int i, r = -1; + BUFFER expn; + + memset (&expn, 0, sizeof (expn)); + expn.data = expn.dptr = line; + expn.dsize = strlen (line); + + *err->data = 0; + + SKIPWS (expn.dptr); + while (*expn.dptr) + { + if (*expn.dptr == '#') + break; /* rest of line is a comment */ + if (*expn.dptr == ';') + { + expn.dptr++; + continue; + } + mutt_extract_token (token, &expn, 0); + for (i = 0; Commands[i].name; i++) + { + if (!strcmp (token->data, Commands[i].name)) + { + if (Commands[i].func (token, &expn, Commands[i].data, err) != 0) + goto finish; + break; + } + } + if (!Commands[i].name) + { + snprintf (err->data, err->dsize, "%s: unknown command", token->data); + goto finish; + } + } + r = 0; +finish: + if (expn.destroy) + FREE (&expn.data); + return (r); +} + +char *mutt_getnamebyvalue (int val, const struct mapping_t *map) +{ + int i; + + for (i=0; map[i].name; i++) + if (map[i].value == val) + return (map[i].name); + return NULL; +} + +int mutt_getvaluebyname (const char *name, const struct mapping_t *map) +{ + int i; + + for (i = 0; map[i].name; i++) + if (strcasecmp (map[i].name, name) == 0) + return (map[i].value); + return (-1); +} + +#ifdef DEBUG +static void start_debug (void) +{ + time_t t; + int i; + char buf[_POSIX_PATH_MAX]; + char buf2[_POSIX_PATH_MAX]; + + /* rotate the old debug logs */ + for (i=3; i>=0; i--) + { + snprintf (buf, sizeof(buf), "%s/.muttdebug%d", Homedir, i); + snprintf (buf2, sizeof(buf2), "%s/.muttdebug%d", Homedir, i+1); + rename (buf, buf2); + } + if ((debugfile = safe_fopen(buf, "w")) != NULL) + { + t = time (0); + fprintf (debugfile, "Mutt %s started at %s.\nDebugging at level %d.\n\n", + VERSION, asctime (localtime (&t)), debuglevel); + } +} +#endif + +static int mutt_execute_commands (LIST *p) +{ + BUFFER err, token; + char errstr[SHORT_STRING]; + + memset (&err, 0, sizeof (err)); + err.data = errstr; + err.dsize = sizeof (errstr); + memset (&token, 0, sizeof (token)); + for (; p; p = p->next) + { + if (mutt_parse_rc_line (p->data, &token, &err) != 0) + { + fprintf (stderr, "Error in command line: %s\n", err.data); + FREE (&token.data); + return (-1); + } + } + FREE (&token.data); + return 0; +} + +void mutt_init (int skip_sys_rc, LIST *commands) +{ + struct passwd *pw; + struct utsname utsname; + char *p, buffer[STRING], error[STRING]; + int i, default_rc = 0, need_pause = 0; + BUFFER err; + + memset (&err, 0, sizeof (err)); + err.data = error; + err.dsize = sizeof (error); + + /* on one of the systems I use, getcwd() does not return the same prefix + as is listed in the passwd file */ + if ((p = getenv ("HOME"))) + Homedir = safe_strdup (p); + + /* Get some information about the user */ + if ((pw = getpwuid (getuid ()))) + { + Username = safe_strdup (pw->pw_name); + if (!Homedir) + Homedir = safe_strdup (pw->pw_dir); + if ((p = strchr (pw->pw_gecos, ','))) + Realname = mutt_substrdup (pw->pw_gecos, p); + else + Realname = safe_strdup (pw->pw_gecos); + Shell = safe_strdup (pw->pw_shell); + } + else + { + if (!Homedir) + { + mutt_endwin (NULL); + fputs ("unable to determine home directory", stderr); + exit (1); + } + if ((p = getenv ("USER"))) + Username = safe_strdup (p); + else + { + mutt_endwin (NULL); + fputs ("unable to determine user", stderr); + exit (1); + } + Shell = safe_strdup ((p = getenv ("SHELL")) ? p : "/bin/sh"); + } + +#ifdef DEBUG + /* Start up debugging mode if requested */ + if (debuglevel > 0) + start_debug (); +#endif + + /* And about the host... */ + uname (&utsname); + /* some systems report the FQDN instead of just the hostname */ + if ((p = strchr (utsname.nodename, '.'))) + { + Hostname = mutt_substrdup (utsname.nodename, p); + p++; + strfcpy (buffer, p, sizeof (buffer)); /* save the domain for below */ + } + else + Hostname = safe_strdup (utsname.nodename); + +#ifndef DOMAIN +#define DOMAIN buffer + if (!p && getdnsdomainname (buffer, sizeof (buffer)) == -1) + Fqdn = safe_strdup ("@"); + else +#endif /* DOMAIN */ + { +# ifdef HIDDEN_HOST + Fqdn = safe_strdup (DOMAIN); +# else + Fqdn = safe_malloc (strlen (DOMAIN) + strlen (Hostname) + 2); + sprintf (Fqdn, "%s.%s", Hostname, DOMAIN); +# endif /* HIDDEN_HOST */ + } + + if ((p = getenv ("MAIL"))) + Spoolfile = safe_strdup (p); + else + { +#ifdef HOMESPOOL + snprintf (buffer, sizeof (buffer), "%s/%s", Homedir, MAILPATH); +#else + snprintf (buffer, sizeof (buffer), "%s/%s", MAILPATH, Username); +#endif + Spoolfile = safe_strdup (buffer); + } + + if ((p = getenv ("MAILCAPS"))) + MailcapPath = safe_strdup (p); + else + { + /* Default search path from RFC1524 */ + MailcapPath = safe_strdup ("~/.mailcap:" SHAREDIR "/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap"); + } + + Tempdir = safe_strdup ((p = getenv ("TMPDIR")) ? p : "/tmp"); + + + +#ifdef _PGPPATH +#ifdef _PGPV2PATH + PgpV2 = safe_strdup (_PGPV2PATH); + if ((p = getenv("PGPPATH")) != NULL) + { + snprintf (buffer, sizeof (buffer), "%s/pubring.pgp", p); + PgpV2Pubring = safe_strdup (buffer); + snprintf (buffer, sizeof (buffer), "%s/secring.pgp", p); + PgpV2Secring = safe_strdup (buffer); + } + else + { + snprintf (buffer, sizeof (buffer), "%s/.pgp/pubring.pgp", Homedir); + PgpV2Pubring = safe_strdup (buffer); + snprintf (buffer, sizeof (buffer), "%s/.pgp/secring.pgp", Homedir); + PgpV2Secring = safe_strdup (buffer); + } +#endif + +#ifdef _PGPV3PATH + PgpV3 = safe_strdup (_PGPV3PATH); + if ((p = getenv("PGPPATH")) != NULL) + { + snprintf (buffer, sizeof (buffer), "%s/pubring.pkr", p); + PgpV3Pubring = safe_strdup (buffer); + snprintf (buffer, sizeof (buffer), "%s/secring.skr", p); + PgpV3Secring = safe_strdup (buffer); + } + else + { + snprintf (buffer, sizeof (buffer), "%s/.pgp/pubring.pkr", Homedir); + PgpV3Pubring = safe_strdup (buffer); + snprintf (buffer, sizeof (buffer), "%s/.pgp/secring.skr", Homedir); + PgpV3Secring = safe_strdup (buffer); + } +#endif + +#endif /* _PGPPATH */ + + + +#ifdef USE_POP + PopUser = safe_strdup (Username); +#endif + + Editor = safe_strdup ((p = getenv ("EDITOR")) ? p : "vi"); + Visual = safe_strdup ((p = getenv ("VISUAL")) ? p : Editor); + + if ((p = getenv ("REPLYTO")) != NULL) + { + BUFFER buf, token; + + snprintf (buffer, sizeof (buffer), "Reply-To: %s", p); + + memset (&buf, 0, sizeof (buf)); + buf.data = buf.dptr = buffer; + buf.dsize = strlen (buffer); + + memset (&token, 0, sizeof (token)); + parse_my_hdr (&token, &buf, 0, &err); + FREE (&token.data); + } + + /* Set standard defaults */ + for (i = 0; MuttVars[i].option; i++) + mutt_restore_default (&MuttVars[i]); + +#ifndef LOCALES_HACK + /* Do we have a locale definition? */ + if (((p = getenv ("LC_ALL")) != NULL && p[0]) || + ((p = getenv ("LANG")) != NULL && p[0]) || + ((p = getenv ("LC_CTYPE")) != NULL && p[0])) + set_option (OPTLOCALES); +#endif + + mutt_init_history (); + + if (!Muttrc) + { + snprintf (buffer, sizeof (buffer), "%s/.muttrc-%s", Homedir, VERSION); + if (access (buffer, F_OK) == -1) + snprintf (buffer, sizeof (buffer), "%s/.muttrc", Homedir); + default_rc = 1; + Muttrc = safe_strdup (buffer); + } + else + { + strfcpy (buffer, Muttrc, sizeof (buffer)); + FREE (&Muttrc); + mutt_expand_path (buffer, sizeof (buffer)); + Muttrc = safe_strdup (buffer); + } + FREE (&AliasFile); + AliasFile = safe_strdup (Muttrc); + + /* Process the global rc file if it exists and the user hasn't explicity + requested not to via "-n". */ + if (!skip_sys_rc) + { + snprintf (buffer, sizeof (buffer), "%s/Muttrc-%s", SHAREDIR, VERSION); + if (access (buffer, F_OK) == -1) + snprintf (buffer, sizeof (buffer), "%s/Muttrc", SHAREDIR); + if (access (buffer, F_OK) != -1) + { + if (source_rc (buffer, &err) != 0) + { + fputs (err.data, stderr); + fputc ('\n', stderr); + need_pause = 1; + } + } + } + + /* Read the user's initialization file. */ + if (access (Muttrc, F_OK) != -1) + { + if (!option (OPTNOCURSES)) + endwin (); + if (source_rc (Muttrc, &err) != 0) + { + fputs (err.data, stderr); + fputc ('\n', stderr); + need_pause = 1; + } + } + else if (!default_rc) + { + /* file specified by -F does not exist */ + snprintf (buffer, sizeof (buffer), "%s: %s", Muttrc, strerror (errno)); + mutt_endwin (buffer); + exit (1); + } + + if (mutt_execute_commands (commands) != 0) + need_pause = 1; + + if (need_pause && !option (OPTNOCURSES)) + { + if (mutt_any_key_to_continue (NULL) == -1) + mutt_exit(1); + } + + set_option (OPTWEED); /* turn weeding on by default */ +} diff --git a/init.h b/init.h new file mode 100644 index 00000000..40515718 --- /dev/null +++ b/init.h @@ -0,0 +1,342 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "sort.h" +#include "buffy.h" + +#define DT_MASK 0x0f +#define DT_BOOL 1 /* boolean option */ +#define DT_NUM 2 /* a number */ +#define DT_STR 3 /* a string */ +#define DT_PATH 4 /* a pathname */ +#define DT_QUAD 5 /* quad-option (yes/no/ask-yes/ask-no) */ +#define DT_SORT 6 /* sorting methods */ +#define DT_RX 7 /* regular expressions */ +#define DT_MAGIC 8 /* mailbox type */ +#define DT_SYN 9 /* synonym for another variable */ + +#define DTYPE(x) ((x) & DT_MASK) + +/* subtypes */ +#define DT_SUBTYPE_MASK 0xf0 +#define DT_SORT_ALIAS 0x10 +#define DT_SORT_BROWSER 0x20 + +/* flags to parse_set() */ +#define M_SET_INV (1<<0) /* default is to invert all vars */ +#define M_SET_UNSET (1<<1) /* default is to unset all vars */ +#define M_SET_RESET (1<<2) /* default is to reset all vars to default */ + +/* forced redraw/resort types */ +#define R_NONE 0 +#define R_INDEX (1<<0) +#define R_PAGER (1<<1) +#define R_RESORT (1<<2) /* resort the mailbox */ +#define R_RESORT_SUB (1<<3) /* resort subthreads */ +#define R_BOTH (R_INDEX | R_PAGER) +#define R_RESORT_BOTH (R_RESORT | R_RESORT_SUB) + +struct option_t +{ + char *option; + short type; + short flags; + unsigned long data; + unsigned long init; /* initial value */ +}; + +#define UL (unsigned long) + +#ifndef ISPELL +#define ISPELL "ispell" +#endif + +struct option_t MuttVars[] = { + { "abort_nosubject", DT_QUAD, R_NONE, OPT_SUBJECT, M_ASKYES }, + { "abort_unmodified", DT_QUAD, R_NONE, OPT_ABORT, M_YES }, + { "alias_file", DT_PATH, R_NONE, UL &AliasFile, UL "~/.muttrc" }, + { "alias_format", DT_STR, R_NONE, UL &AliasFmt, UL "%2n %t %-10a %r" }, + { "allow_8bit", DT_BOOL, R_NONE, OPTALLOW8BIT, 1 }, + { "alternates", DT_RX, R_BOTH, UL &Alternates, 0 }, + { "arrow_cursor", DT_BOOL, R_BOTH, OPTARROWCURSOR, 0 }, + { "ascii_chars", DT_BOOL, R_BOTH, OPTASCIICHARS, 0 }, + { "askbcc", DT_BOOL, R_NONE, OPTASKBCC, 0 }, + { "askcc", DT_BOOL, R_NONE, OPTASKCC, 0 }, + { "attribution", DT_STR, R_NONE, UL &Attribution, UL "On %d, %n wrote:" }, + { "autoedit", DT_BOOL, R_NONE, OPTAUTOEDIT, 0 }, + { "auto_tag", DT_BOOL, R_NONE, OPTAUTOTAG, 0 }, + { "beep", DT_BOOL, R_NONE, OPTBEEP, 1 }, + { "beep_new", DT_BOOL, R_NONE, OPTBEEPNEW, 0 }, + { "charset", DT_STR, R_NONE, UL &Charset, UL "iso-8859-1" }, + { "check_new", DT_BOOL, R_NONE, OPTCHECKNEW, 1 }, + { "confirmappend", DT_BOOL, R_NONE, OPTCONFIRMAPPEND, 1 }, + { "confirmcreate", DT_BOOL, R_NONE, OPTCONFIRMCREATE, 1 }, + { "copy", DT_QUAD, R_NONE, OPT_COPY, M_YES }, + { "date_format", DT_STR, R_BOTH, UL &DateFmt, UL "!%a, %b %d, %Y at %I:%M:%S%p %Z" }, + { "delete_format", DT_STR, R_NONE, UL &DeleteFmt, UL "[-- Attachment from %u deleted %<%b %d %T %Y> --]" }, + { "default_hook", DT_STR, R_NONE, UL &DefaultHook, UL "~f %s !~P | (~P ~C %s)" }, + { "delete", DT_QUAD, R_NONE, OPT_DELETE, M_ASKYES }, + { "dsn_notify", DT_STR, R_NONE, UL &DsnNotify, UL "" }, + { "dsn_return", DT_STR, R_NONE, UL &DsnReturn, UL "" }, + { "edit_headers", DT_BOOL, R_NONE, OPTEDITHDRS, 0 }, + { "edit_hdrs", DT_SYN, R_NONE, UL "edit_headers", 0 }, + { "editor", DT_PATH, R_NONE, UL &Editor, 0 }, + { "empty_to", DT_STR, R_NONE, UL &EmptyTo, UL "undisclosed-recipients" }, + { "escape", DT_STR, R_NONE, UL &EscChar, UL "~" }, + { "fast_reply", DT_BOOL, R_NONE, OPTFASTREPLY, 0 }, + { "fcc_attach", DT_BOOL, R_NONE, OPTFCCATTACH, 1 }, + { "folder", DT_PATH, R_NONE, UL &Maildir, UL "~/Mail" }, + { "folder_format", DT_STR, R_NONE, UL &FolderFormat, UL "%N %F %2l %-8.8u %-8.8g %8s %d %f" }, + { "followup_to", DT_BOOL, R_NONE, OPTFOLLOWUPTO, 1 }, + { "force_name", DT_BOOL, R_NONE, OPTFORCENAME, 0 }, + { "forward_decode", DT_BOOL, R_NONE, OPTFORWDECODE, 1 }, + { "forw_decode", DT_SYN, R_NONE, UL "forward_decode", 0 }, + { "forward_format", DT_STR, R_NONE, UL &ForwFmt, UL "[%a: %s]" }, + { "forw_format", DT_SYN, R_NONE, UL "forward_format", 0 }, + { "forward_quote", DT_BOOL, R_NONE, OPTFORWQUOTE, 0 }, + { "forw_quote", DT_SYN, R_NONE, UL "forward_quote", 0 }, + { "hdr_format", DT_SYN, R_NONE, UL "index_format", 0 }, + { "hdrs", DT_BOOL, R_NONE, OPTHDRS, 1 }, + { "header", DT_BOOL, R_NONE, OPTHEADER, 0 }, + { "help", DT_BOOL, R_BOTH, OPTHELP, 1 }, + { "history", DT_NUM, R_NONE, UL &HistSize, 10 }, + { "hostname", DT_STR, R_NONE, UL &Fqdn, 0 }, + { "in_reply_to", DT_STR, R_NONE, UL &InReplyTo, UL "%i; from %n on %{!%a, %b %d, %Y at %I:%M:%S%p %Z}" }, + { "include", DT_QUAD, R_NONE, OPT_INCLUDE, M_ASKYES }, + { "indent_string", DT_STR, R_NONE, UL &Prefix, UL "> " }, + { "indent_str", DT_SYN, R_NONE, UL "indent_string", 0 }, + { "index_format", DT_STR, R_BOTH, UL &HdrFmt, UL "%4C %Z %{%b %d} %-15.15L (%4l) %s" }, + { "ignore_list_reply_to", DT_BOOL, R_NONE, OPTIGNORELISTREPLYTO, 0 }, + { "ispell", DT_PATH, R_NONE, UL &Ispell, UL ISPELL }, + { "locale", DT_STR, R_BOTH, UL &Locale, UL "C" }, + { "mail_check", DT_NUM, R_NONE, UL &BuffyTimeout, 5 }, + { "mailcap_path", DT_STR, R_NONE, UL &MailcapPath, 0 }, + { "mark_old", DT_BOOL, R_BOTH, OPTMARKOLD, 1 }, + { "markers", DT_BOOL, R_PAGER, OPTMARKERS, 1 }, + { "mask", DT_RX, R_NONE, UL &Mask, UL "^(\\.\\.$|[^.])" }, + { "mbox", DT_PATH, R_BOTH, UL &Inbox, UL "~/mbox" }, + { "mbox_type", DT_MAGIC,R_NONE, UL &DefaultMagic, M_MBOX }, + { "metoo", DT_BOOL, R_NONE, OPTMETOO, 0 }, + { "menu_scroll", DT_BOOL, R_NONE, OPTMENUSCROLL, 0 }, + { "meta_key", DT_BOOL, R_NONE, OPTMETAKEY, 0 }, + { "mime_forward", DT_QUAD, R_NONE, OPT_MIMEFWD, 0 }, + { "mime_forward_decode", DT_BOOL, R_NONE, OPTMIMEFORWDECODE, 0 }, + { "mime_fwd", DT_SYN, R_NONE, UL "mime_forward", 0 }, + { "move", DT_QUAD, R_NONE, OPT_MOVE, M_ASKNO }, + { "message_format", DT_STR, R_NONE, UL &MsgFmt, UL "%s" }, + { "msg_format", DT_SYN, R_NONE, UL "message_format", 0 }, + { "pager", DT_PATH, R_NONE, UL &Pager, UL "builtin" }, + { "pager_context", DT_NUM, R_NONE, UL &PagerContext, 0 }, + { "pager_format", DT_STR, R_PAGER, UL &PagerFmt, UL "-%S- %C/%m: %-20.20n %s" }, + { "pager_index_lines",DT_NUM, R_PAGER, UL &PagerIndexLines, 0 }, + { "pager_stop", DT_BOOL, R_NONE, OPTPAGERSTOP, 0 }, + + + +#ifdef _PGPPATH + + { "pgp_autosign", DT_BOOL, R_NONE, OPTPGPAUTOSIGN, 0 }, + { "pgp_autoencrypt", DT_BOOL, R_NONE, OPTPGPAUTOENCRYPT, 0 }, + { "pgp_encryptself", DT_BOOL, R_NONE, OPTPGPENCRYPTSELF, 1 }, + { "pgp_long_ids", DT_BOOL, R_NONE, OPTPGPLONGIDS, 0 }, + { "pgp_replyencrypt", DT_BOOL, R_NONE, OPTPGPREPLYENCRYPT, 0 }, + { "pgp_replysign", DT_BOOL, R_NONE, OPTPGPREPLYSIGN, 0 }, + { "pgp_sign_as", DT_STR, R_NONE, UL &PgpSignAs, 0 }, + { "pgp_sign_micalg", DT_STR, R_NONE, UL &PgpSignMicalg, UL "pgp-md5" }, + { "pgp_strict_enc", DT_BOOL, R_NONE, OPTPGPSTRICTENC, 1 }, + { "pgp_timeout", DT_NUM, R_NONE, UL &PgpTimeout, 300 }, + { "pgp_verify_sig", DT_QUAD, R_NONE, OPT_VERIFYSIG, M_YES }, + + { "pgp_v2", DT_PATH, R_NONE, UL &PgpV2, 0 }, + { "pgp_v2_language", DT_STR, R_NONE, UL &PgpV2Language, UL "en" }, + { "pgp_v2_pubring", DT_PATH, R_NONE, UL &PgpV2Pubring, 0 }, + { "pgp_v2_secring", DT_PATH, R_NONE, UL &PgpV2Secring, 0 }, + + { "pgp_v5", DT_PATH, R_NONE, UL &PgpV3, 0 }, + { "pgp_v5_language", DT_STR, R_NONE, UL &PgpV3Language, 0 }, + { "pgp_v5_pubring", DT_PATH, R_NONE, UL &PgpV3Pubring, 0 }, + { "pgp_v5_secring", DT_PATH, R_NONE, UL &PgpV3Secring, 0 }, + +# ifdef HAVE_PGP2 + { "pgp_default_version", DT_STR, R_NONE, UL &PgpDefaultVersion, UL "pgp2" }, +# else +# ifdef HAVE_PGP5 + { "pgp_default_version", DT_STR, R_NONE, UL &PgpDefaultVersion, UL "pgp5" }, +# endif +# endif + { "pgp_receive_version", DT_STR, R_NONE, UL &PgpReceiveVersion, UL "default" }, + { "pgp_send_version", DT_STR, R_NONE, UL &PgpSendVersion, UL "default" }, + { "pgp_key_version", DT_STR, R_NONE, UL &PgpKeyVersion, UL "default" }, + +#endif /* _PGPPATH */ + + + + { "pipe_split", DT_BOOL, R_NONE, OPTPIPESPLIT, 0 }, + { "pipe_decode", DT_BOOL, R_NONE, OPTPIPEDECODE, 0 }, + { "pipe_sep", DT_STR, R_NONE, UL &PipeSep, UL "\n" }, +#ifdef USE_POP + { "pop_delete", DT_BOOL, R_NONE, OPTPOPDELETE, 0 }, + { "pop_host", DT_STR, R_NONE, UL &PopHost, UL "" }, + { "pop_port", DT_NUM, R_NONE, UL &PopPort, 110 }, + { "pop_pass", DT_STR, R_NONE, UL &PopPass, UL "" }, + { "pop_user", DT_STR, R_NONE, UL &PopUser, UL "" }, +#endif /* USE_POP */ + { "post_indent_string",DT_STR, R_NONE, UL &PostIndentString, UL "" }, + { "post_indent_str", DT_SYN, R_NONE, UL "post_indent_string", 0 }, + { "postpone", DT_QUAD, R_NONE, OPT_POSTPONE, M_ASKYES }, + { "postponed", DT_PATH, R_NONE, UL &Postponed, UL "~/postponed" }, + { "print", DT_QUAD, R_NONE, OPT_PRINT, M_ASKNO }, + { "print_command", DT_PATH, R_NONE, UL &PrintCmd, UL "lpr" }, + { "print_cmd", DT_SYN, R_NONE, UL "print_command", 0 }, + { "prompt_after", DT_BOOL, R_NONE, OPTPROMPTAFTER, 1 }, + { "query_command", DT_PATH, R_NONE, UL &QueryCmd, UL "" }, + { "quit", DT_QUAD, R_NONE, OPT_QUIT, M_YES }, + { "quote_regexp", DT_RX, R_PAGER, UL &QuoteRegexp, UL "^([ \t]*[|>:}#])+" }, + { "reply_regexp", DT_RX, R_INDEX|R_RESORT, UL &ReplyRegexp, UL "^(re([\\[0-9\\]+])*|aw):[ \t]*" }, + { "read_inc", DT_NUM, R_NONE, UL &ReadInc, 10 }, + { "read_only", DT_BOOL, R_NONE, OPTREADONLY, 0 }, + { "realname", DT_STR, R_BOTH, UL &Realname, 0 }, + { "recall", DT_QUAD, R_NONE, OPT_RECALL, M_ASKYES }, + { "record", DT_PATH, R_NONE, UL &Outbox, UL "" }, + { "reply_to", DT_QUAD, R_NONE, OPT_REPLYTO, M_ASKYES }, + { "resolve", DT_BOOL, R_NONE, OPTRESOLVE, 1 }, + { "reverse_alias", DT_BOOL, R_BOTH, OPTREVALIAS, 0 }, + { "reverse_name", DT_BOOL, R_BOTH, OPTREVNAME, 0 }, + { "save_address", DT_BOOL, R_NONE, OPTSAVEADDRESS, 0 }, + { "save_empty", DT_BOOL, R_NONE, OPTSAVEEMPTY, 1 }, + { "save_name", DT_BOOL, R_NONE, OPTSAVENAME, 0 }, + { "sendmail", DT_PATH, R_NONE, UL &Sendmail, UL SENDMAIL " -oem -oi" }, + { "sendmail_wait", DT_NUM, R_NONE, UL &SendmailWait, 0 }, + { "shell", DT_PATH, R_NONE, UL &Shell, 0 }, + { "sig_dashes", DT_BOOL, R_NONE, OPTSIGDASHES, 1 }, + { "signature", DT_PATH, R_NONE, UL &Signature, UL "~/.signature" }, + { "simple_search", DT_STR, R_NONE, UL &SimpleSearch, UL "~f %s | ~s %s" }, + { "smart_wrap", DT_BOOL, R_PAGER, OPTWRAP, 1 }, + { "sort", DT_SORT, R_INDEX|R_RESORT, UL &Sort, SORT_DATE }, + { "sort_alias", DT_SORT|DT_SORT_ALIAS, R_NONE, UL &SortAlias, SORT_ALIAS }, + { "sort_aux", DT_SORT, R_INDEX|R_RESORT_BOTH, UL &SortAux, SORT_DATE }, + { "sort_browser", DT_SORT|DT_SORT_BROWSER, R_NONE, UL &BrowserSort, SORT_SUBJECT }, + { "sort_re", DT_BOOL, R_INDEX|R_RESORT_BOTH, OPTSORTRE, 1 }, + { "spoolfile", DT_PATH, R_NONE, UL &Spoolfile, 0 }, + { "status_chars", DT_STR, R_BOTH, UL &StChars, UL "-*%" }, + { "status_format", DT_STR, R_BOTH, UL &Status, UL "-%r-Mutt: %f [Msgs:%?M?%M/?%m%?n? New:%n?%?o? Old:%o?%?d? Del:%d?%?F? Flag:%F?%?t? Tag:%t?%?p? Post:%p?%?b? Inc:%b?%?l? %l?]---(%s/%S)-%>-(%P)---" }, + { "status_on_top", DT_BOOL, R_BOTH, OPTSTATUSONTOP, 0 }, + { "strict_threads", DT_BOOL, R_RESORT|R_INDEX, OPTSTRICTTHREADS, 0 }, + { "suspend", DT_BOOL, R_NONE, OPTSUSPEND, 1 }, + { "thorough_search", DT_BOOL, R_NONE, OPTTHOROUGHSRC, 0 }, + { "tilde", DT_BOOL, R_PAGER, OPTTILDE, 0 }, + { "timeout", DT_NUM, R_NONE, UL &Timeout, 600 }, + { "tmpdir", DT_PATH, R_NONE, UL &Tempdir, 0 }, + { "to_chars", DT_STR, R_BOTH, UL &Tochars, UL " +TCF" }, + { "use_8bitmime", DT_BOOL, R_NONE, OPTUSE8BITMIME, 0 }, + { "use_domain", DT_BOOL, R_NONE, OPTUSEDOMAIN, 1 }, + { "use_from", DT_BOOL, R_NONE, OPTUSEFROM, 1 }, + { "use_mailcap", DT_QUAD, R_NONE, OPT_USEMAILCAP, 1 }, + { "visual", DT_PATH, R_NONE, UL &Visual, 0 }, + { "wait_key", DT_BOOL, R_NONE, OPTWAITKEY, 1 }, + { "wrap_search", DT_BOOL, R_NONE, OPTWRAPSEARCH, 1 }, + { "write_inc", DT_NUM, R_NONE, UL &WriteInc, 10 }, + { NULL } +}; + +const struct mapping_t SortMethods[] = { + { "date", SORT_DATE }, + { "date-sent", SORT_DATE }, + { "date-received", SORT_RECEIVED }, + { "mailbox-order", SORT_ORDER }, + { "subject", SORT_SUBJECT }, + { "from", SORT_FROM }, + { "size", SORT_SIZE }, + { "threads", SORT_THREADS }, + { "to", SORT_TO }, + { "score", SORT_SCORE }, + { NULL, 0 } +}; + +const struct mapping_t SortBrowserMethods[] = { + { "alpha", SORT_SUBJECT }, + { "date", SORT_DATE }, + { "size", SORT_SIZE }, + { "unsorted", SORT_ORDER }, + { NULL } +}; + +const struct mapping_t SortAliasMethods[] = { + { "alias", SORT_ALIAS }, + { "address", SORT_ADDRESS }, + { "unsorted", SORT_ORDER }, + { NULL } +}; + +/* functions used to parse commands in a rc file */ + +static int parse_list (BUFFER *, BUFFER *, unsigned long, BUFFER *); +static int parse_unlist (BUFFER *, BUFFER *, unsigned long, BUFFER *); +static int parse_alias (BUFFER *, BUFFER *, unsigned long, BUFFER *); +static int parse_unalias (BUFFER *, BUFFER *, unsigned long, BUFFER *); +static int parse_ignore (BUFFER *, BUFFER *, unsigned long, BUFFER *); +static int parse_unignore (BUFFER *, BUFFER *, unsigned long, BUFFER *); +static int parse_source (BUFFER *, BUFFER *, unsigned long, BUFFER *); +static int parse_set (BUFFER *, BUFFER *, unsigned long, BUFFER *); +static int parse_my_hdr (BUFFER *, BUFFER *, unsigned long, BUFFER *); +static int parse_unmy_hdr (BUFFER *, BUFFER *, unsigned long, BUFFER *); + +struct command_t +{ + char *name; + int (*func) (BUFFER *, BUFFER *, unsigned long, BUFFER *); + unsigned long data; +}; + +struct command_t Commands[] = { + { "alias", parse_alias, 0 }, + { "auto_view", parse_list, UL &AutoViewList }, + { "alternative_order", parse_list, UL &AlternativeOrderList}, + { "bind", mutt_parse_bind, 0 }, +#ifdef HAVE_COLOR + { "color", mutt_parse_color, 0 }, + { "uncolor", mutt_parse_uncolor, 0 }, +#endif + { "fcc-hook", mutt_parse_hook, M_FCCHOOK }, + { "fcc-save-hook", mutt_parse_hook, M_FCCHOOK | M_SAVEHOOK }, + { "folder-hook", mutt_parse_hook, M_FOLDERHOOK }, + { "hdr_order", parse_list, UL &HeaderOrderList }, + { "ignore", parse_ignore, 0 }, + { "lists", parse_list, UL &MailLists }, + { "macro", mutt_parse_macro, 0 }, + { "mailboxes", mutt_parse_mailboxes, 0 }, + { "mbox-hook", mutt_parse_hook, M_MBOXHOOK }, + { "mono", mutt_parse_mono, 0 }, + { "my_hdr", parse_my_hdr, 0 }, + { "push", mutt_parse_push, 0 }, + { "reset", parse_set, M_SET_RESET }, + { "save-hook", mutt_parse_hook, M_SAVEHOOK }, + { "score", mutt_parse_score, 0 }, + { "send-hook", mutt_parse_hook, M_SENDHOOK }, + { "set", parse_set, 0 }, + { "source", parse_source, 0 }, + { "toggle", parse_set, M_SET_INV }, + { "unalias", parse_unalias, 0 }, + { "unignore", parse_unignore, 0 }, + { "unlists", parse_unlist, UL &MailLists }, + { "unmy_hdr", parse_unmy_hdr, 0 }, + { "unscore", mutt_parse_unscore, 0 }, + { "unset", parse_set, M_SET_UNSET }, + { NULL } +}; diff --git a/install-sh b/install-sh new file mode 100755 index 00000000..89fc9b09 --- /dev/null +++ b/install-sh @@ -0,0 +1,238 @@ +#! /bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. +# + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +tranformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/keymap.c b/keymap.c new file mode 100644 index 00000000..0eebfd32 --- /dev/null +++ b/keymap.c @@ -0,0 +1,637 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_menu.h" +#include "mutt_curses.h" +#include "keymap.h" +#include "mapping.h" + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include "functions.h" + +struct mapping_t Menus[] = { + { "alias", MENU_ALIAS }, + { "attach", MENU_ATTACH }, + { "browser", MENU_FOLDER }, + { "compose", MENU_COMPOSE }, + { "editor", MENU_EDITOR }, + { "generic", MENU_GENERIC }, + { "index", MENU_MAIN }, + { "pager", MENU_PAGER }, + { "postpone", MENU_POST }, + + + +#ifdef _PGPPATH + { "pgp", MENU_PGP }, +#endif + + + + { "query", MENU_QUERY }, + { NULL, 0 } +}; + +#define mutt_check_menu(s) mutt_getvaluebyname(s, Menus) + +static struct mapping_t KeyNames[] = { + { "<PageUp>", KEY_PPAGE }, + { "<PageDown>", KEY_NPAGE }, + { "<Up>", KEY_UP }, + { "<Down>", KEY_DOWN }, + { "<Right>", KEY_RIGHT }, + { "<Left>", KEY_LEFT }, + { "<Delete>", KEY_DC }, + { "<BackSpace>",KEY_BACKSPACE }, + { "<Insert>", KEY_IC }, + { "<Home>", KEY_HOME }, + { "<End>", KEY_END }, + { "<Enter>", KEY_ENTER }, + { "<Return>", M_ENTER_C }, + { NULL, 0 } +}; + +/* contains the last key the user pressed */ +int LastKey; + +struct keymap_t *Keymaps[MENU_MAX]; + +static struct keymap_t *allocKeys (int len, keycode_t *keys) +{ + struct keymap_t *p; + + p = safe_calloc (1, sizeof (struct keymap_t)); + p->len = len; + p->keys = safe_malloc (len * sizeof (keycode_t)); + memcpy (p->keys, keys, len * sizeof (keycode_t)); + return (p); +} + +static int parsekeys (char *s, keycode_t *d, int max) +{ + int n, len = max; + + while (*s && len) + { + if ((n = mutt_getvaluebyname (s, KeyNames)) != -1) + { + s += strlen (s); + *d = n; + } + else if (tolower (*s) == 'f' && isdigit (s[1])) + { + n = 0; + for (s++; isdigit (*s) ; s++) + { + n *= 10; + n += *s - '0'; + } + *d = KEY_F(n); + } + else + { + *d = *s; + s++; + } + d++; + len--; + } + + return (max - len); +} + +/* insert a key sequence into the specified map. the map is sorted by ASCII + * value (lowest to highest) + */ +void km_bindkey (char *s, int menu, int op, char *macro) +{ + struct keymap_t *map, *tmp, *last = NULL, *next; + keycode_t buf[MAX_SEQ]; + int len, pos = 0, lastpos = 0; + + len = parsekeys (s, buf, MAX_SEQ); + + map = allocKeys (len, buf); + map->op = op; + map->macro = safe_strdup (macro); + + tmp = Keymaps[menu]; + + while (tmp) + { + if (pos >= len || pos >= tmp->len) + { + /* map and tmp match, but have different lengths, so overwrite */ + do + { + len = tmp->eq; + next = tmp->next; + if (tmp->macro) + free (tmp->macro); + free (tmp->keys); + free (tmp); + tmp = next; + } + while (tmp && len >= pos); + map->eq = len; + break; + } + else if (buf[pos] == tmp->keys[pos]) + pos++; + else if (buf[pos] < tmp->keys[pos]) + { + /* found location to insert between last and tmp */ + map->eq = pos; + break; + } + else /* buf[pos] > tmp->keys[pos] */ + { + last = tmp; + lastpos = pos; + if (pos > tmp->eq) + pos = tmp->eq; + tmp = tmp->next; + } + } + + map->next = tmp; + if (last) + { + last->next = map; + last->eq = lastpos; + } + else + Keymaps[menu] = map; +} + +static void push_string (char *s) +{ + char *pp, *p = s + strlen (s) - 1; + size_t l; + int i; + + while (p >= s) + { + /* if we see something like "<PageUp>", look to see if it is a real + function name and return the corresponding value */ + if (*p == '>') + { + for (pp = p - 1; pp >= s && *pp != '<'; pp--) + ; + if (pp >= s) + { + l = p - pp + 1; + for (i = 0; KeyNames[i].name; i++) + { + if (!strncasecmp (pp, KeyNames[i].name, l)) + break; + } + if (KeyNames[i].name) + { + /* found a match */ + mutt_ungetch (KeyNames[i].value); + p = pp - 1; + continue; + } + } + } + mutt_ungetch (*p--); + } +} + +static int retry_generic (int menu, keycode_t *keys, int keyslen, int lastkey) +{ + if (menu != MENU_EDITOR && menu != MENU_GENERIC && menu != MENU_PAGER) + { + if (lastkey) + mutt_ungetch (lastkey); + for (; keyslen; keyslen--) + mutt_ungetch (keys[keyslen - 1]); + return (km_dokey (MENU_GENERIC)); + } + if (menu != MENU_EDITOR) + { + /* probably a good idea to flush input here so we can abort macros */ + mutt_flushinp (); + } + return OP_NULL; +} + +/* return values: + * >0 function to execute + * OP_NULL no function bound to key sequence + * -1 error occured while reading input + */ +int km_dokey (int menu) +{ + struct keymap_t *map = Keymaps[menu]; + int pos = 0; + int n = 0; + + if (!map) + return (retry_generic (menu, NULL, 0, 0)); + + FOREVER + { + if ((LastKey = mutt_getch ()) == ERR) + return (-1); + + while (LastKey > map->keys[pos]) + { + if (pos > map->eq || !map->next) + return (retry_generic (menu, map->keys, pos, LastKey)); + map = map->next; + } + + if (LastKey != map->keys[pos]) + return (retry_generic (menu, map->keys, pos, LastKey)); + + if (++pos == map->len) + { + if (map->op != OP_MACRO) + return (map->op); + + if (n++ == 10) + { + mutt_flushinp (); + mutt_error ("Macro loop detected."); + return (-1); + } + + push_string (map->macro); + map = Keymaps[menu]; + pos = 0; + } + } + + /* not reached */ +} + +static void create_bindings (struct binding_t *map, int menu) +{ + int i; + + for (i = 0 ; map[i].name ; i++) + if (map[i].seq) + km_bindkey (map[i].seq, menu, map[i].op, NULL); +} + +char *km_keyname (int c) +{ + static char buf[5]; + char *p; + + if ((p = mutt_getnamebyvalue (c, KeyNames))) + return p; + + switch (c) + { + case '\033': + return "ESC"; + case ' ': + return "SPC"; + case '\n': + case '\r': + return "RET"; + case '\t': + return "TAB"; + } + + if (c < 256 && c > -128 && iscntrl ((unsigned char) c)) + { + if (c < 0) + c += 256; + + if (c < 128) + { + buf[0] = '^'; + buf[1] = (c + '@') & 0x7f; + buf[2] = 0; + } + else + snprintf (buf, sizeof (buf), "\\%d%d%d", c >> 6, (c >> 3) & 7, c & 7); + } + else if (c >= KEY_F0 && c < KEY_F(256)) /* this maximum is just a guess */ + sprintf (buf, "F%d", c - KEY_F0); + else if (IsPrint (c)) + snprintf (buf, sizeof (buf), "%c", (unsigned char) c); + else + snprintf (buf, sizeof (buf), "\\x%hx", (unsigned short) c); + return (buf); +} + +int km_expand_key (char *s, size_t len, struct keymap_t *map) +{ + size_t l; + int p = 0; + + if (!map) + return (0); + + FOREVER + { + strfcpy (s, km_keyname (map->keys[p]), len); + len -= (1 + (l = strlen (s))); + + if (++p >= map->len || !len) + return (1); + + s += l; + *(s++) = ' '; + } + + /* not reached */ +} + +struct keymap_t *km_find_func (int menu, int func) +{ + struct keymap_t *map = Keymaps[menu]; + + for (; map; map = map->next) + if (map->op == func) + break; + return (map); +} + +void km_init (void) +{ + memset (Keymaps, 0, sizeof (struct keymap_t *) * MENU_MAX); + + create_bindings (OpAttach, MENU_ATTACH); + create_bindings (OpBrowser, MENU_FOLDER); + create_bindings (OpCompose, MENU_COMPOSE); + create_bindings (OpMain, MENU_MAIN); + create_bindings (OpPager, MENU_PAGER); + create_bindings (OpPost, MENU_POST); + create_bindings (OpQuery, MENU_QUERY); + + + +#ifdef _PGPPATH + create_bindings (OpPgp, MENU_PGP); +#endif + + + + /* bindings for the line editor */ + create_bindings (OpEditor, MENU_EDITOR); + + km_bindkey ("<up>", MENU_EDITOR, OP_EDITOR_HISTORY_UP, NULL); + km_bindkey ("<down>", MENU_EDITOR, OP_EDITOR_HISTORY_DOWN, NULL); + km_bindkey ("<left>", MENU_EDITOR, OP_EDITOR_BACKWARD_CHAR, NULL); + km_bindkey ("<right>", MENU_EDITOR, OP_EDITOR_FORWARD_CHAR, NULL); + km_bindkey ("<home>", MENU_EDITOR, OP_EDITOR_BOL, NULL); + km_bindkey ("<end>", MENU_EDITOR, OP_EDITOR_EOL, NULL); + km_bindkey ("<backspace>", MENU_EDITOR, OP_EDITOR_BACKSPACE, NULL); + km_bindkey ("<delete>", MENU_EDITOR, OP_EDITOR_BACKSPACE, NULL); + km_bindkey ("\177", MENU_EDITOR, OP_EDITOR_BACKSPACE, NULL); + + /* generic menu keymap */ + create_bindings (OpGeneric, MENU_GENERIC); + + km_bindkey ("<home>", MENU_GENERIC, OP_FIRST_ENTRY, NULL); + km_bindkey ("<end>", MENU_GENERIC, OP_LAST_ENTRY, NULL); + km_bindkey ("<pagedown>", MENU_GENERIC, OP_NEXT_PAGE, NULL); + km_bindkey ("<pageup>", MENU_GENERIC, OP_PREV_PAGE, NULL); + km_bindkey ("<right>", MENU_GENERIC, OP_NEXT_PAGE, NULL); + km_bindkey ("<left>", MENU_GENERIC, OP_PREV_PAGE, NULL); + km_bindkey ("<up>", MENU_GENERIC, OP_PREV_ENTRY, NULL); + km_bindkey ("<down>", MENU_GENERIC, OP_NEXT_ENTRY, NULL); + km_bindkey ("1", MENU_GENERIC, OP_JUMP, NULL); + km_bindkey ("2", MENU_GENERIC, OP_JUMP, NULL); + km_bindkey ("3", MENU_GENERIC, OP_JUMP, NULL); + km_bindkey ("4", MENU_GENERIC, OP_JUMP, NULL); + km_bindkey ("5", MENU_GENERIC, OP_JUMP, NULL); + km_bindkey ("6", MENU_GENERIC, OP_JUMP, NULL); + km_bindkey ("7", MENU_GENERIC, OP_JUMP, NULL); + km_bindkey ("8", MENU_GENERIC, OP_JUMP, NULL); + km_bindkey ("9", MENU_GENERIC, OP_JUMP, NULL); + + /* Miscellaneous extra bindings */ + + km_bindkey (" ", MENU_MAIN, OP_DISPLAY_MESSAGE, NULL); + km_bindkey ("<up>", MENU_MAIN, OP_MAIN_PREV_UNDELETED, NULL); + km_bindkey ("<down>", MENU_MAIN, OP_MAIN_NEXT_UNDELETED, NULL); + km_bindkey ("J", MENU_MAIN, OP_NEXT_ENTRY, NULL); + km_bindkey ("K", MENU_MAIN, OP_PREV_ENTRY, NULL); + km_bindkey ("x", MENU_MAIN, OP_EXIT, NULL); + + km_bindkey ("x", MENU_PAGER, OP_PAGER_EXIT, NULL); + km_bindkey ("q", MENU_PAGER, OP_PAGER_EXIT, NULL); + km_bindkey ("<backspace>", MENU_PAGER, OP_PREV_LINE, NULL); + km_bindkey ("<pagedown>", MENU_PAGER, OP_NEXT_PAGE, NULL); + km_bindkey ("<pageup>", MENU_PAGER, OP_PREV_PAGE, NULL); + km_bindkey ("<up>", MENU_PAGER, OP_MAIN_PREV_UNDELETED, NULL); + km_bindkey ("<right>", MENU_PAGER, OP_MAIN_NEXT_UNDELETED, NULL); + km_bindkey ("<down>", MENU_PAGER, OP_MAIN_NEXT_UNDELETED, NULL); + km_bindkey ("<left>", MENU_PAGER, OP_MAIN_PREV_UNDELETED, NULL); + km_bindkey ("<home>", MENU_PAGER, OP_PAGER_TOP, NULL); + km_bindkey ("<end>", MENU_PAGER, OP_PAGER_BOTTOM, NULL); + km_bindkey ("1", MENU_PAGER, OP_JUMP, NULL); + km_bindkey ("2", MENU_PAGER, OP_JUMP, NULL); + km_bindkey ("3", MENU_PAGER, OP_JUMP, NULL); + km_bindkey ("4", MENU_PAGER, OP_JUMP, NULL); + km_bindkey ("5", MENU_PAGER, OP_JUMP, NULL); + km_bindkey ("6", MENU_PAGER, OP_JUMP, NULL); + km_bindkey ("7", MENU_PAGER, OP_JUMP, NULL); + km_bindkey ("8", MENU_PAGER, OP_JUMP, NULL); + km_bindkey ("9", MENU_PAGER, OP_JUMP, NULL); + + km_bindkey ("<return>", MENU_ALIAS, OP_TAG, NULL); +} + +void km_error_key (int menu) +{ + char buf[SHORT_STRING]; + + if (km_expand_key (buf, sizeof (buf), km_find_func (menu, OP_HELP))) + mutt_error ("Key is not bound. Press '%s' for help.", buf); + else + mutt_error ("Key is not bound. See the manual."); +} + +int mutt_parse_push (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + int r = 0; + + mutt_extract_token (buf, s, M_TOKEN_CONDENSE); + if (MoreArgs (s)) + { + strfcpy (err->data, "push: too many arguments", err->dsize); + r = -1; + } + else + push_string (buf->data); + return (r); +} + +/* expects to see: <menu-string> <key-string> */ +char *parse_keymap (int *menu, BUFFER *s, BUFFER *err) +{ + BUFFER buf; + + memset (&buf, 0, sizeof (buf)); + + /* menu name */ + mutt_extract_token (&buf, s, 0); + if (MoreArgs (s)) + { + if ((*menu = mutt_check_menu (buf.data)) == -1) + { + snprintf (err->data, err->dsize, "%s: no such menu", buf.data); + } + else + { + /* key sequence */ + mutt_extract_token (&buf, s, 0); + + if (!*buf.data) + { + strfcpy (err->data, "null key sequence", err->dsize); + } + else if (MoreArgs (s)) + return (buf.data); + } + } + else + { + strfcpy (err->data, "too few arguments", err->dsize); + } + FREE (&buf.data); + return (NULL); +} + +static int +try_bind (char *key, int menu, char *func, struct binding_t *bindings) +{ + int i; + + for (i = 0; bindings[i].name; i++) + if (strcmp (func, bindings[i].name) == 0) + { + km_bindkey (key, menu, bindings[i].op, NULL); + return (0); + } + return (-1); +} + +struct binding_t *km_get_table (int menu) +{ + switch (menu) + { + case MENU_MAIN: + return OpMain; + case MENU_GENERIC: + return OpGeneric; + case MENU_COMPOSE: + return OpCompose; + case MENU_PAGER: + return OpPager; + case MENU_POST: + return OpPost; + case MENU_FOLDER: + return OpBrowser; + case MENU_ATTACH: + return OpAttach; + case MENU_EDITOR: + return OpEditor; + case MENU_QUERY: + return OpQuery; + + + +#ifdef _PGPPATH + case MENU_PGP: + return OpPgp; +#endif + + + + } + return NULL; +} + +/* bind menu-name '<key_sequence>' function-name */ +int mutt_parse_bind (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + struct binding_t *bindings = NULL; + char *key; + int menu, r = 0; + + if ((key = parse_keymap (&menu, s, err)) == NULL) + return (-1); + + /* function to execute */ + mutt_extract_token (buf, s, 0); + if (MoreArgs (s)) + { + strfcpy (err->data, "bind: too many arguments", err->dsize); + r = -1; + } + else if (strcasecmp ("noop", buf->data) == 0) + km_bindkey (key, menu, OP_NULL, NULL); /* the `unbind' command */ + else + { + /* First check the "generic" list of commands */ + if (menu == MENU_PAGER || menu == MENU_EDITOR || menu == MENU_GENERIC || + try_bind (key, menu, buf->data, OpGeneric) != 0) + { + /* Now check the menu-specific list of commands (if they exist) */ + bindings = km_get_table (menu); + if (bindings && try_bind (key, menu, buf->data, bindings) != 0) + { + snprintf (err->data, err->dsize, "%s: no such function in map", buf->data); + r = -1; + } + } + } + FREE (&key); + return (r); +} + +/* macro <menu> <key> <macro> */ +int mutt_parse_macro (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + int menu, r = -1; + char *key; + + if ((key = parse_keymap (&menu, s, err)) == NULL) + return (-1); + + mutt_extract_token (buf, s, M_TOKEN_CONDENSE); + /* make sure the macro sequence is not an empty string */ + if (!*buf->data) + { + strfcpy (err->data, "macro: empty key sequence", err->dsize); + } + else if (MoreArgs (s)) + { + strfcpy (err->data, "macro: too many arguments", err->dsize); + } + else + { + km_bindkey (key, menu, OP_MACRO, buf->data); + r = 0; + } + FREE (&key); + return (r); +} diff --git a/keymap.h b/keymap.h new file mode 100644 index 00000000..580bc2ba --- /dev/null +++ b/keymap.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef KEYMAP_H +#define KEYMAP_H + +/* maximal length of a key binding sequence used for buffer in km_bindkey */ +#define MAX_SEQ 8 + +/* type for key storage, the rest of mutt works fine with int type */ +typedef short keycode_t; + +void km_bindkey (char *, int, int, char *); +int km_dokey (int); + +/* entry in the keymap tree */ +struct keymap_t +{ + char *macro; /* macro expansion (op == OP_MACRO) */ + struct keymap_t *next; /* next key in map */ + short op; /* operation to perform */ + short eq; /* number of leading keys equal to next entry */ + short len; /* length of key sequence (unit: sizeof (keycode_t)) */ + keycode_t *keys; /* key sequence */ +}; + +char *km_keyname (int); +int km_expand_key (char *, size_t, struct keymap_t *); +struct keymap_t *km_find_func (int, int); +void km_init (void); +void km_error_key (int); + +enum +{ + MENU_ALIAS, + MENU_ATTACH, + MENU_COMPOSE, + MENU_EDITOR, + MENU_FOLDER, + MENU_GENERIC, + MENU_MAIN, + MENU_PAGER, + MENU_POST, + +#ifdef _PGPPATH + MENU_PGP, +#endif + + + + + + MENU_QUERY, + MENU_MAX +}; + +/* the keymap trees (one for each menu) */ +extern struct keymap_t *Keymaps[]; + +/* dokey() records the last real key pressed */ +extern int LastKey; + +extern struct mapping_t Menus[]; + +struct binding_t +{ + char *name; /* name of the function */ + int op; /* function id number */ + char *seq; /* default key binding */ +}; + +struct binding_t *km_get_table (int menu); + +extern struct binding_t OpGeneric[]; +extern struct binding_t OpPost[]; +extern struct binding_t OpMain[]; +extern struct binding_t OpAttach[]; +extern struct binding_t OpPager[]; +extern struct binding_t OpCompose[]; +extern struct binding_t OpBrowser[]; +extern struct binding_t OpEditor[]; +extern struct binding_t OpQuery[]; + +#ifdef _PGPPATH +extern struct binding_t OpPgp[]; +#endif /* _PGPPATH */ + +#include "keymap_defs.h" + +#endif /* KEYMAP_H */ diff --git a/lib.c b/lib.c new file mode 100644 index 00000000..e71027b9 --- /dev/null +++ b/lib.c @@ -0,0 +1,1037 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "mime.h" + +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/wait.h> +#include <errno.h> +#include <pwd.h> +#include <sys/stat.h> +#include <fcntl.h> + +BODY *mutt_new_body (void) +{ + BODY *p = (BODY *) safe_calloc (1, sizeof (BODY)); + + p->disposition = DISPATTACH; + p->use_disp = 1; + return (p); +} + +BODY *mutt_dup_body (BODY *b) +{ + BODY *bn; + + bn = mutt_new_body(); + memcpy(bn, b, sizeof (BODY)); + return bn; +} + +void mutt_free_body (BODY **p) +{ + BODY *a = *p, *b; + + while (a) + { + b = a; + a = a->next; + + if (b->parameter) + mutt_free_parameter (&b->parameter); + if (b->unlink && b->filename) + unlink (b->filename); + safe_free ((void **) &b->filename); + safe_free ((void **) &b->content); + safe_free ((void **) &b->subtype); + safe_free ((void **) &b->description); + safe_free ((void **) &b->form_name); + + if (b->hdr) + { + /* Don't free twice (b->hdr->content = b->parts) */ + b->hdr->content = NULL; + mutt_free_header(&b->hdr); + } + + if (b->parts) + mutt_free_body (&b->parts); + + safe_free ((void **) &b); + } + + *p = 0; +} + +void mutt_free_parameter (PARAMETER **p) +{ + PARAMETER *t = *p; + PARAMETER *o; + + while (t) + { + safe_free ((void **) &t->attribute); + safe_free ((void **) &t->value); + o = t; + t = t->next; + safe_free ((void **) &o); + } + *p = 0; +} + +LIST *mutt_add_list (LIST *head, const char *data) +{ + LIST *tmp; + + for (tmp = head; tmp && tmp->next; tmp = tmp->next) + ; + if (tmp) + { + tmp->next = safe_malloc (sizeof (LIST)); + tmp = tmp->next; + } + else + head = tmp = safe_malloc (sizeof (LIST)); + + tmp->data = safe_strdup (data); + tmp->next = NULL; + return head; +} + +void mutt_free_list (LIST **list) +{ + LIST *p; + + if (!list) return; + while (*list) + { + p = *list; + *list = (*list)->next; + safe_free ((void **) &p->data); + safe_free ((void **) &p); + } +} + +HEADER *mutt_dup_header(HEADER *h) +{ + HEADER *hnew; + + hnew = mutt_new_header(); + memcpy(hnew, h, sizeof (HEADER)); + return hnew; +} + +void mutt_free_header (HEADER **h) +{ + mutt_free_envelope (&(*h)->env); + mutt_free_body (&(*h)->content); + safe_free ((void **) &(*h)->tree); + safe_free ((void **) &(*h)->path); + safe_free ((void **) h); +} + +/* returns true if the header contained in "s" is in list "t" */ +int mutt_matches_ignore (const char *s, LIST *t) +{ + for (; t; t = t->next) + { + if (!strncasecmp (s, t->data, strlen (t->data)) || *t->data == '*') + return 1; + } + return 0; +} + +/* prepend the path part of *path to *link */ +void mutt_expand_link (char *newpath, const char *path, const char *link) +{ + const char *lb = NULL; + size_t len; + + /* link is full path */ + if (*link == '/') + { + strfcpy (newpath, link, _POSIX_PATH_MAX); + return; + } + + if ((lb = strrchr (path, '/')) == NULL) + { + /* no path in link */ + strfcpy (newpath, link, _POSIX_PATH_MAX); + return; + } + + len = lb - path + 1; + memcpy (newpath, path, len); + strfcpy (newpath + len, link, _POSIX_PATH_MAX - len); +} + +char *mutt_expand_path (char *s, size_t slen) +{ + char p[_POSIX_PATH_MAX] = ""; + char *q = NULL; + + if (*s == '~') + { + if (*(s + 1) == '/' || *(s + 1) == 0) + snprintf (p, sizeof (p), "%s%s", Homedir, s + 1); + else + { + struct passwd *pw; + + q = strchr (s + 1, '/'); + if (q) + *q = 0; + if ((pw = getpwnam (s + 1))) + snprintf (p, sizeof (p), "%s/%s", pw->pw_dir, q ? q + 1 : ""); + else + { + /* user not found! */ + if (q) + *q = '/'; + return (NULL); + } + } + } + else if (*s == '=' || *s == '+') + snprintf (p, sizeof (p), "%s/%s", NONULL (Maildir), s + 1); + else + { + if (*s == '>') + q = Inbox; + else if (*s == '<') + q = Outbox; + else if (*s == '!') + q = Spoolfile; + else if (*s == '-') + q = LastFolder; + else + return s; + + if (!q) + return s; + snprintf (p, sizeof (p), "%s%s", q, s + 1); + } + if (*p) + strfcpy (s, p, slen); /* replace the string with the expanded version. */ + return (s); +} + +void *safe_calloc (size_t nmemb, size_t size) +{ + void *p; + + if (!nmemb || !size) + return NULL; + if (!(p = calloc (nmemb, size))) + { + mutt_error ("Out of memory"); + sleep (1); + mutt_exit (1); + } + return p; +} + +void *safe_malloc (unsigned int siz) +{ + void *p; + + if (siz == 0) + return 0; + if ((p = (void *) malloc (siz)) == 0) + { + mutt_error ("Out of memory!"); + sleep (1); + mutt_exit (1); + } + return (p); +} + +void safe_realloc (void **p, size_t siz) +{ + void *r; + + if (siz == 0) + { + if (*p) + { + free (*p); + *p = NULL; + } + return; + } + + if (*p) + r = (void *) realloc (*p, siz); + else + { + /* realloc(NULL, nbytes) doesn't seem to work under SunOS 4.1.x */ + r = (void *) malloc (siz); + } + + if (!r) + { + mutt_error ("Out of memory!"); + sleep (1); + mutt_exit (1); + } + + *p = r; +} + +void safe_free (void **p) +{ + if (*p) + { + free (*p); + *p = 0; + } +} + +char *safe_strdup (const char *s) +{ + char *p; + size_t l; + + if (!s || !*s) return 0; + l = strlen (s) + 1; + p = (char *)safe_malloc (l); + memcpy (p, s, l); + return (p); +} + +char *mutt_skip_whitespace (char *p) +{ + SKIPWS (p); + return p; +} + +int mutt_copy_bytes (FILE *in, FILE *out, size_t size) +{ + char buf[2048]; + size_t chunk; + + while (size > 0) + { + chunk = (size > sizeof (buf)) ? sizeof (buf) : size; + if ((chunk = fread (buf, 1, chunk, in)) < 1) + break; + if (fwrite (buf, 1, chunk, out) != chunk) + { + dprint (1, (debugfile, "mutt_copy_bytes(): fwrite() returned short byte count\n")); + return (-1); + } + size -= chunk; + } + + return 0; +} + +char *mutt_get_parameter (const char *s, PARAMETER *p) +{ + for (; p; p = p->next) + if (strcasecmp (s, p->attribute) == 0) + return (p->value); + + return NULL; +} + +/* returns 1 if Mutt can't display this type of data, 0 otherwise */ +int mutt_needs_mailcap (BODY *m) +{ + switch (m->type) + { + case TYPETEXT: + + if (!strcasecmp ("plain", m->subtype) || + !strcasecmp ("rfc822-headers", m->subtype) || + !strcasecmp ("enriched", m->subtype)) + return 0; + break; + + + +#ifdef _PGPPATH + case TYPEAPPLICATION: + + if (!strcasecmp ("pgp", m->subtype) || + !strcasecmp ("pgp-signed", m->subtype) || + !strcasecmp ("x-pgp-message", m->subtype)) + return 0; + break; +#endif /* _PGPPATH */ + + + + case TYPEMULTIPART: + case TYPEMESSAGE: + + return 0; + } + + return 1; +} + +int mutt_is_text_type (int t, char *s) +{ + if (t == TYPETEXT) + return 1; + + if (t == TYPEMESSAGE) + { + if (!strcasecmp ("delivery-status", s)) + return 1; + } + + + +#ifdef _PGPPATH + if (t == TYPEAPPLICATION) + { + if (!strcasecmp ("pgp-keys", s)) + return 1; + } +#endif /* _PGPPATH */ + + + + return 0; +} + +void mutt_free_envelope (ENVELOPE **p) +{ + if (!*p) return; + rfc822_free_address (&(*p)->return_path); + rfc822_free_address (&(*p)->to); + rfc822_free_address (&(*p)->cc); + rfc822_free_address (&(*p)->bcc); + rfc822_free_address (&(*p)->sender); + rfc822_free_address (&(*p)->from); + rfc822_free_address (&(*p)->reply_to); + rfc822_free_address (&(*p)->mail_followup_to); + safe_free ((void **) &(*p)->subject); + safe_free ((void **) &(*p)->message_id); + mutt_free_list (&(*p)->references); + mutt_free_list (&(*p)->userhdrs); + safe_free ((void **) p); +} + +void mutt_tabs_to_spaces (char *s) +{ + while (*s) + { + if (ISSPACE (*s)) + *s = ' '; + s++; + } +} + +void mutt_mktemp (char *s) +{ + snprintf (s, _POSIX_PATH_MAX, "%s/mutt-%s-%d-%d", NONULL (Tempdir), Hostname, (int) getpid (), Counter++); + unlink (s); +} + +/* convert all characters in the string to lowercase */ +char *mutt_strlower (char *s) +{ + char *p = s; + + while (*p) + { + *p = tolower (*p); + p++; + } + + return (s); +} + +/* strcmp() allowing NULL pointers */ +int mutt_strcmp (const char *s1, const char *s2) +{ + if (s1 != NULL) + { + if (s2 != NULL) + return strcmp (s1, s2); + else + return (1); + } + else + return ((s2 == NULL) ? 0 : -1); +} + +void mutt_free_alias (ALIAS **p) +{ + ALIAS *t; + + while (*p) + { + t = *p; + *p = (*p)->next; + safe_free ((void **) &t->name); + rfc822_free_address (&t->addr); + free (t); + } +} + +/* collapse the pathname using ~ or = when possible */ +void mutt_pretty_mailbox (char *s) +{ + char *p = s, *q = s; + size_t len; + + /* first attempt to collapse the pathname */ + while (*p) + { + if (*p == '/' && p[1] == '/') + { + *q++ = '/'; + p += 2; + } + else if (p[0] == '/' && p[1] == '.' && p[2] == '/') + { + *q++ = '/'; + p += 3; + } + else + *q++ = *p++; + } + *q = 0; + + if (strncmp (s, NONULL (Maildir), (len = strlen (NONULL (Maildir)))) == 0 && s[len] == '/') + { + *s++ = '='; + strcpy (s, s + len); + } + else if (strncmp (s, Homedir, (len = strlen (Homedir))) == 0 && + s[len] == '/') + { + *s++ = '~'; + strcpy (s, s + len - 1); + } +} + +void mutt_unlink (const char *s) +{ + FILE *f; + struct stat sb; + char buf[2048]; + + if (stat (s, &sb) == 0) + { + if ((f = fopen (s, "r+"))) + { + unlink (s); + memset (buf, 0, sizeof (buf)); + while (sb.st_size > 0) + { + fwrite (buf, 1, sizeof (buf), f); + sb.st_size -= sizeof (buf); + } + fclose (f); + } + } +} + +int mutt_copy_stream (FILE *fin, FILE *fout) +{ + size_t l; + char buf[LONG_STRING]; + + while ((l = fread (buf, 1, sizeof (buf), fin)) > 0) + { + if (fwrite (buf, 1, l, fout) != l) + return (-1); + } + + return 0; +} + +void mutt_expand_fmt (char *dest, size_t destlen, const char *fmt, const char *src) +{ + const char *p = fmt; + const char *last = p; + size_t len; + size_t slen = strlen (src); + int found = 0; + + while ((p = strchr (p, '%')) != NULL) + { + if (p[1] == 's') + { + found++; + + len = (size_t) (p - last); + if (len) + { + if (len > destlen - 1) + len = destlen - 1; + + memcpy (dest, last, len); + dest += len; + destlen -= len; + + if (destlen <= 0) + { + *dest = 0; + break; /* no more space */ + } + } + + strfcpy (dest, src, destlen); + if (slen > destlen) + { + /* no more room */ + break; + } + dest += slen; + destlen -= slen; + + p += 2; + last = p; + } + else if (p[1] == '%') + p++; + + p++; + } + + if (found) + strfcpy (dest, last, destlen); + else + snprintf (dest, destlen, "%s '%s'", fmt, src); +} + +/* when opening files for writing, make sure the file doesn't already exist + * to avoid race conditions. + */ +FILE *safe_fopen (const char *path, const char *mode) +{ + struct stat osb, nsb; + + if (mode[0] == 'w') + { + int fd; + int flags = O_CREAT | O_EXCL; + + if (mode[1] == '+') + flags |= O_RDWR; + else + flags |= O_WRONLY; + + if ((fd = open (path, flags, 0600)) < 0) + return NULL; + + /* make sure the file is not symlink */ + if (lstat (path, &osb) < 0 || fstat (fd, &nsb) < 0 || + osb.st_dev != nsb.st_dev || osb.st_ino != nsb.st_ino || + osb.st_rdev != nsb.st_rdev) + { + dprint (1, (debugfile, "safe_fopen():%s is a symlink!\n", path)); + close (fd); + return (NULL); + } + + return (fdopen (fd, mode)); + } + else + return (fopen (path, mode)); +} + +/* return 0 on success, -1 on error */ +int mutt_check_overwrite (const char *attname, const char *path, + char *fname, size_t flen, int flags) +{ + char tmp[_POSIX_PATH_MAX]; + struct stat st; + + strfcpy (fname, path, flen); + if (access (fname, F_OK) != 0) + return 0; + if (stat (fname, &st) != 0) + return -1; + if (S_ISDIR (st.st_mode)) + { + if (mutt_yesorno ("File is a directory, save under it?", 1) != M_YES) + return (-1); + if (!attname || !attname[0]) + { + tmp[0] = 0; + if (mutt_get_field ("File under directory: ", tmp, sizeof (tmp), + M_FILE | M_CLEAR) != 0 || !tmp[0]) + return (-1); + snprintf (fname, flen, "%s/%s", path, tmp); + } + else + snprintf (fname, flen, "%s/%s", path, attname); + } + + if (flags != M_SAVE_APPEND && + access (fname, F_OK) == 0 && + mutt_yesorno ("File exists, overwrite?", 0) != 1) + return (-1); + + return 0; +} + +void mutt_remove_trailing_ws (char *s) +{ + char *p; + + for (p = s + strlen (s) - 1 ; p >= s && ISSPACE (*p) ; p--) + *p = 0; +} + +void mutt_pretty_size (char *s, size_t len, long n) +{ + if (n == 0) + strfcpy (s, "0K", len); + else if (n < 103) + strfcpy (s, "0.1K", len); + else if (n < 10189) /* 0.1K - 9.9K */ + snprintf (s, len, "%3.1fK", n / 1024.0); + else if (n < 1023949) /* 10K - 999K */ + { + /* 51 is magic which causes 10189/10240 to be rounded up to 10 */ + snprintf (s, len, "%ldK", (n + 51) / 1024); + } + else if (n < 10433332) /* 1.0M - 9.9M */ + snprintf (s, len, "%3.1fM", n / 1048576.0); + else /* 10M+ */ + { + /* (10433332 + 52428) / 1048576 = 10 */ + snprintf (s, len, "%ldM", (n + 52428) / 1048576); + } +} + +void mutt_save_path (char *d, size_t dsize, ADDRESS *a) +{ + if (a && a->mailbox) + { + strfcpy (d, a->mailbox, dsize); + if (!option (OPTSAVEADDRESS)) + { + char *p; + + if ((p = strpbrk (d, "%@"))) + *p = 0; + } + mutt_strlower (d); + } + else + *d = 0; +} + +void mutt_safe_path (char *s, size_t l, ADDRESS *a) +{ + char *p; + + mutt_save_path (s, l, a); + for (p = s; *p; p++) + if (*p == '/' || ISSPACE (*p) || !IsPrint ((unsigned char) *p)) + *p = '_'; +} + +/* Read a line from ``fp'' into the dynamically allocated ``s'', + * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed. + * If a line ends with "\", this char and the linefeed is removed, + * and the next line is read too. + */ +char *mutt_read_line (char *s, size_t *size, FILE *fp, int *line) +{ + size_t offset = 0; + char *ch; + + if (!s) + { + s = safe_malloc (STRING); + *size = STRING; + } + + FOREVER + { + if (fgets (s + offset, *size - offset, fp) == NULL) + { + free (s); + return NULL; + } + if ((ch = strchr (s + offset, '\n')) != NULL) + { + (*line)++; + *ch = 0; + if (ch > s && *(ch - 1) == '\r') + *--ch = 0; + if (ch == s || *(ch - 1) != '\\') + return s; + offset = ch - s - 1; + } + else + { + /* There wasn't room for the line -- increase ``s'' */ + offset = *size - 1; /* overwrite the terminating 0 */ + *size += STRING; + safe_realloc ((void **) &s, *size); + } + } +} + +char * +mutt_substrcpy (char *dest, const char *beg, const char *end, size_t destlen) +{ + size_t len; + + len = end - beg; + if (len > destlen - 1) + len = destlen - 1; + memcpy (dest, beg, len); + dest[len] = 0; + return dest; +} + +char *mutt_substrdup (const char *begin, const char *end) +{ + size_t len; + char *p; + + len = end - begin; + p = safe_malloc (len + 1); + memcpy (p, begin, len); + p[len] = 0; + return p; +} + +void mutt_FormatString (char *dest, /* output buffer */ + size_t destlen, /* output buffer len */ + const char *src, /* template string */ + format_t *callback, /* callback for processing */ + unsigned long data, /* callback data */ + format_flag flags) /* callback flags */ +{ + char prefix[SHORT_STRING], buf[LONG_STRING], *cp, *wptr = dest, ch; + char ifstring[SHORT_STRING], elsestring[SHORT_STRING]; + size_t wlen = 0, count, len; + + destlen--; /* save room for the terminal \0 */ + while (*src && wlen < destlen) + { + if (*src == '%') + { + if (*++src == '%') + { + *wptr++ = '%'; + wlen++; + src++; + continue; + } + + if (*src == '?') + { + flags |= M_FORMAT_OPTIONAL; + src++; + } + else + { + flags &= ~M_FORMAT_OPTIONAL; + + /* eat the format string */ + cp = prefix; + count = 0; + while (count < sizeof (prefix) && + (isdigit (*src) || *src == '.' || *src == '-')) + { + *cp++ = *src++; + count++; + } + *cp = 0; + } + + if (!*src) + break; /* bad format */ + + ch = *src++; /* save the character to switch on */ + + if (flags & M_FORMAT_OPTIONAL) + { + if (*src != '?') + break; /* bad format */ + src++; + + /* eat the `if' part of the string */ + cp = ifstring; + count = 0; + while (count < sizeof (ifstring) && *src && *src != '?' && *src != '&') + { + *cp++ = *src++; + count++; + } + *cp = 0; + + /* eat the `else' part of the string (optional) */ + if (*src == '&') + src++; /* skip the & */ + cp = elsestring; + count = 0; + while (count < sizeof (elsestring) && *src && *src != '?') + { + *cp++ = *src++; + count++; + } + *cp = 0; + + if (!*src) + break; /* bad format */ + + src++; /* move past the trailing `?' */ + } + + /* handle generic cases first */ + if (ch == '>') + { + /* right justify to EOL */ + ch = *src++; /* pad char */ + /* calculate space left on line. if we've already written more data + than will fit on the line, ignore the rest of the line */ + count = (COLS < destlen ? COLS : destlen); + if (count > wlen) + { + count -= wlen; /* how many chars left on this line */ + mutt_FormatString (buf, sizeof (buf), src, callback, data, flags); + len = strlen (buf); + if (count > len) + { + count -= len; /* how many chars to pad */ + memset (wptr, ch, count); + wptr += count; + wlen += count; + } + if (len + wlen > destlen) + len = destlen - wlen; + memcpy (wptr, buf, len); + wptr += len; + wlen += len; + } + break; /* skip rest of input */ + } + else if (ch == '|') + { + /* pad to EOL */ + ch = *src++; + if (destlen > COLS) + destlen = COLS; + if (destlen > wlen) + { + count = destlen - wlen; + memset (wptr, ch, count); + wptr += count; + } + break; /* skip rest of input */ + } + else + { + /* use callback function to handle this case */ + src = callback (buf, sizeof (buf), ch, src, prefix, ifstring, elsestring, data, flags); + + if ((len = strlen (buf)) + wlen > destlen) + { + if ((len = destlen - wlen) < 0) + len = 0; + } + memcpy (wptr, buf, len); + wptr += len; + wlen += len; + } + } + else if (*src == '\\') + { + if (!*++src) + break; + switch (*src) + { + case 'n': + *wptr = '\n'; + break; + case 't': + *wptr = '\t'; + break; + case 'r': + *wptr = '\r'; + break; + case 'f': + *wptr = '\f'; + break; + case 'v': + *wptr = '\v'; + break; + default: + *wptr = *src; + break; + } + src++; + wptr++; + wlen++; + } + else + { + *wptr++ = *src++; + wlen++; + } + } + *wptr = 0; + + if (flags & M_FORMAT_MAKEPRINT) + { + /* Make sure that the string is printable by changing all non-printable + chars to dots, or spaces for non-printable whitespace */ + for (cp = dest ; *cp ; cp++) + if (!IsPrint (*cp) && + !((flags & M_FORMAT_TREE) && (*cp <= M_TREE_MAX))) + *cp = isspace ((unsigned char) *cp) ? ' ' : '.'; + } +} + +/* This function allows the user to specify a command to read stdout from in + place of a normal file. If the last character in the string is a pipe (|), + then we assume it is a commmand to run instead of a normal file. */ +FILE *mutt_open_read (const char *path, pid_t *thepid) +{ + FILE *f; + int len = strlen (path); + + if (path[len - 1] == '|') + { + /* read from a pipe */ + + char *s = safe_strdup (path); + + s[len - 1] = 0; + endwin (); + *thepid = mutt_create_filter (s, NULL, &f, NULL); + free (s); + } + else + { + f = fopen (path, "r"); + *thepid = -1; + } + return (f); +} diff --git a/mailbox.h b/mailbox.h new file mode 100644 index 00000000..ad9d8417 --- /dev/null +++ b/mailbox.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* flags for mutt_open_mailbox() */ +#define M_NOSORT (1<<0) /* do not sort the mailbox after opening it */ +#define M_APPEND (1<<1) /* open mailbox for appending messages */ +#define M_READONLY (1<<2) /* open in read-only mode */ +#define M_QUIET (1<<3) /* do not print any messages */ + +/* mx_open_new_message() */ +#define M_ADD_FROM 1 /* add a From_ line */ + +/* return values from mx_check_mailbox() */ +enum +{ + M_NEW_MAIL = 1, /* new mail received in mailbox */ + M_LOCKED, /* couldn't lock the mailbox */ + M_REOPENED /* mailbox was reopened */ +}; + +typedef struct +{ + FILE *fp; /* pointer to the message data */ +#ifdef USE_IMAP + char *path; /* path to temp file */ +#endif /* USE_IMAP */ + short magic; /* type of mailbox this message belongs to */ + short write; /* nonzero if message is open for writing */ +} MESSAGE; + +CONTEXT *mx_open_mailbox (const char *, int, CONTEXT *); + +MESSAGE *mx_open_message (CONTEXT *, int); +MESSAGE *mx_open_new_message (CONTEXT *, HEADER *, int); + +void mx_fastclose_mailbox (CONTEXT *); + +int mx_close_mailbox (CONTEXT *); +int mx_sync_mailbox (CONTEXT *); +int mx_close_message (MESSAGE **msg); +int mx_get_magic (const char *); +int mx_set_magic (const char *); +int mx_check_mailbox (CONTEXT *, int *); diff --git a/main.c b/main.c new file mode 100644 index 00000000..29848c2d --- /dev/null +++ b/main.c @@ -0,0 +1,644 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define MAIN_C 1 + +#include "mutt.h" +#include "mutt_curses.h" +#include "keymap.h" +#include "mailbox.h" +#include "reldate.h" + +#include <string.h> +#include <stdlib.h> +#include <locale.h> +#include <unistd.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/utsname.h> + +const char Notice[] = "\ +Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu>\n\ +Mutt comes with ABSOLUTELY NO WARRANTY; for details type `mutt -vv'.\n\ +Mutt is free software, and you are welcome to redistribute it\n\ +under certain conditions; type `mutt -vv' for details.\n"; + +const char Copyright[] = "\ +Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu>\n\ +\n\ + This program is free software; you can redistribute it and/or modify\n\ + it under the terms of the GNU General Public License as published by\n\ + the Free Software Foundation; either version 2 of the License, or\n\ + (at your option) any later version.\n\ +\n\ + This program is distributed in the hope that it will be useful,\n\ + but WITHOUT ANY WARRANTY; without even the implied warranty of\n\ + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\ + GNU General Public License for more details.\n\ +\n\ + You should have received a copy of the GNU General Public License\n\ + along with this program; if not, write to the Free Software\n\ + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n"; + +void mutt_exit (int code) +{ + mutt_endwin (NULL); + exit (code); +} + +static void mutt_usage (void) +{ + printf ("Mutt %s (%s)\n", VERSION, ReleaseDate); + puts ( +"usage: mutt [ -nRzZ ] [ -e <cmd> ] [ -F <file> ] [ -m <type> ] [ -f <file> ]\n\ + mutt [ -nx ] [ -e <cmd> ] [ -a <file> ] [ -F <file> ] [ -H <file> ] [ -i <file> ] [ -s <subj> ] [ -b <addr> ] [ -c <addr> ] <addr> [ ... ]\n\ + mutt [ -n ] [ -e <cmd> ] [ -F <file> ] -p\n\ + mutt -v[v]\n\ +\n\ +options:\n\ + -a <file>\tattach a file to the message\n\ + -b <address>\tspecify a blind carbon-copy (BCC) address\n\ + -c <address>\tspecify a carbon-copy (CC) address\n\ + -e <command>\tspecify a command to be executed after initialization\n\ + -f <file>\tspecify which mailbox to read\n\ + -F <file>\tspecify an alternate muttrc file\n\ + -H <file>\tspecify a draft file to read header from\n\ + -i <file>\tspecify a file which Mutt should include in the reply\n\ + -m <type>\tspecify a default mailbox type\n\ + -n\t\tcauses Mutt not to read the system Muttrc\n\ + -p\t\trecall a postponed message\n\ + -R\t\topen mailbox in read-only mode\n\ + -s <subj>\tspecify a subject (must be in quotes if it has spaces)\n\ + -v\t\tshow version and compile-time definitions\n\ + -x\t\tsimulate the mailx send mode\n\ + -y\t\tselect a mailbox specified in your `mailboxes' list\n\ + -z\t\texit immediately if there are no messages in the mailbox\n\ + -Z\t\topen the first folder with new message, exit immediately if none\n\ + -h\t\tthis help message"); + + exit (0); +} + +static void show_version (void) +{ + struct utsname uts; + + printf ("Mutt %s (%s)\n", VERSION, ReleaseDate); + puts (Notice); + + uname (&uts); + +#ifdef _AIX + printf ("System: %s %s.%s", uts.sysname, uts.version, uts.release); +#elif defined (SCO) + printf ("System: SCO %s", uts.release); +#else + printf ("System: %s %s", uts.sysname, uts.release); +#endif + +#ifdef NCURSES_VERSION + printf (" [using ncurses %s]", NCURSES_VERSION); +#elif defined(USE_SLANG_CURSES) + printf (" [using slang %d]", SLANG_VERSION); +#endif + + puts ("\nCompile options:"); + +#ifdef DOMAIN + printf ("DOMAIN=\"%s\"\n", DOMAIN); +#else + puts ("-DOMAIN"); +#endif + + puts ( +#ifdef HIDDEN_HOST + "+HIDDEN_HOST " +#else + "-HIDDEN_HOST " +#endif + +#ifdef HOMESPOOL + "+HOMESPOOL " +#else + "-HOMESPOOL " +#endif + +#ifdef USE_SETGID + "+USE_SETGID " +#else + "-USE_SETGID " +#endif + +#ifdef USE_DOTLOCK + "+USE_DOTLOCK " +#else + "-USE_DOTLOCK " +#endif + +#ifdef USE_FCNTL + "+USE_FCNTL " +#else + "-USE_FCNTL " +#endif + +#ifdef USE_FLOCK + "+USE_FLOCK" +#else + "-USE_FLOCK" +#endif + ); + + puts ( +#ifdef USE_POP + "+USE_POP " +#else + "-USE_POP " +#endif + +#ifdef HAVE_REGCOMP + "+HAVE_REGCOMP " +#else + "-HAVE_REGCOMP " +#endif + +#ifdef USE_GNU_RX + "+USE_GNU_RX " +#else + "-USE_GNU_RX " +#endif + +#ifdef HAVE_COLOR + "+HAVE_COLOR " +#else + "-HAVE_COLOR " +#endif + + + +#ifdef _PGPPATH +#ifdef HAVE_PGP5 + "+HAVE_PGP5 " +#endif +#ifdef HAVE_PGP2 + "+HAVE_PGP2 " +#endif +#endif + + + +#ifdef BUFFY_SIZE + "+BUFFY_SIZE " +#else + "-BUFFY_SIZE " +#endif + +#ifdef EXACT_ADDRESS + "+" +#else + "-" +#endif + "EXACT_ADDRESS" + ); + + printf ("SENDMAIL=\"%s\"\n", SENDMAIL); + printf ("MAILPATH=\"%s\"\n", MAILPATH); + printf ("SHAREDIR=\"%s\"\n", SHAREDIR); + +#ifdef ISPELL + printf ("ISPELL=\"%s\"\n", ISPELL); +#else + puts ("-ISPELL"); +#endif + + + +#ifdef _PGPPATH + printf ("_PGPPATH=\"%s\"\n", _PGPPATH); +# ifdef _PGPV2PATH + printf ("_PGPV2PATH=\"%s\"\n", _PGPV2PATH); +# endif +# ifdef _PGPV3PATH + printf ("_PGPV3PATH=\"%s\"\n", _PGPV3PATH); +# endif +#endif + + + + puts ("\nMail bug reports along with this output to <mutt-users@cs.hmc.edu>."); + + exit (0); +} + +static void start_curses (void) +{ + km_init (); /* must come before mutt_init */ + +#ifdef USE_SLANG_CURSES + SLtt_Ignore_Beep = 1; /* don't do that #*$@^! annoying visual beep! */ + SLsmg_Display_Eight_Bit = 128; /* characters above this are printable */ +#else + /* should come before initscr() so that ncurses 4.2 doesn't try to install + its own SIGWINCH handler */ + mutt_signal_init (); +#endif + if (initscr () == NULL) + { + puts ("Error initializing terminal."); + exit (1); + } +#ifdef USE_SLANG_CURSES + /* slang requires the signal handlers to be set after initializing */ + mutt_signal_init (); +#endif + ci_start_color (); + keypad (stdscr, TRUE); + cbreak (); + noecho (); +#if HAVE_TYPEAHEAD + typeahead (-1); /* simulate smooth scrolling */ +#endif +#if HAVE_META + meta (stdscr, TRUE); +#endif +} + +#define M_IGNORE (1<<0) /* -z */ +#define M_BUFFY (1<<1) /* -Z */ +#define M_NOSYSRC (1<<2) /* -n */ +#define M_RO (1<<3) /* -R */ +#define M_SELECT (1<<4) /* -y */ + +int main (int argc, char **argv) +{ + char folder[_POSIX_PATH_MAX] = ""; + char *subject = NULL; + char *includeFile = NULL; + char *draftFile = NULL; + char *newMagic = NULL; + HEADER *msg = NULL; + LIST *attach = NULL; + LIST *commands = NULL; + int sendflags = 0; + int flags = 0; + int version = 0; + int i; + extern char *optarg; + extern int optind; + + mutt_error = mutt_nocurses_error; + + SRAND (time (NULL)); + setlocale (LC_CTYPE, ""); + umask (077); + +#ifdef USE_SETGID + /* Determine the user's default gid and the gid to use for locking the spool + * mailbox on those systems which require setgid "mail" to write to the + * directory. This function also resets the gid to "normal" since the + * effective gid will be "mail" when we start (Mutt attempts to run + * non-setgid whenever possible to reduce the possibility of security holes). + */ + + /* Get the default gid for the user */ + UserGid = getgid (); + + /* it is assumed that we are setgid to the correct gid to begin with */ + MailGid = getegid (); + + /* reset the effective gid to the normal gid */ + if (SETEGID (UserGid) != 0) + { + perror ("setegid"); + exit (0); + } +#endif + + memset (Options, 0, sizeof (Options)); + + while ((i = getopt (argc, argv, "a:b:F:f:c:d:e:H:s:i:hm:npRvxyzZ")) != EOF) + switch (i) + { + case 'a': + attach = mutt_add_list (attach, optarg); + break; + + case 'F': + FREE (&Muttrc); + Muttrc = safe_strdup (optarg); + break; + + case 'f': + strfcpy (folder, optarg, sizeof (folder)); + break; + + case 'b': + case 'c': + if (!msg) + msg = mutt_new_header (); + if (!msg->env) + msg->env = mutt_new_envelope (); + if (i == 'b') + msg->env->bcc = rfc822_parse_adrlist (msg->env->bcc, optarg); + else + msg->env->cc = rfc822_parse_adrlist (msg->env->cc, optarg); + break; + + case 'd': +#ifdef DEBUG + debuglevel = atoi (optarg); + printf ("Debugging at level %d.\n", debuglevel); +#else + printf ("DEBUG was not defined during compilation. Ignored.\n"); +#endif + break; + + case 'e': + commands = mutt_add_list (commands, optarg); + break; + + case 'H': + draftFile = optarg; + break; + + case 'i': + includeFile = optarg; + break; + + case 'm': + /* should take precedence over .muttrc setting, so save it for later */ + newMagic = optarg; + break; + + case 'n': + flags |= M_NOSYSRC; + break; + + case 'p': + sendflags |= SENDPOSTPONED; + break; + + case 'R': + flags |= M_RO; /* read-only mode */ + break; + + case 's': + subject = optarg; + break; + + case 'v': + version++; + break; + + case 'x': /* mailx compatible send mode */ + sendflags |= SENDMAILX; + break; + + case 'y': /* My special hack mode */ + flags |= M_SELECT; + break; + + case 'z': + flags |= M_IGNORE; + break; + + case 'Z': + flags |= M_BUFFY | M_IGNORE; + break; + + default: + mutt_usage (); + } + + switch (version) + { + case 0: + break; + case 1: + show_version (); + break; + default: + printf ("Mutt %s (%s)\n", VERSION, ReleaseDate); + puts (Copyright); + exit (0); + } + + /* Check for a batch send. */ + if (!isatty (0)) + { + set_option (OPTNOCURSES); + sendflags = SENDBATCH; + } + + /* This must come before mutt_init() because curses needs to be started + before calling the init_pair() function to set the color scheme. */ + if (!option (OPTNOCURSES)) + start_curses (); + + /* set defaults and read init files */ + mutt_init (flags & M_NOSYSRC, commands); + mutt_free_list (&commands); + + if (newMagic) + mx_set_magic (newMagic); + + if (!option (OPTNOCURSES)) + { + SETCOLOR (MT_COLOR_NORMAL); + clear (); + mutt_error = mutt_curses_error; + } + + if (sendflags & SENDPOSTPONED) + { + if (!option (OPTNOCURSES)) + mutt_flushinp (); + ci_send_message (SENDPOSTPONED, NULL, NULL, NULL, NULL); + mutt_endwin (NULL); + } + else if (subject || msg || sendflags || draftFile || includeFile || attach || + optind < argc) + { + FILE *fin = NULL; + char buf[LONG_STRING]; + char *tempfile = NULL, *infile = NULL; + + if (!option (OPTNOCURSES)) + mutt_flushinp (); + + if (!msg) + msg = mutt_new_header (); + + if (draftFile) + infile = draftFile; + else + { + if (!msg->env) + msg->env = mutt_new_envelope (); + + for (i = optind; i < argc; i++) + msg->env->to = rfc822_parse_adrlist (msg->env->to, argv[i]); + + if (!msg->env->to && !msg->env->cc) + { + if (!option (OPTNOCURSES)) + mutt_endwin (NULL); + fputs ("No recipients specified.\n", stderr); + exit (1); + } + + msg->env->subject = safe_strdup (subject); + + if (includeFile) + infile = includeFile; + } + + if (infile) + { + if (strcmp ("-", infile) == 0) + fin = stdin; + else + { + char path[_POSIX_PATH_MAX]; + + strfcpy (path, infile, sizeof (path)); + mutt_expand_path (path, sizeof (path)); + if ((fin = fopen (path, "r")) == NULL) + { + if (!option (OPTNOCURSES)) + mutt_endwin (NULL); + perror (path); + exit (1); + } + } + mutt_mktemp (buf); + tempfile = safe_strdup (buf); + + if (draftFile) + msg->env = mutt_read_rfc822_header (fin, NULL); + + if (tempfile) + { + FILE *fout; + + if ((fout = safe_fopen (tempfile, "w")) == NULL) + { + if (!option (OPTNOCURSES)) + mutt_endwin (NULL); + perror (tempfile); + fclose (fin); + free (tempfile); + exit (1); + } + + mutt_copy_stream (fin, fout); + fclose (fout); + if (fin != stdin) + fclose (fin); + } + } + + if (attach) + { + LIST *t = attach; + BODY *a = NULL; + + while (t) + { + if (a) + { + a->next = mutt_make_attach (t->data); + a = a->next; + } + else + msg->content = a = mutt_make_attach (t->data); + if (!a) + { + if (!option (OPTNOCURSES)) + mutt_endwin (NULL); + fprintf (stderr, "%s: unable to attach file.\n", t->data); + mutt_free_list (&attach); + exit (1); + } + t = t->next; + } + mutt_free_list (&attach); + } + + ci_send_message (sendflags, msg, tempfile, NULL, NULL); + + if (!option (OPTNOCURSES)) + mutt_endwin (NULL); + } + else + { + if (flags & M_BUFFY) + { + if (!mutt_buffy_check (0)) + { + mutt_endwin ("No mailbox with new mail."); + exit (1); + } + folder[0] = 0; + mutt_buffy (folder); + } + else if (flags & M_SELECT) + { + folder[0] = 0; + mutt_select_file (folder, sizeof (folder), 1); + if (!folder[0]) + { + mutt_endwin (NULL); + exit (0); + } + } + + if (!folder[0]) + strfcpy (folder, Spoolfile, sizeof (folder)); + mutt_expand_path (folder, sizeof (folder)); + + if (flags & M_IGNORE) + { + struct stat st; + + /* check to see if there are any messages in the folder */ + if (stat (folder, &st) != 0) + { + mutt_endwin (strerror (errno)); + exit (1); + } + + if (st.st_size == 0) + { + mutt_endwin ("Mailbox is empty."); + exit (1); + } + } + + mutt_folder_hook (folder); + + if ((Context = mx_open_mailbox (folder, ((flags & M_RO) || option (OPTREADONLY)) ? M_READONLY : 0, NULL)) != NULL) + { + mutt_index_menu (); + mutt_endwin (NULL); + } + else + mutt_endwin (Errorbuf); + } + + exit (0); +} diff --git a/mapping.h b/mapping.h new file mode 100644 index 00000000..1464075d --- /dev/null +++ b/mapping.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +struct mapping_t +{ + char *name; + int value; +}; + +char *mutt_getnamebyvalue (int, const struct mapping_t *); +char *mutt_compile_help (char *, size_t, int, struct mapping_t *); + +int mutt_getvaluebyname (const char *, const struct mapping_t *); diff --git a/mbox.c b/mbox.c new file mode 100644 index 00000000..0e3211f4 --- /dev/null +++ b/mbox.c @@ -0,0 +1,1008 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* This file contains code to parse ``mbox'' and ``mmdf'' style mailboxes */ + +#include "mutt.h" +#include "mailbox.h" +#include "mx.h" +#include "sort.h" +#include "copy.h" + +#include <sys/stat.h> +#include <dirent.h> +#include <string.h> +#include <utime.h> +#include <sys/file.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> + +/* struct used by mutt_sync_mailbox() to store new offsets */ +struct m_update_t +{ + long hdr; + long body; +}; + +#ifdef USE_DOTLOCK +/* + * Determine whether or not to use a dotlock to lock the indicated file. + * On some systems, the spool directory is not world-writable. If it is + * group-writable, we might need to be setgid() to write the lock. If not + * group-writable, then we assume that fcntl() locking is enough and skip + * the dotlocking. + * + * return values: + * 2 need to be setgid to dotlock + * 1 can use a dotlock + * 0 don't use a dotlock + * -1 error + */ +static int can_dotlock (const char *path) +{ + char tmp[_POSIX_PATH_MAX]; + char *p; +#ifdef USE_SETGID + struct stat sb; +#endif + + strfcpy (tmp, path, sizeof (tmp)); + if ((p = strrchr (tmp, '/'))) + *p = 0; + else + strfcpy (tmp, ".", sizeof (tmp)); /* use current directory */ + + if (access (tmp, W_OK) == 0) return 1; + +#ifdef USE_SETGID + if (stat (tmp, &sb) == 0) + { + if ((sb.st_mode & S_IWGRP) == S_IWGRP) + { + /* can dotlock, but need to be setgid */ + if (sb.st_gid == MailGid) + return (2); + else + { + mutt_error ("Need to be running setgid %d to lock mailbox!", sb.st_gid); + return (-1); + } + } + } +#endif + + if (mutt_yesorno ("Can't dotlock mailbox, continue anyway?", 0) == 1) + return 0; + + return (-1); +} +#endif + +/* parameters: + * ctx - context to lock + * excl - exclusive lock? + * retry - should retry if unable to lock? + */ +int mbox_lock_mailbox (CONTEXT *ctx, int excl, int retry) +{ + int r = 0; + +#ifdef USE_DOTLOCK + r = can_dotlock (ctx->path); + + if (r == -1) + return (-1); +#ifdef USE_SETGID + else if (r == 2) + { + /* need to be setgid to lock the mailbox */ + if (SETEGID (MailGid) != 0) + { + mutt_perror ("setegid"); + return (-1); + } + + ctx->setgid = 1; + } +#endif /* USE_SETGID */ +#endif /* USE_DOTLOCK */ + + if ((r = mx_lock_file (ctx->path, fileno (ctx->fp), excl, r, retry)) == 0) + ctx->locked = 1; + +#ifdef USE_SETGID + if (ctx->setgid) + SETEGID (UserGid); +#endif + + return (r); +} + +void mbox_unlock_mailbox (CONTEXT *ctx) +{ + if (ctx->locked) + { + fflush (ctx->fp); + +#ifdef USE_SETGID + if (ctx->setgid) + SETEGID (MailGid); +#endif /* USE_SETGID */ + + mx_unlock_file (ctx->path, fileno (ctx->fp)); + ctx->locked = 0; + +#ifdef USE_SETGID + if (ctx->setgid) + { + SETEGID (UserGid); + ctx->setgid = 0; + } +#endif + } +} + +int mmdf_parse_mailbox (CONTEXT *ctx) +{ + char buf[HUGE_STRING]; + char return_path[LONG_STRING]; + int lines; + time_t t, tz; + long loc, tmploc; + HEADER *hdr; + struct stat sb; +#ifdef NFS_ATTRIBUTE_HACK + struct utimbuf newtime; +#endif + + if (stat (ctx->path, &sb) == -1) + { + mutt_perror (ctx->path); + return (-1); + } + ctx->mtime = sb.st_mtime; + ctx->size = sb.st_size; + +#ifdef NFS_ATTRIBUTE_HACK + if (sb.st_mtime > sb.st_atime) + { + newtime.modtime = sb.st_mtime; + newtime.actime = time (NULL); + utime (ctx->path, &newtime); + } +#endif + + /* precompute the local timezone to speed up calculation of the + received time */ + tz = mutt_local_tz (); + + buf[sizeof (buf) - 1] = 0; + FOREVER + { + if (fgets (buf, sizeof (buf) - 1, ctx->fp) == NULL) + break; + + if (strcmp (buf, MMDF_SEP) == 0) + { + loc = ftell (ctx->fp); + + if (ctx->msgcount == ctx->hdrmax) + mx_alloc_memory (ctx); + ctx->hdrs[ctx->msgcount] = hdr = mutt_new_header (); + hdr->offset = loc; + hdr->index = ctx->msgcount; + + if (fgets (buf, sizeof (buf) - 1, ctx->fp) == NULL) + { + dprint (1, (debugfile, "mmdf_parse_mailbox: unexpected EOF\n")); + break; + } + + return_path[0] = 0; + t = is_from (buf, return_path, sizeof (return_path)); + + if (!t) + fseek (ctx->fp, loc, 0); + else + hdr->received = t + tz; + + hdr->env = mutt_read_rfc822_header (ctx->fp, hdr); + + loc = ftell (ctx->fp); + + if (hdr->content->length > 0 && hdr->lines > 0) + { + tmploc = loc + hdr->content->length; + + if (tmploc < ctx->size) + { + fseek (ctx->fp, tmploc, 0); + if (fgets (buf, sizeof (buf) - 1, ctx->fp) == NULL || + strcmp (MMDF_SEP, buf) != 0) + { + fseek (ctx->fp, loc, 0); + hdr->content->length = -1; + } + } + else + hdr->content->length = -1; + } + else + hdr->content->length = -1; + + if (hdr->content->length < 0) + { + lines = -1; + do { + loc = ftell (ctx->fp); + if (fgets (buf, sizeof (buf) - 1, ctx->fp) == NULL) + break; + lines++; + } while (strcmp (buf, MMDF_SEP) != 0); + + hdr->lines = lines; + hdr->content->length = loc - hdr->content->offset; + } + + if (!hdr->env->return_path && return_path[0]) + hdr->env->return_path = rfc822_parse_adrlist (hdr->env->return_path, return_path); + + if (!hdr->env->from) + hdr->env->from = rfc822_cpy_adr (hdr->env->return_path); + + mx_update_context (ctx); + } + else + { + dprint (1, (debugfile, "mmdf_parse_mailbox: corrupt mailbox!\n")); + mutt_error ("Mailbox is corrupt!"); + return (-1); + } + } + + return 0; +} + +/* Note that this function is also called when new mail is appended to the + * currently open folder, and NOT just when the mailbox is initially read. + * + * NOTE: it is assumed that the mailbox being read has been locked before + * this routine gets called. Strange things could happen if it's not! + */ +int mbox_parse_mailbox (CONTEXT *ctx) +{ + struct stat sb; + char buf[HUGE_STRING], return_path[STRING]; + HEADER *curhdr; + time_t t, tz; + int count = 0, lines = 0; + long loc; +#ifdef NFS_ATTRIBUTE_HACK + struct utimbuf newtime; +#endif + + /* Save information about the folder at the time we opened it. */ + if (stat (ctx->path, &sb) == -1) + { + mutt_perror (ctx->path); + return (-1); + } + + ctx->size = sb.st_size; + ctx->mtime = sb.st_mtime; + +#ifdef NFS_ATTRIBUTE_HACK + if (sb.st_mtime > sb.st_atime) + { + newtime.modtime = sb.st_mtime; + newtime.actime = time (NULL); + utime (ctx->path, &newtime); + } +#endif + + if (!ctx->readonly) + ctx->readonly = access (ctx->path, W_OK) ? 1 : 0; + + /* precompute the local timezone to speed up calculation of the + date received */ + tz = mutt_local_tz (); + + loc = ftell (ctx->fp); + while (fgets (buf, sizeof (buf), ctx->fp) != NULL) + { + if ((t = is_from (buf, return_path, sizeof (return_path)))) + { + /* Save the Content-Length of the previous message */ + if (count > 0) + { +#define PREV ctx->hdrs[ctx->msgcount-1] + + if (PREV->content->length < 0) + { + PREV->content->length = loc - PREV->content->offset - 1; + if (PREV->content->length < 0) + PREV->content->length = 0; + } + if (!PREV->lines) + PREV->lines = lines ? lines - 1 : 0; + } + + count++; + + if (!ctx->quiet && ReadInc && ((count % ReadInc == 0) || count == 1)) + mutt_message ("Reading %s... %d (%d%%)", ctx->path, count, + ftell (ctx->fp) / (ctx->size / 100 + 1)); + + if (ctx->msgcount == ctx->hdrmax) + mx_alloc_memory (ctx); + + curhdr = ctx->hdrs[ctx->msgcount] = mutt_new_header (); + curhdr->received = t + tz; + curhdr->offset = loc; + curhdr->index = ctx->msgcount; + + curhdr->env = mutt_read_rfc822_header (ctx->fp, curhdr); + + /* if we know how long this message is, either just skip over the body, + * or if we don't know how many lines there are, count them now (this will + * save time by not having to search for the next message marker). + */ + if (curhdr->content->length > 0) + { + long tmploc; + + loc = ftell (ctx->fp); + tmploc = loc + curhdr->content->length + 1; + + if (tmploc < ctx->size) + { + /* + * check to see if the content-length looks valid. we expect to + * to see a valid message separator at this point in the stream + */ + fseek (ctx->fp, tmploc, 0); + if (fgets (buf, sizeof (buf), ctx->fp) == NULL || + strncmp ("From ", buf, 5) != 0) + { + dprint (1, (debugfile, "mbox_parse_mailbox: bad content-length in message %d (cl=%ld)\n", curhdr->index, curhdr->content->length)); + dprint (1, (debugfile, "\tLINE: %s", buf)); + fseek (ctx->fp, loc, 0); /* nope, return the previous position */ + curhdr->content->length = -1; + } + } + else if (tmploc != ctx->size) + { + /* content-length would put us past the end of the file, so it + * must be wrong + */ + curhdr->content->length = -1; + } + + if (curhdr->content->length != -1) + { + /* good content-length. check to see if we know how many lines + * are in this message. + */ + if (curhdr->lines == 0) + { + int cl = curhdr->content->length; + + /* count the number of lines in this message */ + fseek (ctx->fp, loc, 0); + while (cl-- > 0) + { + if (fgetc (ctx->fp) == '\n') + curhdr->lines++; + } + } + + /* return to the offset of the next message separator */ + fseek (ctx->fp, tmploc, 0); + } + } + + mx_update_context (ctx); + + if (!curhdr->env->return_path && return_path[0]) + curhdr->env->return_path = rfc822_parse_adrlist (curhdr->env->return_path, return_path); + + if (!curhdr->env->from) + curhdr->env->from = rfc822_cpy_adr (curhdr->env->return_path); + + lines = 0; + } + else + lines++; + + loc = ftell (ctx->fp); + } + + /* + * Only set the content-length of the previous message if we have read more + * than one message during _this_ invocation. If this routine is called + * when new mail is received, we need to make sure not to clobber what + * previously was the last message since the headers may be sorted. + */ + if (count > 0) + { + if (PREV->content->length < 0) + { + PREV->content->length = ftell (ctx->fp) - PREV->content->offset - 1; + if (PREV->content->length < 0) + PREV->content->length = 0; + } + + if (!PREV->lines) + PREV->lines = lines ? lines - 1 : 0; + } + + return (0); +} + +#undef PREV + +/* open a mbox or mmdf style mailbox */ +int mbox_open_mailbox (CONTEXT *ctx) +{ + int rc; + + if ((ctx->fp = fopen (ctx->path, "r")) == NULL) + { + mutt_perror (ctx->path); + return (-1); + } + mutt_block_signals (); + if (mbox_lock_mailbox (ctx, 0, 1) == -1) + { + mutt_unblock_signals (); + return (-1); + } + + if (ctx->magic == M_MBOX) + rc = mbox_parse_mailbox (ctx); + else if (ctx->magic == M_MMDF) + rc = mmdf_parse_mailbox (ctx); + else + rc = -1; + + mbox_unlock_mailbox (ctx); + mutt_unblock_signals (); + return (rc); +} + +/* return 1 if address lists are strictly identical */ +static int strict_addrcmp (const ADDRESS *a, const ADDRESS *b) +{ + while (a && b) + { + if (mutt_strcmp (a->mailbox, b->mailbox) || + mutt_strcmp (a->personal, b->personal)) + return (0); + + a = a->next; + b = b->next; + } + if (a || b) + return (0); + + return (1); +} + +static int strict_cmp_lists (const LIST *a, const LIST *b) +{ + while (a && b) + { + if (mutt_strcmp (a->data, b->data)) + return (0); + + a = a->next; + b = b->next; + } + if (a || b) + return (0); + + return (1); +} + +static int strict_cmp_envelopes (const ENVELOPE *e1, const ENVELOPE *e2) +{ + if (e1 && e2) + { + if (mutt_strcmp (e1->message_id, e2->message_id) || + mutt_strcmp (e1->subject, e2->subject) || + !strict_cmp_lists (e1->references, e2->references) || + !strict_addrcmp (e1->from, e2->from) || + !strict_addrcmp (e1->sender, e2->sender) || + !strict_addrcmp (e1->reply_to, e2->reply_to) || + !strict_addrcmp (e1->to, e2->to) || + !strict_addrcmp (e1->cc, e2->cc) || + !strict_addrcmp (e1->return_path, e2->return_path)) + return (0); + else + return (1); + } + else + { + if (e1 == NULL && e2 == NULL) + return (1); + else + return (0); + } +} + +static int strict_cmp_parameters (const PARAMETER *p1, const PARAMETER *p2) +{ + while (p1 && p2) + { + if (mutt_strcmp (p1->attribute, p2->attribute) || + mutt_strcmp (p1->value, p2->value)) + return (0); + + p1 = p1->next; + p2 = p2->next; + } + if (p1 || p2) + return (0); + + return (1); +} + +static int strict_cmp_bodies (const BODY *b1, const BODY *b2) +{ + if (b1->type != b2->type || + b1->encoding != b2->encoding || + mutt_strcmp (b1->subtype, b2->subtype) || + mutt_strcmp (b1->description, b2->description) || + !strict_cmp_parameters (b1->parameter, b2->parameter) || + b1->length != b2->length) + return (0); + return (1); +} + +/* return 1 if headers are strictly identical */ +int mbox_strict_cmp_headers (const HEADER *h1, const HEADER *h2) +{ + if (h1 && h2) + { + if (h1->received != h2->received || + h1->date_sent != h2->date_sent || + h1->content->length != h2->content->length || + h1->lines != h2->lines || + h1->zhours != h2->zhours || + h1->zminutes != h2->zminutes || + h1->zoccident != h2->zoccident || + h1->mime != h2->mime || + !strict_cmp_envelopes (h1->env, h2->env) || + !strict_cmp_bodies (h1->content, h2->content)) + return (0); + else + return (1); + } + else + { + if (h1 == NULL && h2 == NULL) + return (1); + else + return (0); + } +} + +/* check to see if the mailbox has changed on disk. + * + * return values: + * M_REOPENED mailbox has been reopened + * M_NEW_MAIL new mail has arrived! + * M_LOCKED couldn't lock the file + * 0 no change + * -1 error + */ +int mbox_check_mailbox (CONTEXT *ctx, int *index_hint) +{ + struct stat st; + char buffer[LONG_STRING]; + int unlock = 0; + int modified = 0; + + if (stat (ctx->path, &st) == 0) + { + if (st.st_mtime == ctx->mtime && st.st_size == ctx->size) + return (0); + + if (st.st_size == ctx->size) + { + /* the file was touched, but it is still the same length, so just exit */ + ctx->mtime = st.st_mtime; + return (0); + } + + if (st.st_size > ctx->size) + { + /* lock the file if it isn't already */ + if (!ctx->locked) + { + mutt_block_signals (); + if (mbox_lock_mailbox (ctx, 0, 0) == -1) + { + mutt_unblock_signals (); + /* we couldn't lock the mailbox, but nothing serious happened: + * probably the new mail arrived: no reason to wait till we can + * parse it: we'll get it on the next pass + * */ + return (M_LOCKED); + } + unlock = 1; + } + + /* + * Check to make sure that the only change to the mailbox is that + * message(s) were appended to this file. My heuristic is that we should + * see the message separator at *exactly* what used to be the end of the + * folder. + */ + fseek (ctx->fp, ctx->size, 0); + if (fgets (buffer, sizeof (buffer), ctx->fp) != NULL) + { + if ((ctx->magic == M_MBOX && strncmp ("From ", buffer, 5) == 0) || + (ctx->magic == M_MMDF && strcmp (MMDF_SEP, buffer) == 0)) + { + fseek (ctx->fp, ctx->size, 0); + if (ctx->magic == M_MBOX) + mbox_parse_mailbox (ctx); + else + mmdf_parse_mailbox (ctx); + + /* Only unlock the folder if it was locked inside of this routine. + * It may have been locked elsewhere, like in + * mutt_checkpoint_mailbox(). + */ + + if (unlock) + { + mbox_unlock_mailbox (ctx); + mutt_unblock_signals (); + } + + return (M_NEW_MAIL); /* signal that new mail arrived */ + } + else + modified = 1; + } + else + { + dprint (1, (debugfile, "mbox_check_mailbox: fgets returned NULL.\n")); + modified = 1; + } + } + else + modified = 1; + } + + if (modified) + { + if (mutt_reopen_mailbox (ctx, index_hint) != -1) + { + if (unlock) + { + mbox_unlock_mailbox (ctx); + mutt_unblock_signals (); + } + return (M_REOPENED); + } + } + + /* fatal error */ + + mbox_unlock_mailbox (ctx); + mx_fastclose_mailbox (ctx); + mutt_unblock_signals (); + mutt_error ("Mailbox was corrupted!"); + return (-1); +} + +/* return values: + * 0 success + * -1 failure + */ +int mbox_sync_mailbox (CONTEXT *ctx) +{ + char tempfile[_POSIX_PATH_MAX]; + char buf[16]; + int i, j, save_sort = SORT_ORDER; + int need_sort = 0; /* flag to resort mailbox if new mail arrives */ + int first; /* first message to be written */ + long offset; /* location in mailbox to write changed messages */ + struct stat statbuf; + struct utimbuf utimebuf; + struct m_update_t *newOffset = NULL; + FILE *fp; + + /* sort message by their position in the mailbox on disk */ + if (Sort != SORT_ORDER) + { + save_sort = Sort; + Sort = SORT_ORDER; + mutt_sort_headers (ctx, 0); + } + + /* need to open the file for writing in such a way that it does not truncate + * the file, so use read-write mode. + */ + if ((ctx->fp = freopen (ctx->path, "r+", ctx->fp)) == NULL) + { + mx_fastclose_mailbox (ctx); + mutt_error ("Fatal error! Could not reopen mailbox."); + return (-1); + } + + mutt_block_signals (); + + if (mbox_lock_mailbox (ctx, 1, 1) == -1) + { + mutt_unblock_signals (); + mutt_error ("Unable to lock mailbox!"); + goto bail; + } + + /* Check to make sure that the file hasn't changed on disk */ + if ((i = mbox_check_mailbox (ctx, NULL)) == M_NEW_MAIL || i == M_REOPENED) + { + /* new mail arrived, or mailbox reopened */ + need_sort = i; + goto bail; + } + else if (i < 0) + { + /* fatal error */ + Sort = save_sort; + return (-1); + } + + /* Create a temporary file to write the new version of the mailbox in. */ + mutt_mktemp (tempfile); + if ((i = open (tempfile, O_WRONLY | O_EXCL | O_CREAT, 0600)) == -1 || + (fp = fdopen (i, "w")) == NULL) + { + mutt_error ("Could not create temporary file!"); + goto bail; + } + + /* find the first deleted/changed message. we save a lot of time by only + * rewriting the mailbox from the point where it has actually changed. + */ + for (i = 0 ; i < ctx->msgcount && !ctx->hdrs[i]->deleted && + !ctx->hdrs[i]->changed && !ctx->hdrs[i]->attach_del; i++) + ; + if (i == ctx->msgcount) + { + /* this means ctx->changed or ctx->deleted was set, but no + * messages were found to be changed or deleted. This should + * never happen, is we presume it is a bug in mutt. + */ + mutt_error ("sync: mbox modified, but no modified messages! (report this bug)"); + sleep(5); /* the mutt_error /will/ get cleared! */ + dprint(1, (debugfile, "mbox_sync_mailbox(): no modified messages.\n")); + goto bail; + } + + + first = i; /* save the index of the first changed/deleted message */ + offset = ctx->hdrs[i]->offset; /* where to start overwriting */ + /* the offset stored in the header does not include the MMDF_SEP, so make + * sure we seek to the correct location + */ + if (ctx->magic == M_MMDF) + offset -= strlen (MMDF_SEP); + + /* allocate space for the new offsets */ + newOffset = safe_calloc (ctx->msgcount - first, sizeof (struct m_update_t)); + + for (i = first, j = 0; i < ctx->msgcount; i++) + { + if (! ctx->hdrs[i]->deleted) + { + j++; + if (!ctx->quiet && WriteInc && ((j % WriteInc) == 0 || j == 1)) + mutt_message ("Writing messages... %d (%d%%)", j, + ftell (ctx->fp) / (ctx->size / 100 + 1)); + + if (ctx->magic == M_MMDF) + { + if (fputs (MMDF_SEP, fp) == EOF) + goto bail; + } + + /* save the new offset for this message. we add `offset' because the + * temporary file only contains saved message which are located after + * `offset' in the real mailbox + */ + newOffset[i - first].hdr = ftell (fp) + offset; + + if (mutt_copy_message (fp, ctx, ctx->hdrs[i], M_CM_UPDATE, CH_FROM | CH_UPDATE | CH_UPDATE_LEN) == -1) + goto bail; + + /* Since messages could have been deleted, the offsets stored in memory + * will be wrong, so update what we can, which is the offset of this + * message, and the offset of the body. If this is a multipart message, + * we just flush the in memory cache so that the message will be reparsed + * if the user accesses it later. + */ + newOffset[i - first].body = ftell (fp) - ctx->hdrs[i]->content->length + offset; + mutt_free_body (&ctx->hdrs[i]->content->parts); + + if (fputs (ctx->magic == M_MMDF ? MMDF_SEP : "\n", fp) == EOF) + goto bail; + } + } + + if (fclose (fp) != 0) + { + dprint(1, (debugfile, "mutt_sync_mailbox(): fclose() returned non-zero.\n")); + unlink (tempfile); + goto bail; + } + + /* Save the state of this folder. */ + if (stat (ctx->path, &statbuf) == -1) + { + unlink (tempfile); + goto bail; + } + + if ((fp = fopen (tempfile, "r")) == NULL) + { + mutt_unblock_signals (); + mx_fastclose_mailbox (ctx); + dprint (1, (debugfile, "mbox_sync_mailbox: unable to reopen temp copy of mailbox!\n")); + mutt_perror (tempfile); + return (-1); + } + + fseek (ctx->fp, offset, 0); /* seek the append location */ + + /* do a sanity check to make sure the mailbox looks ok */ + if (fgets (buf, sizeof (buf), ctx->fp) == NULL || + (ctx->magic == M_MBOX && strncmp ("From ", buf, 5) != 0) || + (ctx->magic == M_MMDF && strcmp (MMDF_SEP, buf) != 0)) + { + dprint (1, (debugfile, "mbox_sync_mailbox(): message not in expected position.")); + dprint (1, (debugfile, "\tLINE: %s\n", buf)); + i = -1; + } + else + { + fseek (ctx->fp, offset, 0); /* return to proper offset */ + + /* copy the temp mailbox back into place starting at the first + * change/deleted message + */ + i = mutt_copy_stream (fp, ctx->fp); + + if (ferror (ctx->fp)) + i = -1; + + if (i == 0) + { + ctx->size = ftell (ctx->fp); /* update the size of the mailbox */ + ftruncate (fileno (ctx->fp), ctx->size); + } + } + + fclose (fp); + mbox_unlock_mailbox (ctx); + + if (fclose (ctx->fp) != 0 || i == -1) + { + /* error occured while writing the mailbox back, so keep the temp copy + * around + */ + + char savefile[_POSIX_PATH_MAX]; + + snprintf (savefile, sizeof (savefile), "%s/mutt.%s-%s-%d", + NONULL (Tempdir), Username, Hostname, getpid ()); + rename (tempfile, savefile); + mutt_unblock_signals (); + mx_fastclose_mailbox (ctx); + mutt_pretty_mailbox (savefile); + mutt_error ("Write failed! Saved partial mailbox to %s", savefile); + return (-1); + } + + /* Restore the previous access/modification times */ + utimebuf.actime = statbuf.st_atime; + utimebuf.modtime = statbuf.st_mtime; + utime (ctx->path, &utimebuf); + + /* reopen the mailbox in read-only mode */ + if ((ctx->fp = fopen (ctx->path, "r")) == NULL) + { + unlink (tempfile); + mutt_unblock_signals (); + mx_fastclose_mailbox (ctx); + mutt_error ("Fatal error! Could not reopen mailbox!"); + Sort = save_sort; + return (-1); + } + + /* update the offsets of the rewritten messages */ + for (i = first, j = first; i < ctx->msgcount; i++) + { + if (!ctx->hdrs[i]->deleted) + { + ctx->hdrs[i]->offset = newOffset[i - first].hdr; + ctx->hdrs[i]->content->hdr_offset = newOffset[i - first].hdr; + ctx->hdrs[i]->content->offset = newOffset[i - first].body; + ctx->hdrs[i]->index = j++; + } + } + safe_free ((void **) &newOffset); + unlink (tempfile); /* remove partial copy of the mailbox */ + mutt_unblock_signals (); + Sort = save_sort; /* Restore the default value. */ + + return (0); /* signal success */ + +bail: /* Come here in case of disaster */ + + /* this is ok to call even if we haven't locked anything */ + mbox_unlock_mailbox (ctx); + + mutt_unblock_signals (); + safe_free ((void **) &newOffset); + + if ((ctx->fp = freopen (ctx->path, "r", ctx->fp)) == NULL) + { + mutt_error ("Could not reopen mailbox!"); + mx_fastclose_mailbox (ctx); + return (-1); + } + + if (need_sort || save_sort != Sort) + { + Sort = save_sort; + /* if the mailbox was reopened, the thread tree will be invalid so make + * sure to start threading from scratch. */ + mutt_sort_headers (ctx, (need_sort == M_REOPENED)); + } + + return (-1); +} + +/* close a mailbox opened in write-mode */ +int mbox_close_mailbox (CONTEXT *ctx) +{ +#ifdef USE_SETGID + if (ctx->setgid) + SETEGID (MailGid); +#endif + + mx_unlock_file (ctx->path, fileno (ctx->fp)); + +#ifdef USE_SETGID + if (ctx->setgid) + { + SETEGID (UserGid); + ctx->setgid = 0; + } +#endif + + mutt_unblock_signals (); + mx_fastclose_mailbox (ctx); + return 0; +} diff --git a/menu.c b/menu.c new file mode 100644 index 00000000..f582a1eb --- /dev/null +++ b/menu.c @@ -0,0 +1,826 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "mutt_menu.h" + +#include <string.h> +#include <stdlib.h> + +#define M_MODEFMT "-- Mutt: %s" + +static void print_enriched_string (int attr, unsigned char *s, int do_color) +{ + while (*s) + { + if (*s < M_TREE_MAX) + { + if (do_color) + SETCOLOR (MT_COLOR_TREE); + while (*s && *s < M_TREE_MAX) + { + switch (*s) + { + case M_TREE_LLCORNER: + addch (option (OPTASCIICHARS) ? '`' : ACS_LLCORNER); + break; + case M_TREE_ULCORNER: + addch (option (OPTASCIICHARS) ? ',' : ACS_ULCORNER); + break; + case M_TREE_LTEE: + addch (option (OPTASCIICHARS) ? '|' : ACS_LTEE); + break; + case M_TREE_HLINE: + addch (option (OPTASCIICHARS) ? '-' : ACS_HLINE); + break; + case M_TREE_VLINE: + addch (option (OPTASCIICHARS) ? '|' : ACS_VLINE); + break; + case M_TREE_SPACE: + addch (' '); + break; + case M_TREE_RARROW: + addch ('>'); + break; + case M_TREE_STAR: + addch ('*'); /* fake thread indicator */ + break; + case M_TREE_HIDDEN: + addch ('&'); + break; + } + s++; + } + if (do_color) attrset(attr); + } + else + { + addch (*s); + s++; + } + } +} + +void menu_pad_string (char *s, size_t l) +{ +#if !defined(HAVE_BKGDSET) && !defined (USE_SLANG_CURSES) + int n = strlen (s); +#endif + int shift = option (OPTARROWCURSOR) ? 3 : 0; + + l--; /* save room for the terminal \0 */ + if (l > COLS - shift) + l = COLS - shift; +#if !defined (HAVE_BKGDSET) && !defined (USE_SLANG_CURSES) + /* we have to pad the string with blanks to the end of line */ + if (n < l) + { + while (n < l) + s[n++] = ' '; + s[n] = 0; + } + else +#endif + s[l] = 0; +} + +void menu_redraw_full (MUTTMENU *menu) +{ + SETCOLOR (MT_COLOR_NORMAL); + /* clear() doesn't optimize screen redraws */ + move (0, 0); + clrtobot (); + + if (option (OPTHELP)) + { + SETCOLOR (MT_COLOR_STATUS); + mvprintw (option (OPTSTATUSONTOP) ? LINES-2 : 0, 0, "%-*.*s", COLS, COLS, menu->help); + SETCOLOR (MT_COLOR_NORMAL); + menu->offset = 1; + menu->pagelen = LINES - 3; + } + else + { + menu->offset = option (OPTSTATUSONTOP) ? 1 : 0; + menu->pagelen = LINES - 2; + } + + mutt_show_error (); + + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; +} + +void menu_redraw_index (MUTTMENU *menu) +{ + char buf[STRING]; + int i; + + for (i = menu->top; i < menu->top + menu->pagelen; i++) + { + if (i < menu->max) + { + menu->make_entry (buf, sizeof (buf), menu, i); + menu_pad_string (buf, sizeof (buf)); + + if (option (OPTARROWCURSOR)) + { + attrset (menu->color (i)); + CLEARLINE (i - menu->top + menu->offset); + + if (i == menu->current) + { + SETCOLOR (MT_COLOR_INDICATOR); + addstr ("->"); + attrset (menu->color (i)); + addch (' '); + } + else + move (i - menu->top + menu->offset, 3); + + print_enriched_string (menu->color(i), (unsigned char *) buf, 1); + SETCOLOR (MT_COLOR_NORMAL); + } + else + { + if (i == menu->current) + { + SETCOLOR (MT_COLOR_INDICATOR); + BKGDSET (MT_COLOR_INDICATOR); + } + else + attrset (menu->color (i)); + + CLEARLINE (i - menu->top + menu->offset); + print_enriched_string (menu->color(i), (unsigned char *) buf, i != menu->current); + SETCOLOR (MT_COLOR_NORMAL); + BKGDSET (MT_COLOR_NORMAL); + } + } + else + CLEARLINE (i - menu->top + menu->offset); + } + menu->redraw = 0; +} + +void menu_redraw_motion (MUTTMENU *menu) +{ + char buf[STRING]; + + move (menu->oldcurrent + menu->offset - menu->top, 0); + SETCOLOR (MT_COLOR_NORMAL); + BKGDSET (MT_COLOR_NORMAL); + + if (option (OPTARROWCURSOR)) + { + /* clear the pointer */ + attrset (menu->color (menu->oldcurrent)); + addstr (" "); + + if (menu->redraw & REDRAW_MOTION_RESYNCH) + { + clrtoeol (); + menu->make_entry (buf, sizeof (buf), menu, menu->oldcurrent); + menu_pad_string (buf, sizeof (buf)); + move (menu->oldcurrent + menu->offset - menu->top, 3); + print_enriched_string (menu->color(menu->oldcurrent), (unsigned char *) buf, 1); + SETCOLOR (MT_COLOR_NORMAL); + } + + /* now draw it in the new location */ + move (menu->current + menu->offset - menu->top, 0); + SETCOLOR (MT_COLOR_INDICATOR); + addstr ("->"); + SETCOLOR (MT_COLOR_NORMAL); + } + else + { + /* erase the current indicator */ + attrset (menu->color (menu->oldcurrent)); + clrtoeol (); + menu->make_entry (buf, sizeof (buf), menu, menu->oldcurrent); + menu_pad_string (buf, sizeof (buf)); + print_enriched_string (menu->color(menu->oldcurrent), (unsigned char *) buf, 1); + + /* now draw the new one to reflect the change */ + menu->make_entry (buf, sizeof (buf), menu, menu->current); + menu_pad_string (buf, sizeof (buf)); + SETCOLOR (MT_COLOR_INDICATOR); + BKGDSET (MT_COLOR_INDICATOR); + CLEARLINE (menu->current - menu->top + menu->offset); + print_enriched_string (menu->color(menu->current), (unsigned char *) buf, 0); + SETCOLOR (MT_COLOR_NORMAL); + BKGDSET (MT_COLOR_NORMAL); + } + menu->redraw &= REDRAW_STATUS; +} + +void menu_redraw_current (MUTTMENU *menu) +{ + char buf[STRING]; + + move (menu->current + menu->offset - menu->top, 0); + menu->make_entry (buf, sizeof (buf), menu, menu->current); + menu_pad_string (buf, sizeof (buf)); + + if (option (OPTARROWCURSOR)) + { + int attr = menu->color (menu->current); + attrset (attr); + clrtoeol (); + SETCOLOR (MT_COLOR_INDICATOR); + addstr ("->"); + attrset (attr); + addch (' '); + menu_pad_string (buf, sizeof (buf)); + print_enriched_string (menu->color(menu->current), (unsigned char *) buf, 1); + SETCOLOR (MT_COLOR_NORMAL); + } + else + { + SETCOLOR (MT_COLOR_INDICATOR); + BKGDSET (MT_COLOR_INDICATOR); + clrtoeol (); + print_enriched_string (menu->color(menu->current), (unsigned char *) buf, 0); + SETCOLOR (MT_COLOR_NORMAL); + BKGDSET (MT_COLOR_NORMAL); + } + menu->redraw &= REDRAW_STATUS; +} + +void menu_check_recenter (MUTTMENU *menu) +{ + if (menu->current >= menu->top + menu->pagelen) + { + if (option (OPTMENUSCROLL)) + menu->top = menu->current - menu->pagelen + 1; + else + menu->top += menu->pagelen * ((menu->current - menu->top) / menu->pagelen); + menu->redraw = REDRAW_INDEX; + } + else if (menu->current < menu->top) + { + if (option (OPTMENUSCROLL)) + menu->top = menu->current; + else + { + menu->top -= menu->pagelen * ((menu->top + menu->pagelen - 1 - menu->current) / menu->pagelen); + if (menu->top < 0) + menu->top = 0; + } + menu->redraw = REDRAW_INDEX; + } +} + +void menu_jump (MUTTMENU *menu) +{ + int n; + char buf[SHORT_STRING]; + + if (menu->max) + { + mutt_ungetch (LastKey); + buf[0] = 0; + if (mutt_get_field ("Jump to: ", buf, sizeof (buf), 0) == 0 && buf[0]) + { + n = atoi (buf) - 1; + if (n >= 0 && n < menu->max) + { + menu->current = n; + menu->redraw = REDRAW_MOTION; + } + else + mutt_error ("Invalid index number."); + } + } + else + mutt_error ("No entries."); +} + +void menu_next_line (MUTTMENU *menu) +{ + if (menu->max) + { + if (menu->top < menu->max - 1) + { + menu->top++; + if (menu->current < menu->top) + menu->current++; + menu->redraw = REDRAW_INDEX; + } + else + mutt_error ("You cannot scroll down farther."); + } + else + mutt_error ("No entries."); +} + +void menu_prev_line (MUTTMENU *menu) +{ + if (menu->top > 0) + { + menu->top--; + if (menu->current >= menu->top + menu->pagelen) + menu->current--; + menu->redraw = REDRAW_INDEX; + } + else + mutt_error ("You cannot scroll up farther."); +} + +void menu_next_page (MUTTMENU *menu) +{ + if (menu->max) + { + if (menu->top + menu->pagelen < menu->max) + { + menu->top += menu->pagelen; + if (menu->current < menu->top) + menu->current = menu->top; + menu->redraw = REDRAW_INDEX; + } + else if (menu->current != menu->max - 1) + { + menu->current = menu->max - 1; + menu->redraw = REDRAW_MOTION; + } + else + mutt_error ("You are on the last page."); + } + else + mutt_error ("No entries."); +} + +void menu_prev_page (MUTTMENU *menu) +{ + if (menu->top > 0) + { + if ((menu->top -= menu->pagelen) < 0) + menu->top = 0; + if (menu->current >= menu->top + menu->pagelen) + menu->current = menu->top + menu->pagelen - 1; + menu->redraw = REDRAW_INDEX; + } + else if (menu->current) + { + menu->current = 0; + menu->redraw = REDRAW_MOTION; + } + else + mutt_error ("You are on the first page."); +} + +void menu_top_page (MUTTMENU *menu) +{ + if (menu->current != menu->top) + { + menu->current = menu->top; + menu->redraw = REDRAW_MOTION; + } +} + +void menu_bottom_page (MUTTMENU *menu) +{ + if (menu->max) + { + menu->current = menu->top + menu->pagelen - 1; + if (menu->current > menu->max - 1) + menu->current = menu->max - 1; + menu->redraw = REDRAW_MOTION; + } + else + mutt_error ("No entries."); +} + +void menu_middle_page (MUTTMENU *menu) +{ + int i; + + if (menu->max) + { + i = menu->top + menu->pagelen; + if (i > menu->max - 1) + i = menu->max - 1; + menu->current = menu->top + (i - menu->top) / 2; + menu->redraw = REDRAW_MOTION; + } + else + mutt_error ("No entries."); +} + +void menu_first_entry (MUTTMENU *menu) +{ + if (menu->max) + { + menu->current = 0; + menu->redraw = REDRAW_MOTION; + } + else + mutt_error ("No entries."); +} + +void menu_last_entry (MUTTMENU *menu) +{ + if (menu->max) + { + menu->current = menu->max - 1; + menu->redraw = REDRAW_MOTION; + } + else + mutt_error ("No entries."); +} + +void menu_half_up (MUTTMENU *menu) +{ + if (menu->top > 0) + { + if ((menu->top -= menu->pagelen / 2) < 0) + menu->top = 0; + if (menu->current >= menu->top + menu->pagelen) + menu->current = menu->top + menu->pagelen - 1; + menu->redraw = REDRAW_INDEX; + } + else if (menu->current) + { + menu->current = 0; + menu->redraw = REDRAW_MOTION; + } + else + mutt_error ("First entry is shown."); +} + +void menu_half_down (MUTTMENU *menu) +{ + if (menu->max) + { + if (menu->top + menu->pagelen < menu->max) + { + menu->top += menu->pagelen / 2; + if (menu->current < menu->top) + menu->current = menu->top; + menu->redraw = REDRAW_INDEX; + } + else if (menu->current != menu->max - 1) + { + menu->current = menu->max - 1; + menu->redraw = REDRAW_INDEX; + } + else + mutt_error ("Last entry is shown."); + } + else + mutt_error ("No entries."); +} + +void menu_current_top (MUTTMENU *menu) +{ + if (menu->max) + { + menu->top = menu->current; + menu->redraw = REDRAW_INDEX; + } + else + mutt_error ("No entries."); +} + +void menu_current_middle (MUTTMENU *menu) +{ + if (menu->max) + { + menu->top = menu->current - menu->pagelen / 2; + if (menu->top < 0) + menu->top = 0; + menu->redraw = REDRAW_INDEX; + } + else + mutt_error ("No entries."); +} + +void menu_current_bottom (MUTTMENU *menu) +{ + if (menu->max) + { + menu->top = menu->current - menu->pagelen + 1; + if (menu->top < 0) + menu->top = 0; + menu->redraw = REDRAW_INDEX; + } + else + mutt_error ("No entries."); +} + +void menu_next_entry (MUTTMENU *menu) +{ + if (menu->current < menu->max - 1) + { + menu->current++; + menu->redraw = REDRAW_MOTION; + } + else + mutt_error ("You are on the last entry."); +} + +void menu_prev_entry (MUTTMENU *menu) +{ + if (menu->current) + { + menu->current--; + menu->redraw = REDRAW_MOTION; + } + else + mutt_error ("You are on the first entry."); +} + +static int default_color (int i) +{ + return ColorDefs[MT_COLOR_NORMAL]; +} + +MUTTMENU *mutt_new_menu (void) +{ + MUTTMENU *p = (MUTTMENU *) safe_calloc (1, sizeof (MUTTMENU)); + + p->offset = 1; + p->redraw = REDRAW_FULL; + p->pagelen = PAGELEN; + p->color = default_color; + return (p); +} + +void mutt_menuDestroy (MUTTMENU **p) +{ + safe_free ((void **) &(*p)->searchBuf); + safe_free ((void **) p); +} + +#define M_SEARCH_UP 1 +#define M_SEARCH_DOWN 2 + +static int menu_search (MUTTMENU *menu, int op) +{ + int r; + int searchDir = (menu->searchDir == M_SEARCH_UP) ? -1 : 1; + regex_t re; + char buf[SHORT_STRING]; + + if (op != OP_SEARCH_NEXT && op != OP_SEARCH_OPPOSITE) + { + strfcpy (buf, menu->searchBuf ? menu->searchBuf : "", sizeof (buf)); + if (mutt_get_field ("Search for: ", buf, sizeof (buf), M_CLEAR) != 0 || !buf[0]) + return (-1); + safe_free ((void **) &menu->searchBuf); + menu->searchBuf = safe_strdup (buf); + menu->searchDir = (op == OP_SEARCH) ? M_SEARCH_DOWN : M_SEARCH_UP; + } + else + { + if (!menu->searchBuf) + { + mutt_error ("No search pattern."); + return (-1); + } + + if (op == OP_SEARCH_OPPOSITE) + searchDir = -searchDir; + } + + if ((r = REGCOMP (&re, menu->searchBuf, REG_NOSUB | mutt_which_case (menu->searchBuf))) != 0) + { + regerror (r, &re, buf, sizeof (buf)); + regfree (&re); + mutt_error ("%s", buf); + return (-1); + } + + r = menu->current + searchDir; + while (r >= 0 && r < menu->max) + { + if (menu->search (menu, &re, r) == 0) + { + regfree (&re); + return r; + } + + r += searchDir; + } + + regfree (&re); + mutt_error ("Not found."); + return (-1); +} + +int mutt_menuLoop (MUTTMENU *menu) +{ + int i = OP_NULL; + char buf[STRING]; + + FOREVER + { + mutt_curs_set (0); + + /* See if all or part of the screen needs to be updated. */ + if (menu->redraw & REDRAW_FULL) + { + menu_redraw_full (menu); + /* allow the caller to do any local configuration */ + return (OP_REDRAW); + } + + menu_check_recenter (menu); + + if (menu->redraw & REDRAW_STATUS) + { + snprintf (buf, sizeof (buf), M_MODEFMT, menu->title); + SETCOLOR (MT_COLOR_STATUS); + mvprintw (option (OPTSTATUSONTOP) ? 0 : LINES - 2, 0, "%-*.*s", COLS, COLS, buf); + SETCOLOR (MT_COLOR_NORMAL); + menu->redraw &= ~REDRAW_STATUS; + } + + if (menu->redraw & REDRAW_INDEX) + menu_redraw_index (menu); + else if (menu->redraw & (REDRAW_MOTION | REDRAW_MOTION_RESYNCH)) + menu_redraw_motion (menu); + else if (menu->redraw == REDRAW_CURRENT) + menu_redraw_current (menu); + + menu->oldcurrent = menu->current; + + /* move the cursor out of the way */ + move (menu->current - menu->top + menu->offset, + (option (OPTARROWCURSOR) ? 2 : COLS-1)); + + mutt_refresh (); + i = km_dokey (menu->menu); + if (i == OP_TAG_PREFIX) + { + mvaddstr (LINES - 1, 0, "Tag-"); + i = km_dokey (menu->menu); + menu->tagprefix = 1; + CLEARLINE (LINES - 1); + } + else if (menu->tagged && option (OPTAUTOTAG)) + menu->tagprefix = 1; + else + menu->tagprefix = 0; + + mutt_curs_set (1); + +#if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM) + if (Signals & S_SIGWINCH) + { + mutt_resize_screen (); + menu->redraw = REDRAW_FULL; + Signals &= ~S_SIGWINCH; + } +#endif + + if (i == -1) + continue; + + mutt_clear_error (); + + switch (i) + { + case OP_NEXT_ENTRY: + menu_next_entry (menu); + break; + case OP_PREV_ENTRY: + menu_prev_entry (menu); + break; + case OP_HALF_DOWN: + menu_half_down (menu); + break; + case OP_HALF_UP: + menu_half_up (menu); + break; + case OP_NEXT_PAGE: + menu_next_page (menu); + break; + case OP_PREV_PAGE: + menu_prev_page (menu); + break; + case OP_NEXT_LINE: + menu_next_line (menu); + break; + case OP_PREV_LINE: + menu_prev_line (menu); + break; + case OP_FIRST_ENTRY: + menu_first_entry (menu); + break; + case OP_LAST_ENTRY: + menu_last_entry (menu); + break; + case OP_TOP_PAGE: + menu_top_page (menu); + break; + case OP_MIDDLE_PAGE: + menu_middle_page (menu); + break; + case OP_BOTTOM_PAGE: + menu_bottom_page (menu); + break; + case OP_CURRENT_TOP: + menu_current_top (menu); + break; + case OP_CURRENT_MIDDLE: + menu_current_middle (menu); + break; + case OP_CURRENT_BOTTOM: + menu_current_bottom (menu); + break; + case OP_SEARCH: + case OP_SEARCH_REVERSE: + case OP_SEARCH_NEXT: + case OP_SEARCH_OPPOSITE: + if (menu->search) + { + menu->oldcurrent = menu->current; + if ((menu->current = menu_search (menu, i)) != -1) + menu->redraw = REDRAW_MOTION; + else + menu->current = menu->oldcurrent; + } + else + mutt_error ("Search is not implemented for this menu."); + break; + + case OP_JUMP: + menu_jump (menu); + break; + + case OP_ENTER_COMMAND: + mutt_enter_command (); + if (option (OPTFORCEREDRAWINDEX)) + { + menu->redraw = REDRAW_FULL; + unset_option (OPTFORCEREDRAWINDEX); + unset_option (OPTFORCEREDRAWPAGER); + } + break; + + case OP_TAG: + if (menu->tag) + { + if (menu->max) + { + if (menu->tag (menu, menu->current)) + menu->tagged++; + else + menu->tagged--; + if (option (OPTRESOLVE) && menu->current < menu->max - 1) + { + menu->current++; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + else + menu->redraw = REDRAW_CURRENT; + } + else + mutt_error ("No entries."); + } + else + mutt_error ("Tagging is not supported."); + break; + + case OP_SHELL_ESCAPE: + mutt_shell_escape (); + MAYBE_REDRAW (menu->redraw); + break; + + case OP_REDRAW: + clearok (stdscr, TRUE); + menu->redraw = REDRAW_FULL; + break; + + case OP_HELP: + mutt_help (menu->menu); + menu->redraw = REDRAW_FULL; + break; + + case OP_NULL: + km_error_key (menu->menu); + break; + + default: + return (i); + } + } + /* not reached */ +} diff --git a/mh.c b/mh.c new file mode 100644 index 00000000..b4f088ad --- /dev/null +++ b/mh.c @@ -0,0 +1,765 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * This file contains routines specific to MH and ``maildir'' style mailboxes + */ + +#include "mutt.h" +#include "mx.h" +#include "mailbox.h" +#include "copy.h" +#include "buffy.h" + +#include <sys/stat.h> +#include <dirent.h> +#include <limits.h> +#include <unistd.h> +#include <ctype.h> +#include <errno.h> +#include <string.h> + +void mh_parse_message (CONTEXT *ctx, + const char *subdir, + const char *fname, + int *count, + int isOld) +{ + char path[_POSIX_PATH_MAX]; + char *p; + FILE *f; + HEADER *h; + struct stat st; + + if (subdir) + snprintf (path, sizeof (path), "%s/%s/%s", ctx->path, subdir, fname); + else + snprintf (path, sizeof (path), "%s/%s", ctx->path, fname); + + if ((f = fopen (path, "r")) != NULL) + { + (*count)++; + + if (!ctx->quiet && ReadInc && ((*count % ReadInc) == 0 || *count == 1)) + mutt_message ("Reading %s... %d", ctx->path, *count); + + if (ctx->msgcount == ctx->hdrmax) + mx_alloc_memory (ctx); + + h = ctx->hdrs[ctx->msgcount] = mutt_new_header (); + + if (subdir) + { + snprintf (path, sizeof (path), "%s/%s", subdir, fname); + h->path = safe_strdup (path); + } + else + h->path = safe_strdup (fname); + + h->env = mutt_read_rfc822_header (f, h); + + fstat (fileno (f), &st); + fclose (f); + + if (!h->received) + h->received = h->date_sent; + + if (h->content->length <= 0) + h->content->length = st.st_size - h->content->offset; + + /* index doesn't have a whole lot of meaning for MH and maildir mailboxes, + * but this is used to find the current message after a resort in + * the `index' event loop. + */ + h->index = ctx->msgcount; + + if (ctx->magic == M_MAILDIR) + { + /* maildir stores its flags in the filename, so ignore the flags in + * the header of the message + */ + + h->old = isOld; + + if ((p = strchr (h->path, ':')) != NULL && strncmp (p + 1, "2,", 2) == 0) + { + p += 3; + while (*p) + { + switch (*p) + { + case 'F': + + h->flagged = 1; + break; + + case 'S': /* seen */ + + h->read = 1; + break; + + case 'R': /* replied */ + + h->replied = 1; + break; + } + p++; + } + } + } + + /* set flags and update context info */ + mx_update_context (ctx); + } +} + +/* + * Mark all the mails in ctx read. + */ +void tag_all_read (CONTEXT * ctx) +{ + int i; + + ctx->new = 0; + if (ctx->hdrs == NULL) + return; + for (i = 0; i < ctx->msgcount; i++) + { + ctx->hdrs[i]->read = 1; + ctx->hdrs[i]->old = 1; + } +} + +/* + * Mark one mail as unread + */ +int tag_unread (CONTEXT * ctx, char *name) +{ + int i; + + if (ctx->hdrs == NULL) + return (1); + for (i = ctx->msgcount - 1; i >= 0; i--) + if (!strcmp (name, ctx->hdrs[i]->path)) + { + ctx->hdrs[i]->read = 0; + ctx->hdrs[i]->old = 0; + return (1); + } + return (0); +} + +/* Parse a .mh_sequences file. Only supports "unseen:" field. + */ + +#define SKIP_BLANK(p) \ + while ((*(p) == ' ') || (*(p) == '\t')) p++; + +#define SKIP_EOL(p) \ +{ \ + while ((*p != '\0') && (*(p) != '\n') && (*(p) != '\r')) p++; \ + while ((*(p) == '\n') || (*(p) == '\r')) p++; \ +} + +/* Parses and returns the next unsigned number in the string <cur>, + advancing the pointer to the character after the number ended. + */ +static unsigned parse_number (const char **cur) +{ + unsigned i = 0; + for (; (**cur >= '0') && (**cur <= '9'); (*cur)++) + { + i *= 10; + i += **cur - '0'; + } + return i; +} + +/* if context is NULL, it uses <folder> as the mailbox path, otherwise, + uses ctx->path. If context is not null, tags all the unread messages + and sets the position correctly. + returns the number of unread messages in the mailbox. + */ +int mh_parse_sequences (CONTEXT * ctx, const char *folder) +{ + FILE *mh_sequences; + struct stat sb; + char *content; + const char *cur; + int len; + int base, last; + char buf[_POSIX_PATH_MAX]; + char filename[100]; + int unread = 0; + static HASH *cache = 0; + int *h; + + if (!cache) { + cache = hash_create (100); /* If this ain't enough, read less mail! */ + if (!cache) + { + mutt_error ("mh_parse_sequences: Unable to allocate hash table!\n"); + return -1; + } + } + + if (ctx) + folder = ctx->path; + + snprintf (buf, sizeof (buf), "%s/.mh_sequences", folder); + if (stat (buf, &sb) != 0) + return (-1); + + if (ctx) + tag_all_read (ctx); + + if (!ctx && sb.st_mtime < BuffyDoneTime && (h = hash_find (cache, folder))) /* woo! we win! */ + return *h; + + /* + * Parse the .mh_sequence to find the unread ones. + */ + + content = safe_malloc (sb.st_size + 100); + if (content == NULL) + { + mutt_perror ("malloc"); + return (-1); + } + mh_sequences = fopen (buf, "r"); + if (mh_sequences == NULL) + { + mutt_message ("Cannot open %s", buf); + safe_free ((void **) &content); + return (-1); + } + + len = fread (content, 1, sb.st_size, mh_sequences); + content[len] = '\0'; + + fclose (mh_sequences); + + cur = content; + SKIP_BLANK (cur); + while (*cur != '\0') + { + if (!strncmp (cur, "unseen", 6)) + { + cur += 6; + SKIP_BLANK (cur); + if (*cur == ':') + { + cur++; + SKIP_BLANK (cur); + } + while ((*cur >= '0') && (*cur <= '9')) + { + base = parse_number (&cur); + SKIP_BLANK (cur); + if (*cur == '-') + { + cur++; + SKIP_BLANK (cur); + last = parse_number (&cur); + } + else + last = base; + if (ctx) + for (; base <= last; base++) + { + sprintf (filename, "%d", base); + if (tag_unread (ctx, filename)) + unread++; + } + else + unread += last - base + 1; + SKIP_BLANK (cur); + } + } + SKIP_EOL (cur); + SKIP_BLANK (cur); + } + if (unread != 0) + { + mutt_message ("Folder %s : %d unread", folder, unread); + } + if (ctx) + ctx->new = unread; + + safe_free ((void **) &content); + + /* Cache the data so we only have to do 'stat's in the future */ + if (!(h = hash_find (cache, folder))) + { + h = safe_malloc (sizeof (int)); + if (!h) /* dear god, I hope not... */ + return -1; + hash_insert (cache, folder, h, 1); /* every other time we just adjust the pointer */ + } + *h = unread; + + return unread; +} + +/* Ignore the garbage files. A valid MH message consists of only + * digits. Deleted message get moved to a filename with a comma before + * it. + */ +int mh_valid_message (const char *s) +{ + for (; *s; s++) + { + if (!isdigit (*s)) + return 0; + } + return 1; +} + +/* Read a MH/maildir style mailbox. + * + * args: + * ctx [IN/OUT] context for this mailbox + * subdir [IN] NULL for MH mailboxes, otherwise the subdir of the + * maildir mailbox to read from + */ +int mh_read_dir (CONTEXT *ctx, const char *subdir) +{ + DIR *dirp; + struct dirent *de; + char buf[_POSIX_PATH_MAX]; + int isOld = 0; + int count = 0; + struct stat st; + int has_mh_sequences = 0; + + if (subdir) + { + snprintf (buf, sizeof (buf), "%s/%s", ctx->path, subdir); + isOld = (strcmp ("cur", subdir) == 0) && option (OPTMARKOLD); + } + else + strfcpy (buf, ctx->path, sizeof (buf)); + + if (stat (buf, &st) == -1) + return (-1); + + if ((dirp = opendir (buf)) == NULL) + return (-1); + + if (!subdir || (subdir && strcmp (subdir, "new") == 0)) + ctx->mtime = st.st_mtime; + + while ((de = readdir (dirp)) != NULL) + { + if (ctx->magic == M_MH) + { + if (!mh_valid_message (de->d_name)) + { + if (!strcmp (de->d_name, ".mh_sequences")) + has_mh_sequences++; + continue; + } + } + else if (*de->d_name == '.') + { + /* Skip files that begin with a dot. This currently isn't documented + * anywhere, but it was a suggestion from the author of QMail on the + * mailing list. + */ + continue; + } + + mh_parse_message (ctx, subdir, de->d_name, &count, isOld); + } + { + int i; +#define this_body ctx->hdrs[i]->content + ctx->size = 0; + for (i = 0; i < ctx->msgcount; i++) + ctx->size += this_body->length + this_body->offset - + this_body->hdr_offset; +#undef this_body + } + + /* + * MH style . + */ + if (has_mh_sequences) + { + mh_parse_sequences (ctx, NULL); + } + + closedir (dirp); + return 0; +} + +/* read a maildir style mailbox */ +int maildir_read_dir (CONTEXT * ctx) +{ + /* maildir looks sort of like MH, except that there are two subdirectories + * of the main folder path from which to read messages + */ + if (mh_read_dir (ctx, "new") == -1 || mh_read_dir (ctx, "cur") == -1) + return (-1); + + return 0; +} + +/* Open a new (unique) message in a maildir mailbox. In order to avoid the + * need for locks, the filename is generated in such a way that it is unique, + * even over NFS: <time>.<pid>_<count>.<hostname>. The _<count> part is + * optional, but required for programs like Mutt which do not change PID for + * each message that is created in the mailbox (otherwise you could end up + * creating only a single file per second). + */ +void maildir_create_filename (const char *path, HEADER *hdr, char *msg, char *full) +{ + char subdir[_POSIX_PATH_MAX]; + char suffix[16]; + struct stat sb; + + /* the maildir format stores the status flags in the filename */ + suffix[0] = 0; + + if (hdr && (hdr->flagged || hdr->replied || hdr->read)) + { + sprintf (suffix, ":2,%s%s%s", + hdr->flagged ? "F" : "", + hdr->replied ? "R" : "", + hdr->read ? "S" : ""); + } + + if (hdr && (hdr->read || hdr->old)) + strfcpy (subdir, "cur", sizeof (subdir)); + else + strfcpy (subdir, "new", sizeof (subdir)); + + FOREVER + { + snprintf (msg, _POSIX_PATH_MAX, "%s/%ld.%d_%d.%s%s", + subdir, time (NULL), getpid (), Counter++, Hostname, suffix); + snprintf (full, _POSIX_PATH_MAX, "%s/%s", path, msg); + if (stat (full, &sb) == -1 && errno == ENOENT) return; + } +} + +/* save changes to a message to disk */ +static int mh_sync_message (CONTEXT *ctx, int msgno) +{ + HEADER *h = ctx->hdrs[msgno]; + FILE *f; + FILE *d; + int rc = -1; + char oldpath[_POSIX_PATH_MAX]; + char newpath[_POSIX_PATH_MAX]; + + snprintf (oldpath, sizeof (oldpath), "%s/%s", ctx->path, h->path); + + mutt_mktemp (newpath); + if ((f = safe_fopen (newpath, "w")) == NULL) + { + dprint (1, (debugfile, "mh_sync_message: %s: %s (errno %d).\n", + newpath, strerror (errno), errno)); + return (-1); + } + + rc = mutt_copy_message (f, ctx, h, M_CM_UPDATE, CH_UPDATE | CH_UPDATE_LEN); + + if (rc == 0) + { + /* replace the original version of the message with the new one */ + if ((f = freopen (newpath, "r", f)) != NULL) + { + unlink (newpath); + if ((d = mx_open_file_lock (oldpath, "w")) != NULL) + { + mutt_copy_stream (f, d); + mx_unlock_file (oldpath, fileno (d)); + fclose (d); + } + else + { + fclose (f); + return (-1); + } + fclose (f); + } + else + { + mutt_perror (newpath); + return (-1); + } + + /* + * if the status of this message changed, the offsets for the body parts + * will be wrong, so free up the memory. This is ok since it will get + * parsed again the next time the user tries to view it. + */ + mutt_free_body (&h->content->parts); + } + else + { + fclose (f); + unlink (newpath); + } + + return (rc); +} + +static int maildir_sync_message (CONTEXT *ctx, int msgno) +{ + HEADER *h = ctx->hdrs[msgno]; + char newpath[_POSIX_PATH_MAX]; + char partpath[_POSIX_PATH_MAX]; + char fullpath[_POSIX_PATH_MAX]; + char oldpath[_POSIX_PATH_MAX]; + char *p; + + if ((p = strchr (h->path, '/')) == NULL) + { + dprint (1, (debugfile, "maildir_sync_message: %s: unable to find subdir!\n", + h->path)); + return (-1); + } + p++; + strfcpy (newpath, p, sizeof (newpath)); + + /* kill the previous flags */ + if ((p = strchr (newpath, ':')) != NULL) *p = 0; + + if (h->replied || h->read || h->flagged) + { + strcat (newpath, ":2,"); + if (h->flagged) strcat (newpath, "F"); + if (h->replied) strcat (newpath, "R"); + if (h->read) strcat (newpath, "S"); + } + + /* decide which subdir this message belongs in */ + strfcpy (partpath, (h->read || h->old) ? "cur" : "new", sizeof (partpath)); + strcat (partpath, "/"); + strcat (partpath, newpath); + snprintf (fullpath, sizeof (fullpath), "%s/%s", ctx->path, partpath); + snprintf (oldpath, sizeof (oldpath), "%s/%s", ctx->path, h->path); + + if (strcmp (fullpath, oldpath) == 0 && !h->attach_del) + { + /* message hasn't really changed */ + return 0; + } + + /* + * At this point, this should work to actually delete the attachment + * without breaking anything else (mh_sync_message doesn't appear to + * make any mailbox assumptions except the one file per message, which + * is compatible with maildir + */ + if (h->attach_del) + { + char tmppath[_POSIX_PATH_MAX]; + char ftpath[_POSIX_PATH_MAX]; + + strfcpy (tmppath, "tmp/", sizeof (tmppath)); + strcat (tmppath, newpath); + + snprintf (ftpath, sizeof (ftpath), "%s/%s", ctx->path, tmppath); + dprint (1, (debugfile, "maildir_sync_message: deleting attachment!\n")); + + if (rename (oldpath, ftpath) != 0) + { + mutt_perror ("rename"); + return (-1); + } + safe_free ((void **)&h->path); + h->path = safe_strdup (tmppath); + dprint (1, (debugfile, "maildir_sync_message: deleting attachment2!\n")); + mh_sync_message (ctx, msgno); + dprint (1, (debugfile, "maildir_sync_message: deleting attachment3!\n")); + if (rename (ftpath, fullpath) != 0) + { + mutt_perror ("rename"); + return (-1); + } + } + else + { + if (rename (oldpath, fullpath) != 0) + { + mutt_perror ("rename"); + return (-1); + } + } + safe_free ((void **) &h->path); + h->path = safe_strdup (partpath); + return (0); +} + +int mh_sync_mailbox (CONTEXT * ctx) +{ + char path[_POSIX_PATH_MAX], tmp[_POSIX_PATH_MAX]; + int i, rc = 0; + FILE *mh_sequences; + + + for (i = 0; i < ctx->msgcount; i++) + { + if (ctx->hdrs[i]->deleted) + { + snprintf (path, sizeof (path), "%s/%s", ctx->path, ctx->hdrs[i]->path); + if (ctx->magic == M_MAILDIR) + unlink (path); + else + { + /* MH just moves files out of the way when you delete them */ + snprintf (tmp, sizeof (tmp), "%s/,%s", ctx->path, ctx->hdrs[i]->path); + unlink (tmp); + rename (path, tmp); + } + } + else if (ctx->hdrs[i]->changed || ctx->hdrs[i]->attach_del) + { + if (ctx->magic == M_MAILDIR) + maildir_sync_message (ctx, i); + else + { + /* FOO - seems ok to ignore errors, but might want to warn... */ + mh_sync_message (ctx, i); + } + } + } + + snprintf (path, sizeof (path), "%s/%s", ctx->path, ".mh_sequences"); + mh_sequences = fopen (path, "w"); + if (mh_sequences == NULL) + { + mutt_message ("fopen %s failed", path); + } + else + { + fprintf (mh_sequences, "unseen: "); + for (i = 0; i < ctx->msgcount; i++) + if ((ctx->hdrs[i]->read == 0) && !(ctx->hdrs[i]->deleted)) + fprintf (mh_sequences, "%s ", ctx->hdrs[i]->path); + fprintf (mh_sequences, "\n"); + fclose (mh_sequences); + } + + return (rc); +} + +/* check for new mail */ +int mh_check_mailbox (CONTEXT * ctx, int *index_hint) +{ + DIR *dirp; + struct dirent *de; + char buf[_POSIX_PATH_MAX]; + struct stat st; + LIST *lst = NULL, *tmp = NULL, *prev; + int i, lng = 0; + int count = 0; + + /* MH users might not like the behavior of this function because it could + * take awhile if there are many messages in the mailbox. + */ + if (!option (OPTCHECKNEW)) + return (0); /* disabled */ + + if (ctx->magic == M_MH) + strfcpy (buf, ctx->path, sizeof (buf)); + else + snprintf (buf, sizeof (buf), "%s/new", ctx->path); + + if (stat (buf, &st) == -1) + return (-1); + + if (st.st_mtime == ctx->mtime) + return (0); /* unchanged */ + + if ((dirp = opendir (buf)) == NULL) + return (-1); + + while ((de = readdir (dirp)) != NULL) + { + if (ctx->magic == M_MH) + { + if (!mh_valid_message (de->d_name)) + continue; + } + else /* maildir */ + { + if (*de->d_name == '.') + continue; + } + + if (tmp) + { + tmp->next = mutt_new_list (); + tmp = tmp->next; + } + else + lst = tmp = mutt_new_list (); + tmp->data = safe_strdup (de->d_name); + } + closedir (dirp); + + ctx->mtime = st.st_mtime; /* save the time we scanned at */ + + if (!lst) + return 0; + + /* if maildir, skip the leading "new/" */ + lng = (ctx->magic == M_MAILDIR) ? 4 : 0; + + for (i = 0; i < ctx->msgcount; i++) + { + if (ctx->magic == M_MAILDIR && ctx->hdrs[i]->old) + continue; /* only look at NEW messages */ + + prev = NULL; + tmp = lst; + while (tmp) + { + if (strcmp (tmp->data, ctx->hdrs[i]->path + lng) == 0) + { + if (prev) + prev->next = tmp->next; + else + lst = lst->next; + safe_free ((void **) &tmp->data); + safe_free ((void **) &tmp); + break; + } + else + { + prev = tmp; + tmp = tmp->next; + } + } + } + + for (tmp = lst; tmp; tmp = lst) + { + mh_parse_message (ctx, (ctx->magic == M_MH ? NULL : "new"), tmp->data, &count, 0); + + lst = tmp->next; + safe_free ((void **) &tmp->data); + safe_free ((void **) &tmp); + } + + return (1); +} diff --git a/mime.h b/mime.h new file mode 100644 index 00000000..662bae1c --- /dev/null +++ b/mime.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Content-Type */ +enum +{ + TYPEOTHER, + TYPEAUDIO, + TYPEAPPLICATION, + TYPEIMAGE, + TYPEMESSAGE, + TYPEMULTIPART, + TYPETEXT, + TYPEVIDEO +}; + +/* Content-Transfer-Encoding */ +enum +{ + ENCOTHER, + ENC7BIT, + ENC8BIT, + ENCQUOTEDPRINTABLE, + ENCBASE64, + ENCBINARY +}; + +/* Content-Disposition values */ +enum +{ + DISPINLINE, + DISPATTACH, + DISPFORMDATA +}; + +/* MIME encoding/decoding global vars */ +extern int Index_hex[]; +extern int Index_64[]; +extern char Base64_chars[]; + +#define hexval(c) Index_hex[(int)(c)] +#define base64val(c) Index_64[(int)(c)] + +#define is_multipart(x) \ + ((x)->type == TYPEMULTIPART \ + || ((x)->type == TYPEMESSAGE && (!strcasecmp((x)->subtype, "rfc822") \ + || !strcasecmp((x)->subtype, "news")))) + +extern const char *BodyTypes[]; +extern const char *BodyEncodings[]; + +#define TYPE(X) BodyTypes[(X)] +#define ENCODING(X) BodyEncodings[(X)] diff --git a/mime.types b/mime.types new file mode 100644 index 00000000..1ce7cab4 --- /dev/null +++ b/mime.types @@ -0,0 +1,80 @@ +# +# sample mime.types +# +application/andrew-inset ez +application/excel xls +application/octet-stream bin +application/oda oda +application/pdf pdf +application/pgp pgp +application/postscript ps PS eps +application/rtf rtf +application/x-arj-compressed arj +application/x-bcpio bcpio +application/x-chess-pgn pgn +application/x-cpio cpio +application/x-csh csh +application/x-debian-package deb +application/x-msdos-program com exe bat +application/x-dvi dvi +application/x-gtar gtar +application/x-gunzip gz +application/x-hdf hdf +application/x-latex latex +application/x-mif mif +application/x-netcdf cdf nc +application/x-perl pl pm +application/x-rar-compressed rar +application/x-sh sh +application/x-shar shar +application/x-sv4cpio sv4cpio +application/x-sv4crc sv4crc +application/x-tar tar +application/x-tar-gz tgz tar.gz +application/x-tcl tcl +application/x-tex tex +application/x-texinfo texi texinfo +application/x-troff t tr roff +application/x-troff-man man +application/x-troff-me me +application/x-troff-ms ms +application/x-ustar ustar +application/x-wais-source src +application/x-zip-compressed zip + +audio/basic snd +audio/midi mid midi +audio/ulaw au +audio/x-aiff aif aifc aiff +audio/x-wav wav + +image/gif gif +image/ief ief +image/jpeg jpe jpeg jpg +image/png png +image/tiff tif tiff +image/x-cmu-raster ras +image/x-portable-anymap pnm +image/x-portable-bitmap pbm +image/x-portable-graymap pgm +image/x-portable-pixmap ppm +image/x-rgb rgb +image/x-xbitmap xbm +image/x-xpixmap xpm +image/x-xwindowdump xwd + +text/html html htm +text/plain asc txt +text/richtext rtx +text/tab-separated-values tsv +text/x-setext etx + +video/dl dl +video/fli fli +video/gl gl +video/mpeg mp2 mpe mpeg mpg +video/quicktime mov qt +video/x-msvideo avi +video/x-sgi-movie movie + +x-world/x-vrml vrm vrml wrl diff --git a/mkinstalldirs b/mkinstalldirs new file mode 100755 index 00000000..d0fd194f --- /dev/null +++ b/mkinstalldirs @@ -0,0 +1,40 @@ +#! /bin/sh +# mkinstalldirs --- make directory hierarchy +# Author: Noah Friedman <friedman@prep.ai.mit.edu> +# Created: 1993-05-16 +# Public domain + +# $Id$ + +errstatus=0 + +for file +do + set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'` + shift + + pathcomp= + for d + do + pathcomp="$pathcomp$d" + case "$pathcomp" in + -* ) pathcomp=./$pathcomp ;; + esac + + if test ! -d "$pathcomp"; then + echo "mkdir $pathcomp" 1>&2 + + mkdir "$pathcomp" || lasterr=$? + + if test ! -d "$pathcomp"; then + errstatus=$lasterr + fi + fi + + pathcomp="$pathcomp/" + done +done + +exit $errstatus + +# mkinstalldirs ends here diff --git a/mutt.h b/mutt.h new file mode 100644 index 00000000..34b8bd55 --- /dev/null +++ b/mutt.h @@ -0,0 +1,593 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "config.h" + +#include <stdio.h> +#include <sys/types.h> +#include <time.h> +#include <limits.h> +#include <stdarg.h> + +#include "rfc822.h" +#include "hash.h" + +/* nifty trick I stole from ELM 2.5alpha. */ +#ifdef MAIN_C +#define WHERE +#define INITVAL(x) = x +#else +#define WHERE extern +#define INITVAL(x) +#endif + +#define TRUE 1 +#define FALSE 0 + +#define HUGE_STRING 5120 +#define LONG_STRING 1024 +#define STRING 256 +#define SHORT_STRING 128 + +/* flags for mutt_copy_header() */ +#define CH_UPDATE 1 /* update the status and x-status fields? */ +#define CH_WEED (1<<1) /* weed the headers? */ +#define CH_DECODE (1<<2) /* do RFC1522 decoding? */ +#define CH_XMIT (1<<3) /* transmitting this message? */ +#define CH_FROM (1<<4) /* retain the "From " message separator? */ +#define CH_PREFIX (1<<5) /* use Prefix string? */ +#define CH_NOSTATUS (1<<6) /* supress the status and x-status fields */ +#define CH_REORDER (1<<7) /* Re-order output of headers */ +#define CH_NONEWLINE (1<<8) /* don't output terminating newline */ +#define CH_MIME (1<<9) /* ignore MIME fields */ +#define CH_UPDATE_LEN (1<<10) /* update Lines: and Content-Length: */ +#define CH_TXTPLAIN (1<<11) /* generate text/plain MIME headers */ + +/* flags for mutt_enter_string() */ +#define M_ALIAS 1 /* do alias "completion" by calling up the alias-menu */ +#define M_FILE (1<<1) /* do file completion */ +#define M_EFILE (1<<2) /* do file completion, plus incoming folders */ +#define M_CMD (1<<3) /* do completion on previous word */ +#define M_PASS (1<<4) /* password mode (no echo) */ +#define M_CLEAR (1<<5) /* clear input if printable character is pressed */ + +/* flags for mutt_get_token() */ +#define M_TOKEN_EQUAL 1 /* treat '=' as a special */ +#define M_TOKEN_CONDENSE (1<<1) /* ^(char) to control chars (macros) */ +#define M_TOKEN_SPACE (1<<2) /* don't treat whitespace as a term */ +#define M_TOKEN_QUOTE (1<<3) /* don't interpret quotes */ +#define M_TOKEN_PATTERN (1<<4) /* !)|~ are terms (for patterns) */ +#define M_TOKEN_COMMENT (1<<5) /* don't reap comments */ +#define M_TOKEN_SEMICOLON (1<<6) /* don't treat ; as special */ + +typedef struct +{ + char *data; /* pointer to data */ + char *dptr; /* current read/write position */ + size_t dsize; /* length of data */ + int destroy; /* destroy `data' when done? */ +} BUFFER; + +/* flags for _mutt_system() */ +#define M_DETACH_PROCESS 1 /* detach subprocess from group */ + +/* flags for mutt_FormatString() */ +typedef enum +{ + M_FORMAT_FORCESUBJ = (1<<0), /* print the subject even if unchanged */ + M_FORMAT_TREE = (1<<1), /* draw the thread tree */ + M_FORMAT_MAKEPRINT = (1<<2), /* make sure that all chars are printable */ + M_FORMAT_OPTIONAL = (1<<3) +} format_flag; + +/* types for mutt_add_hook() */ +#define M_FOLDERHOOK 1 +#define M_MBOXHOOK (1<<1) +#define M_SENDHOOK (1<<2) +#define M_FCCHOOK (1<<3) +#define M_SAVEHOOK (1<<4) + +/* tree characters for linearize_tree and print_enriched_string */ +#define M_TREE_LLCORNER 1 +#define M_TREE_ULCORNER 2 +#define M_TREE_LTEE 3 +#define M_TREE_HLINE 4 +#define M_TREE_VLINE 5 +#define M_TREE_SPACE 6 +#define M_TREE_RARROW 7 +#define M_TREE_STAR 8 +#define M_TREE_HIDDEN 9 +#define M_TREE_MAX 10 + +enum +{ + /* modes for mutt_view_attachment() */ + M_REGULAR = 1, + M_MAILCAP, + M_AS_TEXT, + + /* action codes used by mutt_set_flag() and mutt_pattern_function() */ + M_ALL, + M_NONE, + M_NEW, + M_OLD, + M_REPLIED, + M_READ, + M_UNREAD, + M_DELETE, + M_UNDELETE, + M_DELETED, + M_FLAG, + M_TAG, + M_UNTAG, + M_LIMIT, + M_EXPIRED, + M_SUPERSEDED, + + /* actions for mutt_pattern_comp/mutt_pattern_exec */ + M_AND, + M_OR, + M_TO, + M_CC, + M_SUBJECT, + M_FROM, + M_DATE, + M_DATE_RECEIVED, + M_ID, + M_BODY, + M_HEADER, + M_WHOLE_MSG, + M_SENDER, + M_MESSAGE, + M_SCORE, + M_SIZE, + M_REFERENCE, + M_RECIPIENT, + M_LIST, + M_PERSONAL_RECIP, + M_PERSONAL_FROM, + M_ADDRESS, + + /* Options for Mailcap lookup */ + M_EDIT, + M_COMPOSE, + M_PRINT, + M_AUTOVIEW, + + /* Options for mutt_save_attachment */ + M_SAVE_APPEND +}; + +/* possible arguments to set_quadoption() */ +enum +{ + M_NO, + M_YES, + M_ASKNO, + M_ASKYES +}; + +/* quad-option vars */ +enum +{ + +#ifdef _PGPPATH + OPT_VERIFYSIG, /* verify PGP signatures */ +#endif + + OPT_USEMAILCAP, + OPT_PRINT, + OPT_INCLUDE, + OPT_DELETE, + OPT_MIMEFWD, + OPT_MOVE, + OPT_COPY, + OPT_POSTPONE, + OPT_QUIT, + OPT_REPLYTO, + OPT_ABORT, + OPT_RECALL, + OPT_SUBJECT +}; + +/* flags to ci_send_message() */ +#define SENDREPLY (1<<0) +#define SENDGROUPREPLY (1<<1) +#define SENDLISTREPLY (1<<2) +#define SENDFORWARD (1<<3) +#define SENDPOSTPONED (1<<4) +#define SENDBATCH (1<<5) +#define SENDMAILX (1<<6) +#define SENDKEY (1<<7) + +/* boolean vars */ +enum +{ + OPTPROMPTAFTER, + OPTSTATUSONTOP, + OPTALLOW8BIT, + OPTASCIICHARS, + OPTMETOO, + OPTEDITHDRS, + OPTARROWCURSOR, + OPTASKCC, + OPTHEADER, + OPTREVALIAS, + OPTREVNAME, + OPTFORCENAME, + OPTSAVEEMPTY, + OPTPAGERSTOP, + OPTSIGDASHES, + OPTASKBCC, + OPTAUTOEDIT, +#ifdef _PGPPATH + OPTPGPAUTOSIGN, + OPTPGPAUTOENCRYPT, + OPTPGPLONGIDS, + OPTPGPREPLYENCRYPT, + OPTPGPREPLYSIGN, + OPTPGPENCRYPTSELF, + OPTPGPSTRICTENC, +#endif + OPTMARKOLD, + OPTCONFIRMCREATE, + OPTCONFIRMAPPEND, + OPTPOPDELETE, + OPTSAVENAME, + OPTTHOROUGHSRC, + OPTTILDE, + OPTMARKERS, + OPTFCCATTACH, + OPTPIPESPLIT, + OPTPIPEDECODE, + OPTREADONLY, + OPTRESOLVE, + OPTSTRICTTHREADS, + OPTAUTOTAG, + OPTBEEP, + OPTHELP, + OPTHDRS, + OPTWEED, + OPTWRAP, + OPTCHECKNEW, + OPTFASTREPLY, + OPTWAITKEY, + OPTWRAPSEARCH, + OPTIGNORELISTREPLYTO, + OPTSAVEADDRESS, + OPTSUSPEND, + OPTSORTRE, + OPTUSEDOMAIN, + OPTUSEFROM, + OPTUSE8BITMIME, + OPTFORWDECODE, + OPTMIMEFORWDECODE, + OPTFORWQUOTE, + OPTBEEPNEW, + OPTFOLLOWUPTO, + OPTMENUSCROLL, /* scroll menu instead of implicit next-page */ + OPTMETAKEY, /* interpret ALT-x as ESC-x */ + OPTAUXSORT, /* (pseudo) using auxillary sort function */ + OPTFORCEREFRESH, /* (pseudo) refresh even during macros */ + OPTLOCALES, /* (pseudo) set if user has valid locale definition */ + OPTNOCURSES, /* (pseudo) when sending in batch mode */ + OPTNEEDREDRAW, /* (pseudo) to notify caller of a submenu */ + OPTSEARCHREVERSE, /* (pseudo) used by ci_search_command */ + OPTMSGERR, /* (pseudo) used by mutt_error/mutt_message */ + OPTSEARCHINVALID, /* (pseudo) used to invalidate the search pat */ + OPTSIGNALSBLOCKED, /* (pseudo) using by mutt_block_signals () */ + OPTNEEDRESORT, /* (pseudo) used to force a re-sort */ + OPTVIEWATTACH, /* (pseudo) signals that we are viewing attachments */ + OPTFORCEREDRAWINDEX, /* (pseudo) used to force a redraw in the main index */ + OPTFORCEREDRAWPAGER, /* (pseudo) used to force a redraw in the pager */ + OPTSORTSUBTHREADS, /* (pseudo) used when $sort_aux changes */ + OPTNEEDRESCORE, /* (pseudo) set when the `score' command is used */ + +#ifdef _PGPPATH + OPTPGPCHECKTRUST, /* (pseudo) used by pgp_select_key () */ + OPTDONTHANDLEPGPKEYS, /* (pseudo) used to extract PGP keys */ +#endif + + + + + + OPTMAX +}; + +#define mutt_bit_alloc(n) calloc ((n + 7) / 8, sizeof (char)) +#define mutt_bit_set(v,n) v[n/8] |= (1 << (n % 8)) +#define mutt_bit_unset(v,n) v[n/8] &= ~(1 << (n % 8)) +#define mutt_bit_toggle(v,n) v[n/8] ^= (1 << (n % 8)) +#define mutt_bit_isset(v,n) (v[n/8] & (1 << (n % 8))) + +#define set_option(x) mutt_bit_set(Options,x) +#define unset_option(x) mutt_bit_unset(Options,x) +#define toggle_option(x) mutt_bit_toggle(Options,x) +#define option(x) mutt_bit_isset(Options,x) + +/* Bit fields for ``Signals'' */ +#define S_INTERRUPT (1<<1) +#define S_SIGWINCH (1<<2) +#define S_ALARM (1<<3) + +typedef struct list_t +{ + char *data; + struct list_t *next; +} LIST; + +#define mutt_new_list() safe_calloc (1, sizeof (LIST)) +void mutt_add_to_list (LIST **, BUFFER *); +void mutt_free_list (LIST **); +int mutt_matches_ignore (const char *, LIST *); + +/* add an element to a list */ +LIST *mutt_add_list (LIST *, const char *); + +void mutt_init (int, LIST *); + +typedef struct alias +{ + char *name; + ADDRESS *addr; + struct alias *next; + short tagged; + short num; +} ALIAS; + +typedef struct envelope +{ + ADDRESS *return_path; + ADDRESS *from; + ADDRESS *to; + ADDRESS *cc; + ADDRESS *bcc; + ADDRESS *sender; + ADDRESS *reply_to; + ADDRESS *mail_followup_to; + char *subject; + char *real_subj; /* offset of the real subject */ + char *message_id; + char *supersedes; + LIST *references; /* message references (in reverse order) */ + LIST *userhdrs; /* user defined headers */ +} ENVELOPE; + +typedef struct parameter +{ + char *attribute; + char *value; + struct parameter *next; +} PARAMETER; + +/* Information that helps in determing the Content-* of an attachment */ +typedef struct content +{ + long hibin; /* 8-bit characters */ + long lobin; /* unprintable 7-bit chars (eg., control chars) */ + long ascii; /* number of ascii chars */ + long linemax; /* length of the longest line in the file */ + unsigned int space : 1; /* whitespace at the end of lines? */ + unsigned int binary : 1; /* long lines, or CR not in CRLF pair */ + unsigned int from : 1; /* has a line beginning with "From "? */ + unsigned int dot : 1; /* has a line consisting of a single dot? */ +} CONTENT; + +typedef struct body +{ + char *subtype; /* content-type subtype */ + PARAMETER *parameter; /* parameters of the content-type */ + char *description; /* content-description */ + char *form_name; /* Content-Disposition form-data name param */ + long hdr_offset; /* offset in stream where the headers begin. + * this info is used when invoking metamail, + * where we need to send the headers of the + * attachment + */ + long offset; /* offset where the actual data begins */ + long length; /* length (in bytes) of attachment */ + char *filename; /* when sending a message, this is the file + * to which this structure refers + */ + char *d_filename; /* filename to be used for the + * content-disposition header. + * If NULL, filename is used + * instead. + */ + CONTENT *content; /* structure used to store detailed info about + * the content of the attachment. this is used + * to determine what content-transfer-encoding + * is required when sending mail. + */ + struct body *next; /* next attachment in the list */ + struct body *parts; /* parts of a multipart or message/rfc822 */ + struct header *hdr; /* header information for message/rfc822 */ + + unsigned int type : 3; /* content-type primary type */ + unsigned int encoding : 3; /* content-transfer-encoding */ + unsigned int disposition : 2; /* content-disposition */ + unsigned int use_disp : 1; /* Content-Disposition field printed? */ + unsigned int unlink : 1; /* flag to indicate the the file named by + * "filename" should be unlink()ed before + * free()ing this structure + */ + unsigned int tagged : 1; + unsigned int deleted : 1; /* attachment marked for deletion */ + +} BODY; + +typedef struct header +{ + unsigned int mime : 1; /* has a Mime-Version header? */ + unsigned int mailcap : 1; /* requires mailcap to display? */ + unsigned int flagged : 1; /* marked important? */ + unsigned int tagged : 1; + unsigned int deleted : 1; + unsigned int changed : 1; + unsigned int attach_del : 1; /* has an attachment marked for deletion */ + unsigned int old : 1; + unsigned int read : 1; + unsigned int expired : 1; /* already expired? */ + unsigned int superseded : 1; /* got superseded? */ + + + + +#ifdef _PGPPATH + unsigned int pgp : 3; +#endif + + + + + + + + + + unsigned int replied : 1; + unsigned int subject_changed : 1; /* used for threading */ + unsigned int display_subject : 1; /* used for threading */ + unsigned int fake_thread : 1; /* no ref matched, but subject did */ + unsigned int threaded : 1; /* message has been threaded */ + + /* timezone of the sender of this message */ + unsigned int zhours : 5; + unsigned int zminutes : 6; + unsigned int zoccident : 1; + + /* bits used for caching when searching */ + unsigned int searched : 1; + unsigned int matched : 1; + + int pair; /* color-pair to use when displaying in the index */ + + time_t date_sent; /* time when the message was sent (UTC) */ + time_t received; /* time when the message was placed in the mailbox */ + long offset; /* where in the stream does this message begin? */ + int lines; /* how many lines in the body of this message? */ + int index; /* the absolute (unsorted) message number */ + int msgno; /* number displayed to the user */ + int virtual; /* virtual message number */ + int score; + ENVELOPE *env; /* envelope information */ + BODY *content; /* list of MIME parts */ + char *path; + + /* the following are used for threading support */ + struct header *parent; + struct header *child; /* decendants of this message */ + struct header *next; /* next message in this thread */ + struct header *prev; /* previous message in thread */ + struct header *last_sort; /* last message in subthread, for secondary SORT_LAST */ + char *tree; /* character string to print thread tree */ + +} HEADER; + +#include "mutt_regex.h" + +/* flag to mutt_pattern_comp() */ +#define M_FULL_MSG 1 /* enable body and header matching */ + +typedef enum { + M_MATCH_FULL_ADDRESS = 1 +} pattern_exec_flag; + +typedef struct pattern_t +{ + short op; + short not; + int min; + int max; + struct pattern_t *next; + struct pattern_t *child; /* arguments to logical op */ + regex_t *rx; +} pattern_t; + +typedef struct +{ + char *path; + FILE *fp; + time_t mtime; + off_t size; + off_t vsize; + char *pattern; /* limit pattern string */ + pattern_t *limit_pattern; /* compiled limit pattern */ + HEADER **hdrs; + HEADER *tree; /* top of thread tree */ + HASH *id_hash; /* hash table by msg id */ + HASH *subj_hash; /* hash table by subject */ + int *v2r; /* mapping from virtual to real msgno */ + int hdrmax; /* number of pointers in hdrs */ + int msgcount; /* number of messages in the mailbox */ + int vcount; /* the number of virtual messages */ + int tagged; /* how many messages are tagged? */ + int new; /* how many new messages? */ + int unread; /* how many unread messages? */ + int deleted; /* how many deleted messages */ + int flagged; /* how many flagged messages */ + int msgnotreadyet; /* which msg "new" in pager, -1 if none */ +#ifdef USE_IMAP + void *data; /* driver specific data */ + int fd; +#endif /* USE_IMAP */ + + short magic; /* mailbox type */ + + unsigned int locked : 1; /* is the mailbox locked? */ + unsigned int changed : 1; /* mailbox has been modified */ + unsigned int readonly : 1; /* don't allow changes to the mailbox */ + unsigned int dontwrite : 1; /* dont write the mailbox on close */ + unsigned int append : 1; /* mailbox is opened in append mode */ + unsigned int setgid : 1; + unsigned int quiet : 1; /* inhibit status messages? */ + unsigned int revsort : 1; /* mailbox sorted in reverse? */ +} CONTEXT; + +typedef struct attachptr +{ + BODY *content; + char *tree; + int level; +} ATTACHPTR; + +typedef struct +{ + FILE *fpin; + FILE *fpout; + char *prefix; + int flags; +} STATE; + +/* flags for the STATE struct */ +#define M_DISPLAY (1<<0) /* output is displayed to the user */ + + + +#ifdef _PGPPATH +#define M_VERIFY (1<<1) /* perform signature verification */ +#endif + + + +#define state_puts(x,y) fputs(x,(y)->fpout) +#define state_putc(x,y) fputc(x,(y)->fpout) + +#include "protos.h" +#include "globals.h" diff --git a/mutt_curses.h b/mutt_curses.h new file mode 100644 index 00000000..2d5b7eaa --- /dev/null +++ b/mutt_curses.h @@ -0,0 +1,165 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifdef USE_SLANG_CURSES + +#ifndef unix /* this symbol is not defined by the hp-ux compiler (sigh) */ +#define unix +#endif /* unix */ + +#include "slcurses.h" + +#define KEY_DC SL_KEY_DELETE +#define KEY_IC SL_KEY_IC + +/* + * ncurses and SLang seem to send different characters when the Enter key is + * pressed, so define some macros to properly detect the Enter key. + */ +#define M_ENTER_C '\r' +#define M_ENTER_S "\r" + +#else + +#ifdef HAVE_NCURSES_H +#include <ncurses.h> +#else +#include <curses.h> +#endif + +#define M_ENTER_C '\n' +#define M_ENTER_S "\n" + +#endif /* USE_SLANG_CURSES */ + +/* AIX defines ``lines'' in <term.h>, but it's used as a var name in + * various places in Mutt + */ +#ifdef lines +#undef lines +#endif /* lines */ + +#define CLEARLINE(x) move(x,0), clrtoeol() +#define CENTERLINE(x,y) move(y, (COLS-strlen(x))/2), addstr(x) +#define BEEP if (option (OPTBEEP)) beep + +#if ! (defined(USE_SLANG_CURSES) || defined(HAVE_CURS_SET)) +#define curs_set(x) +#endif + +#if !defined(USE_SLANG_CURSES) && defined(HAVE_BKGDSET) +#define BKGDSET(x) bkgdset (ColorDefs[x] | ' ') +#else +#define BKGDSET(x) +#endif + +#if (defined(USE_SLANG_CURSES) || defined(HAVE_CURS_SET)) +void mutt_curs_set (int); +#else +#define mutt_curs_set(x) +#endif +#define PAGELEN (LINES-3) + +#define ctrl(c) ((c)-'@') + +#define CI_is_return(c) ((c) == '\r' || (c) == '\n' || (c) == KEY_ENTER) + +int mutt_getch (void); + +void mutt_endwin (const char *); +void mutt_flushinp (void); +void mutt_refresh (void); +void mutt_resize_screen (void); +void mutt_ungetch (int); + +/* ---------------------------------------------------------------------------- + * Support for color + */ + +enum +{ + MT_COLOR_HDEFAULT = 0, + MT_COLOR_QUOTED, + MT_COLOR_SIGNATURE, + MT_COLOR_INDICATOR, + MT_COLOR_STATUS, + MT_COLOR_TREE, + MT_COLOR_NORMAL, + MT_COLOR_ERROR, + MT_COLOR_TILDE, + MT_COLOR_MARKERS, + MT_COLOR_BODY, + MT_COLOR_HEADER, + MT_COLOR_MESSAGE, + MT_COLOR_ATTACHMENT, + MT_COLOR_SEARCH, + MT_COLOR_BOLD, + MT_COLOR_UNDERLINE, + MT_COLOR_INDEX, + MT_COLOR_MAX +}; + +typedef struct color_line +{ + regex_t rx; + char *pattern; + pattern_t *color_pattern; /* compiled pattern to speed up index color + calculation */ + short fg; + short bg; + int pair; + struct color_line *next; +} COLOR_LINE; + +extern int *ColorQuote; +extern int ColorQuoteUsed; +extern int ColorDefs[]; +extern COLOR_LINE *ColorHdrList; +extern COLOR_LINE *ColorBodyList; +extern COLOR_LINE *ColorIndexList; + +void ci_init_color (void); +void ci_start_color (void); + +#define SETCOLOR(X) attrset(ColorDefs[X]) + +#define MAYBE_REDRAW(x) if (option (OPTNEEDREDRAW)) { unset_option (OPTNEEDREDRAW); x = REDRAW_FULL; } + +/* ---------------------------------------------------------------------------- + * These are here to avoid compiler warnings with -Wall under SunOS 4.1.x + */ + +#if !defined(STDC_HEADERS) && !defined(NCURSES_VERSION) && !defined(USE_SLANG_CURSES) +extern int endwin(); +extern int printw(); +extern int beep(); +extern int isendwin(); +extern int w32addch(); +extern int keypad(); +extern int wclrtobot(); +extern int mvprintw(); +extern int getcurx(); +extern int getcury(); +extern int noecho(); +extern int wdelch(); +extern int wrefresh(); +extern int wmove(); +extern int wclear(); +extern int waddstr(); +extern int wclrtoeol(); +#endif diff --git a/mutt_menu.h b/mutt_menu.h new file mode 100644 index 00000000..e30da697 --- /dev/null +++ b/mutt_menu.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * This file is named mutt_menu.h so it doesn't collide with ncurses menu.h + */ + +#include "keymap.h" +#include "mutt_regex.h" + +#define REDRAW_INDEX (1) +#define REDRAW_MOTION (1<<1) +#define REDRAW_MOTION_RESYNCH (1<<2) +#define REDRAW_CURRENT (1<<3) +#define REDRAW_STATUS (1<<4) +#define REDRAW_FULL (1<<5) +#define REDRAW_BODY (1<<6) + +typedef struct menu_t +{ + char *title; /* the title of this menu */ + char *help; /* quickref for the current menu */ + void *data; /* extra data for the current menu */ + int current; /* current entry */ + int max; /* the number of entries in the menu */ + int redraw; /* when to redraw the screen */ + int menu; /* menu definition for keymap entries. */ + int offset; /* which screen row to start the index */ + int pagelen; /* number of entries per screen */ + int tagprefix; + + /* callback to generate an index line for the requested element */ + void (*make_entry) (char *, size_t, struct menu_t *, int); + + /* how to search the menu */ + int (*search) (struct menu_t *, regex_t *re, int n); + + int (*tag) (struct menu_t *, int i); + + /* color pair to be used for the requested element + * (default function returns ColorDefs[MT_COLOR_NORMAL]) + */ + int (*color) (int i); + + /* the following are used only by mutt_menuLoop() */ + int top; /* entry that is the top of the current page */ + int oldcurrent; /* for driver use only. */ + char *searchBuf; /* last search pattern */ + int searchDir; /* direction of search */ + int tagged; /* number of tagged entries */ +} MUTTMENU; + +void menu_jump (MUTTMENU *); +void menu_redraw_full (MUTTMENU *); +void menu_redraw_index (MUTTMENU *); +void menu_redraw_motion (MUTTMENU *); +void menu_redraw_current (MUTTMENU *); +void menu_first_entry (MUTTMENU *); +void menu_last_entry (MUTTMENU *); +void menu_top_page (MUTTMENU *); +void menu_bottom_page (MUTTMENU *); +void menu_middle_page (MUTTMENU *); +void menu_next_page (MUTTMENU *); +void menu_prev_page (MUTTMENU *); +void menu_next_line (MUTTMENU *); +void menu_prev_line (MUTTMENU *); +void menu_half_up (MUTTMENU *); +void menu_half_down (MUTTMENU *); +void menu_current_top (MUTTMENU *); +void menu_current_middle (MUTTMENU *); +void menu_current_bottom (MUTTMENU *); +void menu_check_recenter (MUTTMENU *); +void menu_status_line (char *, size_t, MUTTMENU *, const char *); + +MUTTMENU *mutt_new_menu (void); +void mutt_menuDestroy (MUTTMENU **); +int mutt_menuLoop (MUTTMENU *); + +/* used in both the index and pager index to make an entry. */ +void index_make_entry (char *, size_t, struct menu_t *, int); +int index_color (int); diff --git a/mutt_regex.h b/mutt_regex.h new file mode 100644 index 00000000..27133bec --- /dev/null +++ b/mutt_regex.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * A (more) generic interface to regular expression matching + */ + +#ifndef MUTT_REGEX_H +#define MUTT_REGEX_H + +#ifdef HAVE_REGCOMP +#include <regex.h> +#else +#include "rxposix.h" +#endif + +/* this is a non-standard option supported by Solaris 2.5.x which allows + * patterns of the form \<...\> + */ +#ifndef REG_WORDS +#define REG_WORDS 0 +#endif + +#define REGCOMP(X,Y,Z) regcomp(X, Y, REG_WORDS|REG_EXTENDED|(Z)) +#define REGEXEC(X,Y) regexec(&X, Y, (size_t)0, (regmatch_t *)0, (int)0) + +typedef struct +{ + char *pattern; /* printable version */ + regex_t *rx; /* compiled expression */ + int not; /* do not match */ +} REGEXP; + +WHERE REGEXP Alternates; +WHERE REGEXP Mask; +WHERE REGEXP QuoteRegexp; +WHERE REGEXP ReplyRegexp; + +#endif /* MUTT_REGEX_H */ diff --git a/mx.c b/mx.c new file mode 100644 index 00000000..604e7f38 --- /dev/null +++ b/mx.c @@ -0,0 +1,1519 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mx.h" +#include "rfc2047.h" +#include "sort.h" +#include "mailbox.h" +#include "copy.h" +#include "keymap.h" + + +#ifdef _PGPPATH +#include "pgp.h" +#endif + + + + +#ifdef BUFFY_SIZE +#include "buffy.h" +#endif + +#include <dirent.h> +#include <fcntl.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#ifndef BUFFY_SIZE +#include <utime.h> +#endif + +/* HP-UX and ConvexOS don't have this macro */ +#ifndef S_ISLNK +#define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK ? 1 : 0) +#endif + +#define mutt_is_spool(s) (strcmp (Spoolfile, s) == 0) + +#define MAXLOCKATTEMPT 5 + +#ifdef USE_DOTLOCK +/* parameters: + * path - file to lock + * retry - should retry if unable to lock? + */ +static int dotlock_file (const char *path, int retry) +{ + const char *pathptr = path; + char lockfile[_POSIX_PATH_MAX]; + char nfslockfile[_POSIX_PATH_MAX]; + char realpath[_POSIX_PATH_MAX]; + struct stat sb; + size_t prev_size = 0; + int count = 0; + int attempt = 0; + int fd; + + /* if the file is a symlink, find the real file to which it refers */ + FOREVER + { + dprint(2,(debugfile,"dotlock_file(): locking %s\n", pathptr)); + + if (lstat (pathptr, &sb) != 0) + { + mutt_perror (pathptr); + return (-1); + } + + if (S_ISLNK (sb.st_mode)) + { + char linkfile[_POSIX_PATH_MAX]; + char linkpath[_POSIX_PATH_MAX]; + + if ((count = readlink (pathptr, linkfile, sizeof (linkfile))) == -1) + { + mutt_perror (path); + return (-1); + } + linkfile[count] = 0; /* readlink() does not NUL terminate the string! */ + mutt_expand_link (linkpath, pathptr, linkfile); + strfcpy (realpath, linkpath, sizeof (realpath)); + pathptr = realpath; + } + else + break; + } + + snprintf (nfslockfile, sizeof (nfslockfile), "%s.%s.%d", pathptr, Hostname, (int) getpid ()); + snprintf (lockfile, sizeof (lockfile), "%s.lock", pathptr); + unlink (nfslockfile); + + while ((fd = open (nfslockfile, O_WRONLY | O_EXCL | O_CREAT, 0)) < 0) + if (errno != EAGAIN) + { + mutt_perror ("cannot open NFS lock file!"); + return (-1); + } + + close (fd); + + count = 0; + FOREVER + { + link (nfslockfile, lockfile); + if (stat (nfslockfile, &sb) != 0) + { + mutt_perror ("stat"); + return (-1); + } + + if (sb.st_nlink == 2) + break; + + if (stat (path, &sb) != 0) + sb.st_size = 0; + + if (count == 0) + prev_size = sb.st_size; + + /* only try to remove the lock if the file is not changing */ + if (prev_size == sb.st_size && ++count >= retry ? MAXLOCKATTEMPT : 0) + { + if (retry && mutt_yesorno ("Lock count exceeded, remove lock?", 1) == 1) + { + unlink (lockfile); + count = 0; + attempt = 0; + continue; + } + else + return (-1); + } + + prev_size = sb.st_size; + + mutt_message ("Waiting for lock attempt #%d...", ++attempt); + sleep (1); + } + + unlink (nfslockfile); + + return 0; +} + +static int undotlock_file (const char *path) +{ + const char *pathptr = path; + char lockfile[_POSIX_PATH_MAX]; + char realpath[_POSIX_PATH_MAX]; + struct stat sb; + int n; + + FOREVER + { + dprint (2,(debugfile,"undotlock: unlocking %s\n",path)); + + if (lstat (pathptr, &sb) != 0) + { + mutt_perror (pathptr); + return (-1); + } + + if (S_ISLNK (sb.st_mode)) + { + char linkfile[_POSIX_PATH_MAX]; + char linkpath[_POSIX_PATH_MAX]; + + if ((n = readlink (pathptr, linkfile, sizeof (linkfile))) == -1) + { + mutt_perror (pathptr); + return (-1); + } + linkfile[n] = 0; /* readlink() does not NUL terminate the string! */ + mutt_expand_link (linkpath, pathptr, linkfile); + strfcpy (realpath, linkpath, sizeof (realpath)); + pathptr = realpath; + continue; + } + else + break; + } + + snprintf (lockfile, sizeof (lockfile), "%s.lock", pathptr); + unlink (lockfile); + return 0; +} +#endif /* USE_DOTLOCK */ + +/* Args: + * excl if excl != 0, request an exclusive lock + * dot if dot != 0, try to dotlock the file + * timeout should retry locking? + */ +int mx_lock_file (const char *path, int fd, int excl, int dot, int timeout) +{ +#if defined (USE_FCNTL) || defined (USE_FLOCK) + int count; + int attempt; + struct stat prev_sb; +#endif + int r = 0; + +#ifdef USE_FCNTL + struct flock lck; + + + memset (&lck, 0, sizeof (struct flock)); + lck.l_type = excl ? F_WRLCK : F_RDLCK; + lck.l_whence = SEEK_SET; + + count = 0; + attempt = 0; + while (fcntl (fd, F_SETLK, &lck) == -1) + { + struct stat sb; + dprint(1,(debugfile, "mx_lock_file(): fcntl errno %d.\n", errno)); + if (errno != EAGAIN && errno != EACCES) + { + mutt_perror ("fcntl"); + return (-1); + } + + if (fstat (fd, &sb) != 0) + sb.st_size = 0; + + if (count == 0) + prev_sb = sb; + + /* only unlock file if it is unchanged */ + if (prev_sb.st_size == sb.st_size && ++count >= timeout?MAXLOCKATTEMPT:0) + { + if (timeout) + mutt_error ("Timeout exceeded while attempting fcntl lock!"); + return (-1); + } + + prev_sb = sb; + + mutt_message ("Waiting for fcntl lock... %d", ++attempt); + sleep (1); + } +#endif /* USE_FCNTL */ + +#ifdef USE_FLOCK + count = 0; + attempt = 0; + while (flock (fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1) + { + struct stat sb; + if (errno != EWOULDBLOCK) + { + mutt_perror ("flock"); + r = -1; + break; + } + + if (fstat(fd,&sb) != 0 ) + sb.st_size=0; + + if (count == 0) + prev_sb=sb; + + /* only unlock file if it is unchanged */ + if (prev_sb.st_size == sb.st_size && ++count >= timeout?MAXLOCKATTEMPT:0) + { + if (timeout) + mutt_error ("Timeout exceeded while attempting flock lock!"); + r = -1; + break; + } + + prev_sb = sb; + + mutt_message ("Waiting for flock attempt... %d", ++attempt); + sleep (1); + } +#endif /* USE_FLOCK */ + +#ifdef USE_DOTLOCK + if (r == 0 && dot) + r = dotlock_file (path,timeout); +#endif /* USE_DOTLOCK */ + + if (r == -1) + { + /* release any other locks obtained in this routine */ + +#ifdef USE_FCNTL + lck.l_type = F_UNLCK; + fcntl (fd, F_SETLK, &lck); +#endif /* USE_FCNTL */ + +#ifdef USE_FLOCK + flock (fd, LOCK_UN); +#endif /* USE_FLOCK */ + + return (-1); + } + + return 0; +} + +int mx_unlock_file (const char *path, int fd) +{ +#ifdef USE_FCNTL + struct flock unlockit = { F_UNLCK, 0, 0, 0 }; + + memset (&unlockit, 0, sizeof (struct flock)); + unlockit.l_type = F_UNLCK; + unlockit.l_whence = SEEK_SET; + fcntl (fd, F_SETLK, &unlockit); +#endif + +#ifdef USE_FLOCK + flock (fd, LOCK_UN); +#endif + +#ifdef USE_DOTLOCK + undotlock_file (path); +#endif + + return 0; +} + +/* open a file and lock it */ +FILE *mx_open_file_lock (const char *path, const char *mode) +{ + FILE *f; + + if ((f = safe_fopen (path, mode)) != NULL) + { + if (mx_lock_file (path, fileno (f), *mode != 'r', 1, 1) != 0) + { + fclose (f); + f = NULL; + } + } + + return (f); +} + +/* try to figure out what type of mailbox ``path'' is + * + * return values: + * M_* mailbox type + * 0 not a mailbox + * -1 error + */ +int mx_get_magic (const char *path) +{ + struct stat st; + int magic = 0; + char tmp[_POSIX_PATH_MAX]; + FILE *f; + +#ifdef USE_IMAP + if (*path == '{') + return M_IMAP; +#endif /* USE_IMAP */ + + if (stat (path, &st) == -1) + { + dprint (1, (debugfile, "mx_get_magic(): unable to stat %s: %s (errno %d).\n", + path, strerror (errno), errno)); + mutt_perror (path); + return (-1); + } + + if (S_ISDIR (st.st_mode)) + { + /* check for maildir-style mailbox */ + + snprintf (tmp, sizeof (tmp), "%s/cur", path); + if (stat (tmp, &st) == 0 && S_ISDIR (st.st_mode)) + return (M_MAILDIR); + + /* check for mh-style mailbox */ + + snprintf (tmp, sizeof (tmp), "%s/.mh_sequences", path); + if (access (tmp, F_OK) == 0) + return (M_MH); + + snprintf (tmp, sizeof (tmp), "%s/.xmhcache", path); + if (access (tmp, F_OK) == 0) + return (M_MH); + } + else if (st.st_size == 0) + { + /* hard to tell what zero-length files are, so assume the default magic */ + if (DefaultMagic == M_MBOX || DefaultMagic == M_MMDF) + return (DefaultMagic); + else + return (M_MBOX); + } + else if ((f = fopen (path, "r")) != NULL) + { +#ifndef BUFFY_SIZE + struct utimbuf times; +#endif + + fgets (tmp, sizeof (tmp), f); + if (strncmp ("From ", tmp, 5) == 0) + magic = M_MBOX; + else if (strcmp (MMDF_SEP, tmp) == 0) + magic = M_MMDF; + fclose (f); +#ifndef BUFFY_SIZE + /* need to restore the times here, the file was not really accessed, + * only the type was accessed. This is important, because detection + * of "new mail" depends on those times set correctly. + */ + times.actime = st.st_atime; + times.modtime = st.st_mtime; + utime (path, ×); +#endif + } + else + { + dprint (1, (debugfile, "mx_get_magic(): unable to open file %s for reading.\n", + path)); + mutt_perror (path); + return (-1); + } + + return (magic); +} + +/* + * set DefaultMagic to the given value + */ +int mx_set_magic (const char *s) +{ + if (strcasecmp (s, "mbox") == 0) + DefaultMagic = M_MBOX; + else if (strcasecmp (s, "mmdf") == 0) + DefaultMagic = M_MMDF; + else if (strcasecmp (s, "mh") == 0) + DefaultMagic = M_MH; + else if (strcasecmp (s, "maildir") == 0) + DefaultMagic = M_MAILDIR; + else + return (-1); + + return 0; +} + +static int mx_open_mailbox_append (CONTEXT *ctx) +{ + ctx->append = 1; + if (access (ctx->path, W_OK) == 0) + { + switch (ctx->magic = mx_get_magic (ctx->path)) + { + case 0: + mutt_error ("%s is not a mailbox.", ctx->path); + /* fall through */ + case -1: + return (-1); + } + } + else if (errno == ENOENT) + { + ctx->magic = DefaultMagic; + + if (ctx->magic == M_MH || ctx->magic == M_MAILDIR) + { + char tmp[_POSIX_PATH_MAX]; + + if (mkdir (ctx->path, S_IRWXU)) + { + mutt_perror (tmp); + return (-1); + } + + if (ctx->magic == M_MAILDIR) + { + snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path); + if (mkdir (tmp, S_IRWXU)) + { + mutt_perror (tmp); + rmdir (ctx->path); + return (-1); + } + + snprintf (tmp, sizeof (tmp), "%s/new", ctx->path); + if (mkdir (tmp, S_IRWXU)) + { + mutt_perror (tmp); + snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path); + rmdir (tmp); + rmdir (ctx->path); + return (-1); + } + snprintf (tmp, sizeof (tmp), "%s/tmp", ctx->path); + if (mkdir (tmp, S_IRWXU)) + { + mutt_perror (tmp); + snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path); + rmdir (tmp); + snprintf (tmp, sizeof (tmp), "%s/new", ctx->path); + rmdir (tmp); + rmdir (ctx->path); + return (-1); + } + } + else + { + int i; + + snprintf (tmp, sizeof (tmp), "%s/.mh_sequences", ctx->path); + if ((i = creat (tmp, S_IRWXU)) == -1) + { + mutt_perror (tmp); + rmdir (ctx->path); + return (-1); + } + close (i); + } + } + } + else + { + mutt_perror (ctx->path); + return (-1); + } + + switch (ctx->magic) + { + case M_MBOX: + case M_MMDF: + if ((ctx->fp = fopen (ctx->path, "a")) == NULL || + mbox_lock_mailbox (ctx, 1, 1) != 0) + { + if (!ctx->fp) + mutt_perror (ctx->path); + return (-1); + } + fseek (ctx->fp, 0, 2); + break; + + case M_MH: + case M_MAILDIR: + /* nothing to do */ + break; + + default: + return (-1); + } + + return 0; +} + +/* + * open a mailbox and parse it + * + * Args: + * flags M_NOSORT do not sort mailbox + * M_APPEND open mailbox for appending + * M_READONLY open mailbox in read-only mode + * M_QUIET only print error messages + * ctx if non-null, context struct to use + */ +CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx) +{ + CONTEXT *ctx = pctx; + int rc; + + if (!ctx) + ctx = safe_malloc (sizeof (CONTEXT)); + memset (ctx, 0, sizeof (CONTEXT)); + ctx->path = safe_strdup (path); + + ctx->msgnotreadyet = -1; + + if (flags & M_QUIET) + ctx->quiet = 1; + if (flags & M_READONLY) + ctx->readonly = 1; + + if (flags & M_APPEND) + { + if (mx_open_mailbox_append (ctx) != 0) + { + mx_fastclose_mailbox (ctx); + if (!pctx) + safe_free ((void **) &ctx); + return NULL; + } + return ctx; + } + + switch (ctx->magic = mx_get_magic (path)) + { + case 0: + mutt_error ("%s is not a mailbox.", path); + /* fall through */ + + case -1: + mx_fastclose_mailbox (ctx); + if (!pctx) + free (ctx); + return (NULL); + } + + /* if the user has a `push' command in their .muttrc, or in a folder-hook, + * it will cause the progress messages not to be displayed because + * mutt_refresh() will think we are in the middle of a macro. so set a + * flag to indicate that we should really refresh the screen. + */ + set_option (OPTFORCEREFRESH); + + /* create hash tables */ + ctx->id_hash = hash_create (257); + ctx->subj_hash = hash_create (257); + + if (!ctx->quiet) + mutt_message ("Reading %s...", ctx->path); + + switch (ctx->magic) + { + case M_MH: + rc = mh_read_dir (ctx, NULL); + break; + + case M_MAILDIR: + rc = maildir_read_dir (ctx); + break; + + case M_MMDF: + case M_MBOX: + rc = mbox_open_mailbox (ctx); + break; + +#ifdef USE_IMAP + case M_IMAP: + rc = imap_open_mailbox (ctx); + break; +#endif /* USE_IMAP */ + + default: + rc = -1; + break; + } + + if (rc == 0) + { + if ((flags & M_NOSORT) == 0) + { + /* avoid unnecessary work since the mailbox is completely unthreaded + to begin with */ + unset_option (OPTSORTSUBTHREADS); + unset_option (OPTNEEDRESCORE); + mutt_sort_headers (ctx, 1); + } + if (!ctx->quiet) + mutt_clear_error (); + } + else + { + mx_fastclose_mailbox (ctx); + if (!pctx) + safe_free ((void **) &ctx); + } + + unset_option (OPTFORCEREFRESH); + return (ctx); +} + +/* free up memory associated with the mailbox context */ +void mx_fastclose_mailbox (CONTEXT *ctx) +{ + int i; + +#ifdef USE_IMAP + if (ctx->magic == M_IMAP) + imap_fastclose_mailbox (ctx); +#endif /* USE_IMAP */ + if (ctx->subj_hash) + hash_destroy (&ctx->subj_hash, NULL); + if (ctx->id_hash) + hash_destroy (&ctx->id_hash, NULL); + for (i = 0; i < ctx->msgcount; i++) + mutt_free_header (&ctx->hdrs[i]); + safe_free ((void **) &ctx->hdrs); + safe_free ((void **) &ctx->v2r); + safe_free ((void **) &ctx->path); + safe_free ((void **) &ctx->pattern); + if (ctx->limit_pattern) + mutt_pattern_free (&ctx->limit_pattern); + if (ctx->fp) + fclose (ctx->fp); + memset (ctx, 0, sizeof (CONTEXT)); +} + +/* save changes to disk */ +static int sync_mailbox (CONTEXT *ctx) +{ +#ifdef BUFFY_SIZE + BUFFY *tmp = NULL; +#endif + int rc = -1; + + if (!ctx->quiet) + mutt_message ("Writing %s...", ctx->path); + switch (ctx->magic) + { + case M_MBOX: + case M_MMDF: + rc = mbox_sync_mailbox (ctx); +#ifdef BUFFY_SIZE + tmp = mutt_find_mailbox (ctx->path); +#endif + break; + + case M_MH: + case M_MAILDIR: + rc = mh_sync_mailbox (ctx); + break; + +#ifdef USE_IMAP + case M_IMAP: + rc = imap_sync_mailbox (ctx); + break; +#endif /* USE_IMAP */ + } + +#ifdef BUFFY_SIZE + if (tmp && tmp->new == 0) + mutt_update_mailbox (tmp); +#endif + return rc; +} + +/* save changes and close mailbox */ +int mx_close_mailbox (CONTEXT *ctx) +{ + int i, move_messages = 0, purge = 1, read_msgs = 0; + int isSpool = 0; + CONTEXT f; + char mbox[_POSIX_PATH_MAX]; + char buf[SHORT_STRING]; + + if (ctx->readonly || ctx->dontwrite) + { + /* mailbox is readonly or we don't want to write */ + mx_fastclose_mailbox (ctx); + return 0; + } + + if (ctx->append) + { + /* mailbox was opened in write-mode */ + if (ctx->magic == M_MBOX || ctx->magic == M_MMDF) + mbox_close_mailbox (ctx); + else + mx_fastclose_mailbox (ctx); + return 0; + } + + for (i = 0; i < ctx->msgcount; i++) + { + if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->read) + read_msgs++; + } + + if (read_msgs && quadoption (OPT_MOVE) != M_NO) + { + char *p; + + if ((p = mutt_find_hook (M_MBOXHOOK, ctx->path))) + { + isSpool = 1; + strfcpy (mbox, p, sizeof (mbox)); + } + else + { + strfcpy (mbox, Inbox, sizeof (mbox)); + isSpool = mutt_is_spool (ctx->path) && !mutt_is_spool (mbox); + } + mutt_expand_path (mbox, sizeof (mbox)); + + if (isSpool) + { + snprintf (buf, sizeof (buf), "Move read messages to %s?", mbox); + if ((move_messages = query_quadoption (OPT_MOVE, buf)) == -1) + return (-1); + } + } + + if (ctx->deleted) + { + snprintf (buf, sizeof (buf), "Purge %d deleted message%s?", + ctx->deleted, ctx->deleted == 1 ? "" : "s"); + if ((purge = query_quadoption (OPT_DELETE, buf)) < 0) + return (-1); + } + + if (option (OPTMARKOLD)) + { + for (i = 0; i < ctx->msgcount; i++) + { + if (!ctx->hdrs[i]->deleted && !ctx->hdrs[i]->old) + mutt_set_flag (ctx, ctx->hdrs[i], M_OLD, 1); + } + } + + if (move_messages) + { + if (mx_open_mailbox (mbox, M_APPEND, &f) == NULL) + return (-1); + + mutt_message ("Moving read messages to %s...", mbox); + + for (i = 0; i < ctx->msgcount; i++) + { + if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted) + { + mutt_append_message (&f, ctx, ctx->hdrs[i], 0, CH_UPDATE_LEN); + ctx->hdrs[i]->deleted = 1; + ctx->deleted++; + } + } + + mx_close_mailbox (&f); + } + else if (!ctx->changed && ctx->deleted == 0) + { + mutt_message ("Mailbox is unchanged."); + mx_fastclose_mailbox (ctx); + return 0; + } + + if (!purge) + { + for (i = 0; i < ctx->msgcount; i++) + ctx->hdrs[i]->deleted = 0; + ctx->deleted = 0; + } + + if (ctx->changed || ctx->deleted) + { + if (sync_mailbox (ctx) == -1) + return (-1); + } + + if (move_messages) + mutt_message ("%d kept, %d moved, %d deleted.", + ctx->msgcount - ctx->deleted, read_msgs, ctx->deleted); + else + mutt_message ("%d kept, %d deleted.", + ctx->msgcount - ctx->deleted, ctx->deleted); + + if (ctx->msgcount == ctx->deleted && + (ctx->magic == M_MMDF || ctx->magic == M_MBOX) && + strcmp (ctx->path, Spoolfile) != 0 && !option (OPTSAVEEMPTY)) + unlink (ctx->path); + + mx_fastclose_mailbox (ctx); + + return 0; +} + +/* save changes to mailbox + * + * return values: + * 0 success + * -1 error + */ +int mx_sync_mailbox (CONTEXT *ctx) +{ + int rc, i, j; + + if (ctx->dontwrite) + { + char buf[STRING], tmp[STRING]; + if (km_expand_key (buf, sizeof(buf), + km_find_func (MENU_MAIN, OP_TOGGLE_WRITE))) + snprintf (tmp, sizeof(tmp), " Press '%s' to toggle write", buf); + else + strfcpy (tmp, "Use 'toggle-write' to re-enable write!", sizeof(tmp)); + + mutt_error ("Mailbox is marked unwritable. %s", tmp); + return -1; + } + else if (ctx->readonly) + { + mutt_error ("Mailbox is read-only."); + return -1; + } + + if (!ctx->changed && !ctx->deleted) + { + mutt_message ("Mailbox is unchanged."); + return (0); + } + + if (ctx->deleted) + { + char buf[SHORT_STRING]; + + snprintf (buf, sizeof (buf), "Purge %d deleted message%s?", + ctx->deleted, ctx->deleted == 1 ? "" : "s"); + if ((rc = query_quadoption (OPT_DELETE, buf)) < 0) + return (-1); + else if (rc == M_NO) + { + if (!ctx->changed) + return 0; /* nothing to do! */ + for (i = 0 ; i < ctx->msgcount ; i++) + ctx->hdrs[i]->deleted = 0; + ctx->deleted = 0; + } + } + + if ((rc = sync_mailbox (ctx)) == 0) + { + mutt_message ("%d kept, %d deleted.", ctx->msgcount - ctx->deleted, + ctx->deleted); + sleep (1); /* allow the user time to read the message */ + + if (ctx->msgcount == ctx->deleted && + (ctx->magic == M_MBOX || ctx->magic == M_MMDF) && + strcmp (ctx->path, Spoolfile) != 0 && !option (OPTSAVEEMPTY)) + { + unlink (ctx->path); + mx_fastclose_mailbox (ctx); + return 0; + } + + /* update memory to reflect the new state of the mailbox */ + ctx->vcount = 0; + ctx->vsize = 0; + ctx->tagged = 0; + ctx->deleted = 0; + ctx->new = 0; + ctx->unread = 0; + ctx->changed = 0; + ctx->flagged = 0; +#define this_body ctx->hdrs[j]->content + for (i = 0, j = 0; i < ctx->msgcount; i++) + { + if (!ctx->hdrs[i]->deleted) + { + if (i != j) + { + ctx->hdrs[j] = ctx->hdrs[i]; + ctx->hdrs[i] = NULL; + } + ctx->hdrs[j]->msgno = j; + if (ctx->hdrs[j]->virtual != -1) + { + ctx->v2r[ctx->vcount] = j; + ctx->hdrs[j]->virtual = ctx->vcount++; + ctx->vsize += this_body->length + this_body->offset - + this_body->hdr_offset; + } + ctx->hdrs[j]->changed = 0; + if (ctx->hdrs[j]->tagged) + ctx->tagged++; + if (ctx->hdrs[j]->flagged) + ctx->flagged++; + if (!ctx->hdrs[j]->read) + { + ctx->unread++; + if (!ctx->hdrs[j]->old) + ctx->new++; + } + j++; + } + else + { + if (ctx->magic == M_MH || ctx->magic == M_MAILDIR) + ctx->size -= (ctx->hdrs[i]->content->length + + ctx->hdrs[i]->content->offset - + ctx->hdrs[i]->content->hdr_offset); + /* remove message from the hash tables */ + if (ctx->hdrs[i]->env->real_subj) + hash_delete (ctx->subj_hash, ctx->hdrs[i]->env->real_subj, ctx->hdrs[i], NULL); + if (ctx->hdrs[i]->env->message_id) + hash_delete (ctx->id_hash, ctx->hdrs[i]->env->message_id, ctx->hdrs[i], NULL); + mutt_free_header (&ctx->hdrs[i]); + } + } +#undef this_body + ctx->msgcount = j; + + mutt_sort_headers (ctx, 1); /* rethread from scratch */ + } + + return (rc); +} + +int mh_open_new_message (MESSAGE *msg, CONTEXT *dest, HEADER *hdr) +{ + int hi = 1; + int fd, n; + char *cp; + char path[_POSIX_PATH_MAX]; + DIR *dirp; + struct dirent *de; + + do + { + if ((dirp = opendir (dest->path)) == NULL) + { + mutt_perror (dest->path); + return (-1); + } + + /* figure out what the next message number is */ + while ((de = readdir (dirp)) != NULL) + { + cp = de->d_name; + while (*cp) + { + if (!isdigit (*cp)) + break; + cp++; + } + if (!*cp) + { + n = atoi (de->d_name); + if (n > hi) + hi = n; + } + } + closedir (dirp); + hi++; + snprintf (path, sizeof (path), "%s/%d", dest->path, hi); + if ((fd = open (path, O_WRONLY | O_EXCL | O_CREAT, 0600)) == -1) + { + if (errno != EEXIST) + { + mutt_perror (path); + return (-1); + } + } + } + while (fd < 0); + + if ((msg->fp = fdopen (fd, "w")) == NULL) + return (-1); + + return 0; +} + +int maildir_open_new_message (MESSAGE *msg, CONTEXT *dest, HEADER *hdr) +{ + char tmp[_POSIX_PATH_MAX]; + char path[_POSIX_PATH_MAX]; + + maildir_create_filename (dest->path, hdr, path, tmp); + if ((msg->fp = safe_fopen (tmp, "w")) == NULL) + return (-1); + return 0; +} + +int mbox_open_new_message (MESSAGE *msg, CONTEXT *dest, HEADER *hdr) +{ + msg->fp = dest->fp; + return 0; +} + +/* args: + * dest destintation mailbox + * hdr message being copied (required for maildir support, because + * the filename depends on the message flags) + */ +MESSAGE *mx_open_new_message (CONTEXT *dest, HEADER *hdr, int flags) +{ + MESSAGE *msg; + int (*func) (MESSAGE *, CONTEXT *, HEADER *); + ADDRESS *p = NULL; + time_t t; + + switch (dest->magic) + { + case M_MMDF: + case M_MBOX: + func = mbox_open_new_message; + break; + case M_MAILDIR: + func = maildir_open_new_message; + break; + case M_MH: + func = mh_open_new_message; + break; + default: + dprint (1, (debugfile, "mx_open_new_message(): function unimplemented for mailbox type %d.\n", + dest->magic)); + return (NULL); + } + + msg = safe_calloc (1, sizeof (MESSAGE)); + msg->magic = dest->magic; + msg->write = 1; + + if (func (msg, dest, hdr) == 0) + { + if (dest->magic == M_MMDF) + fputs (MMDF_SEP, msg->fp); + + if (msg->magic != M_MAILDIR && (flags & M_ADD_FROM)) + { + if (hdr) + { + if (hdr->env->return_path) + p = hdr->env->return_path; + else if (hdr->env->sender) + p = hdr->env->sender; + else + p = hdr->env->from; + + if (!hdr->received) + hdr->received = time (NULL); + t = hdr->received; + } + else + t = time (NULL); + + fprintf (msg->fp, "From %s %s", p ? p->mailbox : Username, ctime (&t)); + } + } + else + safe_free ((void **) &msg); + + return msg; +} + +int mutt_reopen_mailbox (CONTEXT *ctx, int *index_hint) +{ + int (*cmp_headers) (const HEADER *, const HEADER *) = NULL; + HEADER **old_hdrs; + int old_msgcount; + int msg_mod = 0; + int index_hint_set; + int i, j; + int rc = -1; + + /* silent operations */ + ctx->quiet = 1; + + mutt_message ("Reopening mailbox..."); + + /* our heuristics require the old mailbox to be unsorted */ + if (Sort != SORT_ORDER) + { + short old_sort; + + old_sort = Sort; + Sort = SORT_ORDER; + mutt_sort_headers (ctx, 1); + Sort = old_sort; + } + + /* save the old headers */ + old_msgcount = ctx->msgcount; + old_hdrs = ctx->hdrs; + + /* simulate a close */ + hash_destroy (&ctx->id_hash, NULL); + hash_destroy (&ctx->subj_hash, NULL); + safe_free ((void **) &ctx->v2r); + if (ctx->readonly) + { + for (i = 0; i < ctx->msgcount; i++) + mutt_free_header (&(ctx->hdrs[i])); /* nothing to do! */ + safe_free ((void **) &ctx->hdrs); + } + else + ctx->hdrs = NULL; + + ctx->hdrmax = 0; /* force allocation of new headers */ + ctx->msgcount = 0; + ctx->vcount = 0; + ctx->tagged = 0; + ctx->deleted = 0; + ctx->new = 0; + ctx->unread = 0; + ctx->flagged = 0; + ctx->changed = 0; + ctx->id_hash = hash_create (257); + ctx->subj_hash = hash_create (257); + + switch (ctx->magic) + { + case M_MBOX: + fseek (ctx->fp, 0, 0); + cmp_headers = mbox_strict_cmp_headers; + rc = mbox_parse_mailbox (ctx); + break; + + case M_MMDF: + fseek (ctx->fp, 0, 0); + cmp_headers = mbox_strict_cmp_headers; + rc = mmdf_parse_mailbox (ctx); + break; + + case M_MH: + /* cmp_headers = mh_strict_cmp_headers; */ + rc = mh_read_dir (ctx, NULL); + break; + + case M_MAILDIR: + /* cmp_headers = maildir_strict_cmp_headers; */ + rc = maildir_read_dir (ctx); + break; + + default: + rc = -1; + break; + } + + if (rc == -1) + { + /* free the old headers */ + for (j = 0; j < old_msgcount; j++) + mutt_free_header (&(old_hdrs[j])); + safe_free ((void **) &old_hdrs); + + ctx->quiet = 0; + return (-1); + } + + /* now try to recover the old flags */ + + index_hint_set = (index_hint == NULL); + + if (!ctx->readonly) + { + for (i = 0; i < ctx->msgcount; i++) + { + int found = 0; + + /* some messages have been deleted, and new messages have been + * appended at the end; the heuristic is that old messages have then + * "advanced" towards the beginning of the folder, so we begin the + * search at index "i" + */ + for (j = i; j < old_msgcount; j++) + { + if (old_hdrs[j] == NULL) + continue; + if (cmp_headers (ctx->hdrs[i], old_hdrs[j])) + { + found = 1; + break; + } + } + if (!found) + { + for (j = 0; j < i; j++) + { + if (old_hdrs[j] == NULL) + continue; + if (cmp_headers (ctx->hdrs[i], old_hdrs[j])) + { + found = 1; + break; + } + } + } + + if (found) + { + /* this is best done here */ + if (!index_hint_set && *index_hint == j) + *index_hint = i; + + if (old_hdrs[j]->changed) + { + /* Only update the flags if the old header was changed; + * otherwise, the header may have been modified externally, + * and we don't want to lose _those_ changes + */ + mutt_set_flag (ctx, ctx->hdrs[i], M_FLAG, old_hdrs[j]->flagged); + mutt_set_flag (ctx, ctx->hdrs[i], M_REPLIED, old_hdrs[j]->replied); + mutt_set_flag (ctx, ctx->hdrs[i], M_OLD, old_hdrs[j]->old); + mutt_set_flag (ctx, ctx->hdrs[i], M_READ, old_hdrs[j]->read); + } + mutt_set_flag (ctx, ctx->hdrs[i], M_DELETE, old_hdrs[j]->deleted); + mutt_set_flag (ctx, ctx->hdrs[i], M_TAG, old_hdrs[j]->tagged); + + /* we don't need this header any more */ + mutt_free_header (&(old_hdrs[j])); + } + } + + /* free the remaining old headers */ + for (j = 0; j < old_msgcount; j++) + { + if (old_hdrs[j]) + { + mutt_free_header (&(old_hdrs[j])); + msg_mod = 1; + } + } + safe_free ((void **) &old_hdrs); + } + + ctx->quiet = 0; + + return ((ctx->changed || msg_mod) ? M_REOPENED : M_NEW_MAIL); +} + +/* check for new mail */ +int mx_check_mailbox (CONTEXT *ctx, int *index_hint) +{ + if (ctx) + { + switch (ctx->magic) + { + case M_MBOX: + case M_MMDF: + return (mbox_check_mailbox (ctx, index_hint)); + + case M_MH: + case M_MAILDIR: + return (mh_check_mailbox (ctx, index_hint)); + +#ifdef USE_IMAP + case M_IMAP: + return (imap_check_mailbox (ctx, index_hint)); +#endif /* USE_IMAP */ + } + } + + dprint (1, (debugfile, "mx_check_mailbox: null or invalid context.\n")); + return (-1); +} + +/* return a stream pointer for a message */ +MESSAGE *mx_open_message (CONTEXT *ctx, int msgno) +{ + MESSAGE *msg; + + msg = safe_calloc (1, sizeof (MESSAGE)); + switch (msg->magic = ctx->magic) + { + case M_MBOX: + case M_MMDF: + msg->fp = ctx->fp; + break; + + case M_MH: + case M_MAILDIR: + { + HEADER *cur = ctx->hdrs[msgno]; + char path[_POSIX_PATH_MAX]; + + snprintf (path, sizeof (path), "%s/%s", ctx->path, cur->path); + if ((msg->fp = fopen (path, "r")) == NULL) + { + mutt_perror (path); + dprint (1, (debugfile, "mx_open_message: fopen: %s: %s (errno %d).\n", + path, strerror (errno), errno)); + free (msg); + msg = NULL; + } + } + break; + +#ifdef USE_IMAP + case M_IMAP: + if (imap_fetch_message (msg, ctx, msgno) != 0) + { + free (msg); + msg = NULL; + } + break; +#endif /* USE_IMAP */ + + default: + + dprint (1, (debugfile, "mx_open_message(): function not implemented for mailbox type %d.\n", ctx->magic)); + safe_free ((void **) &msg); + break; + } + return (msg); +} + +/* close a pointer to a message */ +int mx_close_message (MESSAGE **msg) +{ + int r = 0; + + if ((*msg)->write) + { + /* add the message terminator */ + switch ((*msg)->magic) + { + case M_MMDF: + fputs (MMDF_SEP, (*msg)->fp); + break; + + case M_MBOX: + fputc ('\n', (*msg)->fp); + break; + } + } + + if ((*msg)->magic == M_MH || (*msg)->magic == M_MAILDIR || (*msg)->magic == M_IMAP) + r = fclose ((*msg)->fp); + + free (*msg); + *msg = NULL; + return (r); +} + +void mx_alloc_memory (CONTEXT *ctx) +{ + int i; + + if (ctx->hdrs) + { + safe_realloc ((void **) &ctx->hdrs, sizeof (HEADER *) * (ctx->hdrmax += 25)); + safe_realloc ((void **) &ctx->v2r, sizeof (int) * ctx->hdrmax); + } + else + { + ctx->hdrs = safe_malloc (sizeof (HEADER *) * (ctx->hdrmax += 25)); + ctx->v2r = safe_malloc (sizeof (int) * ctx->hdrmax); + } + for (i = ctx->msgcount ; i < ctx->hdrmax ; i++) + { + ctx->hdrs[i] = NULL; + ctx->v2r[i] = -1; + } +} + +/* this routine is called to update the counts in the context structure for + * the last message header parsed. + */ +void mx_update_context (CONTEXT *ctx) +{ + HEADER *h = ctx->hdrs[ctx->msgcount]; + + + + + + + + + + + + + +#ifdef _PGPPATH + /* NOTE: this _must_ be done before the check for mailcap! */ + h->pgp = pgp_query (h->content); + if (!h->pgp) +#endif /* _PGPPATH */ + + + + if (mutt_needs_mailcap (h->content)) + h->mailcap = 1; + if (h->flagged) + ctx->flagged++; + if (h->deleted) + ctx->deleted++; + if (!h->read) + { + ctx->unread++; + if (!h->old) + ctx->new++; + } + if (!ctx->pattern) + { + ctx->v2r[ctx->vcount] = ctx->msgcount; + h->virtual = ctx->vcount++; + } + else + h->virtual = -1; + h->msgno = ctx->msgcount; + ctx->msgcount++; + + if (h->env->supersedes) + { + HEADER *h2 = hash_find (ctx->id_hash, h->env->supersedes); + + /* safe_free (&h->env->supersedes); should I ? */ + if (h2) + { + h2->superseded = 1; + mutt_score_message (h2); + } + } + + /* add this message to the hash tables */ + if (h->env->message_id) + hash_insert (ctx->id_hash, h->env->message_id, h, 0); + if (h->env->real_subj) + hash_insert (ctx->subj_hash, h->env->real_subj, h, 1); + + mutt_score_message (h); +} diff --git a/mx.h b/mx.h new file mode 100644 index 00000000..f03c5908 --- /dev/null +++ b/mx.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * This header file contains prototypes for internal functions used by the + * generic mailbox api. None of these functions should be called directly. + */ + +/* supported mailbox formats */ +enum +{ + M_MBOX = 1, + M_MMDF, + M_MH, + M_MAILDIR, + M_IMAP +}; + +WHERE short DefaultMagic INITVAL (M_MBOX); + +#define MMDF_SEP "\001\001\001\001\n" + +int mbox_sync_mailbox (CONTEXT *); +int mbox_open_mailbox (CONTEXT *); +int mbox_check_mailbox (CONTEXT *, int *); +int mbox_close_mailbox (CONTEXT *); +int mbox_lock_mailbox (CONTEXT *, int, int); +int mbox_parse_mailbox (CONTEXT *); +int mmdf_parse_mailbox (CONTEXT *); + +int mh_read_dir (CONTEXT *, const char *); +int mh_sync_mailbox (CONTEXT *); +int mh_check_mailbox (CONTEXT *, int *); +int mh_parse_sequences (CONTEXT *, const char *); + +int maildir_read_dir (CONTEXT *); +void maildir_create_filename (const char *, HEADER *, char *, char *); + +int mbox_strict_cmp_headers (const HEADER *, const HEADER *); +int mutt_reopen_mailbox (CONTEXT *, int *); + +void mx_alloc_memory (CONTEXT *); +void mx_update_context (CONTEXT *); + +FILE *mx_open_file_lock (const char *, const char *); + +int mx_lock_file (const char *, int, int, int, int); +int mx_unlock_file (const char *path, int fd); diff --git a/pager.c b/pager.c new file mode 100644 index 00000000..63f9a260 --- /dev/null +++ b/pager.c @@ -0,0 +1,2207 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "mutt_regex.h" +#include "keymap.h" +#include "mutt_menu.h" +#include "sort.h" +#include "pager.h" +#include "attach.h" + + + +#ifdef _PGPPATH +#include "pgp.h" +#endif + + + + + + + + + +#include <sys/stat.h> +#include <ctype.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +#define M_NOSHOW 0 +#define M_SHOWFLAT (1 << 0) +#define M_SHOWCOLOR (1 << 1) +#define M_HIDE (1 << 2) +#define M_SEARCH (1 << 3) +#define M_TYPES (1 << 4) +#define M_SHOW (M_SHOWCOLOR | M_SHOWFLAT) + +#define ISHEADER(x) ((x) == MT_COLOR_HEADER || (x) == MT_COLOR_HDEFAULT) + +#define IsAttach(x) (x && (x)->bdy) +#define IsHeader(x) (x && (x)->hdr) + +#define CHECK_MODE(x) if (!(x)) \ + { \ + mutt_flushinp (); \ + mutt_error ("Not available in this menu."); \ + break; \ + } + +#define CHECK_READONLY if (Context->readonly) \ + { \ + mutt_flushinp (); \ + mutt_error ("Mailbox is read-only."); \ + break; \ + } + +struct q_class_t +{ + int length; + int color; + char *prefix; + struct q_class_t *next, *prev; + struct q_class_t *down, *up; +}; + +struct syntax_t +{ + int color; + short first; + short last; +}; + +struct line_t +{ + long offset; + short type; + short continuation; + short chunks; + short search_cnt; + struct syntax_t *syntax; + struct syntax_t *search; + struct q_class_t *quote; +}; + +#define ANSI_OFF (1<<0) +#define ANSI_BLINK (1<<1) +#define ANSI_BOLD (1<<2) +#define ANSI_UNDERLINE (1<<3) +#define ANSI_REVERSE (1<<4) +#define ANSI_COLOR (1<<5) + +typedef struct _ansi_attr { + int attr; + int fg; + int bg; + int pair; +} ansi_attr; + +static short InHelp = 0; + +#define NumSigLines 4 + +static int check_sig (const char *s, struct line_t *info, int n) +{ + int count = 0; + + while (n > 0 && count <= NumSigLines) + { + if (info[n].type != MT_COLOR_SIGNATURE) + break; + count++; + n--; + } + + if (count == 0) + return (-1); + + if (count > NumSigLines) + { + /* check for a blank line */ + while (*s) + { + if (!ISSPACE (*s)) + return 0; + s++; + } + + return (-1); + } + + return (0); +} + +static void +resolve_color (struct line_t *lineInfo, int n, int cnt, int flags, int special, + ansi_attr *a) +{ + int def_color; /* color without syntax hilight */ + int color; /* final color */ + static int last_color; /* last color set */ + int search = 0, i, m; + + if (!cnt) + last_color = -1; /* force attrset() */ + + if (lineInfo[n].continuation) + { + if (!cnt && option (OPTMARKERS)) + { + SETCOLOR (MT_COLOR_MARKERS); + addch ('+'); + last_color = ColorDefs[MT_COLOR_MARKERS]; + } + m = (lineInfo[n].syntax)[0].first; + cnt += (lineInfo[n].syntax)[0].last; + } + else + m = n; + if (!(flags & M_SHOWCOLOR)) + def_color = ColorDefs[MT_COLOR_NORMAL]; + else if (lineInfo[m].type == MT_COLOR_HEADER) + def_color = (lineInfo[m].syntax)[0].color; + else + def_color = ColorDefs[lineInfo[m].type]; + + if ((flags & M_SHOWCOLOR) && lineInfo[m].type == MT_COLOR_QUOTED) + { + struct q_class_t *class = lineInfo[m].quote; + + if (class) + { + def_color = class->color; + + while (class && class->length > cnt) + { + def_color = class->color; + class = class->up; + } + } + } + + color = def_color; + if (flags & M_SHOWCOLOR) + { + for (i = 0; i < lineInfo[m].chunks; i++) + { + /* we assume the chunks are sorted */ + if (cnt > (lineInfo[m].syntax)[i].last) + continue; + if (cnt < (lineInfo[m].syntax)[i].first) + break; + if (cnt != (lineInfo[m].syntax)[i].last) + { + color = (lineInfo[m].syntax)[i].color; + break; + } + /* don't break here, as cnt might be + * in the next chunk as well */ + } + } + + if (flags & M_SEARCH) + { + for (i = 0; i < lineInfo[m].search_cnt; i++) + { + if (cnt > (lineInfo[m].search)[i].last) + continue; + if (cnt < (lineInfo[m].search)[i].first) + break; + if (cnt != (lineInfo[m].search)[i].last) + { + color = ColorDefs[MT_COLOR_SEARCH]; + search = 1; + break; + } + } + } + + /* handle "special" bold & underlined characters */ + if (special || a->attr) + { + if (special == A_BOLD || (a->attr & ANSI_BOLD)) + { + if (ColorDefs[MT_COLOR_BOLD] && !search) + color = ColorDefs[MT_COLOR_BOLD]; + else + color ^= A_BOLD; + } + else if (special == A_UNDERLINE || (a->attr & ANSI_UNDERLINE)) + { + if (ColorDefs[MT_COLOR_UNDERLINE] && !search) + color = ColorDefs[MT_COLOR_UNDERLINE]; + else + color ^= A_UNDERLINE; + } + else if (a->attr & ANSI_REVERSE) + { + color ^= A_REVERSE; + } + else if (a->attr & ANSI_BLINK) + { + color ^= A_BLINK; + } +#ifdef HAVE_COLOR + else if (a->attr & ANSI_COLOR) + { + if (a->pair == -1) + a->pair = mutt_alloc_color (a->fg,a->bg); + color = a->pair; + } +#endif + else if (a->attr & ANSI_OFF) + { + a->attr = 0; + } + } + + if (color != last_color) + { + attrset (color); + last_color = color; + } +} + +static void +append_line (struct line_t *lineInfo, int n, int cnt) +{ + int m; + + lineInfo[n+1].type = lineInfo[n].type; + (lineInfo[n+1].syntax)[0].color = (lineInfo[n].syntax)[0].color; + lineInfo[n+1].continuation = 1; + + /* find the real start of the line */ + m = n; + while (m >= 0) + { + if (lineInfo[m].continuation == 0) break; + m--; + } + (lineInfo[n+1].syntax)[0].first = m; + (lineInfo[n+1].syntax)[0].last = (lineInfo[n].continuation) ? + cnt + (lineInfo[n].syntax)[0].last : cnt; +} + +static void +cleanup_quote (struct q_class_t **QuoteList) +{ + struct q_class_t *ptr; + + while (*QuoteList) + { + if ((*QuoteList)->down) + cleanup_quote (&((*QuoteList)->down)); + ptr = (*QuoteList)->next; + safe_free ((void **) &(*QuoteList)->prefix); + safe_free ((void **) QuoteList); + *QuoteList = ptr; + } + + return; +} + +static struct q_class_t * +classify_quote (struct q_class_t **QuoteList, const char *qptr, + int length, int *force_redraw, int *q_level) +{ + struct q_class_t *q_list = *QuoteList; + struct q_class_t *class = NULL, *tmp = NULL, *ptr; + char *tail_qptr; + int offset, tail_lng; + + /* Did I mention how much I like emulating Lisp in C? */ + + /* classify quoting prefix */ + while (q_list) + { + if (length <= q_list->length) + { + if (strncmp (qptr, q_list->prefix, length) == 0) + { + /* same prefix: return the current class */ + if (length == q_list->length) + return q_list; + + /* found shorter common prefix */ + if (tmp == NULL) + { + /* add a node above q_list */ + tmp = (struct q_class_t *) safe_calloc (1, sizeof (struct q_class_t)); + tmp->prefix = (char *) safe_calloc (1, length + 1); + strncpy (tmp->prefix, qptr, length); + tmp->length = length; + if (*q_level >= ColorQuoteUsed) + *q_level = 1; + else + (*q_level)++; + tmp->color = ColorQuote[(*q_level) - 1]; + + /* replace q_list by tmp in the top level list */ + if (q_list->next) + { + tmp->next = q_list->next; + q_list->next->prev = tmp; + } + if (q_list->prev) + { + tmp->prev = q_list->prev; + q_list->prev->next = tmp; + } + + /* make q_list a child of tmp */ + tmp->down = q_list; + q_list->up = tmp; + + /* q_list has no siblings */ + q_list->next = NULL; + q_list->prev = NULL; + + /* update the root if necessary */ + if (q_list == *QuoteList) + *QuoteList = tmp; + + /* tmp should be the return class too */ + class = tmp; + + /* next class to test */ + q_list = tmp->next; + } + else + { + /* save the next sibling for later */ + ptr = q_list->next; + + /* unlink q_list from the top level list */ + if (q_list->next) + q_list->next->prev = q_list->prev; + if (q_list->prev) + q_list->prev->next = q_list->next; + + /* at this point, we have a tmp->down; link q_list to it */ + q_list->next = tmp->down; + tmp->down->prev = q_list; + q_list->prev = NULL; + tmp->down = q_list; + q_list->up = tmp; + + /* next class to test */ + q_list = ptr; + } + + /* in both cases q_list points now to the next top-level node */ + *force_redraw = 1; + continue; + } + else + { + /* shorter, but not a substring of the current class: try next */ + q_list = q_list->next; + continue; + } + } + else + { + /* longer than the top level prefix: try subclassing it */ + if (tmp == NULL && strncmp (qptr, q_list->prefix, q_list->length) == 0) + { + /* ok, we may link it as a subclass */ + ptr = q_list; + offset = q_list->length; + + q_list = q_list->down; + tail_lng = length - offset; + tail_qptr = (char *) qptr + offset; + + while (q_list) + { + if (length <= q_list->length) + { + if (strncmp (tail_qptr, (q_list->prefix) + offset, tail_lng) == 0) + { + /* same prefix: return the current class */ + if (length == q_list->length) + return q_list; + + /* found shorter common prefix */ + if (tmp == NULL) + { + /* add a node above q_list */ + tmp = (struct q_class_t *) safe_calloc (1, + sizeof (struct q_class_t)); + tmp->prefix = (char *) safe_calloc (1, length + 1); + strncpy (tmp->prefix, qptr, length); + tmp->length = length; + if (*q_level >= ColorQuoteUsed) + *q_level = 1; + else + (*q_level)++; + tmp->color = ColorQuote[(*q_level) - 1]; + + /* replace q_list by tmp */ + if (q_list->next) + { + tmp->next = q_list->next; + q_list->next->prev = tmp; + } + if (q_list->prev) + { + tmp->prev = q_list->prev; + q_list->prev->next = tmp; + } + + /* make q_list a child of tmp */ + tmp->down = q_list; + tmp->up = q_list->up; + q_list->up = tmp; + if (tmp->up->down == q_list) + tmp->up->down = tmp; + + /* q_list has no siblings */ + q_list->next = NULL; + q_list->prev = NULL; + + /* tmp should be the return class too */ + class = tmp; + + /* next class to test */ + q_list = tmp->next; + } + else + { + /* save the next sibling for later */ + ptr = q_list->next; + + /* unlink q_list from the top level list */ + if (q_list->next) + q_list->next->prev = q_list->prev; + if (q_list->prev) + q_list->prev->next = q_list->next; + + /* at this point, we have a tmp->down; link q_list to it */ + q_list->next = tmp->down; + tmp->down->prev = q_list; + q_list->prev = NULL; + tmp->down = q_list; + q_list->up = tmp; + + /* next class to test */ + q_list = ptr; + } + + *force_redraw = 1; + continue; + } + else + { + q_list = q_list->next; + continue; + } + } + else + { + /* longer than the current prefix: try subclassing it */ + if (tmp == NULL && strncmp (tail_qptr, (q_list->prefix) + offset, + q_list->length - offset) == 0) + { + /* still a subclass: go down one level */ + ptr = q_list; + offset = q_list->length; + + q_list = q_list->down; + tail_lng = length - offset; + tail_qptr = (char *) qptr + offset; + + continue; + } + else + { + /* nope, try the next prefix */ + q_list = q_list->next; + continue; + } + } + } + + /* if it's still not found so far we mai add it as a sibling */ + if (class == NULL) + { + tmp = (struct q_class_t *) safe_calloc (1, sizeof (struct q_class_t)); + tmp->prefix = (char *) safe_calloc (1, length + 1); + strncpy (tmp->prefix, qptr, length); + tmp->length = length; + if (*q_level >= ColorQuoteUsed) + *q_level = 1; + else + (*q_level)++; + tmp->color = ColorQuote[(*q_level) - 1]; + + if (ptr->down) + { + tmp->next = ptr->down; + ptr->down->prev = tmp; + } + ptr->down = tmp; + tmp->up = ptr; + + return tmp; + } + else + return class; + } + else + { + /* nope, try the next prefix */ + q_list = q_list->next; + continue; + } + } + } + + if (class == NULL) + { + /* not found so far: add it as a top level class */ + class = (struct q_class_t *) safe_calloc (1, sizeof (struct q_class_t)); + class->prefix = (char *) safe_calloc (1, length + 1); + strncpy (class->prefix, qptr, length); + class->length = length; + if (*q_level >= ColorQuoteUsed) + *q_level = 1; + else + (*q_level)++; + class->color = ColorQuote[(*q_level) - 1]; + + if (*QuoteList) + { + class->next = *QuoteList; + (*QuoteList)->prev = class; + } + *QuoteList = class; + } + + return class; +} + +static void +resolve_types (const char *buf, struct line_t *lineInfo, int n, int last, + struct q_class_t **QuoteList, int *q_level, int *force_redraw, + int q_classify) +{ + COLOR_LINE *color_line; + regmatch_t pmatch[1]; + int found, offset, null_rx, i; + + if (n == 0 || ISHEADER (lineInfo[n-1].type)) + { + if (buf[0] == '\n') + lineInfo[n].type = MT_COLOR_NORMAL; + else if (n > 0 && (buf[0] == ' ' || buf[0] == '\t')) + { + lineInfo[n].type = lineInfo[n-1].type; /* wrapped line */ + (lineInfo[n].syntax)[0].color = (lineInfo[n-1].syntax)[0].color; + } + else + { + lineInfo[n].type = MT_COLOR_HDEFAULT; + color_line = ColorHdrList; + while (color_line) + { + if (REGEXEC (color_line->rx, buf) == 0) + { + lineInfo[n].type = MT_COLOR_HEADER; + lineInfo[n].syntax[0].color = color_line->pair; + break; + } + color_line = color_line->next; + } + } + } + else if (strncmp ("[-- ", buf, 4) == 0) + lineInfo[n].type = MT_COLOR_ATTACHMENT; + else if (strcmp ("-- \n", buf) == 0 || strcmp ("-- \r\n", buf) == 0) + { + i = n + 1; + + lineInfo[n].type = MT_COLOR_SIGNATURE; + while (i < last && check_sig (buf, lineInfo, i - 1) == 0 && + (lineInfo[i].type == MT_COLOR_NORMAL || + lineInfo[i].type == MT_COLOR_QUOTED || + lineInfo[i].type == MT_COLOR_HEADER)) + { + /* oops... */ + if (lineInfo[i].chunks) + { + lineInfo[i].chunks = 0; + safe_realloc ((void **) &(lineInfo[n].syntax), + sizeof (struct syntax_t)); + } + lineInfo[i++].type = MT_COLOR_SIGNATURE; + } + } + else if (check_sig (buf, lineInfo, n - 1) == 0) + lineInfo[n].type = MT_COLOR_SIGNATURE; + else if (regexec ((regex_t *) QuoteRegexp.rx, buf, 1, pmatch, 0) == 0 && + strncmp (buf, ">From ", 6)) + { + if (q_classify && lineInfo[n].quote == NULL) + lineInfo[n].quote = classify_quote (QuoteList, buf + pmatch[0].rm_so, + pmatch[0].rm_eo - pmatch[0].rm_so, + force_redraw, q_level); + lineInfo[n].type = MT_COLOR_QUOTED; + } + else + lineInfo[n].type = MT_COLOR_NORMAL; + + /* body patterns */ + if (lineInfo[n].type == MT_COLOR_NORMAL || + lineInfo[n].type == MT_COLOR_QUOTED) + { + i = 0; + + offset = 0; + lineInfo[n].chunks = 0; + do + { + if (!buf[offset]) + break; + + found = 0; + null_rx = 0; + color_line = ColorBodyList; + while (color_line) + { + if (regexec (&color_line->rx, buf + offset, 1, pmatch, + (offset ? REG_NOTBOL : 0)) == 0) + { + if (pmatch[0].rm_eo != pmatch[0].rm_so) + { + if (!found) + { + if (++(lineInfo[n].chunks) > 1) + safe_realloc ((void **)&(lineInfo[n].syntax), + (lineInfo[n].chunks) * sizeof (struct syntax_t)); + } + i = lineInfo[n].chunks - 1; + pmatch[0].rm_so += offset; + pmatch[0].rm_eo += offset; + if (!found || + pmatch[0].rm_so < (lineInfo[n].syntax)[i].first || + (pmatch[0].rm_so == (lineInfo[n].syntax)[i].first && + pmatch[0].rm_eo > (lineInfo[n].syntax)[i].last)) + { + (lineInfo[n].syntax)[i].color = color_line->pair; + (lineInfo[n].syntax)[i].first = pmatch[0].rm_so; + (lineInfo[n].syntax)[i].last = pmatch[0].rm_eo; + } + found = 1; + null_rx = 0; + } + else + null_rx = 1; /* empty regexp; don't add it, but keep looking */ + } + color_line = color_line->next; + } + + if (null_rx) + offset++; /* avoid degenerate cases */ + else + offset = (lineInfo[n].syntax)[i].last; + } while (found || null_rx); + } +} + +static int +fill_buffer (FILE *f, long *last_pos, long offset, unsigned char *buf, + unsigned char *fmt, size_t blen, int *buf_ready) +{ + unsigned char *p; + static int b_read; + + if (*buf_ready == 0) + { + buf[blen - 1] = 0; + if (offset != *last_pos) + fseek (f, offset, 0); + if (fgets ((char *) buf, blen - 1, f) == NULL) + { + fmt[0] = 0; + return (-1); + } + *last_pos = ftell (f); + b_read = (int) (*last_pos - offset); + *buf_ready = 1; + + /* copy "buf" to "fmt", but without bold and underline controls */ + p = buf; + while (*p) + { + if (*p == '\010' && (p > buf)) + { + if (*(p+1) == '_') /* underline */ + p += 2; + else if (*(p+1)) /* bold or overstrike */ + { + *(fmt-1) = *(p+1); + p += 2; + } + else /* ^H */ + *fmt++ = *p++; + } + else + *fmt++ = *p++; + } + *fmt = 0; + } + return b_read; +} + +static int grok_ansi(unsigned char *buf, int pos, ansi_attr *a) +{ + int x = pos; + + while (isdigit(buf[x]) || buf[x] == ';') + x++; + + /* Character Attributes */ + if (a != NULL && buf[x] == 'm') + { + while (pos < x) + { + if (buf[pos] == '1' && (pos+1 == x || buf[pos+1] == ';')) + { + a->attr |= ANSI_BOLD; + pos += 2; + } + else if (buf[pos] == '4' && (pos+1 == x || buf[pos+1] == ';')) + { + a->attr |= ANSI_UNDERLINE; + pos += 2; + } + else if (buf[pos] == '5' && (pos+1 == x || buf[pos+1] == ';')) + { + a->attr |= ANSI_BLINK; + pos += 2; + } + else if (buf[pos] == '7' && (pos+1 == x || buf[pos+1] == ';')) + { + a->attr |= ANSI_REVERSE; + pos += 2; + } + else if (buf[pos] == '0' && (pos+1 == x || buf[pos+1] == ';')) + { +#ifdef HAVE_COLOR + if (a->pair != -1) + mutt_free_color(a->fg,a->bg); +#endif + a->attr = ANSI_OFF; + a->pair = -1; + pos += 2; + } + else if (buf[pos] == '3' && isdigit(buf[pos+1])) + { + a->attr |= ANSI_COLOR; + a->fg = buf[pos+1] - '0'; + pos += 3; + } + else if (buf[pos] == '4' && isdigit(buf[pos+1])) + { + a->attr |= ANSI_COLOR; + a->bg = buf[pos+1] - '0'; + pos += 3; + } + else + { + while (pos < x && buf[pos] != ';') pos++; + pos++; + } + } + } + pos = x; + return pos; +} + +/* + * Args: + * flags M_NOSHOW, don't show characters + * M_SHOWFLAT, show characters (used for displaying help) + * M_SHOWCOLOR, show characters in color + * M_HIDE, don't show quoted text + * M_SEARCH, resolve search patterns + * M_TYPES, compute line's type + * + * Return values: + * -1 EOF was reached + * 0 normal exit, line was not displayed + * >0 normal exit, line was displayed + */ + +static int +display_line (FILE *f, long *last_pos, struct line_t **lineInfo, int n, + int *last, int *max, int flags, struct q_class_t **QuoteList, + int *q_level, int *force_redraw, regex_t *SearchRE) +{ + unsigned char buf[LONG_STRING], fmt[LONG_STRING]; + unsigned char *buf_ptr = buf, c; + int ch, vch, t, col, cnt, b_read; + int buf_ready = 0, change_last = 0; + int special = 0, last_special = 0; + int offset; + int def_color; + int m; + ansi_attr a = {0,0,0,-1}; + regmatch_t pmatch[1]; + + if (n == *last) + { + (*last)++; + change_last = 1; + } + + if (*last == *max) + { + safe_realloc ((void **)lineInfo, sizeof (struct line_t) * (*max += LINES)); + for (ch = *last; ch < *max ; ch++) + { + memset (&((*lineInfo)[ch]), 0, sizeof (struct line_t)); + (*lineInfo)[ch].type = -1; + (*lineInfo)[ch].search_cnt = -1; + (*lineInfo)[ch].syntax = safe_malloc (sizeof (struct syntax_t)); + ((*lineInfo)[ch].syntax)[0].first = ((*lineInfo)[ch].syntax)[0].last = -1; + } + } + + /* only do color hiliting if we are viewing a message */ + if (flags & (M_SHOWCOLOR | M_TYPES)) + { + if ((*lineInfo)[n].type == -1) + { + /* determine the line class */ + if (fill_buffer (f, last_pos, (*lineInfo)[n].offset, buf, fmt, sizeof (buf), &buf_ready) < 0) + { + if (change_last) + (*last)--; + return (-1); + } + + resolve_types ((char *) fmt, *lineInfo, n, *last, + QuoteList, q_level, force_redraw, flags & M_SHOWCOLOR); + } + + /* this also prevents searching through the hidden lines */ + if ((flags & M_HIDE) && (*lineInfo)[n].type == MT_COLOR_QUOTED) + flags = M_NOSHOW; + } + + /* At this point, (*lineInfo[n]).quote may still be undefined. We + * don't wont to compute it every time M_TYPES is set, since this + * would slow down the "bottom" function unacceptably. A compromise + * solution is hence to call regexec() again, just to find out the + * length of the quote prefix. + */ + if ((flags & M_SHOWCOLOR) && !(*lineInfo)[n].continuation && + (*lineInfo)[n].type == MT_COLOR_QUOTED && (*lineInfo)[n].quote == NULL) + { + if (fill_buffer (f, last_pos, (*lineInfo)[n].offset, buf, fmt, sizeof (buf), &buf_ready) < 0) + { + if (change_last) + (*last)--; + return (-1); + } + regexec ((regex_t *) QuoteRegexp.rx, (char *) fmt, 1, pmatch, 0); + (*lineInfo)[n].quote = classify_quote (QuoteList, + (char *) fmt + pmatch[0].rm_so, + pmatch[0].rm_eo - pmatch[0].rm_so, + force_redraw, q_level); + } + + if ((flags & M_SEARCH) && !(*lineInfo)[n].continuation && (*lineInfo)[n].search_cnt == -1) + { + if (fill_buffer (f, last_pos, (*lineInfo)[n].offset, buf, fmt, sizeof (buf), &buf_ready) < 0) + { + if (change_last) + (*last)--; + return (-1); + } + + offset = 0; + (*lineInfo)[n].search_cnt = 0; + while (regexec (SearchRE, (char *) fmt + offset, 1, pmatch, (offset ? REG_NOTBOL : 0)) == 0) + { + if (++((*lineInfo)[n].search_cnt) > 1) + safe_realloc ((void **) &((*lineInfo)[n].search), + ((*lineInfo)[n].search_cnt) * sizeof (struct syntax_t)); + else + (*lineInfo)[n].search = safe_malloc (sizeof (struct syntax_t)); + pmatch[0].rm_so += offset; + pmatch[0].rm_eo += offset; + ((*lineInfo)[n].search)[(*lineInfo)[n].search_cnt - 1].first = pmatch[0].rm_so; + ((*lineInfo)[n].search)[(*lineInfo)[n].search_cnt - 1].last = pmatch[0].rm_eo; + + if (pmatch[0].rm_eo == pmatch[0].rm_so) + offset++; /* avoid degenerate cases */ + else + offset = pmatch[0].rm_eo; + if (!fmt[offset]) + break; + } + } + + if (!(flags & M_SHOW) && (*lineInfo)[n+1].offset > 0) + { + /* we've already scanned this line, so just exit */ + return 0; + } + if ((flags & M_SHOWCOLOR) && *force_redraw && (*lineInfo)[n+1].offset > 0) + { + /* no need to try to display this line... */ + return 1; /* fake display */ + } + + if ((b_read = fill_buffer (f, last_pos, (*lineInfo)[n].offset, buf, fmt, + sizeof (buf), &buf_ready)) < 0) + { + if (change_last) + (*last)--; + return (-1); + } + + /* now chose a good place to break the line */ + + ch = -1; /* index of the last space or TAB */ + cnt = 0; + col = option (OPTMARKERS) ? (*lineInfo)[n].continuation : 0; + while (col < COLS && cnt < b_read) + { + c = *buf_ptr++; + if (c == '\n') + break; + + while (*buf_ptr == '\010' && cnt + 2 < b_read) + { + cnt += 2; + buf_ptr += 2; + c = buf[cnt]; + } + + if (*buf_ptr == '\033' && *(buf_ptr + 1) && *(buf_ptr + 1) == '[') + { + cnt = grok_ansi(buf, cnt+3, NULL); + cnt++; + buf_ptr = buf + cnt; + continue; + } + + if (c == '\t') + { + ch = cnt; + /* expand TABs */ + if ((t = (col & ~7) + 8) < COLS) + { + col = t; + cnt++; + } + else + break; + } + else if (IsPrint (c)) + { + if (c == ' ') + ch = cnt; + col++; + cnt++; + } + else if (iscntrl (c) && c < '@') + { + if (c == '\r' && *buf_ptr == '\n') + cnt++; + else if (col < COLS - 1) + { + col += 2; + cnt++; + } + else + break; + } + else + { + col++; + cnt++; + } + } + + /* move the break point only if smart_wrap is set */ + if (option (OPTWRAP)) + { + if (col == COLS) + { + if (ch != -1 && buf[cnt] != ' ' && buf[cnt] != '\t' && buf[cnt] != '\n' && buf[cnt] != '\r') + { + buf_ptr = buf + ch; + /* skip trailing blanks */ + while (ch && (buf[ch] == ' ' || buf[ch] == '\t' || buf[ch] == '\r')) + ch--; + cnt = ch + 1; + } + else + buf_ptr = buf + cnt; /* a very long word... */ + } + /* skip leading blanks on the next line too */ + while (*buf_ptr == ' ' || *buf_ptr == '\t') + buf_ptr++; + } + + if (*buf_ptr == '\r') + buf_ptr++; + if (*buf_ptr == '\n') + buf_ptr++; + + if ((int) (buf_ptr - buf) < b_read && !(*lineInfo)[n+1].continuation) + append_line (*lineInfo, n, (int) (buf_ptr - buf)); + (*lineInfo)[n+1].offset = (*lineInfo)[n].offset + (long) (buf_ptr - buf); + + /* if we don't need to display the line we are done */ + if (!(flags & M_SHOW)) + return 0; + + if (flags & M_SHOWCOLOR) + { + m = ((*lineInfo)[n].continuation) ? ((*lineInfo)[n].syntax)[0].first : n; + if ((*lineInfo)[m].type == MT_COLOR_HEADER) + def_color = ((*lineInfo)[m].syntax)[0].color; + else + def_color = (*lineInfo)[m].type; + + attrset (def_color); +#ifdef HAVE_BKGDSET + bkgdset (def_color | ' '); +#endif + clrtoeol (); + SETCOLOR (MT_COLOR_NORMAL); + BKGDSET (MT_COLOR_NORMAL); + } + else + clrtoeol (); + + /* display the line */ + col = option (OPTMARKERS) ? (*lineInfo)[n].continuation : 0; + for (ch = 0, vch = 0; ch < cnt; ch++, vch++) + { + special = 0; + + c = buf[ch]; + while (buf[ch+1] == '\010' && ch+2 < b_read) + { + if (buf[ch+2] == c) + { + special = A_BOLD; + last_special = 1; + ch += 2; + } + else if (buf[ch] == '_' || buf[ch+2] == '_') + { + special = A_UNDERLINE; + last_special = 1; + ch += 2; + c = (buf[ch] == '_') ? buf[ch-2] : buf[ch]; + } + else + { + special = 0; /* overstrike: nothing to do! */ + last_special = 0; + ch += 2; + c = buf[ch]; + } + } + + /* Handle ANSI sequences */ + if (c == '\033' && buf[ch+1] == '[') + { + ch = grok_ansi(buf, ch+2, &a); + c = buf[ch]; + continue; + } + + if (c == '\t') + { + if ((flags & (M_SHOWCOLOR | M_SEARCH)) || last_special || a.attr) + { + resolve_color (*lineInfo, n, vch, flags, special, &a); + if (!special) + last_special = 0; + } + + t = (col & ~7) + 8; + while (col < t) + { + addch (' '); + col++; + } + } + else if (IsPrint (c)) + { + if ((flags & (M_SHOWCOLOR | M_SEARCH)) || special || last_special + || a.attr) + resolve_color (*lineInfo, n, vch, flags, special, &a); + if (!special) + last_special = 0; + + addch (c); + col++; + } + else if (iscntrl (c) && c < '@') + { + if ((c != '\r' && c !='\n') || (buf[ch+1] != '\n' && buf[ch+1] != '\0')) + { + if ((flags & (M_SHOWCOLOR | M_SEARCH)) || last_special || a.attr) + { + resolve_color (*lineInfo, n, vch, flags, special, &a); + if (!special) + last_special = 0; + } + + addch ('^'); + addch (c + '@'); + col += 2; + } + } + else + { + if ((flags & (M_SHOWCOLOR | M_SEARCH)) || last_special || a.attr) + { + resolve_color (*lineInfo, n, vch, flags, special, &a); + if (!special) + last_special = 0; + } + + if (ISSPACE (c)) + addch (c); /* unbreakable space */ + else + addch ('.'); + col++; + } + } + + /* avoid a bug in ncurses... */ +#ifndef USE_SLANG_CURSES + if (col == 0) + { + SETCOLOR (MT_COLOR_NORMAL); + addch (' '); + } +#endif + + /* end the last color pattern (needed by S-Lang) */ + if (last_special || (col != COLS && (flags & (M_SHOWCOLOR | M_SEARCH)))) + resolve_color (*lineInfo, n, vch, flags, 0, &a); + + /* ncurses always wraps lines when you get to the right side of the + * screen, but S-Lang seems to only wrap if the next character is *not* + * a newline (grr!). + */ +#ifndef USE_SLANG_CURSES + if (col < COLS) +#endif + addch ('\n'); + + /* build a return code */ + if (!(flags & M_SHOW)) + flags = 0; + + return (flags); +} + +static int +upNLines (int nlines, struct line_t *info, int cur, int hiding) +{ + while (cur > 0 && nlines > 0) + { + cur--; + if (!hiding || info[cur].type != MT_COLOR_QUOTED) + nlines--; + } + + return cur; +} + +/* This pager is actually not so simple as it once was. It now operates in + two modes: one for viewing messages and the other for viewing help. These + can be distinguished by whether or not ``hdr'' is NULL. The ``hdr'' arg + is there so that we can do operations on the current message without the + need to pop back out to the main-menu. */ +int +mutt_pager (const char *banner, const char *fname, int do_color, pager_t *extra) +{ + static char searchbuf[STRING]; + char buffer[LONG_STRING]; + char helpstr[SHORT_STRING]; + int maxLine, lastLine = 0; + struct line_t *lineInfo; + struct q_class_t *QuoteList = NULL; + int i, j, ch = 0, rc = -1, hideQuoted = 0, q_level = 0, force_redraw = 0; + int lines = 0, curline = 0, topline = 0, oldtopline = 0, err, first = 1; + int r = -1; + int redraw = REDRAW_FULL; + FILE *fp = NULL; + long last_pos = 0, last_offset = 0; + int old_smart_wrap, old_markers; + struct stat sb; + regex_t SearchRE; + int SearchCompiled = 0, SearchFlag = 0, SearchBack = 0; + int has_types = (IsHeader(extra) || do_color); /* main message or rfc822 attachment */ + + int bodyoffset = 1; /* offset of first line of real text */ + int statusoffset = 0; /* offset for the status bar */ + int helpoffset = LINES - 2; /* offset for the help bar. */ + int bodylen = LINES - 2 - bodyoffset; /* length of displayable area */ + + MUTTMENU *index = NULL; /* the Pager Index (PI) */ + int indexoffset = 0; /* offset for the PI */ + int indexlen = PagerIndexLines; /* indexlen not always == PIL */ + int indicator = indexlen / 3; /* the indicator line of the PI */ + int old_PagerIndexLines; /* some people want to resize it + * while inside the pager... */ + + static struct mapping_t PagerHelp[] = { + { "Exit", OP_PAGER_EXIT }, + { "PrevPg", OP_PREV_PAGE }, + { "NextPg ", OP_NEXT_PAGE }, + { NULL, 0 } + }; + static struct mapping_t PagerHelpExtra[] = { + { "Attach", OP_VIEW_ATTACHMENTS }, + { "Del", OP_DELETE }, + { "Reply", OP_REPLY }, + { "Next ", OP_MAIN_NEXT_UNDELETED }, + { NULL, 0 } + }; + + do_color = do_color ? M_SHOWCOLOR : M_SHOWFLAT; + + if ((fp = fopen (fname, "r")) == NULL) + { + mutt_perror (fname); + return (-1); + } + + if (stat (fname, &sb) != 0) + { + mutt_perror (fname); + fclose (fp); + return (-1); + } + unlink (fname); + + /* Initialize variables */ + + if (IsHeader (extra) && !extra->hdr->read) + { + Context->msgnotreadyet = extra->hdr->msgno; + mutt_set_flag (Context, extra->hdr, M_READ, 1); + } + + lineInfo = safe_malloc (sizeof (struct line_t) * (maxLine = LINES)); + for (i = 0 ; i < maxLine ; i++) + { + memset (&lineInfo[i], 0, sizeof (struct line_t)); + lineInfo[i].type = -1; + lineInfo[i].search_cnt = -1; + lineInfo[i].syntax = safe_malloc (sizeof (struct syntax_t)); + (lineInfo[i].syntax)[0].first = (lineInfo[i].syntax)[0].last = -1; + } + + mutt_compile_help (helpstr, sizeof (helpstr), MENU_PAGER, PagerHelp); + if (IsHeader (extra)) + { + mutt_compile_help (buffer, sizeof (buffer), MENU_PAGER, PagerHelpExtra); + strcat (helpstr, buffer); + } + if (!InHelp) + { + mutt_make_help (buffer, sizeof (buffer), "Help", MENU_PAGER, OP_HELP); + strcat (helpstr, buffer); + } + + while (ch != -1) + { + mutt_curs_set (0); + + if (redraw & REDRAW_FULL) + { + SETCOLOR (MT_COLOR_NORMAL); + /* clear() doesn't optimize screen redraws */ + move (0, 0); + clrtobot (); + + if (IsHeader (extra) && Context->vcount + 1 < PagerIndexLines) + indexlen = Context->vcount + 1; + else + indexlen = PagerIndexLines; + + indicator = indexlen / 3; + + if (option (OPTSTATUSONTOP)) + { + indexoffset = 0; + statusoffset = IsHeader (extra) ? indexlen : 0; + bodyoffset = statusoffset + 1; + helpoffset = LINES - 2; + bodylen = helpoffset - bodyoffset; + if (!option (OPTHELP)) + bodylen++; + } + else + { + helpoffset = 0; + indexoffset = 1; + statusoffset = LINES - 2; + if (!option (OPTHELP)) + indexoffset = 0; + bodyoffset = indexoffset + (IsHeader (extra) ? indexlen : 0); + bodylen = statusoffset - bodyoffset; + } + + if (option (OPTHELP)) + { + SETCOLOR (MT_COLOR_STATUS); + mvprintw (helpoffset, 0, "%-*.*s", COLS, COLS, helpstr); + SETCOLOR (MT_COLOR_NORMAL); + } + + if (IsHeader (extra) && PagerIndexLines) + { + if (index == NULL) + { + /* only allocate the space if/when we need the index. + Initialise the menu as per the main index */ + index = mutt_new_menu(); + index->menu = MENU_MAIN; + index->make_entry = index_make_entry; + index->color = index_color; + index->max = Context->vcount; + index->current = extra->hdr->virtual; + } + + SETCOLOR (MT_COLOR_NORMAL); + index->offset = indexoffset + (option (OPTSTATUSONTOP) ? 1 : 0); + + index->pagelen = indexlen - 1; + + /* some fudge to work out where abouts the indicator should go */ + if (index->current - indicator < 0) + index->top = 0; + else if (index->max - index->current < index->pagelen - indicator) + index->top = index->max - index->pagelen; + else + index->top = index->current - indicator; + + menu_redraw_index(index); + } + + redraw |= REDRAW_BODY | REDRAW_INDEX | REDRAW_STATUS; + mutt_show_error (); + } + + if ((redraw & REDRAW_BODY) || topline != oldtopline) + { + do { + move (bodyoffset, 0); + curline = oldtopline = topline; + lines = 0; + force_redraw = 0; + + while (lines < bodylen && lineInfo[curline].offset <= sb.st_size - 1) + { + if (display_line (fp, &last_pos, &lineInfo, curline, &lastLine, + &maxLine, do_color | hideQuoted | SearchFlag, + &QuoteList, &q_level, &force_redraw, &SearchRE) > 0) + lines++; + curline++; + } + last_offset = lineInfo[curline].offset; + } while (force_redraw); + + SETCOLOR (MT_COLOR_TILDE); + BKGDSET (MT_COLOR_TILDE); + while (lines < bodylen) + { + clrtoeol (); + if (option (OPTTILDE)) + addch ('~'); + addch ('\n'); + lines++; + } + /* We are going to update the pager status bar, so it isn't + * necessary to reset to normal color now. */ + + redraw |= REDRAW_STATUS; /* need to update the % seen */ + } + + if (redraw & REDRAW_STATUS) + { + /* print out the pager status bar */ + SETCOLOR (MT_COLOR_STATUS); + BKGDSET (MT_COLOR_STATUS); + CLEARLINE (statusoffset); + if (IsHeader (extra)) + { + _mutt_make_string (buffer, + COLS-9 < sizeof (buffer) ? COLS-9 : sizeof (buffer), + NONULL (PagerFmt), extra->hdr, M_FORMAT_MAKEPRINT); + } + printw ("%-*.*s -- (", COLS-10, COLS-10, IsHeader (extra) ? buffer : banner); + if (last_pos < sb.st_size - 1) + printw ("%d%%)", (int) (100 * last_offset / sb.st_size)); + else + addstr (topline == 0 ? "all)" : "end)"); + BKGDSET (MT_COLOR_NORMAL); + SETCOLOR (MT_COLOR_NORMAL); + } + + if ((redraw & REDRAW_INDEX) && index) + { + /* redraw the pager_index indicator, because the + * flags for this message might have changed. */ + menu_redraw_current (index); + + /* print out the index status bar */ + menu_status_line (buffer, sizeof (buffer), index, Status); + move (indexoffset + (option (OPTSTATUSONTOP) ? 0 : (indexlen - 1)), 0); + SETCOLOR (MT_COLOR_STATUS); + printw ("%-*.*s", COLS, COLS, buffer); + SETCOLOR (MT_COLOR_NORMAL); + } + + redraw = 0; + + move (statusoffset, COLS-1); + mutt_refresh (); + ch = km_dokey (MENU_PAGER); + mutt_clear_error (); + mutt_curs_set (1); + + if (Signals & S_INTERRUPT) + { + mutt_query_exit (); + continue; + } +#if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM) + else if (Signals & S_SIGWINCH) + { + mutt_resize_screen (); + + for (i = 0; i < maxLine; i++) + { + lineInfo[i].offset = 0; + lineInfo[i].type = -1; + lineInfo[i].continuation = 0; + lineInfo[i].chunks = 0; + lineInfo[i].search_cnt = -1; + lineInfo[i].quote = NULL; + + safe_realloc ((void **)&(lineInfo[i].syntax), sizeof (struct syntax_t)); + if (SearchCompiled && lineInfo[i].search) + safe_free ((void **) &(lineInfo[i].search)); + } + + if (SearchCompiled) + { + regfree (&SearchRE); + SearchCompiled = 0; + SearchFlag = 0; + } + + lastLine = 0; + topline = 0; + + redraw = REDRAW_FULL; + Signals &= ~S_SIGWINCH; + ch = 0; + continue; + } +#endif + else if (ch == -1) + { + ch = 0; + continue; + } + + rc = ch; + + switch (ch) + { + case OP_PAGER_EXIT: + rc = -1; + ch = -1; + break; + + case OP_NEXT_PAGE: + if (lineInfo[curline].offset < sb.st_size-1) + { + topline = upNLines (PagerContext, lineInfo, curline, hideQuoted); + } + else if (option (OPTPAGERSTOP)) + { + /* emulate "less -q" and don't go on to the next message. */ + mutt_error ("Bottom of message is shown."); + } + else + { + /* end of the current message, so display the next message. */ + rc = OP_MAIN_NEXT_UNDELETED; + ch = -1; + } + break; + + case OP_PREV_PAGE: + if (topline != 0) + { + topline = upNLines (bodylen-PagerContext, lineInfo, topline, hideQuoted); + } + else + mutt_error ("Top of message is shown."); + break; + + case OP_NEXT_LINE: + if (lineInfo[curline].offset < sb.st_size-1) + { + topline++; + if (hideQuoted) + { + while (lineInfo[topline].type == MT_COLOR_QUOTED && + topline < lastLine) + topline++; + } + } + else + mutt_error ("Bottom of message is shown."); + break; + + case OP_PREV_LINE: + if (topline) + topline = upNLines (1, lineInfo, topline, hideQuoted); + else + mutt_error ("Top of message is shown."); + break; + + case OP_PAGER_TOP: + topline = 0; + break; + + case OP_HALF_UP: + if (topline) + topline = upNLines (bodylen/2, lineInfo, topline, hideQuoted); + else + mutt_error ("Top of message is shown."); + break; + + case OP_HALF_DOWN: + if (lineInfo[curline].offset < sb.st_size-1) + { + topline = upNLines (bodylen/2, lineInfo, curline, hideQuoted); + } + else if (option (OPTPAGERSTOP)) + { + /* emulate "less -q" and don't go on to the next message. */ + mutt_error ("Bottom of message is shown."); + } + else + { + /* end of the current message, so display the next message. */ + rc = OP_MAIN_NEXT_UNDELETED; + ch = -1; + } + break; + + case OP_SEARCH_NEXT: + case OP_SEARCH_OPPOSITE: + if (SearchCompiled) + { + if ((!SearchBack && ch==OP_SEARCH_NEXT) || + (SearchBack &&ch==OP_SEARCH_OPPOSITE)) + { + /* searching forward */ + for (i = topline + 1; i < lastLine; i++) + { + if ((!hideQuoted || lineInfo[i].type != MT_COLOR_QUOTED) && + !lineInfo[i].continuation && lineInfo[i].search_cnt > 0) + break; + } + + if (i < lastLine) + topline = i; + else + mutt_error ("Not found."); + } + else + { + /* searching backward */ + for (i = topline - 1; i >= 0; i--) + { + if ((!hideQuoted || (has_types && + lineInfo[i].type != MT_COLOR_QUOTED)) && + !lineInfo[i].continuation && lineInfo[i].search_cnt > 0) + break; + } + + if (i >= 0) + topline = i; + else + mutt_error ("Not found."); + } + + if (lineInfo[topline].search_cnt > 0) + SearchFlag = M_SEARCH; + + break; + } + /* no previous search pattern, so fall through to search */ + + case OP_SEARCH: + case OP_SEARCH_REVERSE: + /* leave SearchBack alone if ch == OP_SEARCH_NEXT */ + if (ch == OP_SEARCH) + SearchBack = 0; + else if (ch == OP_SEARCH_REVERSE) + SearchBack = 1; + + if (mutt_get_field ((SearchBack ? "Reverse search: " : "Search: "), + searchbuf, sizeof (searchbuf), M_CLEAR) != 0 || + !searchbuf[0]) + break; + + if (SearchCompiled) + { + regfree (&SearchRE); + for (i = 0; i < lastLine; i++) + { + if (lineInfo[i].search) + safe_free ((void **) &(lineInfo[i].search)); + lineInfo[i].search_cnt = -1; + } + } + + if ((err = REGCOMP (&SearchRE, searchbuf, REG_NEWLINE | mutt_which_case (searchbuf))) != 0) + { + regerror (err, &SearchRE, buffer, sizeof (buffer)); + mutt_error ("%s", buffer); + regfree (&SearchRE); + for (i = 0; i < maxLine ; i++) + { + /* cleanup */ + if (lineInfo[i].search) + safe_free ((void **) &(lineInfo[i].search)); + lineInfo[i].search_cnt = -1; + } + SearchFlag = 0; + SearchCompiled = 0; + } + else + { + SearchCompiled = 1; + /* update the search pointers */ + i = 0; + while (display_line (fp, &last_pos, &lineInfo, i, &lastLine, + &maxLine, M_SEARCH, &QuoteList, &q_level, + &force_redraw, &SearchRE) == 0) + i++; + + if (!SearchBack) + { + /* searching forward */ + for (i = topline; i < lastLine; i++) + { + if ((!hideQuoted || lineInfo[i].type != MT_COLOR_QUOTED) && + !lineInfo[i].continuation && lineInfo[i].search_cnt > 0) + break; + } + + if (i < lastLine) topline = i; + } + else + { + /* searching backward */ + for (i = topline; i >= 0; i--) + { + if ((!hideQuoted || lineInfo[i].type != MT_COLOR_QUOTED) && + !lineInfo[i].continuation && lineInfo[i].search_cnt > 0) + break; + } + + if (i >= 0) topline = i; + } + + if (lineInfo[topline].search_cnt == 0) + { + SearchFlag = 0; + mutt_error ("Not found."); + } + else + SearchFlag = M_SEARCH; + } + redraw = REDRAW_BODY; + break; + + case OP_SEARCH_TOGGLE: + if (SearchCompiled) + { + SearchFlag ^= M_SEARCH; + redraw = REDRAW_BODY; + } + break; + + case OP_HELP: + /* don't let the user enter the help-menu from the help screen! */ + if (! InHelp) + { + InHelp = 1; + mutt_help (MENU_PAGER); + redraw = REDRAW_FULL; + InHelp = 0; + } + else + mutt_error ("Help is currently being shown."); + break; + + case OP_PAGER_HIDE_QUOTED: + if (has_types) + { + hideQuoted = hideQuoted ? 0 : M_HIDE; + if (hideQuoted && lineInfo[topline].type == MT_COLOR_QUOTED) + topline = upNLines (1, lineInfo, topline, hideQuoted); + else + redraw = REDRAW_BODY; + } + break; + + case OP_PAGER_SKIP_QUOTED: + if (has_types) + { + int dretval = 0; + int new_topline = topline; + + while ((new_topline < lastLine || + (0 == (dretval = display_line (fp, &last_pos, &lineInfo, + new_topline, &lastLine, &maxLine, M_TYPES, + &QuoteList, &q_level, &force_redraw, &SearchRE)))) + && lineInfo[new_topline].type != MT_COLOR_QUOTED) + new_topline++; + + if (dretval < 0) + { + mutt_error ("No more quoted text."); + break; + } + + while ((new_topline < lastLine || + (0 == (dretval = display_line (fp, &last_pos, &lineInfo, + new_topline, &lastLine, &maxLine, M_TYPES, + &QuoteList, &q_level, &force_redraw, &SearchRE)))) + && lineInfo[new_topline].type == MT_COLOR_QUOTED) + new_topline++; + + if (dretval < 0) + { + mutt_error ("No more unquoted text after quoted text."); + break; + } + topline = new_topline; + } + break; + + case OP_PAGER_BOTTOM: /* move to the end of the file */ + if (lineInfo[curline].offset < sb.st_size - 1) + { + i = curline; + /* make sure the types are defined to the end of file */ + while (display_line (fp, &last_pos, &lineInfo, i, &lastLine, + &maxLine, (has_types ? M_TYPES : M_NOSHOW), + &QuoteList, &q_level, &force_redraw, + &SearchRE) == 0) + i++; + topline = upNLines (bodylen, lineInfo, lastLine, hideQuoted); + } + else + mutt_error ("Bottom of message is shown."); + break; + + case OP_REDRAW: + clearok (stdscr, TRUE); + redraw = REDRAW_FULL; + break; + + case OP_NULL: + km_error_key (MENU_PAGER); + break; + + /* -------------------------------------------------------------------- + * The following are operations on the current message rather than + * adjusting the view of the message. + */ + + case OP_BOUNCE_MESSAGE: + CHECK_MODE(IsHeader (extra)); + ci_bounce_message (extra->hdr, &redraw); + break; + + case OP_CREATE_ALIAS: + CHECK_MODE(IsHeader (extra)); + mutt_create_alias (extra->hdr->env, NULL); + MAYBE_REDRAW (redraw); + break; + + case OP_DELETE: + CHECK_MODE(IsHeader (extra)); + CHECK_READONLY; + mutt_set_flag (Context, extra->hdr, M_DELETE, 1); + redraw = REDRAW_STATUS | REDRAW_INDEX; + if (option (OPTRESOLVE)) + { + ch = -1; + rc = OP_MAIN_NEXT_UNDELETED; + }; + break; + + case OP_DELETE_THREAD: + case OP_DELETE_SUBTHREAD: + CHECK_MODE(IsHeader (extra)); + CHECK_READONLY; + + r = mutt_thread_set_flag (extra->hdr, M_DELETE, 1, + ch == OP_DELETE_THREAD ? 0 : 1); + + if (r != -1) + { + if (option (OPTRESOLVE)) + { + rc = (ch == OP_DELETE_THREAD) ? + OP_MAIN_NEXT_THREAD : OP_MAIN_NEXT_SUBTHREAD; + ch = -1; + } + + if (!option (OPTRESOLVE) && PagerIndexLines) + redraw = REDRAW_FULL; + else + redraw = REDRAW_STATUS | REDRAW_INDEX; + } + break; + + case OP_DISPLAY_ADDRESS: + CHECK_MODE(IsHeader (extra)); + mutt_display_address (extra->hdr->env->from); + break; + + case OP_ENTER_COMMAND: + old_smart_wrap = option (OPTWRAP); + old_markers = option (OPTMARKERS); + old_PagerIndexLines = PagerIndexLines; + + mutt_enter_command (); + + if (option (OPTNEEDRESORT)) + { + unset_option (OPTNEEDRESORT); + CHECK_MODE(IsHeader (extra)); + set_option (OPTNEEDRESORT); + } + + if (old_PagerIndexLines != PagerIndexLines) + { + if (index) + mutt_menuDestroy (&index); + index = NULL; + } + + if (option (OPTWRAP) != old_smart_wrap || + option (OPTMARKERS) != old_markers) + { + /* count the real lines above */ + j = 0; + for (i = 0; i <= topline; i++) + { + if (!lineInfo[i].continuation) + j++; + } + + /* we need to restart the whole thing */ + for (i = 0; i < maxLine; i++) + { + lineInfo[i].offset = 0; + lineInfo[i].type = -1; + lineInfo[i].continuation = 0; + lineInfo[i].chunks = 0; + lineInfo[i].search_cnt = -1; + lineInfo[i].quote = NULL; + + safe_realloc ((void **)&(lineInfo[i].syntax), sizeof (struct syntax_t)); + if (SearchCompiled && lineInfo[i].search) + safe_free ((void **) &(lineInfo[i].search)); + } + + if (SearchCompiled) + { + regfree (&SearchRE); + SearchCompiled = 0; + } + SearchFlag = 0; + + /* try to keep the old position */ + topline = 0; + lastLine = 0; + while (j > 0 && display_line (fp, &last_pos, &lineInfo, topline, + &lastLine, &maxLine, + (has_types ? M_TYPES : 0), + &QuoteList, &q_level, &force_redraw, + &SearchRE) == 0) + { + if (! lineInfo[topline].continuation) + j--; + if (j > 0) + topline++; + } + + ch = 0; + } + + if (option (OPTFORCEREDRAWPAGER)) + redraw = REDRAW_FULL; + unset_option (OPTFORCEREDRAWINDEX); + unset_option (OPTFORCEREDRAWPAGER); + break; + + case OP_FLAG_MESSAGE: + CHECK_MODE(IsHeader (extra)); + CHECK_READONLY; + mutt_set_flag (Context, extra->hdr, M_FLAG, !extra->hdr->flagged); + redraw = REDRAW_STATUS | REDRAW_INDEX; + if (option (OPTRESOLVE)) + { + ch = -1; + rc = OP_MAIN_NEXT_UNDELETED; + } + break; + + case OP_PIPE: + CHECK_MODE(IsHeader (extra) || IsAttach (extra)); + if (IsAttach (extra)) + mutt_pipe_attachment_list (extra->fp, 0, extra->bdy, 0); + else + mutt_pipe_message (extra->hdr); + break; + + case OP_PRINT: + CHECK_MODE(IsHeader (extra)); + mutt_print_message (extra->hdr); + break; + + case OP_MAIL: + CHECK_MODE(IsHeader (extra)); + ci_send_message (0, NULL, NULL, NULL, NULL); + redraw = REDRAW_FULL; + break; + + case OP_REPLY: + CHECK_MODE(IsHeader (extra)); + ci_send_message (SENDREPLY, NULL, NULL, extra->ctx, extra->hdr); + redraw = REDRAW_FULL; + break; + + case OP_RECALL_MESSAGE: + CHECK_MODE(IsHeader (extra)); + ci_send_message (SENDPOSTPONED, NULL, NULL, extra->ctx, extra->hdr); + redraw = REDRAW_FULL; + break; + + case OP_GROUP_REPLY: + CHECK_MODE(IsHeader (extra)); + ci_send_message (SENDREPLY | SENDGROUPREPLY, NULL, NULL, extra->ctx, extra->hdr); + redraw = REDRAW_FULL; + break; + + case OP_LIST_REPLY: + CHECK_MODE(IsHeader (extra)); + ci_send_message (SENDREPLY | SENDLISTREPLY, NULL, NULL, extra->ctx, extra->hdr); + redraw = REDRAW_FULL; + break; + + case OP_FORWARD_MESSAGE: + CHECK_MODE(IsHeader (extra)); + ci_send_message (SENDFORWARD, NULL, NULL, extra->ctx, extra->hdr); + redraw = REDRAW_FULL; + break; + + case OP_SAVE: + if (IsAttach (extra)) + { + mutt_save_attachment_list (extra->fp, 0, extra->bdy); + break; + } + /* fall through */ + case OP_COPY_MESSAGE: + case OP_DECODE_SAVE: + case OP_DECODE_COPY: + CHECK_MODE(IsHeader (extra)); + if (mutt_save_message (extra->hdr, + (ch == OP_SAVE || ch == OP_DECODE_SAVE), + (ch == OP_DECODE_SAVE || ch == OP_DECODE_COPY), + &redraw) == 0 && (ch == OP_SAVE || ch == OP_DECODE_SAVE)) + { + if (option (OPTRESOLVE)) + { + ch = -1; + rc = OP_MAIN_NEXT_UNDELETED; + } + else + redraw |= REDRAW_STATUS | REDRAW_INDEX; + } + MAYBE_REDRAW (redraw); + break; + + case OP_SHELL_ESCAPE: + mutt_shell_escape (); + MAYBE_REDRAW (redraw); + break; + + case OP_TAG: + CHECK_MODE(IsHeader (extra)); + mutt_set_flag (Context, extra->hdr, M_TAG, !extra->hdr->tagged); + redraw = REDRAW_STATUS | REDRAW_INDEX; + if (option (OPTRESOLVE)) + { + ch = -1; + rc = OP_MAIN_NEXT_UNDELETED; + } + break; + + case OP_TOGGLE_NEW: + CHECK_MODE(IsHeader (extra)); + CHECK_READONLY; + if (extra->hdr->read || extra->hdr->old) + mutt_set_flag (Context, extra->hdr, M_NEW, 1); + else if (!first) + mutt_set_flag (Context, extra->hdr, M_READ, 1); + first = 0; + Context->msgnotreadyet = -1; + redraw = REDRAW_STATUS | REDRAW_INDEX; + if (option (OPTRESOLVE)) + { + ch = -1; + rc = OP_NEXT_ENTRY; + } + break; + + case OP_UNDELETE: + CHECK_MODE(IsHeader (extra)); + CHECK_READONLY; + mutt_set_flag (Context, extra->hdr, M_DELETE, 0); + redraw = REDRAW_STATUS | REDRAW_INDEX; + if (option (OPTRESOLVE)) + { + ch = -1; + rc = OP_NEXT_ENTRY; + } + break; + + case OP_UNDELETE_THREAD: + case OP_UNDELETE_SUBTHREAD: + CHECK_MODE(IsHeader (extra)); + CHECK_READONLY; + + r = mutt_thread_set_flag (extra->hdr, M_DELETE, 0, + ch == OP_UNDELETE_THREAD ? 0 : 1); + + if (r != -1) + { + if (option (OPTRESOLVE)) + { + rc = (ch == OP_DELETE_THREAD) ? + OP_MAIN_NEXT_THREAD : OP_MAIN_NEXT_SUBTHREAD; + ch = -1; + } + + if (!option (OPTRESOLVE) && PagerIndexLines) + redraw = REDRAW_FULL; + else + redraw = REDRAW_STATUS | REDRAW_INDEX; + } + break; + + case OP_VERSION: + mutt_version (); + break; + + case OP_VIEW_ATTACHMENTS: + CHECK_MODE(IsHeader (extra)); + mutt_view_attachments (extra->hdr); + if (extra->hdr->attach_del) + Context->changed = 1; + redraw = REDRAW_FULL; + break; + + + +#ifdef _PGPPATH + case OP_FORGET_PASSPHRASE: + mutt_forget_passphrase (); + break; + + case OP_MAIL_KEY: + CHECK_MODE(IsHeader(extra)); + ci_send_message (SENDKEY, NULL, NULL, extra->ctx, extra->hdr); + redraw = REDRAW_FULL; + break; + + case OP_EXTRACT_KEYS: + CHECK_MODE(IsHeader(extra)); + pgp_extract_keys_from_messages(extra->hdr); + redraw = REDRAW_FULL; + break; +#endif /* _PGPPATH */ + + + + default: + ch = -1; + break; + } + } + + fclose (fp); + if (IsHeader (extra)) + Context->msgnotreadyet = -1; + + cleanup_quote (&QuoteList); + + for (i = 0; i < maxLine ; i++) + { + safe_free ((void **) &(lineInfo[i].syntax)); + if (SearchCompiled && lineInfo[i].search) + safe_free ((void **) &(lineInfo[i].search)); + } + if (SearchCompiled) + { + regfree (&SearchRE); + SearchCompiled = 0; + } + safe_free ((void **) &lineInfo); + if (index) + mutt_menuDestroy(&index); + return (rc != -1 ? rc : 0); +} diff --git a/pager.h b/pager.h new file mode 100644 index 00000000..bc3fab23 --- /dev/null +++ b/pager.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +typedef struct +{ + CONTEXT *ctx; /* current mailbox */ + HEADER *hdr; /* current message */ + BODY *bdy; /* current attachment */ + FILE *fp; /* source stream */ +} pager_t; + +int mutt_do_pager (const char *, const char *, int, pager_t *); +int mutt_pager (const char *, const char *, int, pager_t *); diff --git a/parse.c b/parse.c new file mode 100644 index 00000000..3ae7f944 --- /dev/null +++ b/parse.c @@ -0,0 +1,1249 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_regex.h" +#include "mailbox.h" +#include "mime.h" +#include "rfc2047.h" + + + +#ifdef _PGPPATH +#include "pgp.h" +#endif /* _PGPPATH */ + + + +#include <string.h> +#include <ctype.h> +#include <sys/stat.h> +#include <stdlib.h> + +/* Reads an arbitrarily long header field, and looks ahead for continuation + * lines. ``line'' must point to a dynamically allocated string; it is + * increased if more space is required to fit the whole line. + */ +static char *read_rfc822_line (FILE *f, char *line, size_t *linelen) +{ + char *buf = line; + char ch; + size_t offset = 0; + + FOREVER + { + if (fgets (buf, *linelen - offset, f) == NULL || /* end of file or */ + (ISSPACE (*line) && !offset)) /* end of headers */ + { + *line = 0; + return (line); + } + + buf += strlen (buf) - 1; + if (*buf == '\n') + { + /* we did get a full line. remove trailing space */ + while (ISSPACE (*buf)) + *buf-- = 0; /* we cannot come beyond line's beginning because + * it begins with a non-space */ + + /* check to see if the next line is a continuation line */ + if ((ch = fgetc (f)) != ' ' && ch != '\t') + { + ungetc (ch, f); + return (line); /* next line is a separate header field or EOH */ + } + + /* eat tabs and spaces from the beginning of the continuation line */ + while ((ch = fgetc (f)) == ' ' || ch == '\t') + ; + ungetc (ch, f); + *++buf = ' '; /* string is still terminated because we removed + at least one whitespace char above */ + } + + buf++; + offset = buf - line; + if (*linelen < offset + STRING) + { + /* grow the buffer */ + *linelen += STRING; + safe_realloc ((void **) &line, *linelen); + buf = line + offset; + } + } + /* not reached */ +} + +static LIST *mutt_parse_references (char *s) +{ + LIST *t, *lst = NULL; + + while ((s = strtok (s, " \t")) != NULL) + { + /* + * some mail clients add other garbage besides message-ids, so do a quick + * check to make sure this looks like a valid message-id + */ + if (*s == '<') + { + t = (LIST *)safe_malloc (sizeof (LIST)); + t->data = safe_strdup (s); + t->next = lst; + lst = t; + } + s = NULL; + } + + return (lst); +} + +int mutt_check_encoding (const char *c) +{ + if (strncasecmp ("7bit", c, sizeof ("7bit")-1) == 0) + return (ENC7BIT); + else if (strncasecmp ("8bit", c, sizeof ("8bit")-1) == 0) + return (ENC8BIT); + else if (strncasecmp ("binary", c, sizeof ("binary")-1) == 0) + return (ENCBINARY); + else if (strncasecmp ("quoted-printable", c, sizeof ("quoted-printable")-1) == 0) + return (ENCQUOTEDPRINTABLE); + else if (strncasecmp ("base64", c, sizeof("base64")-1) == 0) + return (ENCBASE64); + else + return (ENCOTHER); +} + +static PARAMETER *parse_parameters (const char *s) +{ + PARAMETER *head = 0, *cur = 0, *new; + char buffer[LONG_STRING]; + const char *p; + size_t i; + + while (*s) + { + if ((p = strpbrk (s, "=;")) == NULL) + { + dprint(1, (debugfile, "parse_parameters: malformed parameter: %s\n", s)); + return (head); /* just bail out now */ + } + + /* if we hit a ; now the parameter has no value, just skip it */ + if (*p != ';') + { + i = p - s; + + new = mutt_new_parameter (); + + new->attribute = safe_malloc (i + 1); + memcpy (new->attribute, s, i); + new->attribute[i] = 0; + + /* remove whitespace from the end of the attribute name */ + while (ISSPACE (new->attribute[--i])) + new->attribute[i] = 0; + + s = p + 1; /* skip over the = */ + SKIPWS (s); + + if (*s == '"') + { + s++; + for (i=0; *s && *s != '"' && i < sizeof (buffer) - 1; i++, s++) + { + if (*s == '\\') + { + /* Quote the next character */ + buffer[i] = s[1]; + if (!*++s) + break; + } + else + buffer[i] = *s; + } + buffer[i] = 0; + if (*s) + s++; /* skip over the " */ + } + else + { + for (i=0; *s && *s != ' ' && *s != ';' && i < sizeof (buffer) - 1; i++, s++) + buffer[i] = *s; + buffer[i] = 0; + } + + new->value = safe_strdup (buffer); + + /* Add this parameter to the list */ + if (head) + { + cur->next = new; + cur = cur->next; + } + else + head = cur = new; + } + else + { + dprint (1, (debugfile, "parse_parameters(): parameter with no value: %s\n", s)); + s = p; + } + + /* Find the next parameter */ + if (*s != ';' && (s = strchr (s, ';')) == NULL) + break; /* no more parameters */ + + do + { + s++; + + /* Move past any leading whitespace */ + SKIPWS (s); + } + while (*s == ';'); /* skip empty parameters */ + } + + return (head); +} + +int mutt_check_mime_type (const char *s) +{ + if (strcasecmp ("text", s) == 0) + return TYPETEXT; + else if (strcasecmp ("multipart", s) == 0) + return TYPEMULTIPART; + else if (strcasecmp ("application", s) == 0) + return TYPEAPPLICATION; + else if (strcasecmp ("message", s) == 0) + return TYPEMESSAGE; + else if (strcasecmp ("image", s) == 0) + return TYPEIMAGE; + else if (strcasecmp ("audio", s) == 0) + return TYPEAUDIO; + else if (strcasecmp ("video", s) == 0) + return TYPEVIDEO; + else + return TYPEOTHER; +} + +static void parse_content_type (char *s, BODY *ct) +{ + char *pc; + char buffer[SHORT_STRING]; + short i = 0; + + safe_free((void **)&ct->subtype); + mutt_free_parameter(&ct->parameter); + + /* First extract any existing parameters */ + if ((pc = strchr(s, ';')) != NULL) + { + *pc++ = 0; + while (*pc && ISSPACE (*pc)) + pc++; + ct->parameter = parse_parameters(pc); + + /* Some pre-RFC1521 gateways still use the "name=filename" convention */ + if ((pc = mutt_get_parameter("name", ct->parameter)) != 0) + ct->filename = safe_strdup(pc); + } + + /* Now get the subtype */ + if ((pc = strchr(s, '/'))) + { + *pc++ = 0; + while (*pc && !ISSPACE (*pc) && *pc != ';') + { + buffer[i++] = *pc; + pc++; + } + buffer[i] = 0; + ct->subtype = safe_strdup (buffer); + } + + /* Finally, get the major type */ + ct->type = mutt_check_mime_type (s); + + if (ct->subtype == NULL) + { + /* Some older non-MIME mailers (i.e., mailtool, elm) have a content-type + * field, so we can attempt to convert the type to BODY here. + */ + if (ct->type == TYPETEXT) + ct->subtype = safe_strdup ("plain"); + else if (ct->type == TYPEAUDIO) + ct->subtype = safe_strdup ("basic"); + else if (ct->type == TYPEMESSAGE) + ct->subtype = safe_strdup ("rfc822"); + else if (ct->type == TYPEOTHER) + { + ct->type = TYPEAPPLICATION; + snprintf (buffer, sizeof (buffer), "x-%s", s); + ct->subtype = safe_strdup (buffer); + } + else + ct->subtype = safe_strdup ("x-unknown"); + } +} + +static void parse_content_disposition (char *s, BODY *ct) +{ + PARAMETER *parms; + + if (!strncasecmp ("inline", s, 6)) + ct->disposition = DISPINLINE; + else if (!strncasecmp ("form-data", s, 9)) + ct->disposition = DISPFORMDATA; + else + ct->disposition = DISPATTACH; + + /* Check to see if a default filename was given */ + if ((s = strchr (s, ';')) != NULL) + { + s++; + SKIPWS (s); + if ((s = mutt_get_parameter ("filename", (parms = parse_parameters (s)))) != 0) + { + /* free() here because the content-type parsing routine might + * have allocated space if a "name=filename" parameter was + * specified. + */ + safe_free ((void **) &ct->filename); + ct->filename = safe_strdup (s); + } + if ((s = mutt_get_parameter ("name", parms)) != 0) + ct->form_name = safe_strdup (s); + mutt_free_parameter (&parms); + } +} + +/* args: + * fp stream to read from + * + * digest 1 if reading subparts of a multipart/digest, 0 + * otherwise + */ + +BODY *mutt_read_mime_header (FILE *fp, int digest) +{ + BODY *p = mutt_new_body(); + char *c; + char *line = safe_malloc (LONG_STRING); + size_t linelen = LONG_STRING; + + p->hdr_offset = ftell(fp); + + p->encoding = ENC7BIT; /* default from RFC1521 */ + p->type = digest ? TYPEMESSAGE : TYPETEXT; + + while (*(line = read_rfc822_line (fp, line, &linelen)) != 0) + { + /* Find the value of the current header */ + if ((c = strchr (line, ':'))) + { + *c = 0; + c++; + SKIPWS (c); + if (!*c) + { + dprint (1, (debugfile, "mutt_read_mime_header(): skipping empty header field: %s\n", line)); + continue; + } + } + else + { + dprint (1, (debugfile, "read_mime_header: bogus MIME header: %s\n", line)); + break; + } + + if (!strncasecmp ("content-", line, 8)) + { + if (!strcasecmp ("type", line + 8)) + parse_content_type (c, p); + else if (!strcasecmp ("transfer-encoding", line + 8)) + p->encoding = mutt_check_encoding (c); + else if (!strcasecmp ("disposition", line + 8)) + parse_content_disposition (c, p); + else if (!strcasecmp ("description", line + 8)) + { + safe_free ((void **) &p->description); + p->description = safe_strdup (c); + rfc2047_decode (p->description, p->description, strlen (p->description) + 1); + } + } + } + p->offset = ftell (fp); /* Mark the start of the real data */ + if (p->type == TYPETEXT && !p->subtype) + p->subtype = safe_strdup ("plain"); + else if (p->type == TYPEMESSAGE && !p->subtype) + p->subtype = safe_strdup ("rfc822"); + + free (line); + + return (p); +} + +/* parse a MESSAGE/RFC822 body + * + * args: + * fp stream to read from + * + * parent structure which contains info about the message/rfc822 + * body part + * + * NOTE: this assumes that `parent->length' has been set! + */ + +BODY *mutt_parse_messageRFC822 (FILE *fp, BODY *parent) +{ + BODY *msg; + + parent->hdr = mutt_new_header (); + parent->hdr->offset = ftell (fp); + parent->hdr->env = mutt_read_rfc822_header (fp, parent->hdr); + msg = parent->hdr->content; + + /* ignore the length given in the content-length since it could be wrong + and we already have the info to calculate the correct length */ + /* if (msg->length == -1) */ + msg->length = parent->length - (msg->offset - parent->offset); + + /* if body of this message is empty, we can end up with a negative length */ + if (msg->length < 0) + msg->length = 0; + + if (msg->type == TYPEMULTIPART) + msg->parts = mutt_parse_multipart (fp, mutt_get_parameter ("boundary", msg->parameter), msg->offset + msg->length, strcasecmp ("digest", msg->subtype) == 0); + else if (msg->type == TYPEMESSAGE) + msg->parts = mutt_parse_messageRFC822 (fp, msg); + else + return (msg); + + /* try to recover from parsing error */ + if (!msg->parts) + { + msg->type = TYPETEXT; + safe_free ((void **) &msg->subtype); + msg->subtype = safe_strdup ("plain"); + } + + return (msg); +} + +/* parse a multipart structure + * + * args: + * fp stream to read from + * + * boundary body separator + * + * end_off length of the multipart body (used when the final + * boundary is missing to avoid reading too far) + * + * digest 1 if reading a multipart/digest, 0 otherwise + */ + +BODY *mutt_parse_multipart (FILE *fp, const char *boundary, long end_off, int digest) +{ + int blen, len, crlf = 0; + char buffer[LONG_STRING]; + BODY *head = 0, *last = 0, *new = 0; + int i; + int final = 0; /* did we see the ending boundary? */ + + if (!boundary) + { + mutt_error ("multipart message has no boundary parameter!"); + return (NULL); + } + + blen = strlen (boundary); + while (ftell (fp) < end_off && fgets (buffer, LONG_STRING, fp) != NULL) + { + len = strlen (buffer); + + /* take note of the line ending. I'm assuming that either all endings + * will use <CR><LF> or none will. + */ + if (len > 1 && buffer[len - 2] == '\r') + crlf = 1; + + if (buffer[0] == '-' && buffer[1] == '-' && + strncmp (buffer + 2, boundary, blen) == 0) + { + if (last) + { + last->length = ftell (fp) - last->offset - len - 1 - crlf; + if (last->parts && last->parts->length == 0) + last->parts->length = ftell (fp) - last->parts->offset - len - 1 - crlf; + /* if the body is empty, we can end up with a -1 length */ + if (last->length < 0) + last->length = 0; + } + + /* Remove any trailing whitespace, up to the length of the boundary */ + for (i = len - 1; ISSPACE (buffer[i]) && i >= blen + 2; i--) + buffer[i] = 0; + + /* Check for the end boundary */ + if (strcmp (buffer + blen + 2, "--") == 0) + { + final = 1; + break; /* done parsing */ + } + else if (buffer[2 + blen] == 0) + { + new = mutt_read_mime_header (fp, digest); + if (head) + { + last->next = new; + last = new; + } + else + last = head = new; + } + } + } + + /* in case of missing end boundary, set the length to something reasonable */ + if (last && last->length == 0 && !final) + last->length = end_off - last->offset; + + /* parse recursive MIME parts */ + for (last = head; last; last = last->next) + { + switch (last->type) + { + case TYPEMULTIPART: + fseek (fp, last->offset, 0); + last->parts = mutt_parse_multipart (fp, mutt_get_parameter ("boundary", last->parameter), last->offset + last->length, strcasecmp ("digest", last->subtype) == 0); + break; + + case TYPEMESSAGE: + if (last->subtype && + (strcasecmp (last->subtype, "rfc822") == 0 || + strcasecmp (last->subtype, "news") == 0)) + { + fseek (fp, last->offset, 0); + last->parts = mutt_parse_messageRFC822 (fp, last); + } + break; + } + } + + return (head); +} + +static const char *uncomment_timezone (char *buf, size_t buflen, const char *tz) +{ + char *p; + size_t len; + + if (*tz != '(') + return tz; /* no need to do anything */ + tz++; + SKIPWS (tz); + if ((p = strpbrk (tz, " )")) == NULL) + return tz; + len = p - tz; + if (len > buflen - 1) + len = buflen - 1; + memcpy (buf, tz, len); + buf[len] = 0; + return buf; +} + +static struct tz_t +{ + char *tzname; + unsigned char zhours; + unsigned char zminutes; + unsigned char zoccident; /* west of UTC? */ + unsigned char xxx; /* unused */ +} +TimeZones[] = +{ + { "sst", 11, 0, 1, 0 }, /* Samoa */ + { "pst", 8, 0, 1, 0 }, + { "mst", 7, 0, 1, 0 }, + { "pdt", 7, 0, 1, 0 }, + { "cst", 6, 0, 1, 0 }, + { "mdt", 6, 0, 1, 0 }, + { "cdt", 5, 0, 1, 0 }, + { "est", 5, 0, 1, 0 }, + { "ast", 4, 0, 1, 0 }, /* Atlantic */ + { "edt", 4, 0, 1, 0 }, + { "wgt", 3, 0, 1, 0 }, /* Western Greenland */ + { "wgst", 2, 0, 1, 0 }, /* Western Greenland DST */ + { "aat", 1, 0, 1, 0 }, /* Atlantic Africa Time */ + { "egt", 1, 0, 1, 0 }, /* Eastern Greenland */ + { "egst", 0, 0, 0, 0 }, /* Eastern Greenland DST */ + { "gmt", 0, 0, 0, 0 }, + { "utc", 0, 0, 0, 0 }, + { "wat", 0, 0, 0, 0 }, /* West Africa */ + { "wet", 0, 0, 0, 0 }, /* Western Europe */ + { "bst", 1, 0, 0, 0 }, /* British DST */ + { "cat", 1, 0, 0, 0 }, /* Central Africa */ + { "cet", 1, 0, 0, 0 }, /* Central Europe */ + { "met", 1, 0, 0, 0 }, /* this is now officially CET */ + { "west", 1, 0, 0, 0 }, /* Western Europe DST */ + { "cest", 2, 0, 0, 0 }, /* Central Europe DST */ + { "eet", 2, 0, 0, 0 }, /* Eastern Europe */ + { "ist", 2, 0, 0, 0 }, /* Israel */ + { "sat", 2, 0, 0, 0 }, /* South Africa */ + { "ast", 3, 0, 0, 0 }, /* Arabia */ + { "eat", 3, 0, 0, 0 }, /* East Africa */ + { "eest", 3, 0, 0, 0 }, /* Eastern Europe DST */ + { "idt", 3, 0, 0, 0 }, /* Israel DST */ + { "msk", 3, 0, 0, 0 }, /* Moscow */ + { "adt", 4, 0, 0, 0 }, /* Arabia DST */ + { "msd", 4, 0, 0, 0 }, /* Moscow DST */ + { "gst", 4, 0, 0, 0 }, /* Presian Gulf */ + { "smt", 4, 0, 0, 0 }, /* Seychelles */ + { "ist", 5, 30, 0, 0 }, /* India */ + { "ict", 7, 0, 0, 0 }, /* Indochina */ +/*{ "cst", 8, 0, 0, 0 },*/ /* China */ + { "hkt", 8, 0, 0, 0 }, /* Hong Kong */ +/*{ "sst", 8, 0, 0, 0 },*/ /* Singapore */ + { "wst", 8, 0, 0, 0 }, /* Western Australia */ + { "jst", 9, 0, 0, 0 }, /* Japan */ +/*{ "cst", 9, 30, 0, 0 },*/ /* Australian Central Standard Time */ + { "kst", 10, 0, 0, 0 }, /* Korea */ + { "nzst", 12, 0, 0, 0 }, /* New Zealand */ + { "nzdt", 13, 0, 0, 0 }, /* New Zealand DST */ + { NULL, 0, 0, 0, 0 } +}; + +/* parses a date string in RFC822 format: + * + * Date: [ weekday , ] day-of-month month year hour:minute:second timezone + * + * This routine assumes that `h' has been initialized to 0. the `timezone' + * field is optional, defaulting to +0000 if missing. + */ +static time_t parse_date (char *s, HEADER *h) +{ + int count = 0; + char *t; + int hour, min, sec; + struct tm tm; + int i; + int tz_offset = 0; + int zhours = 0; + int zminutes = 0; + int zoccident = 0; + const char *ptz; + char tzstr[SHORT_STRING]; + + /* kill the day of the week, if it exists. */ + if ((t = strchr (s, ','))) + t++; + else + t = s; + SKIPWS (t); + + memset (&tm, 0, sizeof (tm)); + + while ((t = strtok (t, " \t")) != NULL) + { + switch (count) + { + case 0: /* day of the month */ + if (!isdigit (*t)) + return (-1); + tm.tm_mday = atoi (t); + if (tm.tm_mday > 31) + return (-1); + break; + + case 1: /* month of the year */ + if ((i = mutt_check_month (t)) < 0) + return (-1); + tm.tm_mon = i; + break; + + case 2: /* year */ + tm.tm_year = atoi (t); + if (tm.tm_year >= 1900) + tm.tm_year -= 1900; + break; + + case 3: /* time of day */ + if (sscanf (t, "%d:%d:%d", &hour, &min, &sec) == 3) + ; + else if (sscanf (t, "%d:%d", &hour, &min) == 2) + sec = 0; + else + { + dprint(1, (debugfile, "parse_date: could not process time format: %s\n", t)); + return(-1); + } + tm.tm_hour = hour; + tm.tm_min = min; + tm.tm_sec = sec; + break; + + case 4: /* timezone */ + /* sometimes we see things like (MST) or (-0700) so attempt to + * compensate by uncommenting the string if non-RFC822 compliant + */ + ptz = uncomment_timezone (tzstr, sizeof (tzstr), t); + + if (*ptz == '+' || *ptz == '-') + { + if (ptz[1] && ptz[2] && ptz[3] && ptz[4] + && isdigit (ptz[1]) && isdigit (ptz[2]) + && isdigit (ptz[3]) && isdigit (ptz[4])) + { + zhours = (ptz[1] - '0') * 10 + (ptz[2] - '0'); + zminutes = (ptz[3] - '0') * 10 + (ptz[4] - '0'); + + if (ptz[0] == '-') + zoccident = 1; + } + } + else + { + for (i = 0; TimeZones[i].tzname; i++) + if (!strcasecmp (TimeZones[i].tzname, ptz)) + { + zhours = TimeZones[i].zhours; + zminutes = TimeZones[i].zminutes; + zoccident = TimeZones[i].zoccident; + break; + } + + /* ad hoc support for the European MET (now officially CET) TZ */ + if (strcasecmp (t, "MET") == 0) + { + if ((t = strtok (NULL, " \t")) != NULL) + { + if (!strcasecmp (t, "DST")) + zhours++; + } + } + } + tz_offset = zhours * 3600 + zminutes * 60; + if (!zoccident) + tz_offset = -tz_offset; + break; + } + count++; + t = 0; + } + + if (count < 4) /* don't check for missing timezone */ + { + dprint(1,(debugfile, "parse_date(): error parsing date format, using received time\n")); + return (-1); + } + + if (h) + { + h->zhours = zhours; + h->zminutes = zminutes; + h->zoccident = zoccident; + } + + return (mutt_mktime (&tm, 0) + tz_offset); +} + +/* extract the first substring that looks like a message-id */ +static char *extract_message_id (const char *s) +{ + const char *p; + char *r; + size_t l; + + if ((s = strchr (s, '<')) == NULL || (p = strchr (s, '>')) == NULL) + return (NULL); + l = (size_t)(p - s) + 1; + r = safe_malloc (l + 1); + memcpy (r, s, l); + r[l] = 0; + return (r); +} + +void mutt_parse_mime_message (CONTEXT *ctx, HEADER *cur) +{ + MESSAGE *msg; + + if (cur->content->type != TYPEMESSAGE && cur->content->type != TYPEMULTIPART) + return; /* nothing to do */ + + if ((msg = mx_open_message (ctx, cur->msgno))) + { + fseek (msg->fp, cur->content->offset, 0); + + if (cur->content->type == TYPEMULTIPART) + { + if (!cur->content->parts) + cur->content->parts = mutt_parse_multipart (msg->fp, mutt_get_parameter ("boundary", cur->content->parameter), cur->content->offset + cur->content->length, strcasecmp ("digest", cur->content->subtype) == 0); + } + else + { + if (!cur->content->parts) + cur->content->parts = mutt_parse_messageRFC822 (msg->fp, cur->content); + } + + /* try to recover from parsing error */ + if (!cur->content->parts) + { + cur->content->type = TYPETEXT; + safe_free ((void **) &cur->content->subtype); + cur->content->subtype = safe_strdup ("plain"); + } + + + +#ifdef _PGPPATH + cur->pgp = pgp_query (cur->content); +#endif /* _PGPPATH */ + + + + mx_close_message (&msg); + } +} + +/* mutt_read_rfc822_header() -- parses a RFC822 header + * + * Args: + * + * f stream to read from + * + * hdr header structure of current message (optional). If hdr is + * NULL, then we are reading a postponed message, or called + * from mutt_edit_headers() so we should keep a list of the + * user-defined headers. + */ +ENVELOPE *mutt_read_rfc822_header (FILE *f, HEADER *hdr) +{ + ENVELOPE *e = mutt_new_envelope(); + LIST *last = NULL; + char *line = safe_malloc (LONG_STRING); + char *p; + char in_reply_to[STRING]; + long loc; + int matched; + size_t linelen = LONG_STRING; + + in_reply_to[0] = 0; + + if (hdr) + { + hdr->content = mutt_new_body (); + + /* set the defaults from RFC1521 */ + hdr->content->type = TYPETEXT; + hdr->content->subtype = safe_strdup ("plain"); + hdr->content->encoding = ENC7BIT; + hdr->content->length = -1; + } + + loc = ftell (f); + while (*(line = read_rfc822_line (f, line, &linelen)) != 0) + { + matched = 0; + + if ((p = strpbrk (line, ": \t")) == NULL || *p != ':') + { + char return_path[LONG_STRING]; + time_t t; + + /* some bogus MTAs will quote the original "From " line */ + if (strncmp (">From ", line, 6) == 0) + { + loc = ftell (f); + continue; /* just ignore */ + } + else if ((t = is_from (line, return_path, sizeof (return_path)))) + { + /* MH somtimes has the From_ line in the middle of the header! */ + if (hdr && !hdr->received) + hdr->received = t + mutt_local_tz (); + loc = ftell (f); + continue; + } + + fseek (f, loc, 0); + break; /* end of header */ + } + + *p = 0; + p++; + SKIPWS (p); + if (!*p) + continue; /* skip empty header fields */ + + switch (tolower (line[0])) + { + case 'a': + if (strcasecmp (line+1, "pparently-to") == 0) + { + e->to = rfc822_parse_adrlist (e->to, p); + matched = 1; + } + else if (strcasecmp (line+1, "pparently-from") == 0) + { + e->from = rfc822_parse_adrlist (e->from, p); + matched = 1; + } + break; + + case 'b': + if (strcasecmp (line+1, "cc") == 0) + { + e->bcc = rfc822_parse_adrlist (e->bcc, p); + matched = 1; + } + break; + + case 'c': + if (strcasecmp (line+1, "c") == 0) + { + e->cc = rfc822_parse_adrlist (e->cc, p); + matched = 1; + } + else if (strncasecmp (line + 1, "ontent-", 7) == 0) + { + if (strcasecmp (line+8, "type") == 0) + { + if (hdr) + parse_content_type (p, hdr->content); + matched = 1; + } + else if (strcasecmp (line+8, "transfer-encoding") == 0) + { + if (hdr) + hdr->content->encoding = mutt_check_encoding (p); + matched = 1; + } + else if (strcasecmp (line+8, "length") == 0) + { + if (hdr) + hdr->content->length = atoi (p); + matched = 1; + } + else if (strcasecmp (line+8, "description") == 0) + { + if (hdr) + { + safe_free ((void **) &hdr->content->description); + hdr->content->description = safe_strdup (p); + rfc2047_decode (hdr->content->description, + hdr->content->description, + strlen (hdr->content->description) + 1); + } + matched = 1; + } + else if (strcasecmp (line+8, "disposition") == 0) + { + if (hdr) + parse_content_disposition (p, hdr->content); + matched = 1; + } + } + break; + + case 'd': + if (!strcasecmp ("ate", line + 1)) + { + if (hdr) + hdr->date_sent = parse_date (p, hdr); + matched = 1; + } + break; + + case 'e': + if (!strcasecmp ("xpires", line + 1) && + hdr && parse_date (p, hdr) < time (NULL)) + hdr->expired = 1; + break; + + case 'f': + if (!strcasecmp ("rom", line + 1)) + { + e->from = rfc822_parse_adrlist (e->from, p); + matched = 1; + } + break; + + case 'i': + if (!strcasecmp (line+1, "n-reply-to")) + { + if (hdr) + { + strfcpy (in_reply_to, p, sizeof (in_reply_to)); + rfc2047_decode (in_reply_to, in_reply_to, + sizeof (in_reply_to)); + } + } + break; + + case 'l': + if (!strcasecmp (line + 1, "ines")) + { + if (hdr) + hdr->lines = atoi (p); + matched = 1; + } + break; + + case 'm': + if (!strcasecmp (line + 1, "ime-version")) + { + if (hdr) + hdr->mime = 1; + matched = 1; + } + else if (!strcasecmp (line + 1, "essage-id")) + { + /* We add a new "Message-Id:" when building a message */ + safe_free ((void **) &e->message_id); + e->message_id = extract_message_id (p); + matched = 1; + } + else if (!strncasecmp (line + 1, "ail-", 4)) + { + if (!strcasecmp (line + 5, "reply-to")) + { + /* override the Reply-To: field */ + rfc822_free_address (&e->reply_to); + e->reply_to = rfc822_parse_adrlist (e->reply_to, p); + matched = 1; + } + else if (!strcasecmp (line + 5, "followup-to")) + { + e->mail_followup_to = rfc822_parse_adrlist (e->mail_followup_to, p); + matched = 1; + } + } + break; + + case 'r': + if (!strcasecmp (line + 1, "eferences")) + { + mutt_free_list (&e->references); + e->references = mutt_parse_references (p); + matched = 1; + } + else if (!strcasecmp (line + 1, "eply-to")) + { + e->reply_to = rfc822_parse_adrlist (e->reply_to, p); + matched = 1; + } + else if (!strcasecmp (line + 1, "eturn-path")) + { + e->return_path = rfc822_parse_adrlist (e->return_path, p); + matched = 1; + } + else if (!strcasecmp (line + 1, "eceived")) + { + if (hdr && !hdr->received) + { + char *d = strchr (p, ';'); + + if (d) + hdr->received = parse_date (d + 1, NULL); + } + matched = 1; + } + break; + + case 's': + if (!strcasecmp (line + 1, "ubject")) + { + if (!e->subject) + e->subject = safe_strdup (p); + matched = 1; + } + else if (!strcasecmp (line + 1, "ender")) + { + e->sender = rfc822_parse_adrlist (e->sender, p); + matched = 1; + } + else if (!strcasecmp (line + 1, "tatus")) + { + if (hdr) + { + while (*p) + { + switch(*p) + { + case 'r': + hdr->replied = 1; + break; + case 'O': + if (option (OPTMARKOLD)) + hdr->old = 1; + break; + case 'R': + hdr->read = 1; + break; + } + p++; + } + } + matched = 1; + } + else if ((!strcasecmp ("upersedes", line + 1) || + !strcasecmp ("upercedes", line + 1)) && hdr) + e->supersedes = safe_strdup (p); + break; + + case 't': + if (strcasecmp (line+1, "o") == 0) + { + e->to = rfc822_parse_adrlist (e->to, p); + matched = 1; + } + break; + + case 'x': + if (strcasecmp (line+1, "-status") == 0) + { + if (hdr) + { + while (*p) + { + switch (*p) + { + case 'A': + hdr->replied = 1; + break; + case 'D': + hdr->deleted = 1; + break; + case 'F': + hdr->flagged = 1; + break; + default: + break; + } + p++; + } + } + matched = 1; + } + + default: + break; + } + + /* if hdr==NULL, then we are using this to parse either a postponed + * message, or a outgoing message (edit_hdrs), so we want to keep + * track of the user-defined headers + */ + if (!matched && !hdr) + { + if (last) + { + last->next = mutt_new_list (); + last = last->next; + } + else + last = e->userhdrs = mutt_new_list (); + line[strlen (line)] = ':'; + last->data = safe_strdup (line); + } + + loc = ftell (f); + } + + free (line); + + if (hdr) + { + hdr->content->hdr_offset = hdr->offset; + hdr->content->offset = ftell (f); + + /* if an in-reply-to was given, check to see if it is in the references + * list already. if not, add it so we can do a better job of threading. + */ + if (in_reply_to[0] && (p = extract_message_id (in_reply_to)) != NULL) + { + if (!e->references || + (e->references && strcmp (e->references->data, p) != 0)) + { + LIST *tmp = mutt_new_list (); + + tmp->data = p; + tmp->next = e->references; + e->references = tmp; + } + else + safe_free ((void **) &p); + } + + /* do RFC2047 decoding */ + rfc2047_decode_adrlist (e->from); + rfc2047_decode_adrlist (e->to); + rfc2047_decode_adrlist (e->cc); + rfc2047_decode_adrlist (e->reply_to); + rfc2047_decode_adrlist (e->mail_followup_to); + rfc2047_decode_adrlist (e->return_path); + rfc2047_decode_adrlist (e->sender); + + if (e->subject) + { + regmatch_t pmatch[1]; + + rfc2047_decode (e->subject, e->subject, strlen (e->subject) + 1); + + if (regexec (ReplyRegexp.rx, e->subject, 1, pmatch, 0) == 0) + e->real_subj = e->subject + pmatch[0].rm_eo; + else + e->real_subj = e->subject; + } + + /* check for missing or invalid date */ + if (hdr->date_sent <= 0) + { + dprint(1,(debugfile,"read_rfc822_header(): no date found, using received time from msg separator\n")); + hdr->date_sent = hdr->received; + } + } + + return (e); +} + +ADDRESS *mutt_parse_adrlist (ADDRESS *p, const char *s) +{ + const char *q; + + /* check for a simple whitespace separated list of addresses */ + if ((q = strpbrk (s, "\"<>():;,\\")) == NULL) + { + char tmp[HUGE_STRING]; + char *r; + + strfcpy (tmp, s, sizeof (tmp)); + r = tmp; + while ((r = strtok (r, " \t")) != NULL) + { + p = rfc822_parse_adrlist (p, r); + r = NULL; + } + } + else + p = rfc822_parse_adrlist (p, s); + + return p; +} diff --git a/parse.h b/parse.h new file mode 100644 index 00000000..d9cc4c85 --- /dev/null +++ b/parse.h @@ -0,0 +1,7 @@ +BODY *mutt_parse_multipart (FILE *, const char *, long, int); +BODY *mutt_parse_messageRFC822 (FILE *, BODY *); +BODY *mutt_read_mime_header (FILE *, int); + +ENVELOPE *mutt_read_rfc822_header (FILE *, HEADER *); + +time_t is_from (const char *, char *, size_t); diff --git a/pattern.c b/pattern.c new file mode 100644 index 00000000..0435ac76 --- /dev/null +++ b/pattern.c @@ -0,0 +1,1075 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mapping.h" +#include "keymap.h" +#include "mailbox.h" +#include "copy.h" + +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <sys/stat.h> +#include <unistd.h> + + + +#ifdef _PGPPATH +#include "pgp.h" +#endif + + + +static int eat_regexp (pattern_t *pat, BUFFER *, BUFFER *); +static int eat_date (pattern_t *pat, BUFFER *, BUFFER *); +static int eat_range (pattern_t *pat, BUFFER *, BUFFER *); + +struct pattern_flags +{ + int tag; /* character used to represent this op */ + int op; /* operation to perform */ + int class; + int (*eat_arg) (pattern_t *, BUFFER *, BUFFER *); +} +Flags[] = +{ + { 'A', M_ALL, 0, NULL }, + { 'b', M_BODY, M_FULL_MSG, eat_regexp }, + { 'B', M_WHOLE_MSG, M_FULL_MSG, eat_regexp }, + { 'c', M_CC, 0, eat_regexp }, + { 'C', M_RECIPIENT, 0, eat_regexp }, + { 'd', M_DATE, 0, eat_date }, + { 'D', M_DELETED, 0, NULL }, + { 'e', M_SENDER, 0, eat_regexp }, + { 'E', M_EXPIRED, 0, NULL }, + { 'f', M_FROM, 0, eat_regexp }, + { 'F', M_FLAG, 0, NULL }, + { 'h', M_HEADER, M_FULL_MSG, eat_regexp }, + { 'i', M_ID, 0, eat_regexp }, + { 'L', M_ADDRESS, 0, eat_regexp }, + { 'l', M_LIST, 0, NULL }, + { 'm', M_MESSAGE, 0, eat_range }, + { 'n', M_SCORE, 0, eat_range }, + { 'N', M_NEW, 0, NULL }, + { 'O', M_OLD, 0, NULL }, + { 'p', M_PERSONAL_RECIP, 0, NULL }, + { 'P', M_PERSONAL_FROM, 0, NULL }, + { 'Q', M_REPLIED, 0, NULL }, + { 'R', M_READ, 0, NULL }, + { 'r', M_DATE_RECEIVED, 0, eat_date }, + { 's', M_SUBJECT, 0, eat_regexp }, + { 'S', M_SUPERSEDED, 0, NULL }, + { 'T', M_TAG, 0, NULL }, + { 't', M_TO, 0, eat_regexp }, + { 'U', M_UNREAD, 0, NULL }, + { 'x', M_REFERENCE, 0, eat_regexp }, + { 'z', M_SIZE, 0, eat_range }, + { 0 } +}; + +static pattern_t *SearchPattern = NULL; /* current search pattern */ +static char LastSearch[STRING] = { 0 }; /* last pattern searched for */ +static char LastSearchExpn[LONG_STRING] = { 0 }; /* expanded version of + LastSearch */ + +#define M_MAXRANGE -1 + +int mutt_getvaluebychar (char ch, struct mapping_t *table) +{ + int i; + + for (i = 0; table[i].name; i++) + { + if (ch == table[i].name[0]) + return table[i].value; + } + + return (-1); +} + +/* if no uppercase letters are given, do a case-insensitive search */ +int mutt_which_case (const char *s) +{ + while (*s) + { + if (isalpha (*s) && isupper (*s)) + return 0; /* case-sensitive */ + s++; + } + return REG_ICASE; /* case-insensitive */ +} + +static int +msg_search (CONTEXT *ctx, regex_t *rx, char *buf, size_t blen, int op, int msgno) +{ + char tempfile[_POSIX_PATH_MAX]; + MESSAGE *msg = NULL; + STATE s; + struct stat st; + FILE *fp = NULL; + long lng = 0; + int match = 0; + HEADER *h = ctx->hdrs[msgno]; + + if ((msg = mx_open_message (ctx, msgno)) != NULL) + { + if (option (OPTTHOROUGHSRC)) + { + /* decode the header / body */ + memset (&s, 0, sizeof (s)); + s.fpin = msg->fp; + mutt_mktemp (tempfile); + if ((s.fpout = safe_fopen (tempfile, "w+")) == NULL) + { + mutt_perror (tempfile); + return (0); + } + + if (op != M_BODY) + mutt_copy_header (msg->fp, h, s.fpout, CH_FROM | CH_DECODE, NULL); + + if (op != M_HEADER) + { + mutt_parse_mime_message (ctx, h); + + + +#ifdef _PGPPATH + if (h->pgp & PGPENCRYPT && !pgp_valid_passphrase()) + { + mx_close_message (&msg); + fclose (fp); + unlink (tempfile); + return (0); + } +#endif + + + + fseek (msg->fp, h->offset, 0); + mutt_body_handler (h->content, &s); + } + + fp = s.fpout; + fflush (fp); + fseek (fp, 0, 0); + fstat (fileno (fp), &st); + lng = (long) st.st_size; + } + else + { + /* raw header / body */ + fp = msg->fp; + if (op != M_BODY) + { + fseek (fp, h->offset, 0); + lng = h->content->offset - h->offset; + } + if (op != M_HEADER) + { + if (op == M_BODY) + fseek (fp, h->content->offset, 0); + lng += h->content->length; + } + } + + /* search the file "fp" */ + while (lng > 0) + { + if (fgets (buf, blen - 1, fp) == NULL) + break; /* don't loop forever */ + if (regexec (rx, buf, 0, NULL, 0) == 0) + { + match = 1; + break; + } + lng -= strlen (buf); + } + + mx_close_message (&msg); + + if (option (OPTTHOROUGHSRC)) + { + fclose (fp); + unlink (tempfile); + } + } + + return match; +} + +int eat_regexp (pattern_t *pat, BUFFER *s, BUFFER *err) +{ + BUFFER buf; + int r; + + memset (&buf, 0, sizeof (buf)); + if (mutt_extract_token (&buf, s, M_TOKEN_PATTERN | M_TOKEN_COMMENT) != 0 || + !buf.data) + { + snprintf (err->data, err->dsize, "Error in expression: %s", s->dptr); + return (-1); + } + pat->rx = safe_malloc (sizeof (regex_t)); + r = REGCOMP (pat->rx, buf.data, REG_NEWLINE | REG_NOSUB | mutt_which_case (buf.data)); + FREE (&buf.data); + if (r) + { + regerror (r, pat->rx, err->data, err->dsize); + regfree (pat->rx); + safe_free ((void **) &pat->rx); + return (-1); + } + return 0; +} + +int eat_range (pattern_t *pat, BUFFER *s, BUFFER *err) +{ + char *tmp; + + if ((*s->dptr != '-') && (*s->dptr != '<')) + { + /* range minimum */ + if (*s->dptr == '>') + { + pat->max = M_MAXRANGE; + pat->min = strtol (s->dptr + 1, &tmp, 0); + } + else + pat->min = strtol (s->dptr, &tmp, 0); + if (toupper (*tmp) == 'K') /* is there a prefix? */ + { + pat->min *= 1024; + tmp++; + } + else if (toupper (*tmp) == 'M') + { + pat->min *= 1048576; + tmp++; + } + if (*s->dptr == '>') + { + s->dptr = tmp; + return 0; + } + if (*tmp != '-') + { + /* exact value */ + pat->max = pat->min; + s->dptr = tmp; + return 0; + } + tmp++; + } + else + { + s->dptr++; + tmp = s->dptr; + } + + if (isdigit (*tmp)) + { + /* range maximum */ + pat->max = strtol (tmp, &tmp, 0); + if (toupper (*tmp) == 'K') + { + pat->max *= 1024; + tmp++; + } + else if (toupper (*tmp) == 'M') + { + pat->max *= 1048576; + tmp++; + } + } + else + pat->max = M_MAXRANGE; + + s->dptr = tmp; + return 0; +} + +static const char *getDate (const char *s, struct tm *t, BUFFER *err) +{ + char *p; + time_t now = time (NULL); + struct tm *tm = localtime (&now); + + t->tm_mday = strtol (s, &p, 0); + if (t->tm_mday < 1 || t->tm_mday > 31) + { + snprintf (err->data, err->dsize, "Invalid day of month: %s", s); + return NULL; + } + if (*p != '/') + { + /* fill in today's month and year */ + t->tm_mon = tm->tm_mon; + t->tm_year = tm->tm_year; + return p; + } + p++; + t->tm_mon = strtol (p, &p, 0) - 1; + if (t->tm_mon < 0 || t->tm_mon > 11) + { + snprintf (err->data, err->dsize, "Invalid month: %s", p); + return NULL; + } + if (*p != '/') + { + t->tm_year = tm->tm_year; + return p; + } + p++; + t->tm_year = strtol (p, &p, 0); + if (t->tm_year < 70) /* year 2000+ */ + t->tm_year += 100; + else if (t->tm_year > 1900) + t->tm_year -= 1900; + return p; +} + +/* Ny years + Nm months + Nw weeks + Nd days */ +static const char *get_offset (struct tm *tm, const char *s) +{ + char *ps; + int offset = strtol (s, &ps, 0); + + switch (*ps) + { + case 'y': + tm->tm_year -= offset; + break; + case 'm': + tm->tm_mon -= offset; + break; + case 'w': + tm->tm_mday -= 7 * offset; + break; + case 'd': + tm->tm_mday -= offset; + break; + } + mutt_normalize_time (tm); + return (ps + 1); +} + +static int eat_date (pattern_t *pat, BUFFER *s, BUFFER *err) +{ + BUFFER buffer; + struct tm min, max; + + memset (&buffer, 0, sizeof (buffer)); + if (mutt_extract_token (&buffer, s, M_TOKEN_COMMENT | M_TOKEN_PATTERN) != 0 + || !buffer.data) + { + strfcpy (err->data, "error in expression", err->dsize); + return (-1); + } + + memset (&min, 0, sizeof (min)); + /* the `0' time is Jan 1, 1970 UTC, so in order to prevent a negative time + when doing timezone conversion, we use Jan 2, 1970 UTC as the base + here */ + min.tm_mday = 2; + min.tm_year = 70; + + memset (&max, 0, sizeof (max)); + + /* Arbitrary year in the future. Don't set this too high + or mutt_mktime() returns something larger than will + fit in a time_t on some systems */ + max.tm_year = 130; + max.tm_mon = 11; + max.tm_mday = 31; + max.tm_hour = 23; + max.tm_min = 59; + max.tm_sec = 59; + + if (strchr ("<>=", buffer.data[0])) + { + /* offset from current time + <3d less than three days ago + >3d more than three days ago + =3d exactly three days ago */ + time_t now = time (NULL); + struct tm *tm = localtime (&now); + int exact = 0; + + if (buffer.data[0] == '<') + { + memcpy (&min, tm, sizeof (min)); + tm = &min; + } + else + { + memcpy (&max, tm, sizeof (max)); + tm = &max; + + if (buffer.data[0] == '=') + exact++; + } + tm->tm_hour = 23; + tm->tm_min = max.tm_sec = 59; + + get_offset (tm, buffer.data + 1); + + if (exact) + { + /* start at the beginning of the day in question */ + memcpy (&min, &max, sizeof (max)); + min.tm_hour = min.tm_sec = min.tm_min = 0; + } + } + else + { + const char *pc = buffer.data; + + if (*pc != '-') + { + /* mininum date specified */ + if ((pc = getDate (pc, &min, err)) == NULL) + { + FREE (&buffer.data); + return (-1); + } + } + + if (*pc && *pc == '-') + { + /* max date */ + pc++; /* skip the `-' */ + SKIPWS (pc); + if (*pc) + if (!getDate (pc, &max, err)) + { + FREE (&buffer.data); + return (-1); + } + } + else + { + /* search for messages on a specific day */ + max.tm_year = min.tm_year; + max.tm_mon = min.tm_mon; + max.tm_mday = min.tm_mday; + } + } + + pat->min = mutt_mktime (&min, 1); + pat->max = mutt_mktime (&max, 1); + + FREE (&buffer.data); + + return 0; +} + +static struct pattern_flags *lookup_tag (char tag) +{ + int i; + + for (i = 0; Flags[i].tag; i++) + if (Flags[i].tag == tag) + return (&Flags[i]); + return NULL; +} + +static /* const */ char *find_matching_paren (/* const */ char *s) +{ + int level = 1; + + for (; *s; s++) + { + if (*s == '(') + level++; + else if (*s == ')') + { + level--; + if (!level) + break; + } + } + return s; +} + +void mutt_pattern_free (pattern_t **pat) +{ + pattern_t *tmp; + + while (*pat) + { + tmp = *pat; + *pat = (*pat)->next; + + if (tmp->rx) + { + regfree (tmp->rx); + safe_free ((void **) &tmp->rx); + } + if (tmp->child) + mutt_pattern_free (&tmp->child); + safe_free ((void **) &tmp); + } +} + +pattern_t *mutt_pattern_comp (/* const */ char *s, int flags, BUFFER *err) +{ + pattern_t *curlist = NULL; + pattern_t *tmp; + pattern_t *last = NULL; + int not = 0; + int or = 0; + int implicit = 1; /* used to detect logical AND operator */ + struct pattern_flags *entry; + char *p; + char *buf; + BUFFER ps; + + memset (&ps, 0, sizeof (ps)); + ps.dptr = s; + ps.dsize = strlen (s); + + while (*ps.dptr) + { + SKIPWS (ps.dptr); + switch (*ps.dptr) + { + case '!': + ps.dptr++; + not = !not; + break; + case '|': + if (!or) + { + if (!curlist) + { + snprintf (err->data, err->dsize, "error in pattern at: %s", ps.dptr); + return NULL; + } + if (curlist->next) + { + /* A & B | C == (A & B) | C */ + tmp = new_pattern (); + tmp->op = M_AND; + tmp->child = curlist; + + curlist = tmp; + last = curlist; + } + + or = 1; + } + ps.dptr++; + implicit = 0; + not = 0; + break; + case '~': + if (implicit && or) + { + /* A | B & C == (A | B) & C */ + tmp = new_pattern (); + tmp->op = M_OR; + tmp->child = curlist; + curlist = tmp; + last = tmp; + or = 0; + } + + tmp = new_pattern (); + tmp->not = not; + not = 0; + + if (last) + last->next = tmp; + else + curlist = tmp; + last = tmp; + + ps.dptr++; /* move past the ~ */ + if ((entry = lookup_tag (*ps.dptr)) == NULL) + { + snprintf (err->data, err->dsize, "%c: invalid command", *ps.dptr); + mutt_pattern_free (&curlist); + return NULL; + } + if (entry->class && (flags & entry->class) == 0) + { + snprintf (err->data, err->dsize, "%c: not supported in this mode", *ps.dptr); + mutt_pattern_free (&curlist); + return NULL; + } + tmp->op = entry->op; + + ps.dptr++; /* eat the operator and any optional whitespace */ + SKIPWS (ps.dptr); + + if (entry->eat_arg) + { + if (!*ps.dptr) + { + snprintf (err->data, err->dsize, "missing parameter"); + mutt_pattern_free (&curlist); + return NULL; + } + if (entry->eat_arg (tmp, &ps, err) == -1) + { + mutt_pattern_free (&curlist); + return NULL; + } + } + implicit = 1; + break; + case '(': + p = find_matching_paren (ps.dptr + 1); + if (*p != ')') + { + snprintf (err->data, err->dsize, "mismatched parenthesis: %s", ps.dptr); + mutt_pattern_free (&curlist); + return NULL; + } + /* compile the sub-expression */ + buf = mutt_substrdup (ps.dptr + 1, p); + if ((tmp = mutt_pattern_comp (buf, flags, err)) == NULL) + { + FREE (&buf); + mutt_pattern_free (&curlist); + return NULL; + } + FREE (&buf); + if (last) + last->next = tmp; + else + curlist = tmp; + last = tmp; + tmp->not = not; + not = 0; + ps.dptr = p + 1; /* restore location */ + break; + default: + snprintf (err->data, err->dsize, "error in pattern at: %s", ps.dptr); + mutt_pattern_free (&curlist); + return NULL; + } + } + if (!curlist) + { + strfcpy (err->data, "empty pattern", err->dsize); + return NULL; + } + if (curlist->next) + { + tmp = new_pattern (); + tmp->op = or ? M_OR : M_AND; + tmp->child = curlist; + curlist = tmp; + } + return (curlist); +} + +static int +perform_and (pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, HEADER *hdr) +{ + for (; pat; pat = pat->next) + if (mutt_pattern_exec (pat, flags, ctx, hdr) <= 0) + return 0; + return 1; +} + +static int +perform_or (struct pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, HEADER *hdr) +{ + for (; pat; pat = pat->next) + if (mutt_pattern_exec (pat, flags, ctx, hdr) > 0) + return 1; + return 0; +} + +static int match_adrlist (regex_t *rx, int match_personal, ADDRESS *a) +{ + for (; a; a = a->next) + { + if ((a->mailbox && regexec (rx, a->mailbox, 0, NULL, 0) == 0) || + (match_personal && a->personal && regexec (rx, a->personal, 0, NULL, 0) == 0)) + return 1; + } + return 0; +} + +static int match_reference (regex_t *rx, LIST *refs) +{ + for (; refs; refs = refs->next) + if (regexec (rx, refs->data, 0, NULL, 0) == 0) + return 1; + return 0; +} + +static int match_user (ADDRESS *p) +{ + for (; p; p = p->next) + if (mutt_addr_is_user (p)) + return 1; + return 0; +} + +/* flags + M_MATCH_FULL_ADDRESS match both personal and machine address */ +int +mutt_pattern_exec (struct pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, HEADER *h) +{ + char buf[STRING]; + + switch (pat->op) + { + case M_AND: + return (pat->not ^ (perform_and (pat->child, flags, ctx, h) > 0)); + case M_OR: + return (pat->not ^ (perform_or (pat->child, flags, ctx, h) > 0)); + case M_ALL: + return (!pat->not); + case M_EXPIRED: + return (pat->not ^ h->expired); + case M_SUPERSEDED: + return (pat->not ^ h->superseded); + case M_FLAG: + return (pat->not ^ h->flagged); + case M_TAG: + return (pat->not ^ h->tagged); + case M_NEW: + return (pat->not ? h->old || h->read : !(h->old || h->read)); + case M_UNREAD: + return (pat->not ? h->read : !h->read); + case M_REPLIED: + return (pat->not ^ h->replied); + case M_OLD: + return (pat->not ? (!h->old || h->read) : (h->old && !h->read)); + case M_READ: + return (pat->not ^ h->read); + case M_DELETED: + return (pat->not ^ h->deleted); + case M_MESSAGE: + return (pat->not ^ (h->msgno >= pat->min - 1 && (pat->max == M_MAXRANGE || + h->msgno <= pat->max - 1))); + case M_DATE: + return (pat->not ^ (h->date_sent >= pat->min && h->date_sent <= pat->max)); + case M_DATE_RECEIVED: + return (pat->not ^ (h->received >= pat->min && h->received <= pat->max)); + case M_BODY: + case M_HEADER: + case M_WHOLE_MSG: + return (pat->not ^ msg_search (ctx, pat->rx, buf, sizeof (buf), pat->op, h->msgno)); + case M_SENDER: + return (pat->not ^ match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->sender)); + case M_FROM: + return (pat->not ^ match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->from)); + case M_TO: + return (pat->not ^ match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->to)); + case M_CC: + return (pat->not ^ match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->cc)); + case M_SUBJECT: + return (pat->not ^ (h->env->subject && regexec (pat->rx, h->env->subject, 0, NULL, 0) == 0)); + case M_ID: + return (pat->not ^ (h->env->message_id && regexec (pat->rx, h->env->message_id, 0, NULL, 0) == 0)); + case M_SCORE: + return (pat->not ^ (h->score >= pat->min && (pat->max == M_MAXRANGE || + h->score <= pat->max))); + case M_SIZE: + return (pat->not ^ (h->content->length > pat->min && (pat->max == M_MAXRANGE || h->content->length < pat->max))); + case M_REFERENCE: + return (pat->not ^ match_reference (pat->rx, h->env->references)); + case M_ADDRESS: + return (pat->not ^ (match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->from) || + match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->sender) || + match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->to) || + match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->cc))); + break; + case M_RECIPIENT: + return (pat->not ^ (match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->to) || + match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->cc))); + break; + case M_LIST: + return (pat->not ^ (mutt_is_list_recipient (h->env->to) || + mutt_is_list_recipient (h->env->cc))); + case M_PERSONAL_RECIP: + return (pat->not ^ (match_user (h->env->to) || match_user (h->env->cc))); + break; + case M_PERSONAL_FROM: + return (pat->not ^ (match_user (h->env->from))); + break; + } + mutt_error ("error: unknown op %d (report this error).", pat->op); + return (-1); +} + +/* convert a simple search into a real request */ +void mutt_check_simple (char *s, size_t len, const char *simple) +{ + char tmp[LONG_STRING]; + + if (!strchr (s, '~')) /* yup, so spoof a real request */ + { + /* convert old tokens into the new format */ + if (strcasecmp ("all", s) == 0 || + !strcmp ("^", s) || !strcmp (".", s)) /* ~A is more efficient */ + strfcpy (s, "~A", len); + else if (strcasecmp ("del", s) == 0) + strfcpy (s, "~D", len); + else if (strcasecmp ("flag", s) == 0) + strfcpy (s, "~F", len); + else if (strcasecmp ("new", s) == 0) + strfcpy (s, "~N", len); + else if (strcasecmp ("old", s) == 0) + strfcpy (s, "~O", len); + else if (strcasecmp ("repl", s) == 0) + strfcpy (s, "~Q", len); + else if (strcasecmp ("read", s) == 0) + strfcpy (s, "~R", len); + else if (strcasecmp ("tag", s) == 0) + strfcpy (s, "~T", len); + else if (strcasecmp ("unread", s) == 0) + strfcpy (s, "~U", len); + else + { + const char *p = s; + int i = 0; + + tmp[i++] = '"'; + while (*p && i < sizeof (tmp) - 2) + { + if (*p == '\\' || *p == '"') + tmp[i++] = '\\'; + tmp[i++] = *p++; + } + tmp[i++] = '"'; + tmp[i] = 0; + mutt_expand_fmt (s, len, simple, tmp); + } + } +} + +int mutt_pattern_func (int op, char *prompt, HEADER *hdr) +{ + pattern_t *pat; + char buf[LONG_STRING] = "", *simple, error[STRING]; + BUFFER err; + int i; + + if (mutt_get_field (prompt, buf, sizeof (buf), 0) != 0 || !buf[0]) + return (-1); + + mutt_message ("Compiling search pattern..."); + + simple = safe_strdup (buf); + mutt_check_simple (buf, sizeof (buf), NONULL (SimpleSearch)); + + err.data = error; + err.dsize = sizeof (error); + if ((pat = mutt_pattern_comp (buf, M_FULL_MSG, &err)) == NULL) + { + FREE (&simple); + mutt_error ("%s", err.data); + return (-1); + } + + mutt_message ("Executing command on matching messages..."); + + if (op == M_LIMIT) + { + for (i = 0; i < Context->msgcount; i++) + Context->hdrs[i]->virtual = -1; + Context->vcount = 0; + Context->vsize = 0; + } + +#define this_body Context->hdrs[i]->content + + for (i = 0; i < Context->msgcount; i++) + if (mutt_pattern_exec (pat, M_MATCH_FULL_ADDRESS, Context, Context->hdrs[i])) + { + switch (op) + { + case M_DELETE: + mutt_set_flag (Context, Context->hdrs[i], M_DELETE, 1); + break; + case M_UNDELETE: + mutt_set_flag (Context, Context->hdrs[i], M_DELETE, 0); + break; + case M_TAG: + mutt_set_flag (Context, Context->hdrs[i], M_TAG, 1); + break; + case M_UNTAG: + mutt_set_flag (Context, Context->hdrs[i], M_TAG, 0); + break; + case M_LIMIT: + Context->hdrs[i]->virtual = Context->vcount; + Context->v2r[Context->vcount] = i; + Context->vcount++; + Context->vsize+=this_body->length + this_body->offset - + this_body->hdr_offset; + break; + } + } +#undef this_body + + mutt_clear_error (); + + if (op == M_LIMIT) + { + safe_free ((void **) &Context->pattern); + if (Context->limit_pattern) + mutt_pattern_free (&Context->limit_pattern); + if (!Context->vcount) + { + mutt_error ("No messages matched criteria."); + /* restore full display */ + for (i = 0; i < Context->msgcount; i++) + { + Context->hdrs[i]->virtual = i; + Context->v2r[i] = i; + } + + Context->vcount = Context->msgcount; + } + else if (strncmp (buf, "~A", 2) != 0) + { + Context->pattern = simple; + simple = NULL; /* don't clobber it */ + Context->limit_pattern = mutt_pattern_comp (buf, M_FULL_MSG, &err); + } + } + FREE (&simple); + mutt_pattern_free (&pat); + return 0; +} + +int mutt_search_command (int cur, int op) +{ + int i, j; + char buf[STRING]; + char temp[LONG_STRING]; + char error[STRING]; + BUFFER err; + int incr; + HEADER *h; + + if (op != OP_SEARCH_NEXT && op != OP_SEARCH_OPPOSITE) + { + strfcpy (buf, LastSearch, sizeof (buf)); + if (mutt_get_field ((op == OP_SEARCH) ? "Search for: " : "Reverse search for: ", + buf, sizeof (buf), M_CLEAR) != 0 || !buf[0]) + return (-1); + + if (op == OP_SEARCH) + unset_option (OPTSEARCHREVERSE); + else + set_option (OPTSEARCHREVERSE); + + /* compare the *expanded* version of the search pattern in case + $simple_search has changed while we were searching */ + strfcpy (temp, buf, sizeof (temp)); + mutt_check_simple (temp, sizeof (temp), NONULL (SimpleSearch)); + + if (!SearchPattern || strcmp (temp, LastSearchExpn)) + { + set_option (OPTSEARCHINVALID); + strfcpy (LastSearch, buf, sizeof (LastSearch)); + mutt_message ("Compiling search pattern..."); + mutt_pattern_free (&SearchPattern); + err.data = error; + err.dsize = sizeof (error); + if ((SearchPattern = mutt_pattern_comp (temp, M_FULL_MSG, &err)) == NULL) + { + mutt_error ("%s", error); + return (-1); + } + mutt_clear_error (); + } + } + else if (!SearchPattern) + { + mutt_error ("No search pattern."); + return (-1); + } + + if (option (OPTSEARCHINVALID)) + { + for (i = 0; i < Context->msgcount; i++) + Context->hdrs[i]->searched = 0; + unset_option (OPTSEARCHINVALID); + } + + incr = (option (OPTSEARCHREVERSE)) ? -1 : 1; + if (op == OP_SEARCH_OPPOSITE) + incr = -incr; + + for (i = cur + incr, j = 0 ; j != Context->vcount; j++) + { + if (i > Context->vcount - 1) + { + i = 0; + if (option (OPTWRAPSEARCH)) + mutt_message ("Search wrapped to top."); + else + { + mutt_message ("Search hit bottom without finding match"); + return (-1); + } + } + else if (i < 0) + { + i = Context->vcount - 1; + if (option (OPTWRAPSEARCH)) + mutt_message ("Search wrapped to bottom."); + else + { + mutt_message ("Search hit top without finding match"); + return (-1); + } + } + + h = Context->hdrs[Context->v2r[i]]; + if (h->searched) + { + /* if we've already evaulated this message, use the cached value */ + if (h->matched) + return i; + } + else + { + /* remember that we've already searched this message */ + h->searched = 1; + if ((h->matched = (mutt_pattern_exec (SearchPattern, M_MATCH_FULL_ADDRESS, Context, h) > 0))) + return i; + } + + if (Signals & S_INTERRUPT) + { + mutt_error ("Search interrupted."); + Signals &= ~S_INTERRUPT; + return (-1); + } + + i += incr; + } + + mutt_error ("Not found."); + return (-1); +} diff --git a/pgp.c b/pgp.c new file mode 100644 index 00000000..881f325a --- /dev/null +++ b/pgp.c @@ -0,0 +1,1278 @@ +/* + * Copyright (C) 1996,1997 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * This file contains all of the PGP routines necessary to sign, encrypt, + * verify and decrypt PGP messages in either the new PGP/MIME format, or + * in the older Application/Pgp format. It also contains some code to + * cache the user's passphrase for repeat use when decrypting or signing + * a message. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "pgp.h" +#include "mime.h" +#include "parse.h" + +#include <sys/wait.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/stat.h> +#include <errno.h> +#include <ctype.h> + +#ifdef _PGPPATH + + +char PgpPass[STRING]; +static time_t PgpExptime = 0; /* when does the cached passphrase expire? */ + +static struct pgp_vinfo pgp_vinfo[] = +{ + { PGP_V2, "pgp2", &PgpV2, &PgpV2Pubring, &PgpV2Secring, &PgpV2Language }, + { PGP_V3, "pgp3", &PgpV3, &PgpV3Pubring, &PgpV3Secring, &PgpV3Language }, + { PGP_V3, "pgp5", &PgpV3, &PgpV3Pubring, &PgpV3Secring, &PgpV3Language }, + { PGP_UNKNOWN, NULL, NULL, NULL, NULL, NULL} +}; + +static struct +{ + enum pgp_ops op; + char **str; +} +pgp_opvers[] = +{ + { PGP_DECODE, &PgpReceiveVersion }, + { PGP_VERIFY, &PgpReceiveVersion }, + { PGP_DECRYPT, &PgpReceiveVersion }, + { PGP_SIGN, &PgpSendVersion }, + { PGP_ENCRYPT, &PgpSendVersion }, + { PGP_VERIFY_KEY, &PgpSendVersion }, + { PGP_EXTRACT, &PgpKeyVersion }, + { PGP_EXTRACT_KEY, &PgpKeyVersion }, + { PGP_LAST_OP, NULL } +}; + + + +void pgp_void_passphrase (void) +{ + memset (PgpPass, 0, sizeof (PgpPass)); + PgpExptime = 0; +} + +int pgp_valid_passphrase (void) +{ + time_t now = time (NULL); + + if (now < PgpExptime) return 1; /* just use the cached copy. */ + pgp_void_passphrase (); + + if (mutt_get_password ("Enter PGP passphrase:", PgpPass, sizeof (PgpPass)) == 0) + { + PgpExptime = time (NULL) + PgpTimeout; + return (1); + } + else + { + PgpExptime = 0; + return (0); + } + /* not reached */ +} + +void mutt_forget_passphrase (void) +{ + pgp_void_passphrase (); + mutt_message ("PGP passphrase forgotten."); +} + + +struct pgp_vinfo *pgp_get_vinfo(enum pgp_ops op) +{ + int i; + char *version = "default"; + + for(i = 0; pgp_opvers[i].op != PGP_LAST_OP; i++) + { + if(pgp_opvers[i].op == op) + { + version = *pgp_opvers[i].str; + break; + } + } + + if (!strcasecmp(version, "default")) + version = PgpDefaultVersion; + + for(i = 0; pgp_vinfo[i].name; i++) + { + if(!strcasecmp(pgp_vinfo[i].name, version)) + return &pgp_vinfo[i]; + } + + return NULL; +} + +enum pgp_version pgp_version(enum pgp_ops op) +{ + struct pgp_vinfo *info = pgp_get_vinfo(op); + return info ? info->v : PGP_UNKNOWN; +} + + +char *pgp_getring (enum pgp_ops op, int pub) +{ + struct pgp_vinfo *info = pgp_get_vinfo(op); + if(!info) return NULL; + return pub ? *info->pubring : *info->secring; +} + +char *pgp_binary(enum pgp_ops op) +{ + struct pgp_vinfo *info = pgp_get_vinfo(op); + return info ? *info->binary : NULL; +} + + +char *pgp_keyid(KEYINFO *k) +{ + if((k->flags & KEYFLAG_SUBKEY) && k->mainkey) + k = k->mainkey; + + return _pgp_keyid(k); +} + +char *_pgp_keyid(KEYINFO *k) +{ + if(option(OPTPGPLONGIDS)) + return k->keyid; + else + return (k->keyid + 8); +} + +/* ---------------------------------------------------------------------------- + * Routines for handing PGP input. + */ + +/* print the current time to avoid spoofing of the signature output */ +static void pgp_current_time (STATE *s) +{ + time_t t; + char p[STRING]; + + state_puts ("[-- PGP output follows (current time: ", s); + + t = time (NULL); + strfcpy (p, asctime (localtime (&t)), sizeof (p)); + p[strlen (p) - 1] = 0; /* kill the newline */ + state_puts (p, s); + + state_puts (") --]\n", s); +} + + +/* Support for the Application/PGP Content Type. */ + +void application_pgp_handler (BODY *m, STATE *s) +{ + int needpass = -1, pgp_keyblock = 0; + int clearsign = 0; + long start_pos = 0; + long bytes, last_pos, offset; + char buf[HUGE_STRING]; + char outfile[_POSIX_PATH_MAX]; + char tmpfname[_POSIX_PATH_MAX]; + FILE *pgpout = NULL, *pgpin, *pgperr; + FILE *tmpfp; + pid_t thepid; + + + fseek (s->fpin, m->offset, 0); + last_pos = m->offset; + + for (bytes = m->length; bytes > 0;) + { + if (fgets (buf, sizeof (buf) - 1, s->fpin) == NULL) + break; + offset = ftell (s->fpin); + bytes -= (offset - last_pos); /* don't rely on strlen(buf) */ + last_pos = offset; + + if (strncmp ("-----BEGIN PGP ", buf, 15) == 0) + { + clearsign = 0; + start_pos = last_pos; + + if (strcmp ("MESSAGE-----\n", buf + 15) == 0) + needpass = 1; + else if (strcmp ("SIGNED MESSAGE-----\n", buf + 15) == 0) + { + clearsign = 1; + needpass = 0; + } + else if (!option(OPTDONTHANDLEPGPKEYS) && + strcmp ("PUBLIC KEY BLOCK-----\n", buf + 15) == 0) + { + needpass = 0; + pgp_keyblock =1; + } + else + { + if (s->prefix) + state_puts (s->prefix, s); + state_puts (buf, s); + continue; + } + + if(!clearsign || s->flags & M_VERIFY) + { + + /* invoke PGP */ + + mutt_mktemp (outfile); + if ((pgpout = safe_fopen (outfile, "w+")) == NULL) + { + mutt_perror (outfile); + return; + } + + mutt_mktemp (tmpfname); + if ((tmpfp = safe_fopen(tmpfname, "w+")) == NULL) + { + mutt_perror(tmpfname); + fclose(pgpout); pgpout = NULL; + return; + } + + fputs (buf, tmpfp); + while (bytes > 0 && fgets (buf, sizeof (buf) - 1, s->fpin) != NULL) + { + offset = ftell (s->fpin); + bytes -= (offset - last_pos); /* don't rely on strlen(buf) */ + last_pos = offset; + + fputs (buf, tmpfp); + if ((needpass && strcmp ("-----END PGP MESSAGE-----\n", buf) == 0) || + (!needpass + && (strcmp ("-----END PGP SIGNATURE-----\n", buf) == 0 + || strcmp ("-----END PGP PUBLIC KEY BLOCK-----\n",buf) == 0))) + break; + } + + fclose(tmpfp); + + if ((thepid = pgp_invoke_decode (&pgpin, NULL, &pgperr, -1, + fileno (pgpout), -1, tmpfname, needpass)) == -1) + { + fclose (pgpout); pgpout = NULL; + mutt_unlink(tmpfname); + state_puts ("[-- Error: unable to create PGP subprocess --]\n", s); + state_puts (buf, s); + continue; + } + + if (needpass) + { + if (!pgp_valid_passphrase ()) + pgp_void_passphrase (); + fputs (PgpPass, pgpin); + fputc ('\n', pgpin); + } + + fclose (pgpin); + + if (s->flags & M_DISPLAY) + pgp_current_time (s); + + mutt_wait_filter (thepid); + + mutt_unlink(tmpfname); + + if (s->flags & M_DISPLAY) + mutt_copy_stream(pgperr, s->fpout); + fclose (pgperr); + + if (s->flags & M_DISPLAY) + state_puts ("\n[-- End of PGP output --]\n\n", s); + } + + if(s->flags & M_DISPLAY) + { + if (needpass) + state_puts ("[-- BEGIN PGP MESSAGE --]\n\n", s); + else if (pgp_keyblock) + state_puts ("[-- BEGIN PGP PUBLIC KEY BLOCK --]\n", s); + else + state_puts ("[-- BEGIN PGP SIGNED MESSAGE --]\n\n", s); + } + + /* Use PGP's output if there was no clearsig signature. */ + + if(!clearsign) + { + fflush (pgpout); + rewind (pgpout); + while (fgets (buf, sizeof (buf) - 1, pgpout) != NULL) + { + if (s->prefix) + state_puts (s->prefix, s); + state_puts (buf, s); + } + } + + /* Close the temporary files iff they were created. + * The condition used to be !clearsign || s->flags & M_VERIFY, + * but gcc would complain then. + */ + + if(pgpout) + { + fclose (pgpout); + pgpout = NULL; + mutt_unlink(outfile); + } + + /* decode clearsign stuff */ + + if(clearsign) + { + + /* rationale: We want PGP's error messages, but in the times + * of PGP 5.0 we can't rely on PGP to do the dash + * escape decoding - so we have to do this + * ourselves. + */ + + int armor_header = 1; + int complete = 1; + + fseek(s->fpin, start_pos, SEEK_SET); + bytes += (last_pos - start_pos); + last_pos = start_pos; + offset = start_pos; + while(bytes > 0 && fgets(buf, sizeof(buf) - 1, s->fpin) != NULL) + { + offset = ftell(s->fpin); + bytes -= (offset - last_pos); + last_pos = offset; + + if(complete) + { + if (!strcmp(buf, "-----BEGIN PGP SIGNATURE-----\n")) + break; + + if(armor_header) + { + if(*buf == '\n') + armor_header = 0; + } + else + { + if(s->prefix) + state_puts(s->prefix, s); + + if(buf[0] == '-' && buf [1] == ' ') + state_puts(buf + 2, s); + else + state_puts(buf, s); + } + } + else + { + if(!armor_header) + state_puts(buf, s); + } + + complete = strchr(buf, '\n') != NULL; + } + + if (complete && !strcmp(buf, "-----BEGIN PGP SIGNATURE-----\n")) + { + while(bytes > 0 && fgets(buf, sizeof(buf) - 1, s->fpin) != NULL) + { + offset = ftell(s->fpin); + bytes -= (offset - last_pos); + last_pos = offset; + + if(complete && !strcmp(buf, "-----END PGP SIGNATURE-----\n")) + break; + + complete = strchr(buf, '\n') != NULL; + } + } + } + + if (s->flags & M_DISPLAY) + { + if (needpass) + state_puts ("\n[-- END PGP MESSAGE --]\n", s); + else if (pgp_keyblock) + state_puts ("[-- END PGP PUBLIC KEY BLOCK --]\n", s); + else + state_puts ("\n[-- END PGP SIGNED MESSAGE --]\n", s); + } + } + else + { + if (s->prefix) + state_puts (s->prefix, s); + state_puts (buf, s); + } + } + + if (needpass == -1) + { + state_puts ("[-- Error! Could not find beginning of PGP message! --]\n\n", s); + return; + } + +} + +int pgp_query (BODY *m) +{ + char *p; + int t = 0; + + /* Check for old-style APPLICATION/PGP messages */ + if (m->type == TYPEAPPLICATION) + { + if (!strcasecmp (m->subtype, "pgp") || !strcasecmp (m->subtype, "x-pgp-message")) + { + if ((p = mutt_get_parameter ("x-action", m->parameter)) + && (!strcasecmp (p, "sign") || !strcasecmp (p, "signclear"))) + t |= PGPSIGN; + else if((p = mutt_get_parameter ("format", m->parameter)) + && !strcasecmp(p, "keys-only")) + t |= PGPKEY; + + if ((p = mutt_get_parameter ("format", m->parameter)) && + !strcasecmp (p, "keys-only")) + t |= PGPKEY; + + if(!t) t |= PGPENCRYPT; /* not necessarily correct, but... */ + } + + if (!strcasecmp (m->subtype, "pgp-signed")) + t |= PGPSIGN; + + if (!strcasecmp (m->subtype, "pgp-keys")) + t |= PGPKEY; + } + + /* Check for PGP/MIME messages. */ + if (m->type == TYPEMULTIPART) + { + if (strcasecmp (m->subtype, "signed") == 0 && + (p = mutt_get_parameter("protocol", m->parameter)) && + strcasecmp (p, "application/pgp-signature") == 0) + t |= PGPSIGN; + else if ((strcasecmp (m->subtype, "encrypted") == 0) && + (p = mutt_get_parameter ("protocol", m->parameter)) && + strcasecmp (p, "application/pgp-encrypted") == 0) + t |= PGPENCRYPT; + } + + if(m->type == TYPEMULTIPART || m->type == TYPEMESSAGE) + { + BODY *p; + + for(p = m->parts; p; p = p->next) + t |= pgp_query(p); + } + + return t; +} + +/* + * This routine verifies a PGP/MIME signed body. + */ +void pgp_signed_handler (BODY *a, STATE *s) +{ + FILE *fp, *pgpout, *pgperr; + char tempfile[_POSIX_PATH_MAX], sigfile[_POSIX_PATH_MAX], + pgperrfile[_POSIX_PATH_MAX]; + int bytes; + int hadcr; + int c; + pid_t thepid; + + a = a->parts; + + /* First do some error checking to make sure that this looks like a valid + * multipart/signed body. + */ + if (a && a->next && a->next->type == TYPEAPPLICATION && a->next->subtype && + strcasecmp (a->next->subtype, "pgp-signature") == 0) + { + if (s->flags & M_DISPLAY) + { + mutt_mktemp (tempfile); + + if(!(fp = safe_fopen (tempfile, "w"))) + { + mutt_perror(tempfile); + return; + } + + fseek (s->fpin, a->hdr_offset, 0); + bytes = a->length + a->offset - a->hdr_offset; + hadcr = 0; + while (bytes > 0) + { + if((c = fgetc(s->fpin)) == EOF) + break; + + bytes--; + + if(c == '\r') + hadcr = 1; + else + { + if(c == '\n' && !hadcr) + fputc('\r', fp); + + hadcr = 0; + } + + fputc(c, fp); + + } + fclose (fp); + + /* Now grab the signature. Since signature data is required to be 7bit, + * we don't have to worry about doing CTE decoding... + */ + snprintf (sigfile, sizeof (sigfile), "%s.asc", tempfile); + if(!(fp = safe_fopen (sigfile, "w"))) + { + mutt_perror(sigfile); + unlink(tempfile); + return; + } + + fseek (s->fpin, a->next->offset, 0); + mutt_copy_bytes (s->fpin, fp, a->next->length); + fclose (fp); + + mutt_mktemp(pgperrfile); + if(!(pgperr = safe_fopen(pgperrfile, "w+"))) + { + mutt_perror(pgperrfile); + unlink(tempfile); + unlink(sigfile); + return; + } + + pgp_current_time (s); + + if((thepid = pgp_invoke_verify (NULL, &pgpout, NULL, -1, -1, fileno(pgperr), + tempfile, sigfile)) != -1) + { + mutt_copy_stream(pgpout, s->fpout); + fclose (pgpout); + + fflush(pgperr); + rewind(pgperr); + mutt_copy_stream(pgperr, s->fpout); + fclose(pgperr); + + mutt_wait_filter (thepid); + } + + state_puts ("[-- End of PGP output --]\n\n", s); + + mutt_unlink (tempfile); + mutt_unlink (sigfile); + mutt_unlink (pgperrfile); + + /* Now display the signed body */ + state_puts ("[-- The following data is PGP/MIME signed --]\n\n", s); + } + + mutt_body_handler (a, s); + + if (s->flags & M_DISPLAY) + state_puts ("\n[-- End of PGP/MIME signed data --]\n", s); + } + else + { + dprint (1,(debugfile, "pgp_signed_handler: invalid format!\n")); + state_puts ("[-- Error! This message does not comply with the PGP/MIME specification! --]\n\n", s); + mutt_decode_attachment (a, s); /* just treat the data as text/plain... */ + } +} + +/* Extract pgp public keys from messages or attachments */ + +void pgp_extract_keys_from_messages (HEADER *h) +{ + int i; + STATE s; + char tempfname[_POSIX_PATH_MAX]; + + if(h) + { + mutt_parse_mime_message(Context, h); + if(h->pgp & PGPENCRYPT && !pgp_valid_passphrase()) + return; + } + + memset(&s, 0, sizeof(STATE)); + + mutt_mktemp(tempfname); + if(!(s.fpout = safe_fopen(tempfname, "w"))) + { + mutt_perror(tempfname); + return; + } + + set_option(OPTDONTHANDLEPGPKEYS); + + if(!h) + { + for(i = 0; i < Context->vcount; i++) + { + if(Context->hdrs[Context->v2r[i]]->tagged) + { + mutt_parse_mime_message(Context, Context->hdrs[Context->v2r[i]]); + if(Context->hdrs[Context->v2r[i]]->pgp & PGPENCRYPT + && !pgp_valid_passphrase()) + { + fclose(s.fpout); + goto bailout; + } + mutt_pipe_message_to_state(Context->hdrs[Context->v2r[i]], &s); + } + } + } + else + { + mutt_parse_mime_message(Context, h); + if(h->pgp & PGPENCRYPT && !pgp_valid_passphrase()) + { + fclose(s.fpout); + goto bailout; + } + mutt_pipe_message_to_state(h, &s); + } + + fclose(s.fpout); + endwin(); + pgp_invoke_extract(tempfname); + mutt_any_key_to_continue(NULL); + + bailout: + + mutt_unlink(tempfname); + unset_option(OPTDONTHANDLEPGPKEYS); + +} + +static void pgp_extract_keys_from_attachment(FILE *fp, BODY *top) +{ + STATE s; + FILE *tempfp; + char tempfname[_POSIX_PATH_MAX]; + + mutt_mktemp(tempfname); + if(!(tempfp = safe_fopen(tempfname, "w"))) + { + mutt_perror(tempfname); + return; + } + + memset(&s, 0, sizeof(STATE)); + + s.fpin = fp; + s.fpout = tempfp; + + mutt_body_handler(top, &s); + + fclose(tempfp); + + pgp_invoke_extract(tempfname); + mutt_any_key_to_continue(NULL); + + mutt_unlink(tempfname); + +} + +void pgp_extract_keys_from_attachment_list (FILE *fp, int tag, BODY *top) +{ + if(!fp) + { + mutt_error("Internal error. Inform <roessler@guug.de>."); + return; + } + + endwin(); + set_option(OPTDONTHANDLEPGPKEYS); + + for(; top; top = top->next) + { + if(!tag || top->tagged) + pgp_extract_keys_from_attachment (fp, top); + + if(!tag) + break; + } + + unset_option(OPTDONTHANDLEPGPKEYS); +} + +BODY *pgp_decrypt_part (BODY *a, STATE *s, FILE *fpout) +{ + char buf[LONG_STRING]; + FILE *pgpin, *pgpout, *pgperr, *pgptmp; + struct stat info; + BODY *tattach; + int len; + char pgperrfile[_POSIX_PATH_MAX]; + char pgptmpfile[_POSIX_PATH_MAX]; + pid_t thepid; + + mutt_mktemp (pgperrfile); + if ((pgperr = safe_fopen (pgperrfile, "w+")) == NULL) + { + mutt_perror (pgperrfile); + return NULL; + } + unlink (pgperrfile); + + mutt_mktemp (pgptmpfile); + if((pgptmp = safe_fopen (pgptmpfile, "w")) == NULL) + { + mutt_perror (pgptmpfile); + fclose(pgperr); + return NULL; + } + + /* Position the stream at the beginning of the body, and send the data to + * the temporary file. + */ + + fseek (s->fpin, a->offset, 0); + mutt_copy_bytes (s->fpin, pgptmp, a->length); + fclose (pgptmp); + + if ((thepid = pgp_invoke_decrypt (&pgpin, &pgpout, NULL, -1, -1, + fileno (pgperr), pgptmpfile)) == -1) + { + fclose (pgperr); + unlink (pgptmpfile); + if (s->flags & M_DISPLAY) + state_puts ("[-- Error: could not create a PGP subprocess! --]\n\n", s); + return (NULL); + } + + /* send the PGP passphrase to the subprocess */ + fputs (PgpPass, pgpin); + fputc ('\n', pgpin); + fclose(pgpin); + + /* Read the output from PGP, and make sure to change CRLF to LF, otherwise + * read_mime_header has a hard time parsing the message. + */ + while (fgets (buf, sizeof (buf) - 1, pgpout) != NULL) + { + len = strlen (buf); + if (len > 1 && buf[len - 2] == '\r') + strcpy (buf + len - 2, "\n"); + fputs (buf, fpout); + } + + fclose (pgpout); + mutt_wait_filter (thepid); + mutt_unlink(pgptmpfile); + + if (s->flags & M_DISPLAY) + { + fflush (pgperr); + rewind (pgperr); + mutt_copy_stream (pgperr, s->fpout); + state_puts ("[-- End of PGP output --]\n\n", s); + } + fclose (pgperr); + + fflush (fpout); + rewind (fpout); + if ((tattach = mutt_read_mime_header (fpout, 0)) != NULL) + { + /* + * Need to set the length of this body part. + */ + fstat (fileno (fpout), &info); + tattach->length = info.st_size - tattach->offset; + + /* See if we need to recurse on this MIME part. */ + + if (tattach->type == TYPEMULTIPART) + { + fseek (fpout, tattach->offset, 0); + tattach->parts = mutt_parse_multipart (fpout, mutt_get_parameter ("boundary", tattach->parameter), tattach->offset + tattach->length, strcasecmp ("digest", tattach->subtype) == 0); + } + else if (tattach->type == TYPEMESSAGE) + { + fseek (fpout, tattach->offset, 0); + tattach->parts = mutt_parse_messageRFC822 (fpout, tattach); + } + } + + return (tattach); +} + +void pgp_encrypted_handler (BODY *a, STATE *s) +{ + char tempfile[_POSIX_PATH_MAX]; + FILE *fpout, *fpin; + BODY *tattach; + + a = a->parts; + if (!a || a->type != TYPEAPPLICATION || !a->subtype || + strcasecmp ("pgp-encrypted", a->subtype) != 0 || + !a->next || a->next->type != TYPEAPPLICATION || !a->next->subtype || + strcasecmp ("octet-stream", a->next->subtype) != 0) + { + if (s->flags & M_DISPLAY) + state_puts ("[-- Error: malformed PGP/MIME message --]\n\n", s); + return; + } + + /* + * Move forward to the application/pgp-encrypted body. + */ + a = a->next; + + mutt_mktemp (tempfile); + if ((fpout = safe_fopen (tempfile, "w+")) == NULL) + { + if (s->flags & M_DISPLAY) + state_puts ("[-- Error: could not create temporary file --]\n", s); + return; + } + + if (s->flags & M_DISPLAY) pgp_current_time (s); + + if ((tattach = pgp_decrypt_part (a, s, fpout)) != NULL) + { + if (s->flags & M_DISPLAY) + state_puts ("[-- The following data is PGP/MIME encrypted --]\n\n", s); + + fpin = s->fpin; + s->fpin = fpout; + mutt_body_handler (tattach, s); + s->fpin = fpin; + + if (s->flags & M_DISPLAY) + state_puts ("\n[-- End of PGP/MIME encrypted data --]\n", s); + + mutt_free_body (&tattach); + } + + fclose (fpout); + mutt_unlink(tempfile); +} + +/* ---------------------------------------------------------------------------- + * Routines for sending PGP/MIME messages. + */ + +static void convert_to_7bit (BODY *a) +{ + while (a) + { + if (a->type == TYPEMULTIPART) + { + if (a->encoding != ENC7BIT) + { + a->encoding = ENC7BIT; + convert_to_7bit (a->parts); + } + } + else if (a->type == TYPEMESSAGE + && strcasecmp(a->subtype, "delivery-status")) + { + if(a->encoding != ENC7BIT) + mutt_message_to_7bit(a, NULL); + } + else if (a->encoding == ENC8BIT) + a->encoding = ENCQUOTEDPRINTABLE; + else if (a->encoding == ENCBINARY) + a->encoding = ENCBASE64; + else if (a->content && (a->content->from || (a->content->space && option (OPTPGPSTRICTENC)))) + a->encoding = ENCQUOTEDPRINTABLE; + a = a->next; + } +} + +BODY *pgp_sign_message (BODY *a) +{ + PARAMETER *p; + BODY *t; + char buffer[LONG_STRING]; + char sigfile[_POSIX_PATH_MAX], signedfile[_POSIX_PATH_MAX]; + FILE *pgpin, *pgpout, *pgperr, *fp, *sfp; + int err = 0; + int empty = 1; + pid_t thepid; + + convert_to_7bit (a); /* Signed data _must_ be in 7-bit format. */ + + mutt_mktemp (sigfile); + if ((fp = safe_fopen (sigfile, "w")) == NULL) + { + return (NULL); + } + + mutt_mktemp (signedfile); + if ((sfp = safe_fopen(signedfile, "w")) == NULL) + { + mutt_perror(signedfile); + fclose(fp); + unlink(sigfile); + return NULL; + } + + mutt_write_mime_header (a, sfp); + fputc ('\n', sfp); + mutt_write_mime_body (a, sfp); + fclose(sfp); + + if((thepid = pgp_invoke_sign(&pgpin, &pgpout, &pgperr, + -1, -1, -1, signedfile)) == -1) + { + mutt_perror("Can't open PGP subprocess!"); + fclose(fp); + unlink(sigfile); + unlink(signedfile); + return NULL; + } + + fputs(PgpPass, pgpin); + fputc('\n', pgpin); + fclose(pgpin); + + /* + * Read back the PGP signature. Also, change MESSAGE=>SIGNATURE as + * recommended for future releases of PGP. + */ + while (fgets (buffer, sizeof (buffer) - 1, pgpout) != NULL) + { + if (strcmp ("-----BEGIN PGP MESSAGE-----\n", buffer) == 0) + fputs ("-----BEGIN PGP SIGNATURE-----\n", fp); + else if (strcmp("-----END PGP MESSAGE-----\n", buffer) == 0) + fputs ("-----END PGP SIGNATURE-----\n", fp); + else + fputs (buffer, fp); + empty = 0; /* got some output, so we're ok */ + } + + /* check for errors from PGP */ + err = 0; + while (fgets (buffer, sizeof (buffer) - 1, pgperr) != NULL) + { + err = 1; + fputs (buffer, stdout); + } + + mutt_wait_filter (thepid); + fclose (pgperr); + fclose (pgpout); + unlink (signedfile); + + if (fclose (fp) != 0) + { + mutt_perror ("fclose"); + unlink (sigfile); + return (NULL); + } + + if (err) + mutt_any_key_to_continue (NULL); + if (empty) + { + unlink (sigfile); + return (NULL); /* fatal error while signing */ + } + + t = mutt_new_body (); + t->type = TYPEMULTIPART; + t->subtype = safe_strdup ("signed"); + t->use_disp = 0; + t->encoding = ENC7BIT; + + t->parameter = p = mutt_new_parameter (); + p->attribute = safe_strdup ("protocol"); + p->value = safe_strdup ("application/pgp-signature"); + + p->next = mutt_new_parameter (); + p = p->next; + p->attribute = safe_strdup ("micalg"); + p->value = safe_strdup (PgpSignMicalg); + + p->next = mutt_new_parameter (); + p = p->next; + p->attribute = safe_strdup ("boundary"); + p->value = mutt_generate_boundary (); + + t->parts = a; + a = t; + + t->parts->next = mutt_new_body (); + t = t->parts->next; + t->type = TYPEAPPLICATION; + t->subtype = safe_strdup ("pgp-signature"); + t->filename = safe_strdup (sigfile); + t->use_disp = 0; + t->encoding = ENC7BIT; + t->unlink = 1; /* ok to remove this file after sending. */ + + return (a); +} + +/* This routine attempts to find the keyids of the recipients of a message. + * It returns NULL if any of the keys can not be found. + */ +char *pgp_findKeys (ADDRESS *to, ADDRESS *cc, ADDRESS *bcc) +{ + char *pubring = pgp_pubring(PGP_ENCRYPT); + char *key, *keylist = NULL; + size_t keylist_size = 0; + size_t keylist_used = 0; + ADDRESS *tmp = NULL; + ADDRESS **last = &tmp; + ADDRESS *p; + int i; + KEYINFO *db; + KEYINFO *k_info; + + db = pgp_read_keyring(pubring); + + for (i = 0; i < 3; i++) + { + switch (i) + { + case 0: p = to; break; + case 1: p = cc; break; + case 2: p = bcc; break; + default: abort (); + } + + *last = rfc822_cpy_adr (p); + while (*last) + last = &((*last)->next); + } + + tmp = mutt_remove_duplicates (tmp); + + for (p = tmp; p ; p = p->next) + { + if ((k_info = ki_getkeybyaddr (p, db, KEYFLAG_CANENCRYPT)) == NULL) + { + char buf[LONG_STRING]; + snprintf (buf, sizeof (buf), "Enter keyID for %s: ", p->mailbox); + + if ((key = pgp_ask_for_key (pubring, db, buf, p->mailbox, + KEYFLAG_CANENCRYPT, NULL)) == NULL) + { + pgp_closedb (db); + safe_free ((void **)&keylist); + rfc822_free_address (&tmp); + return NULL; + } + } + else + key = pgp_keyid(k_info); + + keylist_size += strlen (key) + 4; + safe_realloc ((void **)&keylist, keylist_size); + sprintf (keylist + keylist_used, "%s0x%s", keylist_used ? " " : "", + key); + keylist_used = strlen (keylist); + } + rfc822_free_address (&tmp); + pgp_closedb (db); + return (keylist); +} + +BODY *pgp_encrypt_message (BODY *a, char *keylist, int sign) +{ + char buf[LONG_STRING]; + char tempfile[_POSIX_PATH_MAX], pgperrfile[_POSIX_PATH_MAX]; + char pgpinfile[_POSIX_PATH_MAX]; + FILE *pgpin, *pgperr, *fpout, *fptmp; + BODY *t; + PARAMETER *p; + int err = 0; + int empty; + pid_t thepid; + + mutt_mktemp (tempfile); + if ((fpout = safe_fopen (tempfile, "w+")) == NULL) + { + mutt_perror (tempfile); + return (NULL); + } + + mutt_mktemp (pgperrfile); + if ((pgperr = safe_fopen (pgperrfile, "w+")) == NULL) + { + mutt_perror (pgperrfile); + unlink(tempfile); + fclose(fpout); + return NULL; + } + unlink (pgperrfile); + + mutt_mktemp(pgpinfile); + if((fptmp = safe_fopen(pgpinfile, "w")) == NULL) + { + mutt_perror(pgpinfile); + unlink(tempfile); + fclose(fpout); + fclose(pgperr); + return NULL; + } + + if (sign) + convert_to_7bit (a); + + mutt_write_mime_header (a, fptmp); + fputc ('\n', fptmp); + mutt_write_mime_body (a, fptmp); + fclose(fptmp); + + if ((thepid = pgp_invoke_encrypt (&pgpin, NULL, NULL, -1, + fileno (fpout), fileno (pgperr), + pgpinfile, keylist, sign)) == -1) + { + fclose (pgperr); + unlink(pgpinfile); + return (NULL); + } + + if (sign) + { + fputs (PgpPass, pgpin); + fputc ('\n', pgpin); + } + fclose(pgpin); + + mutt_wait_filter (thepid); + unlink(pgpinfile); + + fflush (fpout); + rewind (fpout); + empty = (fgetc (fpout) == EOF); + fclose (fpout); + + fflush (pgperr); + rewind (pgperr); + while (fgets (buf, sizeof (buf) - 1, pgperr) != NULL) + { + err = 1; + fputs (buf, stdout); + } + fclose (pgperr); + + /* pause if there is any error output from PGP */ + if (err) + mutt_any_key_to_continue (NULL); + + if (empty) + { + /* fatal error while trying to encrypt message */ + unlink (tempfile); + return (NULL); + } + + t = mutt_new_body (); + t->type = TYPEMULTIPART; + t->subtype = safe_strdup ("encrypted"); + t->encoding = ENC7BIT; + t->use_disp = 0; + + t->parameter = p = mutt_new_parameter (); + p->attribute = safe_strdup ("protocol"); + p->value = safe_strdup ("application/pgp-encrypted"); + + p->next = mutt_new_parameter (); + p = p->next; + p->attribute = safe_strdup ("boundary"); + p->value = mutt_generate_boundary (); + + t->parts = mutt_new_body (); + t->parts->type = TYPEAPPLICATION; + t->parts->subtype = safe_strdup ("pgp-encrypted"); + t->parts->encoding = ENC7BIT; + t->parts->use_disp = 0; + + t->parts->next = mutt_new_body (); + t->parts->next->type = TYPEAPPLICATION; + t->parts->next->subtype = safe_strdup ("octet-stream"); + t->parts->next->encoding = ENC7BIT; + t->parts->next->filename = safe_strdup (tempfile); + t->parts->next->use_disp = 0; + t->parts->next->unlink = 1; /* delete after sending the message */ + + mutt_free_body (&a); /* no longer needed! */ + + return (t); +} + +int pgp_protect (HEADER *msg) +{ + char *pgpkeylist = NULL; + BODY *pbody = NULL; + + /* Do a quick check to make sure that we can find all of the encryption + * keys if the user has requested this service. + */ + + set_option (OPTPGPCHECKTRUST); + + if (msg->pgp & PGPENCRYPT) + { + if ((pgpkeylist = pgp_findKeys (msg->env->to, msg->env->cc, msg->env->bcc)) == NULL) + return (-1); + } + + if ((msg->pgp & PGPSIGN) && !pgp_valid_passphrase ()) + return (-1); + + endwin (); + if (msg->pgp & PGPENCRYPT) + { + pbody = pgp_encrypt_message (msg->content, pgpkeylist, msg->pgp & PGPSIGN); + safe_free ((void **) &pgpkeylist); + if (!pbody) + return (-1); + } + else if (msg->pgp & PGPSIGN) + { + if ((pbody = pgp_sign_message (msg->content)) == NULL) + return (-1); + } + msg->content = pbody; + return 0; +} + +#endif /* _PGPPATH */ diff --git a/pgp.h b/pgp.h new file mode 100644 index 00000000..80af9915 --- /dev/null +++ b/pgp.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 1996,1997 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifdef _PGPPATH + +#define PGPENCRYPT 1 +#define PGPSIGN 2 +#define PGPKEY 4 + +#define KEYFLAG_CANSIGN (1 << 0) +#define KEYFLAG_CANENCRYPT (1 << 1) +#define KEYFLAG_EXPIRED (1 << 8) +#define KEYFLAG_REVOKED (1 << 9) +#define KEYFLAG_DISABLED (1 << 10) +#define KEYFLAG_SUBKEY (1 << 11) +#define KEYFLAG_CRITICAL (1 << 12) +#define KEYFLAG_PREFER_ENCRYPTION (1 << 13) +#define KEYFLAG_PREFER_SIGNING (1 << 14) + +typedef struct keyinfo +{ + char *keyid; + LIST *address; + short flags; + short keylen; + unsigned long gen_time; + const char *algorithm; + struct keyinfo *mainkey; + struct keyinfo *next; +} KEYINFO; + +typedef struct pgp_uid +{ + char *addr; + short trust; +} PGPUID; + +enum pgp_version +{ + PGP_V2, + PGP_V3, + PGP_UNKNOWN +}; + +enum pgp_ops +{ + PGP_DECODE, /* application/pgp */ + PGP_VERIFY, /* PGP/MIME, signed */ + PGP_DECRYPT, /* PGP/MIME, encrypted */ + PGP_SIGN, /* sign data */ + PGP_ENCRYPT, /* encrypt data */ + PGP_EXTRACT, /* extract keys from messages */ + PGP_VERIFY_KEY, /* verify key when selecting */ + PGP_EXTRACT_KEY, /* extract keys from key ring */ + PGP_LAST_OP +}; + +struct pgp_vinfo +{ + enum pgp_version v; + char *name; + char **binary; + char **pubring; + char **secring; + char **language; +}; + + +WHERE char *PgpV2; +WHERE char *PgpV2Language; +WHERE char *PgpV2Pubring; +WHERE char *PgpV2Secring; + +WHERE char *PgpV3; +WHERE char *PgpV3Language; +WHERE char *PgpV3Pubring; +WHERE char *PgpV3Secring; + +WHERE char *PgpSendVersion; +WHERE char *PgpReceiveVersion; +WHERE char *PgpKeyVersion; +WHERE char *PgpDefaultVersion; + +WHERE char *PgpSignAs; +WHERE char *PgpSignMicalg; + +WHERE short PgpTimeout; + + + +BODY *pgp_decrypt_part (BODY *, STATE *, FILE *); +BODY *pgp_make_key_attachment (char *); + +const char *pgp_pkalg_to_mic(const char *); + +char *pgp_ask_for_key (const char *, KEYINFO *, char *, char *, short, char **); + +char *pgp_binary(enum pgp_ops); +char *pgp_getring (enum pgp_ops, int); +char *pgp_keyid(KEYINFO *); +char *_pgp_keyid(KEYINFO *); + +enum pgp_version pgp_version(enum pgp_ops); +struct pgp_vinfo *pgp_get_vinfo(enum pgp_ops); + +int mutt_check_pgp (HEADER *h); +int mutt_parse_pgp_hdr (char *, int); + +int pgp_protect (HEADER *); +int pgp_query (BODY *); +int pgp_valid_passphrase (void); + +KEYINFO *ki_getkeybyaddr (ADDRESS *, KEYINFO *, short); +KEYINFO *ki_getkeybystr (char *, KEYINFO *, short); +KEYINFO *pgp_read_keyring(const char *); + +void application_pgp_handler (BODY *, STATE *); +void mutt_forget_passphrase (void); +void pgp_closedb (KEYINFO *); +void pgp_encrypted_handler (BODY *, STATE *); +void pgp_extract_keys_from_attachment_list (FILE *fp, int tag, BODY *top); +void pgp_extract_keys_from_messages(HEADER *hdr); +void pgp_signed_handler (BODY *, STATE *); +void pgp_void_passphrase (void); + +#define pgp_secring(a) pgp_getring(a, 0) +#define pgp_pubring(a) pgp_getring(a, 1) + + +pid_t pgp_invoke_decode(FILE **, FILE **, FILE **, int, int, int, const char *, int); +pid_t pgp_invoke_verify(FILE **, FILE **, FILE **, int, int, int, const char *, const char *); +pid_t pgp_invoke_decrypt(FILE **, FILE **, FILE **, int, int, int, const char *); +pid_t pgp_invoke_sign(FILE **, FILE **, FILE **, int, int, int, const char *); +pid_t pgp_invoke_encrypt(FILE **, FILE **, FILE **, int, int, int, const char *, const char *, int); +void pgp_invoke_extract(const char *); +pid_t pgp_invoke_verify_key(FILE **, FILE **, FILE **, int, int, int, const char *); +pid_t pgp_invoke_extract_key(FILE **, FILE **, FILE **, int, int, int, const char *); + + +#endif /* _PGPPATH */ diff --git a/pgpinvoke.c b/pgpinvoke.c new file mode 100644 index 00000000..81823b13 --- /dev/null +++ b/pgpinvoke.c @@ -0,0 +1,359 @@ +/* + * Copyright (C) 1997 Thomas Roessler <roessler@guug.de> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <time.h> + +#include "mutt.h" +#include "pgp.h" + + +pid_t pgp_invoke_decode (FILE **pgpin, FILE **pgpout, FILE **pgperr, + int pgpinfd, int pgpoutfd, int pgperrfd, + const char *fname, int need_passphrase) +{ + char cmd[HUGE_STRING]; + struct pgp_vinfo *pgp = pgp_get_vinfo(PGP_DECODE); + + if(!pgp) + { + mutt_error("Unknown PGP version."); + return -1; + } + + switch(pgp->v) + { + case PGP_V2: + snprintf(cmd, sizeof(cmd), "%scat %s%s | " + "%s +language=%s +pubring=%s +secring=%s +verbose=0 +batchmode -f", + need_passphrase ? "PGPPASSFD=0; export PGPPASSFD; " : "", + need_passphrase ? "- " : "", + fname, + NONULL (*pgp->binary), NONULL (*pgp->language), NONULL (*pgp->pubring), NONULL (*pgp->secring)); + break; + + case PGP_V3: + snprintf(cmd, sizeof(cmd), "%scat %s%s | " + "%sv +language=%s +pubring=%s +secring=%s +verbose=0 +batchmode -f " + "--OutputInformationFD=2", + need_passphrase ? "PGPPASSFD=0; export PGPPASSFD; " : "", + need_passphrase ? "- " : "", + fname, + NONULL (*pgp->binary), NONULL (*pgp->language), NONULL (*pgp->pubring), NONULL (*pgp->secring)); + break; + + default: + mutt_error("Unknown PGP version."); + return -1; + } + + return mutt_create_filter_fd(cmd, pgpin, pgpout, pgperr, + pgpinfd, pgpoutfd, pgperrfd); +} + + +pid_t pgp_invoke_verify(FILE **pgpin, FILE **pgpout, FILE **pgperr, + int pgpinfd, int pgpoutfd, int pgperrfd, + const char *signedstuff, const char *sigfile) +{ + char cmd[HUGE_STRING]; + struct pgp_vinfo *pgp = pgp_get_vinfo(PGP_VERIFY); + + if(!pgp) + { + mutt_error("Unknown PGP version."); + return -1; + } + + switch(pgp->v) + { + case PGP_V2: + snprintf(cmd, sizeof(cmd), + "%s +language=%s +pubring=%s +secring=%s +batchmode +verbose=0 %s %s", + NONULL (*pgp->binary), NONULL (*pgp->language), NONULL (*pgp->pubring), NONULL (*pgp->secring), sigfile, signedstuff); + break; + + case PGP_V3: + snprintf(cmd, sizeof(cmd), + "%sv +language=%s +pubring=%s +secring=%s --OutputInformationFD=1 +batchmode +verbose=0 %s %s", + NONULL (*pgp->binary), NONULL (*pgp->language), NONULL (*pgp->pubring), NONULL (*pgp->secring), sigfile, signedstuff); + break; + + default: + mutt_error("Unknown PGP version."); + return -1; + } + + return mutt_create_filter_fd(cmd, pgpin, pgpout, pgperr, + pgpinfd, pgpoutfd, pgperrfd); +} + + + +pid_t pgp_invoke_decrypt(FILE **pgpin, FILE **pgpout, FILE **pgperr, + int pgpinfd, int pgpoutfd, int pgperrfd, + const char *fname) +{ + char cmd[HUGE_STRING]; + struct pgp_vinfo *pgp = pgp_get_vinfo(PGP_DECRYPT); + + if(!pgp) + { + mutt_error("Unknown PGP version."); + return -1; + } + + switch(pgp->v) + { + case PGP_V2: + snprintf(cmd, sizeof(cmd), + "PGPPASSFD=0; export PGPPASSFD; cat - %s | %s +language=%s +pubring=%s +secring=%s " + "+verbose=0 +batchmode -f", + fname, NONULL (*pgp->binary), NONULL (*pgp->language), NONULL (*pgp->pubring), NONULL (*pgp->secring)); + break; + + case PGP_V3: + snprintf(cmd, sizeof(cmd), + "PGPPASSFD=0; export PGPPASSFD; cat - %s | %sv +language=%s +pubring=%s +secring=%s " + "+verbose=0 +batchmode -f --OutputInformationFD=2", + fname, NONULL (*pgp->binary), NONULL (*pgp->language), NONULL (*pgp->pubring), NONULL (*pgp->secring)); + break; + + default: + mutt_error("Unknown PGP version."); + return -1; + + } + + return mutt_create_filter_fd(cmd, pgpin, pgpout, pgperr, + pgpinfd, pgpoutfd, pgperrfd); +} + + +pid_t pgp_invoke_sign(FILE **pgpin, FILE **pgpout, FILE **pgperr, + int pgpinfd, int pgpoutfd, int pgperrfd, + const char *fname) +{ + char cmd[HUGE_STRING]; + struct pgp_vinfo *pgp = pgp_get_vinfo(PGP_SIGN); + + if(!pgp) + { + mutt_error("Unknown PGP version."); + return -1; + } + + switch(pgp->v) + { + case PGP_V2: + snprintf(cmd, sizeof(cmd), + "PGPPASSFD=0; export PGPPASSFD; cat - %s | %s " + "+language=%s +pubring=%s +secring=%s +verbose=0 +batchmode -abfst %s %s", + fname, NONULL (*pgp->binary), NONULL (*pgp->language), NONULL (*pgp->pubring), NONULL (*pgp->secring), + PgpSignAs ? "-u" : "", + PgpSignAs ? PgpSignAs : ""); + break; + + case PGP_V3: + snprintf(cmd, sizeof(cmd), + "PGPPASSFD=0; export PGPPASSFD; cat - %s | %ss " + "+language=%s +pubring=%s +secring=%s +verbose=0 -abft %s %s", + fname, NONULL (*pgp->binary), NONULL (*pgp->language), NONULL (*pgp->pubring), NONULL (*pgp->secring), + PgpSignAs ? "-u" : "", + PgpSignAs ? PgpSignAs : ""); + break; + + default: + mutt_error("Unknown PGP version."); + return -1; + + } + + return mutt_create_filter_fd(cmd, pgpin, pgpout, pgperr, + pgpinfd, pgpoutfd, pgperrfd); +} + + +pid_t pgp_invoke_encrypt(FILE **pgpin, FILE **pgpout, FILE **pgperr, + int pgpinfd, int pgpoutfd, int pgperrfd, + const char *fname, const char *uids, int sign) +{ + char cmd[HUGE_STRING]; + char tmpcmd[HUGE_STRING]; + char *cp; + char *keylist; + struct pgp_vinfo *pgp = pgp_get_vinfo(PGP_ENCRYPT); + + if(!pgp) + { + mutt_error("Unknown PGP version."); + return -1; + } + + switch(pgp->v) + { + case PGP_V2: + snprintf(cmd, sizeof(cmd), + "%scat %s%s | %s +language=%s +pubring=%s +secring=%s +verbose=0 %s +batchmode -aeft%s %s%s %s", + sign ? "PGPPASSFD=0; export PGPPASSFD; " : "", + sign ? "- " : "", + fname, + NONULL (*pgp->binary), NONULL (*pgp->language), NONULL (*pgp->pubring), NONULL (*pgp->secring), + option(OPTPGPENCRYPTSELF) ? "+encrypttoself" : "", + sign ? "s" : "", + sign && PgpSignAs ? "-u " : "", + sign && PgpSignAs ? PgpSignAs : "", + uids); + break; + + case PGP_V3: + snprintf(cmd, sizeof(cmd), + "%scat %s%s | %se +language=%s +pubring=%s +secring=%s +verbose=0 %s +batchmode +nobatchinvalidkeys=off -aft%s %s%s", + sign ? "PGPPASSFD=0; export PGPPASSFD; " : "", + sign ? "- " : "", + fname, + NONULL (*pgp->binary), NONULL (*pgp->language), NONULL (*pgp->pubring), NONULL (*pgp->secring), + option(OPTPGPENCRYPTSELF) ? "+encrypttoself" : "", + sign ? "s" : "", + sign && PgpSignAs ? "-u " : "", + sign && PgpSignAs ? PgpSignAs : ""); + + keylist = safe_strdup(uids); + for(cp = strtok(keylist, " "); cp ; cp = strtok(NULL, " ")) + { + snprintf(tmpcmd, sizeof(tmpcmd), "%s -r %s", + cmd, cp); + strcpy(cmd, tmpcmd); + } + safe_free((void **) &keylist); + break; + + default: + mutt_error("Unknown PGP version."); + return -1; + + } + + return mutt_create_filter_fd(cmd, pgpin, pgpout, pgperr, + pgpinfd, pgpoutfd, pgperrfd); +} + + +void pgp_invoke_extract(const char *fname) +{ + char cmd[HUGE_STRING]; + struct pgp_vinfo *pgp = pgp_get_vinfo(PGP_EXTRACT); + + if(!pgp) + { + mutt_error("Unknown PGP version."); + return; + } + + switch(pgp->v) + { + case PGP_V2: + snprintf(cmd, sizeof(cmd), "%s +language=%s +pubring=%s +secring=%s -ka %s", + NONULL (*pgp->binary), NONULL (*pgp->language), NONULL (*pgp->pubring), NONULL (*pgp->secring), fname); + break; + + case PGP_V3: + snprintf(cmd, sizeof(cmd), "%sk +language=%s +pubring=%s +secring=%s -a --OutputInformationFD=1 %s", + NONULL (*pgp->binary), NONULL (*pgp->language), NONULL (*pgp->pubring), NONULL (*pgp->secring), fname); + break; + + default: + mutt_error("Unknown PGP version."); + return; + + } + mutt_system(cmd); +} + + +pid_t pgp_invoke_verify_key(FILE **pgpin, FILE **pgpout, FILE **pgperr, + int pgpinfd, int pgpoutfd, int pgperrfd, const char *id) +{ + char cmd[HUGE_STRING]; + struct pgp_vinfo *pgp = pgp_get_vinfo(PGP_VERIFY_KEY); + + if(!pgp) + { + mutt_error("Unknown PGP version."); + return -1; + } + + switch(pgp->v) + { + case PGP_V2: + snprintf(cmd, sizeof(cmd), "%s +language=%s +pubring=%s +secring=%s +batchmode -kcc 0x%8s", + NONULL (*pgp->binary), NONULL (*pgp->language), NONULL (*pgp->pubring), NONULL (*pgp->secring), id); + break; + + case PGP_V3: + snprintf(cmd, sizeof(cmd), "%sk +language=%s +pubring=%s +secring=%s +batchmode -c --OutputInformationFD=1 0x%8s", + NONULL (*pgp->binary), NONULL (*pgp->language), NONULL (*pgp->pubring), NONULL (*pgp->secring), id); + break; + + default: + mutt_error("Unknown PGP version."); + return -1; + + } + + return mutt_create_filter_fd(cmd, pgpin, pgpout, pgperr, + pgpinfd, pgpoutfd, pgperrfd); +} + + +pid_t pgp_invoke_extract_key(FILE **pgpin, FILE **pgpout, FILE **pgperr, + int pgpinfd, int pgpoutfd, int pgperrfd, const char *id) +{ + char cmd[HUGE_STRING]; + struct pgp_vinfo *pgp = pgp_get_vinfo(PGP_EXTRACT_KEY); + + if(!pgp) + { + mutt_error("Unknown PGP version."); + return -1; + } + + switch(pgp->v) + { + case PGP_V2: + snprintf(cmd, sizeof(cmd), "%s -kxaf +language=%s +pubring=%s +secring=%s 0x%8s", + NONULL (*pgp->binary), NONULL (*pgp->language), NONULL (*pgp->pubring), NONULL (*pgp->secring), id); + break; + + case PGP_V3: + snprintf(cmd, sizeof(cmd), "%sk -xa +language=%s +pubring=%s +secring=%s --OutputInformationFD=1 0x%8s", + NONULL (*pgp->binary), NONULL (*pgp->language), NONULL (*pgp->pubring), NONULL (*pgp->secring), id); + break; + + default: + mutt_error("Unknown PGP version."); + return -1; + + } + + return mutt_create_filter_fd(cmd, pgpin, pgpout, pgperr, + pgpinfd, pgpoutfd, pgperrfd); +} diff --git a/pgpkey.c b/pgpkey.c new file mode 100644 index 00000000..23120a2d --- /dev/null +++ b/pgpkey.c @@ -0,0 +1,589 @@ +/* + * Copyright (C) 1996,1997 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "mutt_menu.h" +#include "mime.h" +#include "pgp.h" +#include "pager.h" + +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#ifdef _PGPPATH + +static struct pgp_cache { + char *what; + char *dflt; + struct pgp_cache *next; +} * id_defaults = NULL; + +typedef struct +{ + KEYINFO *k; + PGPUID *a; +} pgp_key_t; + +static char trust_flags[] = "?- +"; + +static char *pgp_key_abilities(int flags) +{ + static char buff[3]; + + if(!(flags & KEYFLAG_CANENCRYPT)) + buff[0] = '-'; + else if(flags & KEYFLAG_PREFER_SIGNING) + buff[0] = '.'; + else + buff[0] = 'e'; + + if(!(flags & KEYFLAG_CANSIGN)) + buff[1] = '-'; + else if(flags & KEYFLAG_PREFER_ENCRYPTION) + buff[1] = '.'; + else + buff[1] = 's'; + + buff[2] = '\0'; + + return buff; +} + +static void pgp_entry (char *s, size_t l, MUTTMENU *menu, int num) +{ + pgp_key_t *KeyTable = (pgp_key_t *) menu->data; + + snprintf (s, l, "%4d %c%c %4d/0x%s %-4s %2s %s", + num + 1, + trust_flags[KeyTable[num].a->trust & 0x03], + (KeyTable[num].k->flags & KEYFLAG_CRITICAL ? + 'c' : ' '), + KeyTable[num].k->keylen, + _pgp_keyid(KeyTable[num].k), + KeyTable[num].k->algorithm, + pgp_key_abilities(KeyTable[num].k->flags), + KeyTable[num].a->addr); +} + +static int pgp_search (MUTTMENU *m, regex_t *re, int n) +{ + char buf[LONG_STRING]; + + pgp_entry (buf, sizeof (buf), m, n); + return (regexec (re, buf, 0, NULL, 0)); +} + +static int pgp_compare (const void *a, const void *b) +{ + int r; + + pgp_key_t *s = (pgp_key_t *) a; + pgp_key_t *t = (pgp_key_t *) b; + + if((r = strcasecmp (s->a->addr, t->a->addr)) != 0) + return r; + else + return strcasecmp(pgp_keyid(s->k), pgp_keyid(t->k)); +} + +static KEYINFO *pgp_select_key (LIST *keys, ADDRESS *p, const char *s) +{ + int keymax; + pgp_key_t *KeyTable; + MUTTMENU *menu; + LIST *a; + int i; + int done = 0; + LIST *l; + char helpstr[SHORT_STRING], buf[LONG_STRING]; + char cmd[LONG_STRING], tempfile[_POSIX_PATH_MAX]; + FILE *fp, *devnull; + pid_t thepid; + KEYINFO *info; + + + for (i = 0, l = keys; l; l = l->next) + { + int did_main_key = 0; + + info = (KEYINFO *) l->data; + a = info->address; + retry1: + for (; a; i++, a = a->next) + ; + + if(!did_main_key && info->flags & KEYFLAG_SUBKEY && info->mainkey) + { + did_main_key = 1; + a = info->mainkey->address; + goto retry1; + } + } + + if (i == 0) return NULL; + + keymax = i; + KeyTable = safe_malloc (sizeof (pgp_key_t) * i); + + for (i = 0, l = keys; l; l = l->next) + { + int did_main_key = 0; + info = (KEYINFO *)l->data; + a = info->address; + retry2: + for (; a ; i++, a = a->next) + { + KeyTable[i].k = (KEYINFO *) l->data; + KeyTable[i].a = (PGPUID *)a->data; + } + if(!did_main_key && info->flags & KEYFLAG_SUBKEY && info->mainkey) + { + did_main_key = 1; + a = info->mainkey->address; + goto retry2; + } + } + + qsort (KeyTable, i, sizeof (pgp_key_t), pgp_compare); + + helpstr[0] = 0; + mutt_make_help (buf, sizeof (buf), "Exit ", MENU_PGP, OP_EXIT); + strcat (helpstr, buf); + mutt_make_help (buf, sizeof (buf), "Select ", MENU_PGP, + OP_GENERIC_SELECT_ENTRY); + strcat (helpstr, buf); + mutt_make_help (buf, sizeof (buf), "Check key ", MENU_PGP, OP_VERIFY_KEY); + strcat (helpstr, buf); + mutt_make_help (buf, sizeof (buf), "Help", MENU_PGP, OP_HELP); + strcat (helpstr, buf); + + menu = mutt_new_menu (); + menu->max = keymax; + menu->make_entry = pgp_entry; + menu->search = pgp_search; + menu->menu = MENU_PGP; + menu->help = helpstr; + menu->data = KeyTable; + + strfcpy (buf, "PGP keys matching ", sizeof (buf)); + if (p) + strfcpy (buf, p->mailbox, sizeof (buf) - strlen (buf)); + else + strcat (buf, s); + menu->title = buf; + + info = NULL; + + while (!done) + { + switch (mutt_menuLoop (menu)) + { + + case OP_VERIFY_KEY: + + mutt_mktemp (tempfile); + if ((devnull = fopen ("/dev/null", "w")) == NULL) + { + mutt_perror ("Can't open /dev/null"); + break; + } + if ((fp = safe_fopen (tempfile, "w")) == NULL) + { + fclose (devnull); + mutt_perror ("Can't create temporary file"); + break; + } + + mutt_message ("Invoking PGP..."); + + if((thepid = pgp_invoke_verify_key(NULL, NULL, NULL, -1, + fileno(fp), fileno(devnull), + pgp_keyid(KeyTable[menu->current].k))) == -1) + { + mutt_perror ("Can't create filter"); + unlink (tempfile); + fclose (fp); + fclose (devnull); + } + + mutt_wait_filter (thepid); + fclose (fp); + fclose (devnull); + mutt_clear_error (); + snprintf(cmd, sizeof(cmd), "Key ID: 0x%s", pgp_keyid(KeyTable[menu->current].k)); + mutt_do_pager (cmd, tempfile, 0, NULL); + menu->redraw = REDRAW_FULL; + + break; + + case OP_VIEW_ID: + + mutt_message (KeyTable[menu->current].a->addr); + break; + + case OP_GENERIC_SELECT_ENTRY: + + if (option (OPTPGPCHECKTRUST) && + (KeyTable[menu->current].a->trust & 0x03) < 3) + { + char *s = ""; + char buff[LONG_STRING]; + + switch (KeyTable[menu->current].a->trust & 0x03) + { + case 0: s = "This ID's trust level is undefined."; break; + case 1: s = "This ID is not trusted."; break; + case 2: s = "This ID is only marginally trusted."; break; + } + + snprintf (buff, sizeof(buff), "%s Do you really want to use it?", s); + + if (mutt_yesorno (buff, 0) != 1) + { + mutt_clear_error (); + break; + } + } + + info = KeyTable[menu->current].k; + done = 1; + break; + + case OP_EXIT: + + info = NULL; + done = 1; + break; + } + } + + mutt_menuDestroy (&menu); + safe_free ((void **) &KeyTable); + + return (info); +} + +char *pgp_ask_for_key (const char *ringfile, KEYINFO *udb, char *tag, char *whatfor, + short abilities, char **alg) +{ + KEYINFO *db; + KEYINFO *key; + char *key_id; + char resp[SHORT_STRING]; + struct pgp_cache *l = NULL; + + db = udb ? udb : pgp_read_keyring(ringfile); + + resp[0] = 0; + if (whatfor) + { + + for (l = id_defaults; l; l = l->next) + if (!strcasecmp (whatfor, l->what)) + { + strcpy (resp, l->dflt); + break; + } + } + + + FOREVER + { + if (mutt_get_field (tag, resp, sizeof (resp), M_CLEAR) != 0) + { + if (!udb) pgp_closedb (db); + return NULL; + } + + if (whatfor) + { + if (l) + { + safe_free ((void **)&l->dflt); + l->dflt = safe_strdup (resp); + } + else + { + l = safe_malloc (sizeof (struct pgp_cache)); + l->next = id_defaults; + id_defaults = l; + l->what = safe_strdup (whatfor); + l->dflt = safe_strdup (resp); + } + } + + if ((key = ki_getkeybystr (resp, db, abilities))) + { + key_id = safe_strdup(pgp_keyid (key)); + + if (alg) + *alg = safe_strdup(pgp_pkalg_to_mic(key->algorithm)); + + if (!udb) pgp_closedb (db); + return (key_id); + } + BEEP (); + } + /* not reached */ +} + +/* generate a public key attachment */ + +BODY *pgp_make_key_attachment (char * tempf) +{ + BODY *att; + char buff[LONG_STRING]; + char tempfb[_POSIX_PATH_MAX]; + char *id; + FILE *tempfp; + FILE *devnull; + struct stat sb; + pid_t thepid; + + unset_option (OPTPGPCHECKTRUST); + + if (!(id = pgp_ask_for_key (pgp_pubring(PGP_EXTRACT), + NULL, "Please enter the key ID: ", NULL, 0, NULL))) + return NULL; + + if (!tempf) { + mutt_mktemp (tempfb); + tempf = tempfb; + } + + if ((tempfp = safe_fopen (tempf, tempf == tempfb ? "w" : "a")) == NULL) { + mutt_perror ("Can't create temporary file"); + safe_free ((void **)&id); + return NULL; + } + + if ((devnull = fopen ("/dev/null", "w")) == NULL) { + mutt_perror ("Can't open /dev/null"); + safe_free ((void **)&id); + fclose (tempfp); + if (tempf == tempfb) unlink (tempf); + return NULL; + } + + if ((thepid = pgp_invoke_extract_key(NULL, NULL, NULL, -1, + fileno(tempfp), fileno(devnull), id)) == -1) + { + mutt_perror ("Can't create filter"); + unlink (tempf); + fclose (tempfp); + fclose (devnull); + safe_free ((void **)&id); + return NULL; + } + + mutt_wait_filter (thepid); + + fclose (tempfp); + fclose (devnull); + + att = mutt_new_body (); + att->filename = safe_strdup (tempf); + att->unlink = 1; + att->type = TYPEAPPLICATION; + att->subtype = safe_strdup ("pgp-keys"); + snprintf (buff, sizeof (buff), "PGP Key 0x%s.", id); + att->description = safe_strdup (buff); + mutt_update_encoding (att); + + stat (tempf, &sb); + att->length = sb.st_size; + + safe_free ((void **)&id); + return att; +} + +static char *mutt_stristr (char *haystack, char *needle) +{ + char *p, *q; + + if (!haystack) + return NULL; + if (!needle) + return (haystack); + + while (*(p = haystack)) + { + for (q = needle ; *p && *q && tolower (*p) == tolower (*q) ; p++, q++) + ; + if (!*q) + return (haystack); + haystack++; + } + return NULL; +} + +KEYINFO *ki_getkeybyaddr (ADDRESS *a, KEYINFO *k, short abilities) +{ + ADDRESS *r, *p; + LIST *l = NULL, *t = NULL; + LIST *q; + int weak = 0; + int weak_association; + int match; + int did_main_key; + PGPUID *u; + + for ( ; k ; k = k->next) + { + if(k->flags & (KEYFLAG_REVOKED | KEYFLAG_EXPIRED | KEYFLAG_DISABLED)) + continue; + + if(abilities && !(k->flags & abilities)) + continue; + + q = k->address; + did_main_key = 0; + weak_association = 1; + match = 0; + + retry: + + for (;q ; q = q->next) + { + u = (PGPUID *) q->data; + r = rfc822_parse_adrlist(NULL, u->addr); + + for(p = r; p && weak_association; p = p->next) + { + if ((p->mailbox && a->mailbox && + strcasecmp (p->mailbox, a->mailbox) == 0) || + (a->personal && p->personal && + strcasecmp (a->personal, p->personal) == 0)) + { + match = 1; + + if(((u->trust & 0x03) == 3) && + (p->mailbox && a->mailbox && !strcasecmp(p->mailbox, a->mailbox))) + weak_association = 0; + } + } + rfc822_free_address(&r); + } + + if(match) + { + t = mutt_new_list (); + t->data = (void *) k; + t->next = l; + l = t; + + if(weak_association) + weak = 1; + + } + + if(!did_main_key && !match && k->flags & KEYFLAG_SUBKEY && k->mainkey) + { + did_main_key = 1; + q = k->mainkey->address; + goto retry; + } + } + + if (l) + { + if (l->next || weak) + { + /* query for which key the user wants */ + k = pgp_select_key (l, a, NULL); + } + else + k = (KEYINFO *)l->data; + + /* mutt_free_list() frees the .data member, so clear the pointers */ + + for(t = l; t; t = t->next) + t->data = NULL; + + mutt_free_list (&l); + } + + return (k); +} + +KEYINFO *ki_getkeybystr (char *p, KEYINFO *k, short abilities) +{ + LIST *t = NULL, *l = NULL; + LIST *a; + + for(; k; k = k->next) + { + int did_main_key = 0; + + if(k->flags & (KEYFLAG_REVOKED | KEYFLAG_EXPIRED | KEYFLAG_DISABLED)) + continue; + + if(abilities && !(k->flags & abilities)) + continue; + + a = k->address; + + retry: + + for(; a ; a = a->next) + { + + if (!*p || strcasecmp (p, pgp_keyid(k)) == 0 || + (!strncasecmp(p, "0x", 2) && !strcasecmp(p+2, pgp_keyid(k))) || + (option(OPTPGPLONGIDS) && !strncasecmp(p, "0x", 2) && + !strcasecmp(p+2, k->keyid+8)) || + mutt_stristr(((PGPUID *)a->data)->addr,p)) + { + t = mutt_new_list (); + t->data = (void *)k; + t->next = l; + l = t; + break; + } + } + + if(!did_main_key && k->flags & KEYFLAG_SUBKEY && k->mainkey) + { + did_main_key = 1; + a = k->mainkey->address; + goto retry; + } + } + + if (l) + { + k = pgp_select_key (l, NULL, p); + set_option(OPTNEEDREDRAW); + + for(t = l; t; t = t->next) + t->data = NULL; + + mutt_free_list (&l); + } + + return (k); +} + + + +#endif /* _PGPPATH */ diff --git a/pgppubring.c b/pgppubring.c new file mode 100644 index 00000000..322b32a3 --- /dev/null +++ b/pgppubring.c @@ -0,0 +1,819 @@ +/* + * Copyright (C) 1997 Thomas Roessler <roessler@guug.de> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <time.h> + +#include "sha.h" +#include "mutt.h" +#include "pgp.h" + +#define CHUNKSIZE 1024 + +static unsigned char *pbuf = NULL; +static size_t plen = 0; + +enum packet_tags { + PT_RES0 = 0, /* reserved */ + PT_ESK, /* Encrypted Session Key */ + PT_SIG, /* Signature Packet */ + PT_CESK, /* Conventionally Encrypted Session Key Packet */ + PT_OPS, /* One-Pass Signature Packet */ + PT_SECKEY, /* Secret Key Packet */ + PT_PUBKEY, /* Public Key Packet */ + PT_SUBSECKEY, /* Secret Subkey Packet */ + PT_COMPRESSED, /* Compressed Data Packet */ + PT_SKE, /* Symmetrically Encrypted Data Packet */ + PT_MARKER, /* Marker Packet */ + PT_LITERAL, /* Literal Data Packet */ + PT_TRUST, /* Trust Packet */ + PT_NAME, /* Name Packet */ + PT_SUBKEY, /* Subkey Packet */ + PT_RES15, /* Reserved */ + PT_COMMENT /* Comment Packet */ +}; + +const char *pgp_packet_name[] = { + "reserved", + "Encrypted Session Key", + "Signature Packet", + "Conventionally Encrypted Session Key Packet", + "One-Pass Signature Packet", + "Secret Key Packet", + "Public Key Packet", + "Secret Subkey Packet", + "Compressed Data Packet", + "Symmetrically Encrypted Data Packet", + "Marker Packet", + "Literal Data Packet", + "Trust Packet", + "Name Packet", + "Subkey Packet", + "Reserved", + "Comment Packet" +}; + +static const char *pkalgbytype(unsigned char type) +{ + switch(type) + { + case 1: return "RSA"; + case 2: return "RSA"; + case 3: return "RSA"; + case 16: return "ElG"; + case 17: return "DSA"; + default: return "unk"; + } +} + + +static struct +{ + char *pkalg; + char *micalg; +} +pktomic[] = +{ + { "RSA", "pgp-md5" }, + { "ElG", "pgp-rmd160" }, + { "DSA", "pgp-sha1" }, + { NULL, "x-unknown" } +}; + + +const char *pgp_pkalg_to_mic(const char *alg) +{ + int i; + + for(i = 0; pktomic[i].pkalg; i++) + { + if(!strcasecmp(pktomic[i].pkalg, alg)) + break; + } + + return pktomic[i].micalg; +} + + +/* unused */ + +#if 0 + +static const char *hashalgbytype(unsigned char type) +{ + switch(type) + { + case 1: return "MD5"; + case 2: return "SHA1"; + case 3: return "RIPE-MD/160"; + case 4: return "HAVAL"; + default: return "unknown"; + } +} + +#endif + +static short canencrypt(unsigned char type) +{ + switch(type) + { + case 1: + case 2: + case 16: + return 1; + default: + return 0; + } +} + +static short cansign(unsigned char type) +{ + switch(type) + { + case 1: + case 3: + case 17: + return 1; + default: + return 0; + } +} + +/* return values: + * + * 1 = sign only + * 2 = encrypt only + * 3 = both + */ + +static short get_abilities(unsigned char type) +{ + return (canencrypt(type) << 1) | cansign(type); +} + +static int read_material(size_t material, size_t *used, FILE *fp) +{ + if(*used + material >= plen) + { + unsigned char *p; + size_t nplen; + + nplen = *used + material + CHUNKSIZE; + + if(!(p = realloc(pbuf, nplen))) + { + mutt_perror("realloc"); + return -1; + } + plen = nplen; + pbuf = p; + } + + if(fread(pbuf + *used, 1, material, fp) < material) + { + mutt_perror("fread"); + return -1; + } + + *used += material; + return 0; +} + +static unsigned char *pgp_read_packet(FILE *fp, size_t *len) +{ + size_t used = 0; + long startpos; + unsigned char ctb; + unsigned char b; + size_t material; + + startpos = ftell(fp); + + if(!plen) + { + plen = CHUNKSIZE; + pbuf = safe_malloc(plen); + } + + if(fread(&ctb, 1, 1, fp) < 1) + { + if(!feof(fp)) + mutt_perror("fread"); + goto bail; + } + + if(!(ctb & 0x80)) + { + goto bail; + } + + if(ctb & 0x40) /* handle PGP 5.0 packets. */ + { + int partial = 0; + pbuf[0] = ctb; used++; + + do { + if(fread(&b, 1, 1, fp) < 1) + { + mutt_perror("fread"); + goto bail; + } + + if(b < 192) + { + material = b; + partial = 0; + material -= 1; + } + else if(192 <= b && b <= 223) + { + material = (b - 192) * 256; + if(fread(&b, 1, 1, fp) < 1) + { + mutt_perror("fread"); + goto bail; + } + material += b + 192; + partial = 0; + material -= 2; + } + else + { + material = 1 << (b & 0x1f); + partial = 1; + material -= 1; + } + + if(read_material(material, &used, fp) == -1) + goto bail; + + } while (partial); + } + else /* Old-Style PGP */ + { + int bytes = 0; + pbuf[0] = 0x80 | ((ctb >> 2) & 0x0f); + used++; + + switch(ctb & 0x03) + { + case 0: + { + if(fread(&b, 1, 1, fp) < 1) + { + mutt_perror("fread"); + goto bail; + } + + material = b; + break; + } + + case 1: + bytes = 2; + + case 2: + { + int i; + + if(!bytes) bytes = 4; + + material = 0; + + for(i = 0; i < bytes; i++) + { + if(fread(&b, 1, 1, fp) < 1) + { + mutt_perror("fread"); + goto bail; + } + + material = (material << 8) + b; + } + break; + } + + default: + goto bail; + } + + if(read_material(material, &used, fp) == -1) + goto bail; + } + + if(len) + *len = used; + + return pbuf; + + bail: + + fseek(fp, startpos, SEEK_SET); + return NULL; +} + +static KEYINFO *pgp_new_keyinfo(void) +{ + KEYINFO *p; + + p = safe_malloc(sizeof(KEYINFO)); + p->keyid = NULL; + p->address = NULL; + p->flags = 0; + p->next = NULL; + p->keylen = 0; + + return p; +} + +static KEYINFO *pgp_parse_pgp2_key(unsigned char *buff, size_t l) +{ + KEYINFO *p; + unsigned char alg; + size_t expl; + unsigned long id; + unsigned long gen_time = 0; + unsigned short exp_days = 0; + size_t j; + int i, k; + unsigned char scratch[LONG_STRING]; + + if(l < 12) + return NULL; + + p = pgp_new_keyinfo(); + + for(i = 0, j = 2; i < 4; i++) + gen_time = (gen_time << 8) + buff[j++]; + + for(i = 0; i < 2; i++) + exp_days = (exp_days << 8) + buff[j++]; + + if(exp_days && time(NULL) > gen_time + exp_days * 24 * 3600) + p->flags |= KEYFLAG_EXPIRED; + + alg = buff[j++]; + + p->algorithm = pkalgbytype(alg); + p->flags |= get_abilities(alg); + + expl = 0; + for(i = 0; i < 2; i++) + expl = (expl << 8) + buff[j++]; + + p->keylen = expl; + + expl = (expl + 7)/ 8; + if(expl < 4) + goto bailout; + + + j += expl - 8; + + for(k = 0; k < 2; k++) + { + for(id = 0, i = 0; i < 4; i++) + id = (id << 8) + buff[j++]; + + snprintf((char *)scratch + k * 8, sizeof(scratch) - k * 8, + "%08lX", id); + } + + p->keyid = safe_strdup((char *)scratch); + + return p; + + bailout: + + safe_free((void **)&p); + return NULL; +} + +static void pgp_make_pgp3_fingerprint(unsigned char *buff, size_t l, + unsigned char *digest) +{ + unsigned char dummy; + SHA_CTX context; + + SHA1_Init(&context); + + dummy = buff[0] & 0x3f; + + if(dummy == PT_SUBSECKEY || dummy == PT_SUBKEY || dummy == PT_SECKEY) + dummy = PT_PUBKEY; + + dummy = (dummy << 2) | 0x81; + SHA1_Update(&context, &dummy, 1); + dummy = ((l - 1) >> 8) & 0xff; + SHA1_Update(&context, &dummy, 1); + dummy = (l - 1) & 0xff; + SHA1_Update(&context, &dummy, 1); + SHA1_Update(&context, buff + 1, l - 1); + SHA1_Final(digest, &context); + +} + +static void skip_bignum(unsigned char *buff, size_t l, size_t j, + size_t *toff, size_t n) +{ + size_t len; + + do + { + len = (buff[j] << 8) + buff[j+1]; + j += (len + 7) / 8 + 2; + } while(j <= l && --n > 0); + + if(toff) *toff = j; +} + + +static KEYINFO *pgp_parse_pgp3_key(unsigned char *buff, size_t l) +{ + KEYINFO *p; + unsigned char alg; + unsigned char digest[SHA_DIGEST_LENGTH]; + unsigned char scratch[LONG_STRING]; + unsigned long gen_time = 0; + unsigned long id; + int i, k; + short len; + size_t j; + + p = pgp_new_keyinfo(); + j = 2; + + for(i = 0; i < 4; i++) + gen_time = (gen_time << 8) + buff[j++]; + + p->gen_time = gen_time; + + alg = buff[j++]; + + p->algorithm = pkalgbytype(alg); + p->flags |= get_abilities(alg); + + if (alg == 17) + skip_bignum(buff, l, j, &j, 3); + else if(alg == 16) + skip_bignum(buff, l, j, &j, 2); + + len = (buff[j] << 8) + buff[j+1]; + p->keylen = len; + + if (alg >=1 && alg <= 3) + skip_bignum(buff, l, j, &j, 2); + else if(alg == 17 || alg == 16) + skip_bignum(buff, l, j, &j, 1); + + pgp_make_pgp3_fingerprint(buff, j, digest); + + for(k = 0; k < 2; k++) + { + for(id = 0, i = SHA_DIGEST_LENGTH - 8 + k*4; + i < SHA_DIGEST_LENGTH + (k - 1) * 4; i++) + id = (id << 8) + digest[i]; + + snprintf((char *)scratch + k * 8, sizeof(scratch) - k * 8, "%08lX", id); + } + + p->keyid = safe_strdup((char *)scratch); + + + return p; +} + +static KEYINFO *pgp_parse_keyinfo(unsigned char *buff, size_t l) +{ + if(!buff || l < 2) + return NULL; + + switch(buff[1]) + { + case 2: + case 3: + return pgp_parse_pgp2_key(buff, l); + case 4: + return pgp_parse_pgp3_key(buff, l); + default: + return NULL; + } +} + +static int pgp_parse_pgp2_sig(unsigned char *buff, size_t l, KEYINFO *p) +{ + unsigned char sigtype; + unsigned char pkalg; + unsigned char hashalg; + long sig_gen_time; + unsigned long signerid; + size_t j; + int i; + + if(l < 22) + return -1; + + j = 3; + sigtype = buff[j++]; + + sig_gen_time = 0; + for(i = 0; i < 4; i++) + sig_gen_time = (sig_gen_time << 8) + buff[j++]; + + j += 4; + signerid = 0; + for(i = 0; i < 4; i++) + signerid = (signerid << 8) + buff[j++]; + + pkalg = buff[j++]; + hashalg = buff[j++]; + + if(sigtype == 0x20 || sigtype == 0x28) + p->flags |= KEYFLAG_REVOKED; + + return 0; +} + +static int pgp_parse_pgp3_sig(unsigned char *buff, size_t l, KEYINFO *p) +{ + unsigned char sigtype; + unsigned char pkalg; + unsigned char hashalg; + unsigned char skt; + long sig_gen_time = -1; + long validity = -1; + long key_validity = -1; + long signerid = 0; + size_t ml; + size_t j; + int i; + short ii; + short have_critical_spks=0; + + if(l < 7) + return -1; + + j = 2; + + sigtype = buff[j++]; + pkalg = buff[j++]; + hashalg = buff[j++]; + + for(ii = 0; ii < 2; ii++) + { + size_t skl; + size_t nextone; + + ml = (buff[j] << 8) + buff[j+1]; + j += 2; + + if(j + ml > l) break; + + nextone = j; + while(ml) + { + j = nextone; + skl = buff[j++]; + if(!--ml) break; + + if(skl >= 192) + { + skl = (skl - 192) * 256 + buff[j++] + 192; + if(!--ml) break; + } + + ml -= skl; + if(ml < 0) + break; + + nextone = j + skl; + skt = buff[j++]; + + switch(skt & 0x7f) + { + case 2: /* creation time */ + { + if(skl < 4) + break; + sig_gen_time = 0; + for(i = 0; i < 4; i++) + sig_gen_time = (sig_gen_time << 8) + buff[j++]; + + break; + } + case 3: /* expiration time */ + { + if(skl < 4) + break; + validity = 0; + for(i = 0; i < 4; i++) + validity = (validity << 8) + buff[j++]; + break; + } + case 9: /* key expiration time */ + { + if(skl < 4) + break; + key_validity = 0; + for(i = 0; i < 4; i++) + key_validity = (key_validity << 8) + buff[j++]; + break; + } + case 16: /* issuer key ID */ + { + if(skl < 8) + break; + j += 4; + signerid = 0; + for(i = 0; i < 4; i++) + signerid = (signerid << 8) + buff[j++]; + break; + } + case 10: /* CMR key */ break; + case 4: /* exportable */ + case 5: /* trust */ + case 6: /* regexp */ + case 7: /* revocable */ + case 11: /* Pref. symm. alg. */ + case 12: /* revocation key */ + case 20: /* notation data */ + case 21: /* pref. hash */ + case 22: /* pref. comp.alg. */ + case 23: /* key server prefs. */ + case 24: /* pref. key server */ + default: + { + if(skt & 0x80) + have_critical_spks = 1; + } + } + } + j = nextone; + } + + if(sigtype == 0x20 || sigtype == 0x28) + p->flags |= KEYFLAG_REVOKED; + if(key_validity != -1 && time(NULL) > p->gen_time + key_validity) + p->flags |= KEYFLAG_EXPIRED; + if(have_critical_spks) + p->flags |= KEYFLAG_CRITICAL; + + return 0; + +} + + +static int pgp_parse_sig(unsigned char *buff, size_t l, KEYINFO *p) +{ + if(!buff || l < 2 || !p) + return -1; + + switch(buff[1]) + { + case 2: + case 3: + return pgp_parse_pgp2_sig(buff, l, p); + case 4: + return pgp_parse_pgp3_sig(buff, l, p); + default: + return -1; + } +} + + +KEYINFO *pgp_read_keyring(const char *fname) +{ + FILE *fp; + unsigned char *buff; + unsigned char pt = 0; + unsigned char last_pt; + size_t l; + KEYINFO *db = NULL, **end, *p = NULL; + KEYINFO *supkey = NULL; + PGPUID *uid = NULL; + LIST **addr = NULL; + + if(!(fp = fopen(fname, "r"))) + { + mutt_perror("fopen"); + return NULL; + + } + + end = &db; + + while((buff = pgp_read_packet(fp, &l)) != NULL) + { + last_pt = pt; + pt = buff[0] & 0x3f; + + if(l < 1) + continue; + + switch(pt) + { + case PT_SECKEY: + case PT_PUBKEY: + case PT_SUBKEY: + case PT_SUBSECKEY: + { + if(p) + end = &(p->next); + + if(!(*end = p = pgp_parse_keyinfo(buff, l))) + break; + + addr = &p->address; + + if(pt == PT_SUBKEY || pt == PT_SUBSECKEY) + { + p->flags |= KEYFLAG_SUBKEY; + p->mainkey = supkey; + } + else + supkey = p; + + break; + } + + case PT_SIG: + { + pgp_parse_sig(buff, l, p); + break; + } + case PT_TRUST: + { + if(last_pt == PT_SECKEY || last_pt == PT_PUBKEY || + last_pt == PT_SUBKEY || last_pt == PT_SUBSECKEY) + { + if(buff[1] & 0x20) + p->flags |= KEYFLAG_DISABLED; + } + else if(last_pt == PT_NAME) + uid->trust = buff[1]; + break; + } + case PT_NAME: + { + char *chr; + chr = safe_malloc(l); + memcpy(chr, buff + 1, l - 1); + chr[l-1] = '\0'; + *addr = mutt_new_list(); + (*addr)->data = safe_malloc(sizeof(PGPUID)); + uid = (PGPUID *) (*addr)->data; + uid->addr = chr; + uid->trust = 0; + addr = &(*addr)->next; + + /* the following tags are generated by + * pgp 2.6.3in. + */ + + if(strstr(chr, "ENCR")) + p->flags |= KEYFLAG_PREFER_ENCRYPTION; + if(strstr(chr, "SIGN")) + p->flags |= KEYFLAG_PREFER_SIGNING; + + break; + } + } + } + fclose(fp); + return db; +} + +void pgp_closedb (KEYINFO *k) +{ + KEYINFO *tmp; + LIST *q; + + while (k) + { + if (k->keyid) safe_free ((void **)&k->keyid); + for(q = k->address; q; q = q-> next) + safe_free((void **)&q->data); + tmp = k; + k = k->next; + safe_free ((void **)&tmp); + } +} diff --git a/pop.c b/pop.c new file mode 100644 index 00000000..c3495a04 --- /dev/null +++ b/pop.c @@ -0,0 +1,313 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Rather crude POP3 support. + */ + +#include "mutt.h" +#include "mailbox.h" +#include "mx.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <string.h> +#include <unistd.h> + +static int getLine (int fd, char *s, int len) +{ + char ch; + int bytes = 0; + + while (read (fd, &ch, 1) > 0) + { + *s++ = ch; + bytes++; + if (ch == '\n') + { + *s = 0; + return (bytes); + } + /* make sure not to overwrite the buffer */ + if (bytes == len - 1) + { + *s = 0; + return bytes; + } + } + *s = 0; + return (-1); +} + +static int getPass (void) +{ + if (!PopPass) + { + char tmp[SHORT_STRING]; + if (mutt_get_password ("POP Password: ", tmp, sizeof (tmp)) != 0) + return 0; + PopPass = safe_strdup (tmp); + } + return 1; +} + +void mutt_fetchPopMail (void) +{ + struct sockaddr_in sin; +#if SIZEOF_LONG == 4 + long n; +#else + int n; +#endif + struct hostent *he; + char buffer[2048]; + char msgbuf[SHORT_STRING]; + int s, i, msgs, bytes, err = 0; + CONTEXT ctx; + MESSAGE *msg = NULL; + + if (!PopHost) + { + mutt_error ("POP host is not defined."); + return; + } + + if (!PopUser) + { + mutt_error ("No POP username is defined."); + return; + } + + if (!getPass ()) return; + + s = socket (AF_INET, SOCK_STREAM, IPPROTO_IP); + + memset ((char *) &sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons (PopPort); + + if ((n = inet_addr (PopHost)) == -1) + { + /* Must be a DNS name */ + if ((he = gethostbyname (PopHost)) == NULL) + { + mutt_error ("Could not find address for host %s.", PopHost); + return; + } + memcpy ((void *)&sin.sin_addr, *(he->h_addr_list), he->h_length); + } + else + memcpy ((void *)&sin.sin_addr, (void *)&n, sizeof(n)); + + mutt_message ("Connecting to %s", inet_ntoa (sin.sin_addr)); + + if (connect (s, (struct sockaddr *) &sin, sizeof (struct sockaddr_in)) == -1) + { + mutt_perror ("connect"); + return; + } + + if (getLine (s, buffer, sizeof (buffer)) == -1) + goto fail; + + if (strncmp (buffer, "+OK", 3) != 0) + { + mutt_remove_trailing_ws (buffer); + mutt_error (buffer); + goto finish; + } + + sprintf (buffer, "user %s\r\n", PopUser); + write (s, buffer, strlen (buffer)); + + if (getLine (s, buffer, sizeof (buffer)) == -1) + goto fail; + + if (strncmp (buffer, "+OK", 3) != 0) + { + mutt_remove_trailing_ws (buffer); + mutt_error (buffer); + goto finish; + } + + sprintf (buffer, "pass %s\r\n", PopPass); + write (s, buffer, strlen (buffer)); + + if (getLine (s, buffer, sizeof (buffer)) == -1) + goto fail; + + if (strncmp (buffer, "+OK", 3) != 0) + { + PopPass[0] = 0; /* void the given password */ + mutt_remove_trailing_ws (buffer); + mutt_error (buffer[0] ? buffer : "Server closed connection!"); + goto finish; + } + + /* find out how many messages are in the mailbox. */ + write (s, "stat\r\n", 6); + + if (getLine (s, buffer, sizeof (buffer)) == -1) + goto fail; + + if (strncmp (buffer, "+OK", 3) != 0) + { + mutt_remove_trailing_ws (buffer); + mutt_error (buffer); + goto finish; + } + + sscanf (buffer, "+OK %d %d", &msgs, &bytes); + + if (msgs == 0) + { + mutt_message ("No new mail in POP mailbox."); + goto finish; + } + + if (mx_open_mailbox (Spoolfile, M_APPEND, &ctx) == NULL) + goto finish; + + snprintf (msgbuf, sizeof (msgbuf), + "Reading %d new message%s (%d bytes)...", msgs, msgs > 1 ? "s" : "", bytes); + mutt_message (msgbuf); + + for (i = 1 ; i <= msgs ; i++) + { + sprintf (buffer, "retr %d\r\n", i); + write (s, buffer, strlen (buffer)); + + if (getLine (s, buffer, sizeof (buffer)) == -1) + { + mx_fastclose_mailbox (&ctx); + goto fail; + } + + if (strncmp (buffer, "+OK", 3) != 0) + { + mutt_remove_trailing_ws (buffer); + mutt_error (buffer); + break; + } + + if ((msg = mx_open_new_message (&ctx, NULL, M_ADD_FROM)) == NULL) + { + err = 1; + break; + } + + /* Now read the actual message. */ + FOREVER + { + char *p; + int chunk; + + if ((chunk = getLine (s, buffer, sizeof (buffer))) == -1) + { + mutt_error ("Error reading message!"); + err = 1; + break; + } + + /* check to see if we got a full line */ + if (buffer[chunk-2] == '\r' && buffer[chunk-1] == '\n') + { + if (strcmp(".\r\n", buffer) == 0) + { + /* end of message */ + break; + } + + /* change CRLF to just LF */ + buffer[chunk-2] = '\n'; + buffer[chunk-1] = 0; + chunk--; + + /* see if the line was byte-stuffed */ + if (buffer[0] == '.') + { + p = buffer + 1; + chunk--; + } + else + p = buffer; + } + else + p = buffer; + + fwrite (p, 1, chunk, msg->fp); + } + + if (mx_close_message (&msg) != 0) + { + mutt_error ("Error while writing mailbox!"); + err = 1; + } + + if (err) + break; + + if (option (OPTPOPDELETE)) + { + /* delete the message on the server */ + sprintf (buffer, "dele %d\r\n", i); + write (s, buffer, strlen (buffer)); + + /* eat the server response */ + getLine (s, buffer, sizeof (buffer)); + if (strncmp (buffer, "+OK", 3) != 0) + { + err = 1; + mutt_remove_trailing_ws (buffer); + mutt_error (buffer); + break; + } + } + + mutt_message ("%s [%d messages read]", msgbuf, i); + } + + if (msg) + mx_close_message (&msg); + mx_close_mailbox (&ctx); + + if (err) + { + /* make sure no messages get deleted */ + write (s, "rset\r\n", 6); + getLine (s, buffer, sizeof (buffer)); /* snarf the response */ + } + +finish: + + /* exit gracefully */ + write (s, "quit\r\n", 6); + getLine (s, buffer, sizeof (buffer)); /* snarf the response */ + close (s); + return; + + /* not reached */ + +fail: + + mutt_error ("Server closed connection!"); + close (s); +} diff --git a/postpone.c b/postpone.c new file mode 100644 index 00000000..805a3c68 --- /dev/null +++ b/postpone.c @@ -0,0 +1,434 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_menu.h" +#include "rfc1524.h" +#include "mime.h" +#include "mailbox.h" +#include "mapping.h" + +#include <ctype.h> +#include <unistd.h> +#include <string.h> +#include <sys/stat.h> + +static struct mapping_t PostponeHelp[] = { + { "Exit", OP_EXIT }, + { "Del", OP_DELETE }, + { "Undel", OP_UNDELETE }, + { "Help", OP_HELP }, + { NULL } +}; + + + +#ifdef _PGPPATH +#include "pgp.h" +#endif /* _PGPPATH */ + + + +static short PostCount = 0; +static time_t LastModify = 0; +static CONTEXT *PostContext = NULL; + +int mutt_num_postponed (void) +{ + struct stat st; + CONTEXT ctx; + + if (!Postponed || stat (Postponed, &st) == -1) + { + PostCount = 0; + LastModify = 0; + return (0); + } + else if (S_ISDIR (st.st_mode)) + { + /* if we have a maildir mailbox, we need to stat the "new" dir */ + + char buf[_POSIX_PATH_MAX]; + + snprintf (buf, sizeof (buf), "%s/new", Postponed); + if (access (buf, F_OK) == 0 && stat (buf, &st) == -1) + { + PostCount = 0; + LastModify = 0; + return 0; + } + } + + if (LastModify < st.st_mtime) + { + LastModify = st.st_mtime; + + if (access (Postponed, R_OK | F_OK) != 0) + return (PostCount = 0); + if (mx_open_mailbox (Postponed, M_NOSORT | M_QUIET, &ctx) == NULL) + PostCount = 0; + else + PostCount = ctx.msgcount; + mx_fastclose_mailbox (&ctx); + } + + return (PostCount); +} + +static void post_entry (char *s, size_t slen, MUTTMENU *menu, int entry) +{ + CONTEXT *ctx = (CONTEXT *) menu->data; + + mutt_make_string (s, slen, NONULL (HdrFmt), ctx->hdrs[entry]); +} + +static HEADER *select_msg (void) +{ + MUTTMENU *menu; + int i, done=0, r=-1; + char helpstr[SHORT_STRING]; + + menu = mutt_new_menu (); + menu->make_entry = post_entry; + menu->menu = MENU_POST; + menu->max = PostContext->msgcount; + menu->title = "Postponed Messages"; + menu->data = PostContext; + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_POST, PostponeHelp); + + while (!done) + { + switch (i = mutt_menuLoop (menu)) + { + case OP_DELETE: + case OP_UNDELETE: + mutt_set_flag (PostContext, PostContext->hdrs[menu->current], M_DELETE, (i == OP_DELETE) ? 1 : 0); + PostCount = PostContext->msgcount - PostContext->deleted; + if (option (OPTRESOLVE) && menu->current < menu->max - 1) + { + menu->oldcurrent = menu->current; + menu->current++; + if (menu->current >= menu->top + menu->pagelen) + { + menu->top = menu->current; + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + } + else + menu->redraw |= REDRAW_MOTION_RESYNCH; + } + else + menu->redraw = REDRAW_CURRENT; + break; + + case OP_GENERIC_SELECT_ENTRY: + r = menu->current; + done = 1; + break; + + case OP_EXIT: + done = 1; + break; + } + } + + mutt_menuDestroy (&menu); + return (r > -1 ? PostContext->hdrs[r] : NULL); +} + +/* args: + * ctx Context info, used when recalling a message to which + * we reply. + * hdr envelope/attachment info for recalled message + * cur if message was a reply, `cur' is set to the message which + * `hdr' is in reply to + * + * return vals: + * -1 error/no messages + * 0 normal exit + * SENDREPLY recalled message is a reply + */ +int mutt_get_postponed (CONTEXT *ctx, HEADER *hdr, HEADER **cur) +{ + HEADER *h; + MESSAGE *msg; + int code = SENDPOSTPONED; + LIST *tmp; + LIST *last = NULL; + LIST *next; + char file[_POSIX_PATH_MAX]; + char *p; + int opt_delete; + + if (!Postponed) + return (-1); + + if ((PostContext = mx_open_mailbox (Postponed, M_NOSORT, NULL)) == NULL) + { + PostCount = 0; + mutt_error ("No postponed messages."); + return (-1); + } + + if (! PostContext->msgcount) + { + PostCount = 0; + mx_close_mailbox (PostContext); + safe_free ((void **) &PostContext); + mutt_error ("No postponed messages."); + return (-1); + } + + if (PostContext->msgcount == 1) + { + /* only one message, so just use that one. */ + h = PostContext->hdrs[0]; + } + else if ((h = select_msg ()) == NULL) + { + mx_close_mailbox (PostContext); + safe_free ((void **) &PostContext); + return (-1); + } + + if ((msg = mx_open_message (PostContext, h->msgno)) == NULL) + { + mx_close_mailbox (PostContext); + safe_free ((void **) &PostContext); + return (-1); + } + + fseek (msg->fp, h->offset, 0); + hdr->env = mutt_read_rfc822_header (msg->fp, NULL); + + if (h->content->type == TYPEMESSAGE || h->content->type == TYPEMULTIPART) + { + BODY *b; + + fseek (msg->fp, h->content->offset, 0); + + if (h->content->type == TYPEMULTIPART) + { + h->content->parts = mutt_parse_multipart (msg->fp, + mutt_get_parameter ("boundary", h->content->parameter), + h->content->offset + h->content->length, + strcasecmp ("digest", h->content->subtype) == 0); + } + else + h->content->parts = mutt_parse_messageRFC822 (msg->fp, h->content); + + /* Now that we know what was in the other message, convert to the new + * message. + */ + hdr->content = h->content->parts; + b = h->content->parts; + while (b != NULL) + { + file[0] = '\0'; + if (b->filename) + strfcpy (file, b->filename, sizeof (file)); + mutt_adv_mktemp (file); + if (mutt_save_attachment (msg->fp, b, file, 0) == -1) + { + mutt_free_envelope (&hdr->env); + mutt_free_body (&hdr->content); + mx_close_message (&msg); + mx_fastclose_mailbox (PostContext); + safe_free ((void **) &PostContext); + return (-1); + } + safe_free ((void *) &b->filename); + b->filename = safe_strdup (file); + b->unlink = 1; + mutt_free_body (&b->parts); + b = b->next; + } + h->content->parts = NULL; + } + else + { + mutt_mktemp (file); + if (mutt_save_attachment (msg->fp, h->content, file, 0) == -1) + { + mutt_free_envelope (&hdr->env); + mx_close_message (&msg); + mx_fastclose_mailbox (PostContext); + safe_free ((void **) &PostContext); + return (-1); + } + hdr->content = mutt_make_attach (file); + hdr->content->use_disp = 0; /* no content-disposition */ + hdr->content->unlink = 1; /* delete when we are done */ + } + + mx_close_message (&msg); + + /* finished with this message, so delete it. */ + mutt_set_flag (PostContext, h, M_DELETE, 1); + + /* update the count for the status display */ + PostCount = PostContext->msgcount - PostContext->deleted; + + /* avoid the "purge deleted messages" prompt */ + opt_delete = quadoption (OPT_DELETE); + set_quadoption (OPT_DELETE, M_YES); + mx_close_mailbox (PostContext); + set_quadoption (OPT_DELETE, opt_delete); + + safe_free ((void **) &PostContext); + + for (tmp = hdr->env->userhdrs; tmp; ) + { + if (strncasecmp ("X-Mutt-References:", tmp->data, 18) == 0) + { + if (ctx) + { + /* if a mailbox is currently open, look to see if the orignal message + the user attempted to reply to is in this mailbox */ + p = tmp->data + 18; + SKIPWS (p); + *cur = hash_find (ctx->id_hash, p); + } + + /* Remove the X-Mutt-References: header field. */ + next = tmp->next; + if (last) + last->next = tmp->next; + else + hdr->env->userhdrs = tmp->next; + tmp->next = NULL; + mutt_free_list (&tmp); + tmp = next; + if (*cur) + code |= SENDREPLY; + } + + + +#ifdef _PGPPATH + else if (strncmp ("Pgp:", tmp->data, 4) == 0) + { + hdr->pgp = mutt_parse_pgp_hdr (tmp->data+4, 1); + + /* remove the pgp field */ + next = tmp->next; + if (last) + last->next = tmp->next; + else + hdr->env->userhdrs = tmp->next; + tmp->next = NULL; + mutt_free_list (&tmp); + tmp = next; + } +#endif /* _PGPPATH */ + + + + else + { + last = tmp; + tmp = tmp->next; + } + } + return (code); +} + + + +#ifdef _PGPPATH + +int mutt_parse_pgp_hdr (char *p, int set_signas) +{ + int pgp = 0; + char pgp_sign_as[LONG_STRING] = "\0", *q; + char pgp_sign_micalg[LONG_STRING] = "\0"; + + SKIPWS (p); + for (; *p; p++) + { + + switch (*p) + { + case 'e': + case 'E': + pgp |= PGPENCRYPT; + break; + + case 's': + case 'S': + pgp |= PGPSIGN; + q = pgp_sign_as; + + if (*(p+1) == '<') + { + for (p += 2; + *p && *p != '>' && q < pgp_sign_as + sizeof (pgp_sign_as) - 1; + *q++ = *p++) + ; + + if (*p!='>') + { + mutt_error ("Illegal PGP header"); + return 0; + } + } + + *q = '\0'; + break; + + case 'm': + case 'M': + q = pgp_sign_micalg; + + if(*(p+1) == '<') + { + for(p += 2; *p && *p != '>' && q < pgp_sign_micalg + sizeof(pgp_sign_micalg) - 1; + *q++ = *p++) + ; + + if(*p != '>') + { + mutt_error("Illegal PGP header"); + return 0; + } + } + + *q = '\0'; + break; + + default: + mutt_error ("Illegal PGP header"); + return 0; + } + + } + + if (set_signas || *pgp_sign_as) + { + safe_free((void **) &PgpSignAs); + PgpSignAs = safe_strdup(pgp_sign_as); + } + + if (set_signas || *pgp_sign_micalg) + { + safe_free((void **) &PgpSignMicalg); + PgpSignMicalg = safe_strdup(pgp_sign_micalg); + } + + return pgp; +} +#endif /* _PGPPATH */ diff --git a/protos.h b/protos.h new file mode 100644 index 00000000..678db40b --- /dev/null +++ b/protos.h @@ -0,0 +1,384 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define FOREVER while (1) + +#ifdef DEBUG +#define dprint(N,X) if(debuglevel>=N) fprintf X +#else +#define dprint(N,X) +#endif + +#define NONULL(x) x?x:"" + +#define MoreArgs(p) (*p->dptr && *p->dptr != ';' && *p->dptr != '#') + +#define mutt_make_string(A,B,C,D) _mutt_make_string(A,B,C,D,0) +void _mutt_make_string (char *, size_t, const char *, HEADER *, format_flag); + +int mutt_extract_token (BUFFER *, BUFFER *, int); + +int mutt_add_string (BUFFER *, const char *); +int mutt_add_char (BUFFER *, char); + +#define mutt_system(x) _mutt_system(x,0) +int _mutt_system (const char *, int); + +#define mutt_next_thread(x) _mutt_aside_thread(x,1,0) +#define mutt_previous_thread(x) _mutt_aside_thread(x,0,0) +#define mutt_next_subthread(x) _mutt_aside_thread(x,1,1) +#define mutt_previous_subthread(x) _mutt_aside_thread(x,0,1) +int _mutt_aside_thread (HEADER *, short, short); + +#define ISSPACE(c) isspace((unsigned char)c) + +#define mutt_new_parameter() safe_calloc (1, sizeof (PARAMETER)) +#define mutt_new_header() safe_calloc (1, sizeof (HEADER)) +#define mutt_new_envelope() safe_calloc (1, sizeof (ENVELOPE)) + +typedef const char * format_t (char *, size_t, char, const char *, const char *, const char *, const char *, unsigned long, format_flag); + +void mutt_FormatString (char *, size_t, const char *, format_t *, unsigned long, format_flag); + +void mutt_update_encoding (BODY *a); + +FILE *mutt_open_read (const char *, pid_t *); + +void set_quadoption (int, int); +int query_quadoption (int, const char *); +int quadoption (int); + +ADDRESS *mutt_remove_duplicates (ADDRESS *); +ADDRESS *mutt_expand_aliases (ADDRESS *); +ADDRESS *mutt_parse_adrlist (ADDRESS *, const char *); + +BODY *mutt_dup_body (BODY *); +BODY *mutt_make_attach (const char *); +BODY *mutt_make_multipart (BODY *); +BODY *mutt_new_body (void); +BODY *mutt_parse_multipart (FILE *, const char *, long, int); +BODY *mutt_parse_messageRFC822 (FILE *, BODY *); +BODY *mutt_read_mime_header (FILE *, int); + +ENVELOPE *mutt_read_rfc822_header (FILE *, HEADER *); +HEADER *mutt_dup_header (HEADER *); + +ATTACHPTR **mutt_gen_attach_list (BODY *, ATTACHPTR **, short *, short *, int, int); + +time_t mutt_local_tz (void); +time_t mutt_mktime (struct tm *, int); +time_t is_from (const char *, char *, size_t); + +char *mutt_expand_path (char *, size_t); +char *mutt_find_hook (int, const char *); +char *mutt_generate_boundary (void); +char *mutt_gen_msgid (void); +char *mutt_get_name (ADDRESS *); +char *mutt_get_parameter (const char *, PARAMETER *); +char *mutt_read_line (char *, size_t *, FILE *, int *); +char *mutt_strlower (char *); +char *mutt_skip_whitespace (char *); +char *mutt_substrcpy (char *, const char *, const char *, size_t); +char *mutt_substrdup (const char *, const char *); + +void mutt_add_child_pid (pid_t); +void mutt_alias_menu (char *, size_t, ALIAS *); +void mutt_block_signals (void); +void mutt_block_signals_system (void); +void mutt_body_handler (BODY *, STATE *); +void mutt_bounce_message (HEADER *, ADDRESS *); +void mutt_buffy (char *); +void mutt_check_rescore (CONTEXT *ctx); +void mutt_clear_error (void); +void mutt_create_alias (ENVELOPE *, ADDRESS *); +void mutt_decode_attachment (BODY *, STATE *); +void mutt_default_save (char *, size_t, HEADER *); +void mutt_display_address (ADDRESS *); +void mutt_edit_file (const char *, const char *); +void mutt_edit_headers (const char *, const char *, HEADER *, char *, size_t); +void mutt_curses_error (const char *, ...); +void mutt_enter_command (void); +void mutt_exit (int); +void mutt_expand_fmt (char *, size_t, const char *, const char *); +void mutt_expand_link (char *, const char *, const char *); +void mutt_fetchPopMail (void); +void mutt_folder_hook (char *); +void mutt_free_alias (ALIAS **); +void mutt_free_body (BODY **); +void mutt_free_color (int fg, int bg); +void mutt_free_envelope (ENVELOPE **); +void mutt_free_header (HEADER **); +void mutt_free_parameter (PARAMETER **); +void mutt_generate_header (char *, size_t, HEADER *, int); +void mutt_help (int); +void mutt_index_menu (void); +void mutt_init_history (void); +void mutt_linearize_tree (CONTEXT *, int); +void mutt_make_help (char *, size_t, char *, int, int); +void mutt_message (const char *, ...); +void mutt_message_to_7bit (BODY *, FILE *); +void mutt_mktemp (char *); +void mutt_nocurses_error (const char *, ...); +void mutt_normalize_time (struct tm *); +void mutt_parse_mime_message (CONTEXT *ctx, HEADER *); +void mutt_pipe_message_to_state (HEADER *, STATE *); +void mutt_perror (const char *); +void mutt_pretty_mailbox (char *); +void mutt_pretty_size (char *, size_t, long); +void mutt_print_message (HEADER *); +void mutt_remove_trailing_ws (char *); +void mutt_query_exit (void); +void mutt_query_menu (char *, size_t); +void mutt_safe_path (char *s, size_t l, ADDRESS *a); +void mutt_save_path (char *s, size_t l, ADDRESS *a); +void mutt_score_message (HEADER *); +void mutt_select_fcc (char *, size_t, HEADER *); +void mutt_select_file (char *, size_t, int); +void mutt_send_hook (HEADER *); +void mutt_set_flag (CONTEXT *, HEADER *, int, int); +void mutt_shell_escape (void); +void mutt_show_error (void); +void mutt_signal_init (void); +void mutt_tabs_to_spaces (char *); +void mutt_tag_set_flag (int, int); +void mutt_unblock_signals (void); +void mutt_unblock_signals_system (int); +void mutt_unlink (const char *); +void mutt_update_encoding (BODY *a); +void mutt_update_tree (ATTACHPTR **, short); +void mutt_version (void); +void mutt_view_attachments (HEADER *); + +int mutt_addr_is_user (ADDRESS *); +int mutt_alias_complete (char *, size_t); +int mutt_alloc_color (int fg, int bg); +int mutt_any_key_to_continue (const char *); +int mutt_buffy_check (int); +int mutt_buffy_notify (void); +int mutt_builtin_editor (const char *, HEADER *, HEADER *); +int mutt_can_decode (BODY *); +int mutt_change_flag (HEADER *, int); +int mutt_check_encoding (const char *); +int mutt_check_key (const char *); +int mutt_check_menu (const char *); +int mutt_check_mime_type (const char *); +int mutt_check_month (const char *); +int mutt_check_overwrite (const char *, const char *, char *, size_t, int); +int mutt_complete (char *); +int mutt_compose_attachment (BODY *a); +int mutt_copy_bytes (FILE *, FILE *, size_t); +int mutt_copy_stream (FILE *, FILE *); +int mutt_decode_save_attachment (FILE *, BODY *, char *, int, int); +int mutt_display_message (HEADER *h); +int mutt_edit_attachment(BODY *, int); +int mutt_enter_fname (const char *, char *, size_t, int *, int); +int mutt_enter_string (unsigned char *, size_t, int, int, int); +int mutt_get_field (char *, char *, size_t, int); +int mutt_get_password (char *, char *, size_t); +int mutt_get_postponed (CONTEXT *, HEADER *, HEADER **); +int mutt_is_autoview (char *); +int mutt_is_mail_list (ADDRESS *); +int mutt_is_list_recipient (ADDRESS *a); +int mutt_is_text_type (int, char *); +int mutt_is_valid_mailbox (const char *); +int mutt_needs_mailcap (BODY *); +int mutt_num_postponed (void); +int mutt_parse_bind (BUFFER *, BUFFER *, unsigned long, BUFFER *); +int mutt_parse_color (BUFFER *, BUFFER *, unsigned long, BUFFER *); +int mutt_parse_uncolor (BUFFER *, BUFFER *, unsigned long, BUFFER *); +int mutt_parse_hook (BUFFER *, BUFFER *, unsigned long, BUFFER *); +int mutt_parse_macro (BUFFER *, BUFFER *, unsigned long, BUFFER *); +int mutt_parse_mailboxes (BUFFER *, BUFFER *, unsigned long, BUFFER *); +int mutt_parse_mono (BUFFER *, BUFFER *, unsigned long, BUFFER *); +int mutt_parse_push (BUFFER *, BUFFER *, unsigned long, BUFFER *); +int mutt_parse_rc_line (/* const */ char *, BUFFER *, BUFFER *); +int mutt_parse_score (BUFFER *, BUFFER *, unsigned long, BUFFER *); +int mutt_parse_unscore (BUFFER *, BUFFER *, unsigned long, BUFFER *); +int mutt_pattern_func (int, char *, HEADER *); +int mutt_pipe_attachment (FILE *, BODY *, const char *, char *); +int mutt_pipe_message (HEADER *); +int mutt_print_attachment (FILE *, BODY *); +int mutt_query_complete (char *, size_t); +int mutt_save_attachment (FILE *, BODY *, char *, int); +int mutt_save_message (HEADER *, int, int, int *); +int mutt_search_command (int, int); +int mutt_send_menu (HEADER *, char *, size_t, HEADER *); +int mutt_send_message (HEADER *, const char *); +int mutt_strcmp (const char *, const char *); +int mutt_thread_set_flag (HEADER *, int, int, int); +int mutt_view_attachment (FILE*, BODY *, int); +int mutt_wait_filter (pid_t); +int mutt_which_case (const char *); +int mutt_write_fcc (const char *path, HEADER *hdr, const char *msgid, int); +int mutt_write_mime_body (BODY *, FILE *); +int mutt_write_mime_header (BODY *, FILE *); +int mutt_write_rfc822_header (FILE *, ENVELOPE *, BODY *, int); +int mutt_yesorno (const char *, int); +void mutt_cache_index_colors(CONTEXT *); +void mutt_set_header_color(CONTEXT *, HEADER *); + +int mh_valid_message (const char *); + +pid_t mutt_create_filter (const char *, FILE **, FILE **, FILE **); +pid_t mutt_create_filter_fd (const char *, FILE **, FILE **, FILE **, int, int, int); + +#define FREE(x) safe_free((void **)x) + +char *safe_strdup (const char *); +void *safe_calloc (size_t, size_t); +void *safe_malloc (unsigned int); +void safe_realloc (void **, size_t); +void safe_free (void **); + +FILE *safe_fopen (const char *, const char *); + +ADDRESS *alias_reverse_lookup (ADDRESS *); + +#define strfcpy(A,B,C) strncpy(A,B,C), *(A+(C)-1)=0 + +/* this macro must check for *c == 0 since isspace(0) has unreliable behavior + on some systems */ +#define SKIPWS(c) while (*(c) && isspace ((unsigned char) *(c))) c++; + +#ifdef LOCALES_HACK +#define IsPrint(c) (isprint((unsigned char)(c)) || \ + ((unsigned char)(c) >= 0xa0)) +#else +#define IsPrint(c) (isprint((unsigned char)(c)) || \ + (option (OPTLOCALES) ? 0 : \ + ((unsigned char)(c) >= 0xa0))) +#endif + +#define new_pattern() calloc(1, sizeof (pattern_t)) + +int mutt_pattern_exec (struct pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, HEADER *h); +pattern_t *mutt_pattern_comp (/* const */ char *s, int flags, BUFFER *err); +void mutt_check_simple (char *s, size_t len, const char *simple); +void mutt_pattern_free (pattern_t **pat); + +/* ---------------------------------------------------------------------------- + * Prototypes for broken systems + */ + +#ifdef HAVE_SRAND48 +#define LRAND lrand48 +#define SRAND srand48 +#define DRAND drand48 +#else +#define LRAND rand +#define SRAND srand +#define DRAND (double)rand +#endif /* HAVE_SRAND48 */ + +#ifdef HAVE_SETEGID +#define SETEGID setegid +#else +#define SETEGID setgid +#endif + +int getdnsdomainname (char *, size_t); + +/* According to SCO support, this is how to detect SCO */ +#if defined (_M_UNIX) || defined (M_OS) +#define SCO +#endif + +/* SCO Unix uses chsize() instead of ftruncate() */ +#ifndef HAVE_FTRUNCATE +#define ftruncate chsize +#endif + +#ifndef HAVE_SNPRINTF +extern int snprintf (char *, size_t, const char *, ...); +#endif + +#ifndef HAVE_VSNPRINTF +extern int vsnprintf (char *, size_t, const char *, va_list); +#endif + +#ifndef HAVE_STRERROR +#ifndef STDC_HEADERS +extern int sys_nerr; +extern char *sys_errlist[]; +#endif + +#define strerror(x) ((x) > 0 && (x) < sys_nerr) ? sys_errlist[(x)] : 0 +#endif /* !HAVE_STRERROR */ + +/* AIX doesn't define these in any headers (sigh) */ +int strcasecmp (const char *, const char *); +int strncasecmp (const char *, const char *, size_t); + +#ifdef _AIX +int setegid (gid_t); +#endif /* _AIX */ + +#ifndef STDC_HEADERS +extern FILE *fdopen (); +extern int system (); +extern int puts (); +extern int fputs (); +extern int fputc (); +extern int fseek (); +extern char *strchr (); +extern int getopt (); +extern int fputs (); +extern int fputc (); +extern int fclose (); +extern int fprintf(); +extern int printf (); +extern int fgetc (); +extern int tolower (); +extern int toupper (); +extern int sscanf (); +extern size_t fread (); +extern size_t fwrite (); +extern int system (); +extern int rename (); +extern time_t time (); +extern struct tm *localtime (); +extern char *asctime (); +extern char *strpbrk (); +extern int fflush (); +extern long lrand48 (); +extern void srand48 (); +extern time_t mktime (); +extern int vsprintf (); +extern int ungetc (); +extern char *mktemp (); +extern int ftruncate (); +extern void *memset (); +extern int pclose (); +extern int socket (); +extern int connect (); +extern size_t strftime (); +extern int lstat (); +extern void rewind (); +extern int readlink (); + +/* IRIX barfs on empty var decls because the system include file uses elipsis + in the declaration. So declare all the args to avoid compiler errors. This + should be harmless on other systems. */ +int ioctl (int, int, ...); + +#endif + +/* unsorted */ +void ci_bounce_message (HEADER *, int *); +void ci_send_message (int, HEADER *, char *, CONTEXT *, HEADER *); +void ci_attach (BODY *); diff --git a/query.c b/query.c new file mode 100644 index 00000000..f9d75e97 --- /dev/null +++ b/query.c @@ -0,0 +1,456 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_menu.h" +#include "sort.h" + +#include <string.h> +#include <stdlib.h> +#include <ctype.h> + +typedef struct query +{ + ADDRESS *addr; + char *name; + char *other; + struct query *next; +} QUERY; + +typedef struct entry +{ + int tagged; + QUERY *data; +} ENTRY; + +static struct mapping_t QueryHelp[] = { + { "Exit", OP_EXIT }, + { "Mail", OP_MAIL }, + { "New Query", OP_QUERY }, + { "Make Alias", OP_CREATE_ALIAS }, + { "Search", OP_SEARCH }, + { "Help", OP_HELP }, + { NULL } +}; + +/* Variables for outsizing output format */ +static int FirstColumn; +static int SecondColumn; + +static void query_menu (char *buf, size_t buflen, QUERY *results, int retbuf); + +static ADDRESS *result_to_addr (QUERY *r) +{ + static ADDRESS tmp; + + tmp = *r->addr; + + if(!tmp.next && !tmp.personal) + tmp.personal = r->name; + + return &tmp; +} + +static QUERY *run_query (char *s, int quiet) +{ + FILE *fp; + QUERY *first = NULL; + QUERY *cur = NULL; + char cmd[_POSIX_PATH_MAX]; + char buf[STRING]; + char msg[STRING]; + char *p; + pid_t thepid; + int l; + + + snprintf (cmd, sizeof (cmd), QueryCmd, s); + + if ((thepid = mutt_create_filter (cmd, NULL, &fp, NULL)) < 0) + { + dprint (1, (debugfile, "unable to fork command: %s", cmd)); + return 0; + } + if (!quiet) + mutt_message ("Waiting for response..."); + fgets (msg, sizeof (msg) - 1, fp); + while (fgets(buf, sizeof (buf) - 1, fp)) + { + if (first == NULL) + { + FirstColumn = 0; + SecondColumn = 0; + first = (QUERY *) safe_calloc (1, sizeof (QUERY)); + cur = first; + } + else + { + cur->next = (QUERY *) safe_calloc (1, sizeof (QUERY)); + cur = cur->next; + } + p = strtok(buf, "\t\n"); + if (p) + { + l = strlen (p); + if (l > SecondColumn) + SecondColumn = l; + + cur->addr = rfc822_parse_adrlist (cur->addr, p); + p = strtok(NULL, "\t\n"); + if (p) + { + l = strlen (p); + if (l > FirstColumn) + FirstColumn = l; + cur->name = safe_strdup (p); + p = strtok(NULL, "\t\n"); + if (p) + { + cur->other = safe_strdup (p); + } + } + } + } + fclose (fp); + if (mutt_wait_filter (thepid)) + { + dprint (1, (debugfile, "Error: %s\n", msg)); + if (!quiet) + mutt_error (msg); + } + else + { + if (!quiet) + mutt_message (msg); + } + + return first; +} + +int query_search (MUTTMENU *m, regex_t *re, int n) +{ + ENTRY *table = (ENTRY *) m->data; + + return (regexec (re, table[n].data->name, 0, NULL, 0)); +} + +/* This is the callback routine from mutt_menuLoop() which is used to generate + * a menu entry for the requested item number. + */ +void query_entry (char *s, size_t slen, MUTTMENU *m, int num) +{ + ENTRY *table = (ENTRY *) m->data; + char buf[SHORT_STRING] = ""; + + while (FirstColumn + SecondColumn > 70) + { + FirstColumn = FirstColumn * 3 / 4; + SecondColumn = SecondColumn * 3 / 4; + } + + rfc822_write_address (buf, sizeof (buf), table[num].data->addr); + + snprintf (s, slen, " %c %3d %-*.*s %-*.*s %s", + table[num].tagged ? '*':' ', + num+1, + FirstColumn+2, + FirstColumn+2, + table[num].data->name, + SecondColumn+2, + SecondColumn+2, + buf, + table[num].data->other); +} + +int query_tag (MUTTMENU *menu, int n) +{ + return (((ENTRY *) menu->data)[n].tagged = !((ENTRY *) menu->data)[n].tagged); +} + +int mutt_query_complete (char *buf, size_t buflen) +{ + QUERY *results = NULL; + + results = run_query (buf, 1); + if (results) + { + /* only one response? */ + if (results->next == NULL) + { + buf[0] = '\0'; + rfc822_write_address (buf, buflen, result_to_addr(results)); + mutt_clear_error (); + return (0); + } + /* multiple results, choose from query menu */ + query_menu (buf, buflen, results, 1); + } + return (0); +} + +void mutt_query_menu (char *buf, size_t buflen) +{ + if (buf == NULL) + { + char buffer[STRING] = ""; + + query_menu (buffer, sizeof (buffer), NULL, 0); + } + else + { + query_menu (buf, buflen, NULL, 1); + } +} + +static void query_menu (char *buf, size_t buflen, QUERY *results, int retbuf) +{ + MUTTMENU *menu; + HEADER *msg = NULL; + ENTRY *QueryTable = NULL; + QUERY *queryp = NULL; + int i, done = 0; + int op; + char helpstr[SHORT_STRING]; + char title[STRING] = "Query"; + + menu = mutt_new_menu (); + menu->make_entry = query_entry; + menu->search = query_search; + menu->tag = query_tag; + menu->menu = MENU_QUERY; + menu->title = title; + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_QUERY, QueryHelp); + + if (results == NULL) + { + if (!QueryCmd) + { + mutt_error ("Query command not defined."); + return; + } + /* Prompt for Query */ + if (mutt_get_field ("Query: ", buf, buflen, 0) == 0 && buf[0]) + { + results = run_query (buf, 0); + } + } + + if (results) + { + /* tell whoever called me to redraw the screen when I return */ + set_option (OPTNEEDREDRAW); + + snprintf (title, sizeof (title), "Query '%s'", buf); + + /* count the number of results */ + for (queryp = results; queryp; queryp = queryp->next) + menu->max++; + + menu->data = QueryTable = (ENTRY *) safe_calloc (menu->max, sizeof (ENTRY)); + + for (i = 0, queryp = results; queryp; queryp = queryp->next, i++) + QueryTable[i].data = queryp; + + while (!done) + { + switch ((op = mutt_menuLoop (menu))) + { + case OP_QUERY_APPEND: + case OP_QUERY: + if (mutt_get_field ("Query: ", buf, buflen, 0) == 0 && buf[0]) + { + QUERY *newresults = NULL; + + newresults = run_query (buf, 0); + + menu->redraw = REDRAW_FULL; + if (newresults) + { + snprintf (title, sizeof (title), "Query '%s'", buf); + + if (op == OP_QUERY) + { + queryp = results; + while (queryp) + { + rfc822_free_address (&queryp->addr); + safe_free ((void **)&queryp->name); + safe_free ((void **)&queryp->other); + results = queryp->next; + safe_free ((void **)&queryp); + queryp = results; + } + results = newresults; + safe_free ((void **) &QueryTable); + } + else + { + /* append */ + for (queryp = results; queryp->next; queryp = queryp->next); + + queryp->next = newresults; + } + + + menu->current = 0; + mutt_menuDestroy (&menu); + menu = mutt_new_menu (); + menu->make_entry = query_entry; + menu->search = query_search; + menu->tag = query_tag; + menu->menu = MENU_QUERY; + menu->title = title; + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_QUERY, QueryHelp); + + /* count the number of results */ + for (queryp = results; queryp; queryp = queryp->next) + menu->max++; + + if (op == OP_QUERY) + { + menu->data = QueryTable = + (ENTRY *) safe_calloc (menu->max, sizeof (ENTRY)); + + for (i = 0, queryp = results; queryp; + queryp = queryp->next, i++) + QueryTable[i].data = queryp; + } + else + { + int clear = 0; + + /* append */ + safe_realloc ((void **)&QueryTable, menu->max * sizeof (ENTRY)); + + menu->data = QueryTable; + + for (i = 0, queryp = results; queryp; + queryp = queryp->next, i++) + { + /* once we hit new entries, clear/init the tag */ + if (queryp == newresults) + clear = 1; + + QueryTable[i].data = queryp; + if (clear) + QueryTable[i].tagged = 0; + } + } + } + } + break; + + case OP_CREATE_ALIAS: + if (menu->tagprefix) + { + ADDRESS *naddr = NULL; + + for (i = 0; i < menu->max; i++) + if (QueryTable[i].tagged) + rfc822_append (&naddr, result_to_addr(QueryTable[i].data)); + + mutt_create_alias (NULL, naddr); + } + else + { + mutt_create_alias (NULL, result_to_addr(QueryTable[menu->current].data)); + } + break; + + case OP_GENERIC_SELECT_ENTRY: + if (retbuf) + { + done = 2; + break; + } + /* fall through to OP_MAIL */ + + case OP_MAIL: + msg = mutt_new_header (); + msg->env = mutt_new_envelope (); + if (!menu->tagprefix) + { + msg->env->to = + rfc822_cpy_adr (result_to_addr(QueryTable[menu->current].data)); + } + else + { + for (i = 0; i < menu->max; i++) + if (QueryTable[i].tagged) + rfc822_append (&msg->env->to, result_to_addr(QueryTable[i].data)); + } + ci_send_message (0, msg, NULL, Context, NULL); + menu->redraw = REDRAW_FULL; + break; + + case OP_EXIT: + done = 1; + break; + } + } + + /* if we need to return the selected entries */ + if (retbuf && (done == 2)) + { + int tagged = 0; + size_t curpos = 0; + + memset (buf, 0, buflen); + + /* check for tagged entries */ + for (i = 0; i < menu->max; i++) + { + if (QueryTable[i].tagged) + { + if (curpos == 0) + { + tagged = 1; + rfc822_write_address (buf, buflen, result_to_addr(QueryTable[i].data)); + curpos = strlen (buf); + } + else if (curpos + 2 < buflen) + { + strcat (buf, ", "); + rfc822_write_address ((char *) buf + curpos + 1, buflen - curpos - 1, + result_to_addr(QueryTable[i].data)); + curpos = strlen (buf); + } + } + } + /* then enter current message */ + if (!tagged) + { + rfc822_write_address (buf, buflen, result_to_addr(QueryTable[menu->current].data)); + } + } + + queryp = results; + while (queryp) + { + rfc822_free_address (&queryp->addr); + safe_free ((void **)&queryp->name); + safe_free ((void **)&queryp->other); + results = queryp->next; + safe_free ((void **)&queryp); + queryp = results; + } + safe_free ((void **) &QueryTable); + } + + mutt_menuDestroy (&menu); +} diff --git a/reap.pl b/reap.pl new file mode 100755 index 00000000..90e532bd --- /dev/null +++ b/reap.pl @@ -0,0 +1,26 @@ +#!/usr/local/bin/perl +# +# A small script to strip out any "illegal" PGP code to make sure it is +# safe for International export. +# +$word = shift; +$illegal = 0; +$count = 0; +while (<>) +{ + if (/^#if/) + { + if (/${word}/) { $illegal = 1; } + if ($illegal) { $count++; } + } + elsif ($illegal && /^#endif/) + { + $count--; + if ($count == 0) + { + $illegal = 0; + next; + } + } + print if (! $illegal); +} diff --git a/recvattach.c b/recvattach.c new file mode 100644 index 00000000..2ee69560 --- /dev/null +++ b/recvattach.c @@ -0,0 +1,779 @@ +/* + * Copyright (C) 1996,1997 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "mutt_menu.h" +#include "rfc1524.h" +#include "mime.h" +#include "mailbox.h" +#include "attach.h" +#include "mapping.h" +#include "mx.h" +#include "copy.h" + + + +#ifdef _PGPPATH +#include "pgp.h" +#endif + + + +#include <ctype.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/wait.h> +#include <string.h> +#include <errno.h> + +static struct mapping_t AttachHelp[] = { + { "Exit", OP_EXIT }, + { "Save", OP_SAVE }, + { "Pipe", OP_PIPE }, + { "Print", OP_PRINT }, + { "Help", OP_HELP }, + { NULL } +}; + +void mutt_update_tree (ATTACHPTR **idx, short idxlen) +{ + char buf[STRING]; + char *s; + int x; + + for (x = 0; x < idxlen; x++) + { + if (2 * (idx[x]->level + 2) < sizeof (buf)) + { + if (idx[x]->level) + { + s = buf + 2 * (idx[x]->level - 1); + *s++ = (idx[x]->content->next) ? M_TREE_LTEE : M_TREE_LLCORNER; + *s++ = M_TREE_HLINE; + *s++ = M_TREE_RARROW; + } + else + s = buf; + *s = 0; + } + + if (idx[x]->tree) + { + if (strcmp (idx[x]->tree, buf) != 0) + { + safe_free ((void **) &idx[x]->tree); + idx[x]->tree = safe_strdup (buf); + } + } + else + idx[x]->tree = safe_strdup (buf); + + if (2 * (idx[x]->level + 2) < sizeof (buf) && idx[x]->level) + { + s = buf + 2 * (idx[x]->level - 1); + *s++ = (idx[x]->content->next) ? '\005' : '\006'; + *s++ = '\006'; + } + } +} + +ATTACHPTR **mutt_gen_attach_list (BODY *m, + ATTACHPTR **idx, + short *idxlen, + short *idxmax, + int level, + int compose) +{ + ATTACHPTR *new; + + for (; m; m = m->next) + { + if (*idxlen == *idxmax) + safe_realloc ((void **) &idx, sizeof (ATTACHPTR *) * (*idxmax += 5)); + + if (m->type == TYPEMULTIPART && m->parts) + { + idx = mutt_gen_attach_list (m->parts, idx, idxlen, idxmax, level, compose); + } + else + { + new = idx[(*idxlen)++] = (ATTACHPTR *) safe_calloc (1, sizeof (ATTACHPTR)); + new->content = m; + new->level = level; + + /* We don't support multipart messages in the compose menu yet */ + if (!compose && m->type == TYPEMESSAGE && + (!strcasecmp (m->subtype, "rfc822") || + !strcasecmp (m->subtype, "news")) && is_multipart (m->parts)) + { + idx = mutt_gen_attach_list (m->parts, idx, idxlen, idxmax, level + 1, compose); + } + } + } + + if (level == 0) + mutt_update_tree (idx, *idxlen); + + return (idx); +} + +void attach_entry (char *b, size_t blen, MUTTMENU *menu, int num) +{ + char t[SHORT_STRING]; + char s[SHORT_STRING]; + char size[SHORT_STRING]; + ATTACHPTR **idx = (ATTACHPTR **) menu->data; + BODY *m; + + m = idx[num]->content; + s[0] = 0; + if (m->type == TYPEMESSAGE && (!strcasecmp ("rfc822", m->subtype) || + !strcasecmp ("news", m->subtype)) && MsgFmt[0]) + _mutt_make_string (s, sizeof (s), MsgFmt, m->hdr, + M_FORMAT_FORCESUBJ | M_FORMAT_MAKEPRINT); + + mutt_pretty_size (size, sizeof (size), m->length); + snprintf (t, sizeof (t), "[%.7s/%.10s, %.6s, %s]", + TYPE (m->type), m->subtype, ENCODING (m->encoding), size); + snprintf (b, blen, " %c%c %2d %-34.34s %s%s", + m->deleted ? 'D' : ' ', + m->tagged ? '*' : ' ', + num + 1, + t, + idx[num]->tree ? idx[num]->tree : "", + s[0] ? s : (m->description ? m->description : + (m->filename ? m->filename : "<no description>"))); +} + +int mutt_tag_attach (MUTTMENU *menu, int n) +{ + return (((ATTACHPTR **) menu->data)[n]->content->tagged = !((ATTACHPTR **) menu->data)[n]->content->tagged); +} + +static void mutt_query_save_attachment (FILE *fp, BODY *body) +{ + char buf[_POSIX_PATH_MAX], tfile[_POSIX_PATH_MAX]; + + if (fp && body->filename) + strfcpy (buf, body->filename, sizeof (buf)); + else + buf[0] = 0; + if (mutt_get_field ("Save to file: ", buf, sizeof (buf), M_FILE | M_CLEAR) != 0 || !buf[0]) + return; + mutt_expand_path (buf, sizeof (buf)); + if (mutt_check_overwrite (body->filename, buf, tfile, sizeof (tfile), 0)) + return; + mutt_message ("Saving..."); + if (mutt_save_attachment (fp, body, tfile, 0) == 0) + mutt_message ("Attachment saved."); +} + +void mutt_save_attachment_list (FILE *fp, int tag, BODY *top) +{ + for (; top; top = top->next) + { + if (!tag || top->tagged) + mutt_query_save_attachment (fp, top); + else if (top->parts) + mutt_save_attachment_list (fp, 1, top->parts); + if (!tag) + return; + } +} + +static void +mutt_query_pipe_attachment (char *command, FILE *fp, BODY *body, int filter) +{ + char tfile[_POSIX_PATH_MAX]; + char warning[STRING+_POSIX_PATH_MAX]; + + if (filter) + { + snprintf (warning, sizeof (warning), + "WARNING! You are about to overwrite %s, continue?", + body->filename); + if (mutt_yesorno (warning, M_NO) != M_YES) { + CLEARLINE (LINES-1); + return; + } + mutt_mktemp (tfile); + } + else + tfile[0] = 0; + + if (mutt_pipe_attachment (fp, body, command, tfile)) + { + if (filter) + { + mutt_unlink (body->filename); + mutt_rename_file (tfile, body->filename); + mutt_update_encoding (body); + mutt_message ("Attachment filtered."); + } + } + else + { + if (filter && tfile[0]) + mutt_unlink (tfile); + } +} + +static void +pipe_attachment_list (char *command, FILE *fp, int tag, BODY *top, int filter) +{ + for (; top; top = top->next) + { + if (!tag || top->tagged) + mutt_query_pipe_attachment (command, fp, top, filter); + else if (top->parts) + pipe_attachment_list (command, fp, tag, top->parts, filter); + if (!tag) + break; + } +} + +void mutt_pipe_attachment_list (FILE *fp, int tag, BODY *top, int filter) +{ + char buf[SHORT_STRING]; + + if (fp) + filter = 0; /* sanity check: we can't filter in the recv case yet */ + + buf[0] = 0; + if (mutt_get_field ((filter ? "Filter through: " : "Pipe to: "), + buf, sizeof (buf), 0) != 0 || !buf[0]) + return; + mutt_expand_path (buf, sizeof (buf)); + pipe_attachment_list (buf, fp, tag, top, filter); +} + +static void print_attachment_list (FILE *fp, int tag, BODY *top) +{ + for (; top; top = top->next) + { + if (!tag || top->tagged) + mutt_print_attachment (fp, top); + else if (top->parts) + mutt_print_attachment_list (fp, tag, top->parts); + if (!tag) + return; + } +} + +void mutt_print_attachment_list (FILE *fp, int tag, BODY *top) +{ + if (query_quadoption (OPT_PRINT, tag ? "Print tagged attachment(s)?" : "Print attachment?") != M_YES) + return; + print_attachment_list (fp, tag, top); +} + +int mutt_is_message_type (int type, char *subtype) +{ + if (type != TYPEMESSAGE) + return 0; + if (strcasecmp (subtype, "rfc822") == 0 || strcasecmp (subtype, "news") == 0) + return 1; + return 0; +} + +static void +bounce_attachment_list (ADDRESS *adr, int tag, BODY *body, HEADER *hdr) +{ + for (; body; body = body->next) + { + if (!tag || body->tagged) + { + if (!mutt_is_message_type (body->type, body->subtype)) + { + mutt_error ("You may only bounce message/rfc822 parts."); + continue; + } + body->hdr->msgno = hdr->msgno; + mutt_bounce_message (body->hdr, adr); + } + else if (body->parts) + bounce_attachment_list (adr, tag, body->parts, hdr); + if (!tag) + break; + } +} + +static void query_bounce_attachment (int tag, BODY *top, HEADER *hdr) +{ + char prompt[SHORT_STRING]; + char buf[HUGE_STRING]; + ADDRESS *adr = NULL; + int rc; + + buf[0] = 0; + snprintf (prompt, sizeof (prompt), "Bounce %smessage%s to: ", + tag ? "tagged " : "", tag ? "s" : ""); + rc = mutt_get_field (prompt, buf, sizeof (buf), M_ALIAS); + + if (rc || !buf[0]) + return; + + adr = rfc822_parse_adrlist (adr, buf); + adr = mutt_expand_aliases (adr); + buf[0] = 0; + rfc822_write_address (buf, sizeof (buf), adr); + snprintf (prompt, sizeof (prompt), "Bounce message%s to %s...?", (tag ? "s" : ""), buf); + if (mutt_yesorno (prompt, 1) != 1) + { + rfc822_free_address (&adr); + CLEARLINE (LINES-1); + return; + } + bounce_attachment_list (adr, tag, top, hdr); + rfc822_free_address (&adr); +} + +static void +copy_tagged_attachments (FILE *fpout, FILE *fpin, const char *boundary, BODY *bdy) +{ + for (; bdy; bdy = bdy->next) + { + if (bdy->tagged) + { + fprintf (fpout, "--%s\n", boundary); + fseek (fpin, bdy->hdr_offset, 0); + mutt_copy_bytes (fpin, fpout, bdy->length + bdy->offset - bdy->hdr_offset); + } + else if (bdy->parts) + copy_tagged_attachments (fpout, fpin, boundary, bdy->parts); + } +} + +static int +create_tagged_message (const char *tempfile, + int tag, + CONTEXT *ctx, + HEADER *cur, + BODY *body) +{ + char *boundary; + MESSAGE *msg, *src; + CONTEXT tmpctx; + int magic; + + magic = DefaultMagic; + DefaultMagic = M_MBOX; + mx_open_mailbox (tempfile, M_APPEND, &tmpctx); + msg = mx_open_new_message (&tmpctx, cur, M_ADD_FROM); + src = mx_open_message (ctx, cur->msgno); + + if (tag) + { + mutt_copy_header (src->fp, cur, msg->fp, CH_XMIT, NULL); + boundary = mutt_get_parameter ("boundary", cur->content->parameter); + copy_tagged_attachments (msg->fp, src->fp, boundary, cur->content->parts); + fprintf (msg->fp, "--%s--\n", boundary); + } + else + { + /* single attachment */ + mutt_copy_header (src->fp, cur, msg->fp, CH_XMIT | CH_MIME | CH_NONEWLINE, NULL); + fputs ("Mime-Version: 1.0\n", msg->fp); + mutt_write_mime_header (body, msg->fp); + fputc ('\n', msg->fp); + fseek (src->fp, body->offset, 0); + mutt_copy_bytes (src->fp, msg->fp, body->length); + } + + mx_close_message (&msg); + mx_close_message (&src); + mx_close_mailbox (&tmpctx); + DefaultMagic = magic; + return 0; +} + +/* op flag to ci_send_message() + tag operate on tagged attachments? + hdr current message + body current attachment */ +static void reply_attachment_list (int op, int tag, HEADER *hdr, BODY *body) +{ + HEADER *hn; + char tempfile[_POSIX_PATH_MAX]; + CONTEXT *ctx; + + if (!tag && body->hdr) + { + hn = body->hdr; + hn->msgno = hdr->msgno; /* required for MH/maildir */ + ctx = Context; + } + else + { + /* build a fake message which consists of only the tagged attachments */ + mutt_mktemp (tempfile); + create_tagged_message (tempfile, tag, Context, hdr, body); + ctx = mx_open_mailbox (tempfile, M_QUIET, NULL); + hn = ctx->hdrs[0]; + } + + ci_send_message (op, NULL, NULL, ctx, hn); + + if (hn->replied && !hdr->replied) + mutt_set_flag (Context, hdr, M_REPLIED, 1); + + if (ctx != Context) + { + mx_fastclose_mailbox (ctx); + safe_free ((void **) &ctx); + unlink (tempfile); + } +} + +void +mutt_attach_display_loop (MUTTMENU *menu, int op, FILE *fp, ATTACHPTR **idx) +{ + int old_optweed = option (OPTWEED); + + set_option (OPTWEED); + do + { + switch (op) + { + case OP_DISPLAY_HEADERS: + toggle_option (OPTWEED); + /* fall through */ + + case OP_VIEW_ATTACH: + op = mutt_view_attachment (fp, idx[menu->current]->content, M_REGULAR); + break; + + case OP_NEXT_ENTRY: + case OP_MAIN_NEXT_UNDELETED: /* hack */ + if (menu->current < menu->max - 1) + { + menu->current++; + op = OP_VIEW_ATTACH; + } + else + op = OP_NULL; + break; + case OP_PREV_ENTRY: + case OP_MAIN_PREV_UNDELETED: /* hack */ + if (menu->current > 0) + { + menu->current--; + op = OP_VIEW_ATTACH; + } + else + op = OP_NULL; + break; + default: + op = OP_NULL; + } + } + while (op != OP_NULL); + + if (option (OPTWEED) != old_optweed) + toggle_option (OPTWEED); +} + +void mutt_view_attachments (HEADER *hdr) +{ + + + +#ifdef _PGPPATH + char tempfile[_POSIX_PATH_MAX]; + int pgp = 0; +#endif + + + + char helpstr[SHORT_STRING]; + MUTTMENU *menu; + BODY *cur; + MESSAGE *msg; + FILE *fp; + ATTACHPTR **idx = NULL; + short idxlen = 0; + short idxmax = 0; + int flags = 0; + int op; + + /* make sure we have parsed this message */ + mutt_parse_mime_message (Context, hdr); + + if ((msg = mx_open_message (Context, hdr->msgno)) == NULL) + return; + + + +#ifdef _PGPPATH + + if((hdr->pgp & PGPENCRYPT) && !pgp_valid_passphrase()) + { + mx_close_message(&msg); + return; + } + + if ((hdr->pgp & PGPENCRYPT) && hdr->content->type == TYPEMULTIPART) + { + STATE s; + + memset (&s, 0, sizeof (s)); + s.fpin = msg->fp; + mutt_mktemp (tempfile); + if ((fp = safe_fopen (tempfile, "w+")) == NULL) + { + mutt_perror (tempfile); + mx_close_message (&msg); + return; + } + cur = pgp_decrypt_part (hdr->content->parts->next, &s, fp); + rewind (fp); + + pgp = 1; + } + else +#endif /* _PGPPATH */ + + + + + + + + + + + + + + { + fp = msg->fp; + cur = hdr->content; + } + + idx = mutt_gen_attach_list (cur, idx, &idxlen, &idxmax, 0, 0); + + menu = mutt_new_menu (); + menu->max = idxlen; + menu->make_entry = attach_entry; + menu->tag = mutt_tag_attach; + menu->menu = MENU_ATTACH; + menu->title = "Attachments"; + menu->data = idx; + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_ATTACH, AttachHelp); + + FOREVER + { + switch (op = mutt_menuLoop (menu)) + { + case OP_DISPLAY_HEADERS: + case OP_VIEW_ATTACH: + mutt_attach_display_loop (menu, op, fp, idx); + menu->redraw = REDRAW_FULL; + break; + + case OP_ATTACH_VIEW_MAILCAP: + mutt_view_attachment (fp, idx[menu->current]->content, M_MAILCAP); + menu->redraw = REDRAW_FULL; + break; + + case OP_ATTACH_VIEW_TEXT: + mutt_view_attachment (fp, idx[menu->current]->content, M_AS_TEXT); + menu->redraw = REDRAW_FULL; + break; + + + +#ifdef _PGPPATH + case OP_EXTRACT_KEYS: + pgp_extract_keys_from_attachment_list (fp, menu->tagprefix, menu->tagprefix ? cur : idx[menu->current]->content); + menu->redraw = REDRAW_FULL; + break; +#endif + + + + case OP_PRINT: + mutt_print_attachment_list (fp, menu->tagprefix, menu->tagprefix ? cur : idx[menu->current]->content); + break; + + case OP_PIPE: + mutt_pipe_attachment_list (fp, menu->tagprefix, menu->tagprefix ? cur : idx[menu->current]->content, 0); + break; + + case OP_SAVE: + mutt_save_attachment_list (fp, menu->tagprefix, menu->tagprefix ? cur : idx[menu->current]->content); + if (option (OPTRESOLVE) && menu->current < menu->max - 1) + { + menu->current++; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + else + menu->redraw = REDRAW_CURRENT; + break; + + case OP_DELETE: + + if (menu->max == 1) + { + mutt_message ("Only deletion of multipart attachments is supported."); + } + else + { +#ifdef _PGPPATH + if (hdr->pgp) + { + mutt_message ( + "Deletion of attachments from PGP messages is unsupported."); + } + else +#endif + { + if (!menu->tagprefix) + { + idx[menu->current]->content->deleted = 1; + if (option (OPTRESOLVE) && menu->current < menu->max - 1) + { + menu->current++; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + else + menu->redraw = REDRAW_CURRENT; + } + else + { + int x; + + for (x = 0; x < menu->max; x++) + { + if (idx[x]->content->tagged) + { + idx[x]->content->deleted = 1; + menu->redraw = REDRAW_INDEX; + } + } + } + } + } + break; + + case OP_UNDELETE: + if (!menu->tagprefix) + { + idx[menu->current]->content->deleted = 0; + if (option (OPTRESOLVE) && menu->current < menu->max - 1) + { + menu->current++; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + else + menu->redraw = REDRAW_CURRENT; + } + else + { + int x; + + for (x = 0; x < menu->max; x++) + { + if (idx[x]->content->tagged) + { + idx[x]->content->deleted = 0; + menu->redraw = REDRAW_INDEX; + } + } + } + break; + + case OP_BOUNCE_MESSAGE: + query_bounce_attachment (menu->tagprefix, menu->tagprefix ? cur : idx[menu->current]->content, hdr); + break; + + case OP_REPLY: + case OP_GROUP_REPLY: + case OP_LIST_REPLY: + case OP_FORWARD_MESSAGE: + + + +#ifdef _PGPPATH + if ((hdr->pgp & PGPENCRYPT) && hdr->content->type == TYPEMULTIPART) + { + mutt_error ( + "This operation is not currently supported for PGP messages."); + break; + } +#endif + + + + if (op == OP_FORWARD_MESSAGE) + flags = SENDFORWARD; + else + flags = SENDREPLY | + (op == OP_GROUP_REPLY ? SENDGROUPREPLY : 0) | + (op == OP_LIST_REPLY ? SENDLISTREPLY : 0); + reply_attachment_list (flags, + menu->tagprefix, + hdr, + menu->tagprefix ? cur : idx[menu->current]->content); + menu->redraw = REDRAW_FULL; + break; + + case OP_EXIT: + mx_close_message (&msg); + hdr->attach_del = 0; + while (idxlen-- > 0) + { + if (idx[idxlen]->content->deleted) + hdr->attach_del = 1; + safe_free ((void **) &idx[idxlen]->tree); + safe_free ((void **) &idx[idxlen]); + } + if (hdr->attach_del) + hdr->changed = 1; + safe_free ((void **) &idx); + idxmax = 0; + + + + + + + + + + + +#ifdef _PGPPATH + if (pgp) + { + fclose (fp); + mutt_free_body (&cur); + unlink (tempfile); + } +#endif /* _PGPPATH */ + + + + mutt_menuDestroy (&menu); + return; + } + } + + /* not reached */ +} diff --git a/reldate.h b/reldate.h new file mode 100644 index 00000000..84aeef0d --- /dev/null +++ b/reldate.h @@ -0,0 +1 @@ +const char *ReleaseDate = "1998-05-14"; diff --git a/resize.c b/resize.c new file mode 100644 index 00000000..ccbcb9da --- /dev/null +++ b/resize.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" + +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <termios.h> + +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +/* this routine should be called after receiving SIGWINCH */ +void mutt_resize_screen (void) +{ + char *cp; + int fd; + struct winsize w; +#ifdef HAVE_RESIZETERM + int SLtt_Screen_Rows, SLtt_Screen_Cols; +#endif + + SLtt_Screen_Rows = -1; + SLtt_Screen_Cols = -1; + if ((fd = open ("/dev/tty", O_RDONLY)) != -1) + { + if (ioctl (fd, TIOCGWINSZ, &w) != -1) + { + SLtt_Screen_Rows = w.ws_row; + SLtt_Screen_Cols = w.ws_col; + } + close (fd); + } + if (SLtt_Screen_Rows <= 0) + { + if ((cp = getenv ("LINES")) != NULL) + { + SLtt_Screen_Rows = atoi (cp); + } + else + SLtt_Screen_Rows = 24; + } + if (SLtt_Screen_Cols <= 0) + { + if ((cp = getenv ("COLUMNS")) != NULL) + SLtt_Screen_Cols = atoi (cp); + else + SLtt_Screen_Cols = 80; + } +#ifdef USE_SLANG_CURSES + delwin (stdscr); + SLsmg_reset_smg (); + SLsmg_init_smg (); + stdscr = newwin (0, 0, 0, 0); + keypad (stdscr, TRUE); +#else + resizeterm (SLtt_Screen_Rows, SLtt_Screen_Cols); +#endif +} diff --git a/rfc1524.c b/rfc1524.c new file mode 100644 index 00000000..ac16ae8b --- /dev/null +++ b/rfc1524.c @@ -0,0 +1,599 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * rfc1524 defines a format for the Multimedia Mail Configuration, which + * is the standard mailcap file format under Unix which specifies what + * external programs should be used to view/compose/edit multimedia files + * based on content type. + * + * This file contains various functions for implementing a fair subset of + * rfc1524. + */ + +#include "mutt.h" +#include "rfc1524.h" + +#include <ctype.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/wait.h> +#include <string.h> + +/* The command semantics include the following: + * %s is the filename that contains the mail body data + * %t is the content type, like text/plain + * %{parameter} is replaced by the parameter value from the content-type field + * \% is % + * Unsupported rfc1524 parameters: these would probably require some doing + * by mutt, and can probably just be done by piping the message to metamail + * %n is the integer number of sub-parts in the multipart + * %F is "content-type filename" repeated for each sub-part + * + * In addition, this function returns a 0 if the command works on a file, + * and 1 if the command works on a pipe. + */ +int rfc1524_expand_command (BODY *a, char *filename, char *type, + char *command, int clen) +{ + int x=0,y=0; + int needspipe = TRUE; + char buf[LONG_STRING]; + + while (command[x] && x<clen && y<sizeof(buf)) { + if (command[x] == '\\') { + x++; + buf[y++] = command[x++]; + } + else if (command[x] == '%') { + x++; + if (command[x] == '{') { + char param[STRING]; + int z = 0; + char *ret = NULL; + + x++; + while (command[x] && command[x] != '}' && z<sizeof(param)) + param[z++] = command[x++]; + param[z] = '\0'; + dprint(2,(debugfile,"Parameter: %s Returns: %s\n",param,ret)); + ret = mutt_get_parameter(param,a->parameter); + dprint(2,(debugfile,"Parameter: %s Returns: %s\n",param,ret)); + z = 0; + while (ret && ret[z] && y<sizeof(buf)) + buf[y++] = ret[z++]; + } + else if (command[x] == 's' && filename != NULL) + { + char *fn = filename; + + while (*fn && y < sizeof (buf)) + buf[y++] = *fn++; + needspipe = FALSE; + } + else if (command[x] == 't') + { + while (*type && y < sizeof (buf)) + buf[y++] = *type++; + } + x++; + } + else + buf[y++] = command[x++]; + } + buf[y] = '\0'; + strfcpy (command, buf, clen); + + return needspipe; +} + +/* NUL terminates a rfc 1524 field, + * returns start of next field or NULL */ +static char *get_field (char *s) +{ + char *ch; + + if (!s) + return NULL; + + while ((ch = strpbrk (s, ";\\")) != NULL) + { + if (*ch == '\\') + { + s = ch + 1; + if (*s) + s++; + } + else + { + *ch++ = 0; + SKIPWS (ch); + break; + } + } + mutt_remove_trailing_ws (s); + return ch; +} + +static int get_field_text (char *field, char **entry, + char *type, char *filename, int line) +{ + field = mutt_skip_whitespace (field); + if (*field == '=') + { + if (entry) + { + field = mutt_skip_whitespace (++field); + safe_free ((void **) entry); + *entry = safe_strdup (field); + } + return 1; + } + else + { + mutt_error ("Improperly formated entry for type %s in \"%s\" line %d", + type, filename, line); + return 0; + } +} + +static int rfc1524_mailcap_parse (BODY *a, + char *filename, + char *type, + rfc1524_entry *entry, + int opt) +{ + FILE *fp; + char *buf = NULL; + size_t buflen; + char *ch; + char *field; + int found = FALSE; + int copiousoutput; + int composecommand; + int editcommand; + int printcommand; + int btlen; + int line = 0; + + /* rfc1524 mailcap file is of the format: + * base/type; command; extradefs + * type can be * for matching all + * base with no /type is an implicit wild + * command contains a %s for the filename to pass, default to pipe on stdin + * extradefs are of the form: + * def1="definition"; def2="define \;"; + * line wraps with a \ at the end of the line + * # for comments + */ + + /* find length of basetype */ + if ((ch = strchr (type, '/')) == NULL) + return FALSE; + btlen = ch - type; + + if ((fp = fopen (filename, "r")) != NULL) + { + while (!found && (buf = mutt_read_line (buf, &buflen, fp, &line)) != NULL) + { + /* ignore comments */ + if (*buf == '#') + continue; + dprint (2, (debugfile, "mailcap entry: %s\n", buf)); + + /* check type */ + ch = get_field (buf); + if (strcasecmp (buf, type) && + (strncasecmp (buf, type, btlen) || + (buf[btlen] != 0 && /* implicit wild */ + strcmp (buf + btlen, "/*")))) /* wildsubtype */ + continue; + + /* next field is the viewcommand */ + field = ch; + ch = get_field (ch); + if (entry) + entry->command = safe_strdup (field); + + /* parse the optional fields */ + found = TRUE; + copiousoutput = FALSE; + composecommand = FALSE; + editcommand = FALSE; + printcommand = FALSE; + + while (ch) + { + field = ch; + ch = get_field (ch); + dprint (2, (debugfile, "field: %s\n", field)); + + if (!strcasecmp (field, "needsterminal")) + { + if (entry) + entry->needsterminal = TRUE; + } + else if (!strcasecmp (field, "copiousoutput")) + { + copiousoutput = TRUE; + if (entry) + entry->copiousoutput = TRUE; + } + else if (!strncasecmp (field, "composetyped", 12)) + { + /* this compare most occur before compose to match correctly */ + if (get_field_text (field + 12, entry ? &entry->composetypecommand : NULL, + type, filename, line)) + composecommand = TRUE; + } + else if (!strncasecmp (field, "compose", 7)) + { + if (get_field_text (field + 7, entry ? &entry->composecommand : NULL, + type, filename, line)) + composecommand = TRUE; + } + else if (!strncasecmp (field, "print", 5)) + { + if (get_field_text (field + 5, entry ? &entry->printcommand : NULL, + type, filename, line)) + printcommand = TRUE; + } + else if (!strncasecmp (field, "edit", 4)) + { + if (get_field_text (field + 4, entry ? &entry->editcommand : NULL, + type, filename, line)) + editcommand = TRUE; + } + else if (!strncasecmp (field, "nametemplate", 12)) + { + get_field_text (field + 12, entry ? &entry->nametemplate : NULL, + type, filename, line); + } + else if (!strncasecmp (field, "x-convert", 9)) + { + get_field_text (field + 9, entry ? &entry->convert : NULL, + type, filename, line); + } + else if (!strncasecmp (field, "test", 4)) + { + /* + * This routine executes the given test command to determine + * if this is the right entry. + */ + char *test_command = NULL; + size_t len; + + if (get_field_text (field + 4, &test_command, type, filename, line) + && test_command) + { + len = strlen (test_command) + STRING; + safe_realloc ((void **) &test_command, len); + rfc1524_expand_command (a, NULL, type, test_command, len); + if (mutt_system (test_command)) + { + /* a non-zero exit code means test failed */ + found = FALSE; + } + free (test_command); + } + } + } /* while (ch) */ + + if (opt == M_AUTOVIEW) + { + if (!copiousoutput) + found = FALSE; + } + else if (opt == M_COMPOSE) + { + if (!composecommand) + found = FALSE; + } + else if (opt == M_EDIT) + { + if (!editcommand) + found = FALSE; + } + else if (opt == M_PRINT) + { + if (!printcommand) + found = FALSE; + } + + if (!found) + { + /* reset */ + if (entry) + { + safe_free ((void **) &entry->command); + safe_free ((void **) &entry->composecommand); + safe_free ((void **) &entry->composetypecommand); + safe_free ((void **) &entry->editcommand); + safe_free ((void **) &entry->printcommand); + safe_free ((void **) &entry->nametemplate); + safe_free ((void **) &entry->convert); + entry->needsterminal = 0; + entry->copiousoutput = 0; + } + } + } /* while (!found && (buf = mutt_read_line ())) */ + fclose (fp); + } /* if ((fp = fopen ())) */ + safe_free ((void **) &buf); + return found; +} + +rfc1524_entry *rfc1524_new_entry() +{ + rfc1524_entry *tmp; + + tmp = (rfc1524_entry *)safe_malloc(sizeof(rfc1524_entry)); + memset(tmp,0,sizeof(rfc1524_entry)); + + return tmp; +} + +void rfc1524_free_entry(rfc1524_entry **entry) +{ + rfc1524_entry *p = *entry; + + safe_free((void **)&p->command); + safe_free((void **)&p->testcommand); + safe_free((void **)&p->composecommand); + safe_free((void **)&p->composetypecommand); + safe_free((void **)&p->editcommand); + safe_free((void **)&p->printcommand); + safe_free((void **)&p->nametemplate); + safe_free((void **)entry); +} + +/* + * rfc1524_mailcap_lookup attempts to find the given type in the + * list of mailcap files. On success, this returns the entry information + * in *entry, and returns 1. On failure (not found), returns 0. + * If entry == NULL just return 1 if the given type is found. + */ +int rfc1524_mailcap_lookup (BODY *a, char *type, rfc1524_entry *entry, int opt) +{ + char path[_POSIX_PATH_MAX]; + int x; + int found = FALSE; + char *curr = MailcapPath; + + /* rfc1524 specifies that a path of mailcap files should be searched. + * joy. They say + * $HOME/.mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap, etc + * and overriden by the MAILCAPS environment variable, and, just to be nice, + * we'll make it specifiable in .muttrc + */ + if (!*curr) + { + mutt_error ("No mailcap path specified"); + return 0; + } + + while (!found && *curr) + { + x = 0; + while (*curr && *curr != ':' && x < sizeof (path) - 1) + { + path[x++] = *curr; + curr++; + } + if (*curr) + curr++; + + if (!x) + continue; + + path[x] = '\0'; + mutt_expand_path (path, sizeof (path)); + + dprint(2,(debugfile,"Checking mailcap file: %s\n",path)); + found = rfc1524_mailcap_parse (a, path, type, entry, opt); + } + + if (entry && !found) + mutt_error ("mailcap entry for type %s not found", type); + + return found; +} + +/* Modified by blong to accept a "suggestion" for file name. If + * that file exists, then construct one with unique name but + * keep any extension. This might fail, I guess. + * Renamed to mutt_adv_mktemp so I only have to change where it's + * called, and not all possible cases. + */ +void mutt_adv_mktemp (char *s) +{ + char buf[_POSIX_PATH_MAX]; + char tmp[_POSIX_PATH_MAX]; + char *period; + + strfcpy (buf, NONULL (Tempdir), sizeof (buf)); + mutt_expand_path (buf, sizeof (buf)); + if (s[0] == '\0') + { + sprintf (s, "%s/muttXXXXXX", buf); + mktemp (s); + } + else + { + strfcpy (tmp, s, sizeof (tmp)); + sprintf (s, "%s/%s", buf, tmp); + if (access (s, F_OK) != 0) + return; + if ((period = strrchr (tmp, '.')) != NULL) + *period = 0; + sprintf (s, "%s/%s.XXXXXX", buf, tmp); + mktemp (s); + if (period != NULL) + { + *period = '.'; + strcat (s, period); + } + } +} + +/* This routine expands the filename given to match the format of the + * nametemplate given. It returns various values based on what operations + * it performs. + * + * Returns 0 if oldfile is fine as is. + * Returns 1 if newfile specified + */ + +int rfc1524_expand_filename (char *nametemplate, + char *oldfile, + char *newfile, + size_t nflen) +{ + int z = 0; + int i = 0, j = 0; + int lmatch = TRUE; + int match = TRUE; + size_t len = 0; + char *s; + + newfile[0] = 0; + + if (nametemplate && (s = strrchr (nametemplate, '/'))) + nametemplate = s + 1; + + if (oldfile && (s = strrchr (oldfile, '/'))) + { + len = s - oldfile + 1; + if (len > nflen) + len = nflen; + strfcpy (newfile, oldfile, len + 1); + oldfile += len; + } + + /* If nametemplate is NULL, create a newfile from oldfile and return 0 */ + if (!nametemplate) + { + if (oldfile) + strfcpy (newfile, oldfile, nflen); + mutt_adv_mktemp (newfile); + return 0; + } + + /* If oldfile is NULL, just return a newfile name */ + if (!oldfile) + { + snprintf (newfile, nflen, nametemplate, "mutt"); + mutt_adv_mktemp (newfile); + return 0; + } + + /* Next, attempt to determine if the oldfile already matches nametemplate */ + /* Nametemplate is of the form pre%spost, only replace pre or post if + * they don't already match the oldfilename */ + /* Test pre */ + + if ((s = strrchr (nametemplate, '%')) != NULL) + { + newfile[len] = '\0'; + + z = s - nametemplate; + + for (i = 0; i < z && i < nflen; i++) + { + if (oldfile[i] != nametemplate[i]) + { + lmatch=FALSE; + break; + } + } + + if (!lmatch) + { + match = FALSE; + i = nflen - len; + if (i > z) + i = z; + strfcpy (newfile + len, nametemplate, i); + } + + strfcpy (newfile + strlen (newfile), + oldfile, nflen - strlen (newfile)); + + dprint (1, (debugfile,"template: %s, oldfile: %s, newfile: %s\n", + nametemplate, oldfile, newfile)); + + /* test post */ + lmatch = TRUE; + + for (z += 2, i = strlen (oldfile) - 1, j = strlen (nametemplate) - 1; + i && j > z; i--, j--) + if (oldfile[i] != nametemplate[j]) + { + lmatch = FALSE; + break; + } + + if (!lmatch) + { + match = FALSE; + strfcpy (newfile + strlen (newfile), + nametemplate + z, nflen - strlen (newfile)); + } + + if (match) + return 0; + + return 1; + } + else + { + /* no %s in nametemplate, graft unto path of oldfile */ + strfcpy (newfile, nametemplate, nflen); + return 1; + } +} + +/* For nametemplate support, we may need to rename a file. + * If rfc1524_expand_command() is used on a recv'd message, then + * the filename doesn't exist yet, but if its used while sending a message, + * then we need to rename the existing file. + * + * This function returns 0 on successful move, 1 on old file doesn't exist, + * 2 on new file already exists, and 3 on other failure. + */ +int mutt_rename_file (char *oldfile, char *newfile) +{ + FILE *ofp, *nfp; + + if (access (oldfile, F_OK) != 0) + return 1; + if (access (newfile, F_OK) == 0) + return 2; + if ((ofp = fopen (oldfile,"r")) == NULL) + return 3; + if ((nfp = safe_fopen (newfile,"w")) == NULL) + { + fclose(ofp); + return 3; + } + mutt_copy_stream (ofp,nfp); + fclose (nfp); + fclose (ofp); + mutt_unlink (oldfile); + return 0; +} diff --git a/rfc1524.h b/rfc1524.h new file mode 100644 index 00000000..a34e7fee --- /dev/null +++ b/rfc1524.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _RFC1524_H +#define _RFC1524_H + +typedef struct rfc1524_mailcap_entry { +/* char *contenttype; */ /* we don't need this, as we search for it */ + char *command; + char *testcommand; + char *composecommand; + char *composetypecommand; + char *editcommand; + char *printcommand; + char *nametemplate; + char *convert; +/* char *description; */ /* we don't need this */ + unsigned int needsterminal : 1; /* endwin() and system */ + unsigned int copiousoutput : 1; /* needs pager, basically */ +} rfc1524_entry; + +rfc1524_entry *rfc1524_new_entry (); +void rfc1524_free_entry (rfc1524_entry **); +int rfc1524_expand_command (BODY *, char *, char *, char *, int); +int rfc1524_expand_filename (char *, char *, char *, size_t); +int rfc1524_mailcap_lookup (BODY *, char *, rfc1524_entry *, int); +void mutt_adv_mktemp (char *); +int mutt_rename_file (char *, char *); + +#endif /* _RFC1524_H */ diff --git a/rfc2047.c b/rfc2047.c new file mode 100644 index 00000000..733a8d25 --- /dev/null +++ b/rfc2047.c @@ -0,0 +1,393 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mime.h" +#include "rfc2047.h" + +#include <ctype.h> +#include <string.h> + +typedef void encode_t (char *, size_t, const unsigned char *); + +extern char MimeSpecials[]; +extern char B64Chars[]; + +static void q_encode_string (char *d, size_t dlen, const unsigned char *s) +{ + char charset[SHORT_STRING]; + size_t cslen, wordlen; + char *wptr = d; + + snprintf (charset, sizeof (charset), "=?%s?Q?", + strcasecmp ("us-ascii", charset) == 0 ? "unknown-8bit" : Charset); + cslen = strlen (charset); + + strcpy (wptr, charset); + wptr += cslen; + wordlen = cslen; + dlen -= cslen; + + dlen -= 3; /* save room for the word terminator */ + + while (*s && dlen > 0) + { + if (wordlen >= 72) + { + if (dlen < 4 + cslen) + break; + + strcpy (wptr, "?=\n "); + wptr += 4; + dlen -= 4; + strcpy (wptr, charset); + wptr += cslen; + wordlen = cslen; + dlen -= cslen; + } + + if (*s == ' ') + { + *wptr++ = '_'; + wordlen++; + dlen--; + } + else if ((*s & 0x80) || *s == '\t' || strchr (MimeSpecials, *s)) + { + if (wordlen >= 70) + { + if (dlen < 4 + cslen) + break; + + strcpy (wptr, "?=\n "); + wptr += 4; + dlen -= 4; + + strcpy (wptr, charset); + wptr += cslen; + wordlen = cslen; + dlen -= cslen; + } + + if (dlen < 3) + break; + sprintf (wptr, "=%02X", *s); + wptr += 3; + wordlen += 3; + dlen -= 3; + } + else + { + *wptr++ = *s; + wordlen++; + dlen--; + } + s++; + } + + strcpy (wptr, "?="); +} + +static void b_encode_string (char *d, size_t dlen, const unsigned char *s) +{ + char charset[SHORT_STRING]; + char *wptr = d; + int cslen; + int wordlen; + + snprintf (charset, sizeof (charset), "=?%s?B?", Charset); + cslen = strlen (charset); + strcpy (wptr, charset); + wptr += cslen; + wordlen = cslen; + dlen -= cslen; + + dlen -= 3; /* save room for the word terminator */ + + while (*s && dlen >= 4) + { + if (wordlen >= 71) + { + if (dlen < 4 + cslen) + break; + + strcpy (wptr, "?=\n "); + wptr += 4; + dlen -= 4; + + strcpy (wptr, charset); + wptr += cslen; + wordlen = cslen; + dlen -= cslen; + } + + *wptr++ = B64Chars[ (*s >> 2) & 0x3f ]; + *wptr++ = B64Chars[ ((*s & 0x3) << 4) | ((*(s+1) >> 4) & 0xf) ]; + s++; + if (*s) + { + *wptr++ = B64Chars[ ((*s & 0xf) << 2) | ((*(s+1) >> 6) & 0x3) ]; + s++; + if (*s) + { + *wptr++ = B64Chars[ *s & 0x3f ]; + s++; + } + else + *wptr++ = '='; + } + else + { + *wptr++ = '='; + *wptr++ = '='; + } + + wordlen += 4; + dlen -= 4; + } + + strcpy (wptr, "?="); +} + +void rfc2047_encode_string (char *d, size_t dlen, const unsigned char *s) +{ + int count = 0; + int len; + const unsigned char *p = s; + encode_t *encoder; + + /* First check to see if there are any 8-bit characters */ + for (; *p; p++) + { + if (*p & 0x80) + count++; + else if (*p == '=' && *p == '?') + { + count += 2; + p++; + } + } + if (!count) + { + strfcpy (d, (const char *)s, dlen); + return; + } + + if (strcasecmp("us-ascii", Charset) == 0 || + strncasecmp("iso-8859", Charset, 8) == 0) + encoder = q_encode_string; + else + { + /* figure out which encoding generates the most compact representation */ + len = strlen ((char *) s); + if ((count * 2) + len <= (4 * len) / 3) + encoder = q_encode_string; + else + encoder = b_encode_string; + } + + /* Hack to pull the Re: and Fwd: out of the encoded word for better + handling by agents which do not support RFC2047. */ + if (!strncasecmp ("re: ", (char *) s, 4)) + { + strncpy (d, (char *) s, 4); + d += 4; + dlen -= 4; + s += 4; + } + else if (!strncasecmp ("fwd: ", (char *) s, 5)) + { + strncpy (d, (char *) s, 5); + d += 5; + dlen -= 5; + s += 5; + } + + (*encoder) (d, dlen, s); +} + +void rfc2047_encode_adrlist (ADDRESS *addr) +{ + ADDRESS *ptr = addr; + char buffer[STRING]; + + while (ptr) + { + if (ptr->personal) + { + rfc2047_encode_string (buffer, sizeof (buffer), (const unsigned char *)ptr->personal); + safe_free ((void **) &ptr->personal); + ptr->personal = safe_strdup (buffer); + } + ptr = ptr->next; + } +} + +static int rfc2047_decode_word (char *d, const char *s, size_t len) +{ + char *p = safe_strdup (s); + char *pp = p; + char *pd = d; + int enc = 0, filter = 0, count = 0, c1, c2, c3, c4; + + while ((pp = strtok (pp, "?")) != NULL) + { + count++; + switch (count) + { + case 2: + if (strcasecmp (pp, Charset) != 0) + filter = 1; + break; + case 3: + if (toupper (*pp) == 'Q') + enc = ENCQUOTEDPRINTABLE; + else if (toupper (*pp) == 'B') + enc = ENCBASE64; + else + return (-1); + break; + case 4: + if (enc == ENCQUOTEDPRINTABLE) + { + while (*pp && len > 0) + { + if (*pp == '_') + { + *pd++ = ' '; + len--; + } + else if (*pp == '=') + { + *pd++ = (hexval(pp[1]) << 4) | hexval(pp[2]); + len--; + pp += 2; + } + else + { + *pd++ = *pp; + len--; + } + pp++; + } + *pd = 0; + } + else if (enc == ENCBASE64) + { + while (*pp && len > 0) + { + c1 = Index_64[(int) pp[0]]; + c2 = Index_64[(int) pp[1]]; + *pd++ = (c1 << 2) | ((c2 >> 4) & 0x3); + if (--len == 0) break; + + if (pp[2] == '=') break; + + c3 = Index_64[(int) pp[2]]; + *pd++ = ((c2 & 0xf) << 4) | ((c3 >> 2) & 0xf); + if (--len == 0) + break; + + if (pp[3] == '=') + break; + + c4 = Index_64[(int) pp[3]]; + *pd++ = ((c3 & 0x3) << 6) | c4; + if (--len == 0) + break; + + pp += 4; + } + *pd = 0; + } + break; + } + pp = 0; + } + safe_free ((void **) &p); + if (filter) + { + pd = d; + while (*pd) + { + if (!IsPrint ((unsigned char) *pd)) + *pd = '?'; + pd++; + } + } + return (0); +} + +/* try to decode anything that looks like a valid RFC2047 encoded + * header field, ignoring RFC822 parsing rules + */ +void rfc2047_decode (char *d, const char *s, size_t dlen) +{ + const char *p, *q; + size_t n; + int found_encoded = 0; + + dlen--; /* save room for the terminal nul */ + + while (*s && dlen > 0) + { + if ((p = strstr (s, "=?")) == NULL || + (q = strchr (p + 2, '?')) == NULL || + (q = strchr (q + 1, '?')) == NULL || + (q = strstr (q + 1, "?=")) == NULL) + { + /* no encoded words */ + if (d != s) + strfcpy (d, s, dlen + 1); + return; + } + + if (p != s) + { + n = (size_t) (p - s); + /* ignore spaces between encoded words */ + if (!found_encoded || strspn (s, " \t\r\n") != n) + { + if (n > dlen) + n = dlen; + if (d != s) + memcpy (d, s, n); + d += n; + dlen -= n; + } + } + + rfc2047_decode_word (d, p, dlen); + found_encoded = 1; + s = q + 2; + n = strlen (d); + dlen -= n; + d += n; + } + *d = 0; +} + +void rfc2047_decode_adrlist (ADDRESS *a) +{ + while (a) + { + if (a->personal && strstr (a->personal, "=?") != NULL) + rfc2047_decode (a->personal, a->personal, strlen (a->personal) + 1); + a = a->next; + } +} diff --git a/rfc2047.h b/rfc2047.h new file mode 100644 index 00000000..07bb1b8b --- /dev/null +++ b/rfc2047.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +void rfc2047_encode_string (char *, size_t, const unsigned char *); +void rfc2047_encode_adrlist (ADDRESS *); + +void rfc2047_decode (char *, const char *, size_t); +void rfc2047_decode_adrlist (ADDRESS *); diff --git a/rfc822.c b/rfc822.c new file mode 100644 index 00000000..487bea10 --- /dev/null +++ b/rfc822.c @@ -0,0 +1,762 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <string.h> +#include <ctype.h> +#include <stdlib.h> + +#ifndef TESTING +#include "mutt.h" +#else +#define safe_strdup strdup +#define safe_malloc malloc +#define SKIPWS(x) while(isspace(*x))x++ +#define FREE(x) safe_free(x) +#define ISSPACE isspace +#define strfcpy(a,b,c) {if (c) {strncpy(a,b,c);a[c-1]=0;}} +#include "rfc822.h" +#endif + +const char RFC822Specials[] = "@.,:;<>[]\\\"()"; +#define is_special(x) strchr(RFC822Specials,x) + +int RFC822Error = 0; + +/* these must defined in the same order as the numerated errors given in rfc822.h */ +const char *RFC822Errors[] = { + "out of memory", + "mismatched parenthesis", + "mismatched quotes", + "bad route in <>", + "bad address in <>", + "bad address spec" +}; + +void rfc822_dequote_comment (char *s) +{ + char *w = s; + + for (; *s; s++) + { + if (*s == '\\') + { + if (!*++s) + break; /* error? */ + *w++ = *s; + } + else if (*s != '\"') + { + if (w != s) + *w = *s; + w++; + } + } + *w = 0; +} + +void rfc822_free_address (ADDRESS **p) +{ + ADDRESS *t; + + while (*p) + { + t = *p; + *p = (*p)->next; +#ifdef EXACT_ADDRESS + FREE (&t->val); +#endif + FREE (&t->personal); + FREE (&t->mailbox); + FREE (&t); + } +} + +static const char * +parse_comment (const char *s, + char *comment, size_t *commentlen, size_t commentmax) +{ + int level = 1; + + while (*s && level) + { + if (*s == '(') + level++; + else if (*s == ')') + { + if (--level == 0) + { + s++; + break; + } + } + else if (*s == '\\') + { + if (!*++s) + break; + } + if (*commentlen < commentmax) + comment[(*commentlen)++] = *s; + s++; + } + if (level) + { + RFC822Error = ERR_MISMATCH_PAREN; + return NULL; + } + return s; +} + +static const char * +parse_quote (const char *s, char *token, size_t *tokenlen, size_t tokenmax) +{ + if (*tokenlen < tokenmax) + token[(*tokenlen)++] = '"'; + while (*s) + { + if (*tokenlen < tokenmax) + token[(*tokenlen)++] = *s; + if (*s == '"') + return (s + 1); + if (*s == '\\') + { + if (!*++s) + break; + } + s++; + } + RFC822Error = ERR_MISMATCH_QUOTE; + return NULL; +} + +static const char * +next_token (const char *s, char *token, size_t *tokenlen, size_t tokenmax) +{ + if (*s == '(') + return (parse_comment (s + 1, token, tokenlen, tokenmax)); + if (*s == '"') + return (parse_quote (s + 1, token, tokenlen, tokenmax)); + if (is_special (*s)) + { + if (*tokenlen < tokenmax) + token[(*tokenlen)++] = *s; + return (s + 1); + } + while (*s) + { + if (ISSPACE (*s) || is_special (*s)) + break; + if (*tokenlen < tokenmax) + token[(*tokenlen)++] = *s; + s++; + } + return s; +} + +static const char * +parse_mailboxdomain (const char *s, const char *nonspecial, + char *mailbox, size_t *mailboxlen, size_t mailboxmax, + char *comment, size_t *commentlen, size_t commentmax) +{ + const char *ps; + + while (*s) + { + SKIPWS (s); + if (strchr (nonspecial, *s) == NULL && is_special (*s)) + return s; + + if (*s == '(') + { + if (*commentlen && *commentlen < commentmax) + comment[(*commentlen)++] = ' '; + ps = next_token (s, comment, commentlen, commentmax); + } + else + ps = next_token (s, mailbox, mailboxlen, mailboxmax); + if (!ps) + return NULL; + s = ps; + } + + return s; +} + +static const char * +parse_address (const char *s, + char *comment, size_t *commentlen, size_t commentmax, + ADDRESS *addr) +{ + char token[128]; + size_t tokenlen = 0; + + s = parse_mailboxdomain (s, ".\"(\\", + token, &tokenlen, sizeof (token) - 1, + comment, commentlen, commentmax); + if (!s) + return NULL; + + if (*s == '@') + { + if (tokenlen < sizeof (token) - 1) + token[tokenlen++] = '@'; + s = parse_mailboxdomain (s + 1, ".([]\\", + token, &tokenlen, sizeof (token) - 1, + comment, commentlen, commentmax); + if (!s) + return NULL; + } + + token[tokenlen] = 0; + addr->mailbox = safe_strdup (token); + + if (*commentlen && !addr->personal) + { + comment[*commentlen] = 0; + addr->personal = safe_strdup (comment); + } + + return s; +} + +static const char * +parse_route_addr (const char *s, + char *comment, size_t *commentlen, size_t commentmax, + ADDRESS *addr) +{ + SKIPWS (s); + + /* find the end of the route */ + if (*s == '@') + { + char token[128]; + size_t tokenlen = 0; + + while (s && *s == '@') + s = parse_mailboxdomain (s + 1, ".[](\\", token, + &tokenlen, sizeof (token) - 1, + comment, commentlen, commentmax); + if (!s || *s != ':') + { + RFC822Error = ERR_BAD_ROUTE; + return NULL; /* invalid route */ + } + s++; + } + + if ((s = parse_address (s, comment, commentlen, commentmax, addr)) == NULL) + return NULL; + + if (*s != '>' || !addr->mailbox) + { + RFC822Error = ERR_BAD_ROUTE_ADDR; + return NULL; + } + + s++; + return s; +} + +static const char * +parse_addr_spec (const char *s, + char *comment, size_t *commentlen, size_t commentmax, + ADDRESS *addr) +{ + s = parse_address (s, comment, commentlen, commentmax, addr); + if (s && *s && *s != ',' && *s != ';') + { + RFC822Error = ERR_BAD_ADDR_SPEC; + return NULL; + } + return s; +} + +static void +add_addrspec (ADDRESS **top, ADDRESS **last, const char *phrase, + char *comment, size_t *commentlen, size_t commentmax) +{ + ADDRESS *cur = rfc822_new_address (); + + if (parse_addr_spec (phrase, comment, commentlen, commentmax, cur) == NULL) + return; + + if (*last) + (*last)->next = cur; + else + *top = cur; + *last = cur; +} + +ADDRESS *rfc822_parse_adrlist (ADDRESS *top, const char *s) +{ + const char *begin, *ps; + char comment[128], phrase[128]; + size_t phraselen = 0, commentlen = 0; + ADDRESS *cur, *last = NULL; + + RFC822Error = 0; + + last = top; + while (last && last->next) + last = last->next; + + SKIPWS (s); + begin = s; + while (*s) + { + if (*s == ',') + { + if (phraselen) + { + phrase[phraselen] = 0; + add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1); + } + else if (commentlen && last && !last->personal) + { + comment[commentlen] = 0; + last->personal = safe_strdup (comment); + } + +#ifdef EXACT_ADDRESS + if (last) + last->val = mutt_substrdup (begin, s); +#endif + commentlen = 0; + phraselen = 0; + s++; + begin = s; + SKIPWS (begin); + } + else if (*s == '(') + { + if (commentlen && commentlen < sizeof (comment) - 1) + comment[commentlen++] = ' '; + if ((ps = next_token (s, comment, &commentlen, sizeof (comment) - 1)) == NULL) + { + rfc822_free_address (&top); + return NULL; + } + s = ps; + } + else if (*s == ':') + { + cur = rfc822_new_address (); + phrase[phraselen] = 0; + cur->mailbox = safe_strdup (phrase); + cur->group = 1; + + if (last) + last->next = cur; + else + top = cur; + last = cur; + +#ifdef EXACT_ADDRESS + last->val = mutt_substrdup (begin, s); +#endif + + phraselen = 0; + commentlen = 0; + s++; + begin = s; + SKIPWS (begin); + } + else if (*s == ';') + { + if (phraselen) + { + phrase[phraselen] = 0; + add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1); + } + else if (commentlen && !last->personal) + { + comment[commentlen] = 0; + last->personal = safe_strdup (comment); + } +#ifdef EXACT_ADDRESS + if (last) + last->val = mutt_substrdup (begin, s); +#endif + + /* add group terminator */ + cur = rfc822_new_address (); + if (last) + { + last->next = cur; + last = cur; + } + + phraselen = 0; + commentlen = 0; + s++; + begin = s; + SKIPWS (begin); + } + else if (*s == '<') + { + phrase[phraselen] = 0; + cur = rfc822_new_address (); + if (phraselen) + { + if (cur->personal) + free (cur->personal); + /* if we get something like "Michael R. Elkins" remove the quotes */ + rfc822_dequote_comment (phrase); + cur->personal = safe_strdup (phrase); + } + if ((ps = parse_route_addr (s + 1, comment, &commentlen, sizeof (comment) - 1, cur)) == NULL) + { + rfc822_free_address (&top); + rfc822_free_address (&cur); + return NULL; + } + + if (last) + last->next = cur; + else + top = cur; + last = cur; + + phraselen = 0; + commentlen = 0; + s = ps; + } + else + { + if (phraselen && phraselen < sizeof (phrase) - 1) + phrase[phraselen++] = ' '; + if ((ps = next_token (s, phrase, &phraselen, sizeof (phrase) - 1)) == NULL) + { + rfc822_free_address (&top); + return NULL; + } + s = ps; + } + SKIPWS (s); + } + + if (phraselen) + { + phrase[phraselen] = 0; + comment[commentlen] = 0; + add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1); + } + else if (commentlen && last && !last->personal) + { + comment[commentlen] = 0; + last->personal = safe_strdup (comment); + } +#ifdef EXACT_ADDRESS + if (last) + last->val = mutt_substrdup (begin, s); +#endif + + return top; +} + +void rfc822_qualify (ADDRESS *addr, const char *host) +{ + char *p; + + for (; addr; addr = addr->next) + if (!addr->group && addr->mailbox && strchr (addr->mailbox, '@') == NULL) + { + if (!(p = malloc (strlen (addr->mailbox) + strlen (host) + 2))) + return; + sprintf (p, "%s@%s", addr->mailbox, host); + free (addr->mailbox); + addr->mailbox = p; + } +} + +void +rfc822_cat (char *buf, size_t buflen, const char *value, const char *specials) +{ + if (strpbrk (value, specials)) + { + char tmp[256], *pc = tmp; + size_t tmplen = sizeof (tmp) - 3; + + *pc++ = '"'; + for (; *value && tmplen; value++) + { + if (*value == '\\' || *value == '"') + { + *pc++ = '\\'; + tmplen--; + } + *pc++ = *value; + tmplen--; + } + *pc++ = '"'; + *pc = 0; + strfcpy (buf, tmp, buflen); + } + else + strfcpy (buf, value, buflen); +} + +void rfc822_write_address_single (char *buf, size_t buflen, ADDRESS *addr) +{ + size_t len; + char *pbuf = buf; + char *pc; + + if (!addr) + return; + + buflen--; /* save room for the terminal nul */ + +#ifdef EXACT_ADDRESS + if (addr->val) + { + if (!buflen) + goto done; + strfcpy (pbuf, addr->val, buflen); + len = strlen (pbuf); + pbuf += len; + buflen -= len; + if (addr->group) + { + if (!buflen) + goto done; + *pbuf++ = ':'; + buflen--; + *pbuf = 0; + } + return; + } +#endif + + if (addr->personal) + { + if (strpbrk (addr->personal, RFC822Specials)) + { + if (!buflen) + goto done; + *pbuf++ = '"'; + buflen--; + for (pc = addr->personal; *pc && buflen > 0; pc++) + { + if (*pc == '"' || *pc == '\\') + { + if (!buflen) + goto done; + *pbuf++ = '\\'; + buflen--; + } + if (!buflen) + goto done; + *pbuf++ = *pc; + buflen--; + } + if (!buflen) + goto done; + *pbuf++ = '"'; + buflen--; + } + else + { + if (!buflen) + goto done; + strfcpy (pbuf, addr->personal, buflen); + len = strlen (pbuf); + pbuf += len; + buflen -= len; + } + + if (!buflen) + goto done; + *pbuf++ = ' '; + buflen--; + + if (!buflen) + goto done; + *pbuf++ = '<'; + buflen--; + } + + if (addr->mailbox) + { + if (!buflen) + goto done; + strfcpy (pbuf, addr->mailbox, buflen); + len = strlen (pbuf); + pbuf += len; + buflen -= len; + + if (addr->personal) + { + if (!buflen) + goto done; + *pbuf++ = '>'; + buflen--; + } + + if (addr->group) + { + if (!buflen) + goto done; + *pbuf++ = ':'; + buflen--; + if (!buflen) + goto done; + *pbuf++ = ' '; + buflen--; + } + } + else + { + if (!buflen) + goto done; + *pbuf++ = ';'; + buflen--; + } +done: + /* no need to check for length here since we already save space at the + beginning of this routine */ + *pbuf = 0; +} + +/* note: it is assumed that `buf' is nul terminated! */ +void rfc822_write_address (char *buf, size_t buflen, ADDRESS *addr) +{ + char *pbuf = buf; + size_t len = strlen (buf); + + buflen--; /* save room for the terminal nul */ + + if (len > 0) + { + if (len > buflen) + return; /* safety check for bogus arguments */ + + pbuf += len; + buflen -= len; + if (!buflen) + goto done; + *pbuf++ = ','; + buflen--; + if (!buflen) + goto done; + *pbuf++ = ' '; + buflen--; + } + + for (; addr && buflen > 0; addr = addr->next) + { + /* use buflen+1 here because we already saved space for the trailing + nul char, and the subroutine can make use of it */ + rfc822_write_address_single (pbuf, buflen + 1, addr); + + /* this should be safe since we always have at least 1 char passed into + the above call, which means `pbuf' should always be nul terminated */ + len = strlen (pbuf); + pbuf += len; + buflen -= len; + if (addr->next && !addr->group) + { + if (!buflen) + goto done; + *pbuf++ = ','; + buflen--; + if (!buflen) + goto done; + *pbuf++ = ' '; + buflen--; + } + } +done: + *pbuf = 0; +} + +/* this should be rfc822_cpy_adr */ +ADDRESS *rfc822_cpy_adr_real (ADDRESS *addr) +{ + ADDRESS *p = rfc822_new_address (); + +#ifdef EXACT_ADDRESS + p->val = safe_strdup (addr->val); +#endif + p->personal = safe_strdup (addr->personal); + p->mailbox = safe_strdup (addr->mailbox); + p->group = addr->group; + return p; +} + +/* this should be rfc822_cpy_adrlist */ +ADDRESS *rfc822_cpy_adr (ADDRESS *addr) +{ + ADDRESS *top = NULL, *last = NULL; + + for (; addr; addr = addr->next) + { + if (last) + { + last->next = rfc822_cpy_adr_real (addr); + last = last->next; + } + else + top = last = rfc822_cpy_adr_real (addr); + } + return top; +} + +/* append list 'b' to list 'a' and return the last element in the new list */ +ADDRESS *rfc822_append (ADDRESS **a, ADDRESS *b) +{ + ADDRESS *tmp = *a; + + while (tmp && tmp->next) + tmp = tmp->next; + if (!b) + return tmp; + if (tmp) + tmp->next = rfc822_cpy_adr (b); + else + tmp = *a = rfc822_cpy_adr (b); + while (tmp && tmp->next) + tmp = tmp->next; + return tmp; +} + +#ifdef TESTING +int safe_free (void **p) +{ + free(*p); + *p = 0; +} + +int main (int argc, char **argv) +{ + ADDRESS *list; + char buf[256]; + char *str = "aaaaaaaaaaaaaa <bbbbbbbbbbbbbbbb>, ccccccc <dddddddddddddddd>,\n\ + eeeeeeeeee <ffffffffffffffff>, ggggggggggg <hhhhhhhhhhhhhhhhhhh>,\n\ + iiiiiiiiiiiiiiii <jjjjjjjjjjjjjjjjjjjj>,\n\ + kkkkkkkkkkkkkk <lllllllllllllllll>,\n\ + mmmmmmmmmmmmm <nnnnnnnnnnnnnnnnnn>, ooooooooooo <pppppppppppppp>,\n\ + qqqqqqqqqqqqq <rrrrrrrrrrrrrrrr>\n"; + + list = rfc822_parse_adrlist (NULL, str); + buf[0] = 0; + rfc822_write_address (buf, sizeof (buf), list); + rfc822_free_address (&list); + puts (buf); + exit (0); +} +#endif diff --git a/rfc822.h b/rfc822.h new file mode 100644 index 00000000..f9ad5d0e --- /dev/null +++ b/rfc822.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef rfc822_h +#define rfc822_h + +#include "config.h" + +/* possible values for RFC822Error */ +enum +{ + ERR_MEMORY = 1, + ERR_MISMATCH_PAREN, + ERR_MISMATCH_QUOTE, + ERR_BAD_ROUTE, + ERR_BAD_ROUTE_ADDR, + ERR_BAD_ADDR_SPEC +}; + +typedef struct address_t +{ +#ifdef EXACT_ADDRESS + char *val; /* value of address as parsed */ +#endif + char *personal; /* real name of address */ + char *mailbox; /* mailbox and host address */ + int group; /* group mailbox? */ + struct address_t *next; +} +ADDRESS; + +void rfc822_free_address (ADDRESS **); +void rfc822_qualify (ADDRESS *, const char *); +ADDRESS *rfc822_parse_adrlist (ADDRESS *, const char *s); +ADDRESS *rfc822_cpy_adr (ADDRESS *addr); +ADDRESS *rfc822_cpy_adr_real (ADDRESS *addr); +ADDRESS *rfc822_append (ADDRESS **a, ADDRESS *b); +void rfc822_write_address (char *, size_t, ADDRESS *); +void rfc822_write_list (char *, size_t, ADDRESS *); +void rfc822_free_address (ADDRESS **addr); +void rfc822_cat (char *, size_t, const char *, const char *); + +extern int RFC822Error; +extern const char *RFC822Errors[]; + +#define rfc822_error(x) RFC822Errors[x] +#define rfc822_new_address() calloc(1,sizeof(ADDRESS)) + +#endif /* rfc822_h */ diff --git a/rx/COPYING.LIB b/rx/COPYING.LIB new file mode 100644 index 00000000..eb685a5e --- /dev/null +++ b/rx/COPYING.LIB @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), 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 library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + 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. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library 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 Library +specifies a version number of this 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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. 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. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/rx/Makefile b/rx/Makefile new file mode 100644 index 00000000..160bc249 --- /dev/null +++ b/rx/Makefile @@ -0,0 +1,19 @@ +# Generated automatically from Makefile.in by configure. +# +# A simplified Makefile for use with Mutt. +# +# Michael Elkins <me@cs.hmc.edu> +# 1/23/97 +# +OBJS= hashrexp.o rx.o rxanal.o rxbasic.o rxbitset.o rxcset.o \ + rxgnucomp.o rxhash.o rxnfa.o rxnode.o rxposix.o rxsimp.o \ + rxspencer.o rxstr.o rxsuper.o rxunfa.o + + + +librx.a: $(OBJS) + ar rc librx.a $(OBJS) + -ranlib librx.a + +clean: + rm -f *.o librx.a *~ diff --git a/rx/Makefile.in b/rx/Makefile.in new file mode 100644 index 00000000..b52d64c7 --- /dev/null +++ b/rx/Makefile.in @@ -0,0 +1,19 @@ +# +# A simplified Makefile for use with Mutt. +# +# Michael Elkins <me@cs.hmc.edu> +# 1/23/97 +# +OBJS= hashrexp.o rx.o rxanal.o rxbasic.o rxbitset.o rxcset.o \ + rxgnucomp.o rxhash.o rxnfa.o rxnode.o rxposix.o rxsimp.o \ + rxspencer.o rxstr.o rxsuper.o rxunfa.o + +VPATH=@srcdir@ +@SET_MAKE@ + +librx.a: $(OBJS) + ar rc librx.a $(OBJS) + -ranlib librx.a + +clean: + rm -f *.o librx.a *~ diff --git a/rx/_rx.h b/rx/_rx.h new file mode 100644 index 00000000..2c982a25 --- /dev/null +++ b/rx/_rx.h @@ -0,0 +1,176 @@ +/* classes: h_files */ + +#ifndef _RXH +#define _RXH + +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include <sys/types.h> +#include "rxhash.h" +#include "rxcset.h" + + + +struct rx_cache; +struct rx_superset; +struct rx; +struct rx_se_list; + + + +/* Suppose that from some NFA state and next character, more than one + * path through side-effect edges is possible. In what order should + * the paths be tried? A function of type rx_se_list_order answers + * that question. It compares two lists of side effects, and says + * which list comes first. + */ + +#ifdef __STDC__ +typedef int (*rx_se_list_order) (struct rx *, + struct rx_se_list *, + struct rx_se_list *); +#else +typedef int (*rx_se_list_order) (); +#endif + + + +/* Struct RX holds an NFA and cache state for the corresponding super NFA. + */ +struct rx +{ + /* The compiler assigns a unique id to every pattern. + * Like sequence numbers in X, there is a subtle bug here + * if you use Rx in a system that runs for a long time. + * But, because of the way the caches work out, it is almost + * impossible to trigger the Rx version of this bug. + * + * The id is used to validate superstates found in a cache + * of superstates. It isn't sufficient to let a superstate + * point back to the rx for which it was compiled -- the caller + * may be re-using a `struct rx' in which case the superstate + * is not really valid. So instead, superstates are validated + * by checking the sequence number of the pattern for which + * they were built. + */ + int rx_id; + + /* This is memory mgt. state for superstates. This may be + * shared by more than one struct rx. + */ + struct rx_cache * cache; + + /* Every nfa defines the size of its own character set. + * Each superstate has an array of this size, with each element + * a `struct rx_inx'. So, don't make this number too large. + * In particular, don't make it 2^16. + */ + int local_cset_size; + + /* Lists of side effects as stored in the NFA are `hash consed'..meaning + * that lists with the same elements are ==. During compilation, + * this table facilitates hash-consing. + */ + struct rx_hash se_list_memo; + + /* Lists of NFA states are also hashed. + */ + struct rx_hash set_list_memo; + + + + /* The compiler and matcher must build a number of instruction frames. + * The format of these frames is fixed (c.f. struct rx_inx). The values + * of the instruction opcodes is not fixed. + * + * An enumerated type (enum rx_opcode) defines the set of instructions + * that the compiler or matcher might generate. When filling an instruction + * frame, the INX field is found by indexing this instruction table + * with an opcode: + */ + void ** instruction_table; + + /* The list of all states in an NFA. + * The NEXT field of NFA states links this list. + */ + struct rx_nfa_state *nfa_states; + struct rx_nfa_state *start_nfa_states; + struct rx_superset * start_set; + + /* This orders the search through super-nfa paths. + * See the comment near the typedef of rx_se_list_order. + */ + rx_se_list_order se_list_cmp; + + int next_nfa_id; +}; + + + +/* If `regs_allocated' is REGS_UNALLOCATED in the pattern buffer, + * `re_match_2' returns information about at least this many registers + * the first time a `regs' structure is passed. + * + * Also, this is the greatest number of backreferenced subexpressions + * allowed in a pattern being matched without caller-supplied registers. + */ +#ifndef RE_NREGS +#define RE_NREGS 30 +#endif + + +#ifndef emacs +#define CHARBITS 8 +#define CHAR_SET_SIZE (1 << CHARBITS) +#define Sword 1 +#define SYNTAX(c) re_syntax_table[c] +extern char re_syntax_table[CHAR_SET_SIZE]; +#endif + + +/* Should we use malloc or alloca? If REGEX_MALLOC is not defined, we + * use `alloca' instead of `malloc' for the backtracking stack. + * + * Emacs will die miserably if we don't do this. + */ + +#ifdef REGEX_MALLOC +#define REGEX_ALLOCATE malloc +#else /* not REGEX_MALLOC */ +#define REGEX_ALLOCATE alloca +#endif /* not REGEX_MALLOC */ + +#undef MAX +#undef MIN +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +extern void * rx_id_instruction_table[]; +extern struct rx_cache * rx_default_cache; + + +#ifdef __STDC__ + +#else /* STDC */ + +#endif /* STDC */ + + +#endif /* _RXH */ diff --git a/rx/hashrexp.c b/rx/hashrexp.c new file mode 100644 index 00000000..33c22ba4 --- /dev/null +++ b/rx/hashrexp.c @@ -0,0 +1,50 @@ +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "rxall.h" +#include "rxnode.h" +#include "rxhash.h" + +#ifdef __STDC__ +static int +rexp_node_equal (void * va, void * vb) +#else +static int +rexp_node_equal (va, vb) + void * va; + void * vb; +#endif +{ + struct rexp_node * a; + struct rexp_node * b; + + a = (struct rexp_node *)va; + b = (struct rexp_node *)vb; + + return ( (va == vb) + || ( (a->type == b->type) + && (a->params.intval == b->params.intval) + && (a->params.intval2 == b->params.intval2) + && rx_bitset_is_equal (a->params.cset_size, a->params.cset, b->params.cset) + && rexp_node_equal (a->params.pair.left, b->params.pair.left) + && rexp_node_equal (a->params.pair.right, b->params.pair.right))); +} + + diff --git a/rx/inst-rxposix.h b/rx/inst-rxposix.h new file mode 100644 index 00000000..b6c6746d --- /dev/null +++ b/rx/inst-rxposix.h @@ -0,0 +1,155 @@ +/* classes: h_files */ + +#ifndef INST_RXPOSIXH +#define INST_RXPOSIXH +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +struct rx_posix_regex +{ + struct rexp_node * pattern; + struct rexp_node ** subexps; + size_t re_nsub; + unsigned char * translate; + unsigned int newline_anchor:1;/* If true, an anchor at a newline matches.*/ + unsigned int no_sub:1; /* If set, don't return register offsets. */ + unsigned int is_anchored:1; + unsigned int is_nullable:1; + unsigned char fastmap[256]; + void * owner_data; +}; + +typedef struct rx_posix_regex regex_t; + +/* Type for byte offsets within the string. POSIX mandates this. */ +typedef int regoff_t; + +typedef struct rx_registers +{ + regoff_t rm_so; /* Byte offset from string's start to substring's start. */ + regoff_t rm_eo; /* Byte offset from string's start to substring's end. */ + regoff_t final_tag; /* data from the cut operator (only pmatch[0]) */ +} regmatch_t; + + + +/* If any error codes are removed, changed, or added, update the + * `rx_error_msg' table. + */ +#define REG_NOERROR 0 /* Success. */ +#define REG_NOMATCH 1 /* Didn't find a match (for regexec). */ + +/* POSIX regcomp return error codes. + * (In the order listed in the standard.) + */ +#define REG_BADPAT 2 /* Invalid pattern. */ +#define REG_ECOLLATE 3 /* Not implemented. */ +#define REG_ECTYPE 4 /* Invalid character class name. */ +#define REG_EESCAPE 5 /* Trailing backslash. */ +#define REG_ESUBREG 6 /* Invalid back reference. */ +#define REG_EBRACK 7 /* Unmatched left bracket. */ +#define REG_EPAREN 8 /* Parenthesis imbalance. */ +#define REG_EBRACE 9 /* Unmatched \{. */ +#define REG_BADBR 10 /* Invalid contents of \{\}. */ +#define REG_ERANGE 11 /* Invalid range end. */ +#define REG_ESPACE 12 /* Ran out of memory. */ +#define REG_BADRPT 13 /* No preceding re for repetition op. */ + +/* Error codes we've added. + */ +#define REG_EEND 14 /* Premature end. */ +#define REG_ESIZE 15 /* Compiled pattern bigger than 2^16 bytes. */ +#define REG_ERPAREN 16 /* Unmatched ) or \); not returned from regcomp. */ + + +/* + * POSIX `cflags' bits (i.e., information for `regcomp'). + */ + +/* If this bit is set, then use extended regular expression syntax. + * If not set, then use basic regular expression syntax. + */ +#define REG_EXTENDED 1 + +/* If this bit is set, then ignore case when matching. + * If not set, then case is significant. + */ +#define REG_ICASE (REG_EXTENDED << 1) + +/* If this bit is set, then anchors do not match at newline + * characters in the string. + * If not set, then anchors do match at newlines. + */ +#define REG_NEWLINE (REG_ICASE << 1) + +/* If this bit is set, then report only success or fail in regexec. + * If not set, then returns differ between not matching and errors. + */ +#define REG_NOSUB (REG_NEWLINE << 1) + + +/* + * POSIX `eflags' bits (i.e., information for regexec). + */ + +/* If this bit is set, then the beginning-of-line operator doesn't match + * the beginning of the string (presumably because it's not the + * beginning of a line). + * If not set, then the beginning-of-line operator does match the + * beginning of the string. + */ +#define REG_NOTBOL 1 + +/* Like REG_NOTBOL, except for the end-of-line. + */ +#define REG_NOTEOL (REG_NOTBOL << 1) + +/* For regnexec only. Allocate register storage and return that. */ +#define REG_ALLOC_REGS (REG_NOTEOL << 1) + + +#ifdef __STDC__ +extern int regncomp (regex_t * preg, + const char * pattern, int len, + int cflags); +extern int regcomp (regex_t * preg, const char * pattern, int cflags); +extern size_t regerror (int errcode, + const regex_t *preg, + char *errbuf, size_t errbuf_size); +extern int regnexec (const regex_t *preg, + const char *string, int len, + size_t nmatch, regmatch_t **pmatch, + int eflags); +extern int regexec (const regex_t *preg, + const char *string, + size_t nmatch, regmatch_t pmatch[], + int eflags); +extern void regfree (regex_t *preg); + +#else /* STDC */ +extern int regncomp (); +extern int regcomp (); +extern size_t regerror (); +extern int regnexec (); +extern int regexec (); +extern void regfree (); + +#endif /* STDC */ +#endif /* INST_RXPOSIXH */ diff --git a/rx/rx.c b/rx/rx.c new file mode 100644 index 00000000..afa84850 --- /dev/null +++ b/rx/rx.c @@ -0,0 +1,85 @@ +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "rx.h" +#include "rxall.h" +#include "rxhash.h" +#include "rxnfa.h" +#include "rxsuper.h" + + + +const char rx_version_string[] = "GNU Rx version 1.5"; + + +#ifdef __STDC__ +struct rx * +rx_make_rx (int cset_size) +#else +struct rx * +rx_make_rx (cset_size) + int cset_size; +#endif +{ + static int rx_id = 0; + struct rx * new_rx; + new_rx = (struct rx *)malloc (sizeof (*new_rx)); + rx_bzero ((char *)new_rx, sizeof (*new_rx)); + new_rx->rx_id = rx_id++; + new_rx->cache = rx_default_cache; + new_rx->local_cset_size = cset_size; + new_rx->instruction_table = rx_id_instruction_table; + new_rx->next_nfa_id = 0; + return new_rx; +} + +#ifdef __STDC__ +void +rx_free_rx (struct rx * rx) +#else +void +rx_free_rx (rx) + struct rx * rx; +#endif +{ + if (rx->start_set) + rx->start_set->starts_for = 0; + rx_free_nfa (rx); + free (rx); +} + + +#ifdef __STDC__ +void +rx_bzero (char * mem, int size) +#else +void +rx_bzero (mem, size) + char * mem; + int size; +#endif +{ + while (size) + { + *mem = 0; + ++mem; + --size; + } +} diff --git a/rx/rx.h b/rx/rx.h new file mode 100644 index 00000000..ccca945a --- /dev/null +++ b/rx/rx.h @@ -0,0 +1,49 @@ +/* classes: h_files */ + +#ifndef RXH +#define RXH +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "rxhash.h" + + + + +extern const char rx_version_string[]; + + +#ifdef __STDC__ +extern struct rx * rx_make_rx (int cset_size); +extern void rx_free_rx (struct rx * rx); +extern void rx_bzero (char * mem, int size); + +#else /* STDC */ +extern struct rx * rx_make_rx (); +extern void rx_free_rx (); +extern void rx_bzero (); + +#endif /* STDC */ + + + + + +#endif /* RXH */ diff --git a/rx/rxall.h b/rx/rxall.h new file mode 100644 index 00000000..adec72db --- /dev/null +++ b/rx/rxall.h @@ -0,0 +1,36 @@ +/* classes: h_files */ + +#ifndef RXALLH +#define RXALLH +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#if 0 +#include <stdlib.h> +#include "malloc.h" +#endif + +#ifdef __STDC__ + +#else /* STDC */ + +#endif /* STDC */ + + +#endif /* RXALLH */ diff --git a/rx/rxanal.c b/rx/rxanal.c new file mode 100644 index 00000000..431be7ab --- /dev/null +++ b/rx/rxanal.c @@ -0,0 +1,732 @@ +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "rxall.h" +#include "rxanal.h" +#include "rxbitset.h" +#include "rxsuper.h" + + + + +#ifdef __STDC__ +int +rx_posix_analyze_rexp (struct rexp_node *** subexps, + size_t * re_nsub, + struct rexp_node * node, + int id) +#else +int +rx_posix_analyze_rexp (subexps, re_nsub, node, id) + struct rexp_node *** subexps; + size_t * re_nsub; + struct rexp_node * node; + int id; +#endif +{ + if (node) + { + size_t this_subexp; + if (node->type == r_parens) + { + if (node->params.intval >= 0) + { + this_subexp = *re_nsub; + ++*re_nsub; + if (!*subexps) + *subexps = (struct rexp_node **)malloc (sizeof (struct rexp_node *) * *re_nsub); + else + *subexps = (struct rexp_node **)realloc (*subexps, + sizeof (struct rexp_node *) * *re_nsub); + } + } + if (node->params.pair.left) + id = rx_posix_analyze_rexp (subexps, re_nsub, node->params.pair.left, id); + if (node->params.pair.right) + id = rx_posix_analyze_rexp (subexps, re_nsub, node->params.pair.right, id); + switch (node->type) + { + case r_cset: + node->len = 1; + node->observed = 0; + break; + case r_string: + node->len = node->params.cstr.len; + node->observed = 0; + break; + case r_cut: + node->len = 0; + node->observed = 0; + break; + case r_concat: + case r_alternate: + { + int lob, rob; + int llen, rlen; + lob = (!node->params.pair.left ? 0 : node->params.pair.left->observed); + rob = (!node->params.pair.right ? 0 : node->params.pair.right->observed); + llen = (!node->params.pair.left ? 0 : node->params.pair.left->len); + rlen = (!node->params.pair.right ? 0 : node->params.pair.right->len); + node->len = ((llen >= 0) && (rlen >= 0) + ? ((node->type == r_concat) + ? llen + rlen + : ((llen == rlen) ? llen : -1)) + : -1); + node->observed = lob || rob; + break; + } + case r_opt: + case r_star: + case r_plus: + node->len = -1; + node->observed = (node->params.pair.left + ? node->params.pair.left->observed + : 0); + break; + + case r_interval: + node->len = -1; + node->observed = 1; + break; + + case r_parens: + if (node->params.intval >= 0) + { + node->observed = 1; + (*subexps)[this_subexp] = node; + } + else + node->observed = (node->params.pair.left + ? node->params.pair.left->observed + : 0); + node->len = (node->params.pair.left + ? node->params.pair.left->len + : 0); + break; + case r_context: + switch (node->params.intval) + { + default: + node->observed = 1; + node->len = -1; + break; + case '^': + case '$': + case '=': + case '<': + case '>': + case 'b': + case 'B': + case '`': + case '\'': + node->observed = 1; + node->len = 0; + break; + } + break; + } + if (node->observed) + node->id = id++; + return id; + } + return id; +} + +/* Returns 0 unless the pattern can match the empty string. */ +#ifdef __STDC__ +int +rx_fill_in_fastmap (int cset_size, unsigned char * map, struct rexp_node * exp) +#else +int +rx_fill_in_fastmap (cset_size, map, exp) + int cset_size; + unsigned char * map; + struct rexp_node * exp; +#endif +{ + if (!exp) + { + can_match_empty: + { + int x; + for (x = 0; x < cset_size; ++x) + map[x] = 1; + } + return 1; + } + + switch (exp->type) + { + case r_cset: + { + int x; + int most; + + most = exp->params.cset_size; + for (x = 0; x < most; ++x) + if (RX_bitset_member (exp->params.cset, x)) + map[x] = 1; + } + return 0; + + case r_string: + if (exp->params.cstr.len) + { + map[exp->params.cstr.contents[0]] = 1; + return 0; + } + else + return 1; + + case r_cut: + return 1; + + + case r_concat: + return rx_fill_in_fastmap (cset_size, map, exp->params.pair.left); + + /* Why not the right branch? If the left branch + * can't be empty it doesn't matter. If it can, then + * the fastmap is already saturated, and again, the + * right branch doesn't matter. + */ + + + case r_alternate: + return ( rx_fill_in_fastmap (cset_size, map, exp->params.pair.left) + | rx_fill_in_fastmap (cset_size, map, exp->params.pair.right)); + + case r_parens: + case r_plus: + return rx_fill_in_fastmap (cset_size, map, exp->params.pair.left); + + case r_opt: + case r_star: + goto can_match_empty; + /* Why not the subtree? These operators already saturate + * the fastmap. + */ + + case r_interval: + if (exp->params.intval == 0) + goto can_match_empty; + else + return rx_fill_in_fastmap (cset_size, map, exp->params.pair.left); + + case r_context: + goto can_match_empty; + } + + /* this should never happen but gcc seems to like it */ + return 0; + +} + + +#ifdef __STDC__ +int +rx_is_anchored_p (struct rexp_node * exp) +#else +int +rx_is_anchored_p (exp) + struct rexp_node * exp; +#endif +{ + if (!exp) + return 0; + + switch (exp->type) + { + case r_opt: + case r_star: + case r_cset: + case r_string: + case r_cut: + return 0; + + case r_parens: + case r_plus: + case r_concat: + return rx_is_anchored_p (exp->params.pair.left); + + case r_alternate: + return ( rx_is_anchored_p (exp->params.pair.left) + && rx_is_anchored_p (exp->params.pair.right)); + + + case r_interval: + if (exp->params.intval == 0) + return 0; + else + return rx_is_anchored_p (exp->params.pair.left); + + case r_context: + return (exp->params.intval == '^'); + } + + /* this should never happen but gcc seems to like it */ + return 0; +} + + + +#ifdef __STDC__ +enum rx_answers +rx_start_superstate (struct rx_classical_system * frame) +#else +enum rx_answers +rx_start_superstate (frame) + struct rx_classical_system * frame; +#endif +{ + struct rx_superset * start_contents; + struct rx_nfa_state_set * start_nfa_set; + + if (frame->rx->start_set) + start_contents = frame->rx->start_set; + else + { + { + struct rx_possible_future * futures; + futures = rx_state_possible_futures (frame->rx, frame->rx->start_nfa_states); + + if (!futures) + return rx_bogus; + + if (futures->next) + return rx_start_state_with_too_many_futures; + + start_nfa_set = futures->destset; + } + + start_contents = + rx_superstate_eclosure_union (frame->rx, + rx_superset_cons (frame->rx, 0, 0), + start_nfa_set); + + if (!start_contents) + return rx_bogus; + + start_contents->starts_for = frame->rx; + frame->rx->start_set = start_contents; + } + + if ( start_contents->superstate + && (start_contents->superstate->rx_id == frame->rx->rx_id)) + { + if (frame->state) + { + rx_unlock_superstate (frame->rx, frame->state); + } + frame->state = start_contents->superstate; + /* The cached superstate may be in a semifree state. + * We need to lock it and preserve the invariant + * that a locked superstate is never semifree. + * So refresh it. + */ + rx_refresh_this_superstate (frame->rx->cache, frame->state); + rx_lock_superstate (frame->rx, frame->state); + return rx_yes; + } + else + { + struct rx_superstate * state; + + rx_protect_superset (frame->rx, start_contents); + state = rx_superstate (frame->rx, start_contents); + rx_release_superset (frame->rx, start_contents); + if (!state) + return rx_bogus; + if (frame->state) + { + rx_unlock_superstate (frame->rx, frame->state); + } + frame->state = state; + rx_lock_superstate (frame->rx, frame->state); + return rx_yes; + } +} + + + + +#ifdef __STDC__ +enum rx_answers +rx_fit_p (struct rx_classical_system * frame, unsigned const char * burst, int len) +#else +enum rx_answers +rx_fit_p (frame, burst, len) + struct rx_classical_system * frame; + unsigned const char * burst; + int len; +#endif +{ + struct rx_inx * inx_table; + struct rx_inx * inx; + + if (!frame->state) + return rx_bogus; + + if (!len) + { + frame->final_tag = frame->state->contents->is_final; + return (frame->state->contents->is_final + ? rx_yes + : rx_no); + } + + inx_table = frame->state->transitions; + rx_unlock_superstate (frame->rx, frame->state); + + while (len--) + { + struct rx_inx * next_table; + + inx = inx_table + *burst; + next_table = (struct rx_inx *)inx->data; + while (!next_table) + { + struct rx_superstate * state; + state = ((struct rx_superstate *) + ((char *)inx_table + - ((unsigned long) + ((struct rx_superstate *)0)->transitions))); + + switch ((int)inx->inx) + { + case rx_backtrack: + /* RX_BACKTRACK means that we've reached the empty + * superstate, indicating that match can't succeed + * from this point. + */ + frame->state = 0; + return rx_no; + + case rx_cache_miss: + /* Because the superstate NFA is lazily constructed, + * and in fact may erode from underneath us, we sometimes + * have to construct the next instruction from the hard way. + * This invokes one step in the lazy-conversion. + */ + inx = + rx_handle_cache_miss + (frame->rx, state, *burst, inx->data_2); + + if (!inx) + { + frame->state = 0; + return rx_bogus; + } + next_table = (struct rx_inx *)inx->data; + continue; + + + /* No other instructions are legal here. + * (In particular, this function does not handle backtracking + * or the related instructions.) + */ + default: + frame->state = 0; + return rx_bogus; + } + } + inx_table = next_table; + ++burst; + } + + if (inx->data_2) /* indicates a final superstate */ + { + frame->final_tag = (int)inx->data_2; + frame->state = ((struct rx_superstate *) + ((char *)inx_table + - ((unsigned long) + ((struct rx_superstate *)0)->transitions))); + rx_lock_superstate (frame->rx, frame->state); + return rx_yes; + } + frame->state = ((struct rx_superstate *) + ((char *)inx_table + - ((unsigned long) + ((struct rx_superstate *)0)->transitions))); + rx_lock_superstate (frame->rx, frame->state); + return rx_no; +} + + + + +#ifdef __STDC__ +enum rx_answers +rx_advance (struct rx_classical_system * frame, unsigned const char * burst, int len) +#else +enum rx_answers +rx_advance (frame, burst, len) + struct rx_classical_system * frame; + unsigned const char * burst; + int len; +#endif +{ + struct rx_inx * inx_table; + + if (!frame->state) + return rx_bogus; + + if (!len) + return rx_yes; + + inx_table = frame->state->transitions; + rx_unlock_superstate (frame->rx, frame->state); + + while (len--) + { + struct rx_inx * inx; + struct rx_inx * next_table; + + inx = inx_table + *burst; + next_table = (struct rx_inx *)inx->data; + while (!next_table) + { + struct rx_superstate * state; + state = ((struct rx_superstate *) + ((char *)inx_table + - ((unsigned long) + ((struct rx_superstate *)0)->transitions))); + + switch ((int)inx->inx) + { + case rx_backtrack: + /* RX_BACKTRACK means that we've reached the empty + * superstate, indicating that match can't succeed + * from this point. + */ + frame->state = 0; + return rx_no; + + case rx_cache_miss: + /* Because the superstate NFA is lazily constructed, + * and in fact may erode from underneath us, we sometimes + * have to construct the next instruction from the hard way. + * This invokes one step in the lazy-conversion. + */ + inx = + rx_handle_cache_miss + (frame->rx, state, *burst, inx->data_2); + + if (!inx) + { + frame->state = 0; + return rx_bogus; + } + next_table = (struct rx_inx *)inx->data; + continue; + + + /* No other instructions are legal here. + * (In particular, this function does not handle backtracking + * or the related instructions.) + */ + default: + frame->state = 0; + return rx_bogus; + } + } + inx_table = next_table; + ++burst; + } + + frame->state = ((struct rx_superstate *) + ((char *)inx_table + - ((unsigned long) + ((struct rx_superstate *)0)->transitions))); + rx_lock_superstate (frame->rx, frame->state); + return rx_yes; +} + + + +#ifdef __STDC__ +int +rx_advance_to_final (struct rx_classical_system * frame, unsigned const char * burst, int len) +#else +int +rx_advance_to_final (frame, burst, len) + struct rx_classical_system * frame; + unsigned const char * burst; + int len; +#endif +{ + int initial_len; + struct rx_inx * inx_table; + struct rx_superstate * this_state; + + if (!frame->state) + return 0; + + if (!len) + { + frame->final_tag = frame->state->contents->is_final; + return 0; + } + + inx_table = frame->state->transitions; + + initial_len = len; + + this_state = frame->state; + + while (len--) + { + struct rx_inx * inx; + struct rx_inx * next_table; + + /* this_state holds the state for the position we're + * leaving. this_state is locked. + */ + inx = inx_table + *burst; + next_table = (struct rx_inx *)inx->data; + + while (!next_table) + { + struct rx_superstate * state; + + state = ((struct rx_superstate *) + ((char *)inx_table + - ((unsigned long) + ((struct rx_superstate *)0)->transitions))); + + switch ((int)inx->inx) + { + case rx_backtrack: + /* RX_BACKTRACK means that we've reached the empty + * superstate, indicating that match can't succeed + * from this point. + * + * Return to the state for the position prior to what + * we failed at, and return that position. + */ + frame->state = this_state; + frame->final_tag = this_state->contents->is_final; + return (initial_len - len) - 1; + + case rx_cache_miss: + /* Because the superstate NFA is lazily constructed, + * and in fact may erode from underneath us, we sometimes + * have to construct the next instruction from the hard way. + * This invokes one step in the lazy-conversion. + */ + inx = rx_handle_cache_miss + (frame->rx, state, *burst, inx->data_2); + + if (!inx) + { + rx_unlock_superstate (frame->rx, this_state); + frame->state = 0; + return -1; + } + next_table = (struct rx_inx *)inx->data; + continue; + + + /* No other instructions are legal here. + * (In particular, this function does not handle backtracking + * or the related instructions.) + */ + default: + rx_unlock_superstate (frame->rx, this_state); + frame->state = 0; + return -1; + } + } + + /* Release the superstate for the preceeding position: */ + rx_unlock_superstate (frame->rx, this_state); + + /* Compute the superstate for the new position: */ + inx_table = next_table; + this_state = ((struct rx_superstate *) + ((char *)inx_table + - ((unsigned long) + ((struct rx_superstate *)0)->transitions))); + + /* Lock it (see top-of-loop invariant): */ + rx_lock_superstate (frame->rx, this_state); + + /* Check to see if we should stop: */ + if (this_state->contents->is_final) + { + frame->final_tag = this_state->contents->is_final; + frame->state = this_state; + return (initial_len - len); + } + + ++burst; + } + + /* Consumed all of the characters. */ + frame->state = this_state; + frame->final_tag = this_state->contents->is_final; + + /* state already locked (see top-of-loop invariant) */ + return initial_len; +} + + + + +#ifdef __STDC__ +void +rx_terminate_system (struct rx_classical_system * frame) +#else +void +rx_terminate_system (frame) + struct rx_classical_system * frame; +#endif +{ + if (frame->state) + { + rx_unlock_superstate (frame->rx, frame->state); + frame->state = 0; + } +} + + + + + + + + + +#ifdef __STDC__ +void +rx_init_system (struct rx_classical_system * frame, struct rx * rx) +#else +void +rx_init_system (frame, rx) + struct rx_classical_system * frame; + struct rx * rx; +#endif +{ + frame->rx = rx; + frame->state = 0; +} + + + diff --git a/rx/rxanal.h b/rx/rxanal.h new file mode 100644 index 00000000..53dd576d --- /dev/null +++ b/rx/rxanal.h @@ -0,0 +1,79 @@ +/* classes: h_files */ + +#ifndef RXANALH +#define RXANALH +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "rxcset.h" +#include "rxnode.h" +#include "rxsuper.h" + + + + +enum rx_answers +{ + rx_yes = 0, + rx_no = 1, + rx_bogus = -1, + rx_start_state_with_too_many_futures = rx_bogus - 1 + /* n < 0 -- error */ +}; + +struct rx_classical_system +{ + struct rx * rx; + struct rx_superstate * state; + int final_tag; +}; + + + +#ifdef __STDC__ +extern int rx_posix_analyze_rexp (struct rexp_node *** subexps, + size_t * re_nsub, + struct rexp_node * node, + int id); +extern int rx_fill_in_fastmap (int cset_size, unsigned char * map, struct rexp_node * exp); +extern int rx_is_anchored_p (struct rexp_node * exp); +extern enum rx_answers rx_start_superstate (struct rx_classical_system * frame); +extern enum rx_answers rx_fit_p (struct rx_classical_system * frame, unsigned const char * burst, int len); +extern enum rx_answers rx_advance (struct rx_classical_system * frame, unsigned const char * burst, int len); +extern int rx_advance_to_final (struct rx_classical_system * frame, unsigned const char * burst, int len); +extern void rx_terminate_system (struct rx_classical_system * frame); +extern void rx_init_system (struct rx_classical_system * frame, struct rx * rx); + +#else /* STDC */ +extern int rx_posix_analyze_rexp (); +extern int rx_fill_in_fastmap (); +extern int rx_is_anchored_p (); +extern enum rx_answers rx_start_superstate (); +extern enum rx_answers rx_fit_p (); +extern enum rx_answers rx_advance (); +extern int rx_advance_to_final (); +extern void rx_terminate_system (); +extern void rx_init_system (); + +#endif /* STDC */ + + + +#endif /* RXANALH */ diff --git a/rx/rxbasic.c b/rx/rxbasic.c new file mode 100644 index 00000000..e6e0e594 --- /dev/null +++ b/rx/rxbasic.c @@ -0,0 +1,118 @@ +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "rxall.h" +#include "rxbasic.h" +#include "rxstr.h" + + + + +int rx_basic_unfaniverse_delay = RX_DEFAULT_NFA_DELAY; +static struct rx_unfaniverse * rx_basic_uv = 0; + + + +static int +init_basic_once () +{ + if (rx_basic_uv) + return 0; + rx_basic_uv = rx_make_unfaniverse (rx_basic_unfaniverse_delay); + return (rx_basic_uv ? 0 : -1); +} + + +#ifdef __STDC__ +struct rx_unfaniverse * +rx_basic_unfaniverse (void) +#else +struct rx_unfaniverse * +rx_basic_unfaniverse () +#endif +{ + if (init_basic_once ()) + return 0; + return rx_basic_uv; +} + + +static char * silly_hack = 0; + +#ifdef __STDC__ +struct rx_solutions * +rx_basic_make_solutions (struct rx_registers * regs, struct rexp_node * expression, struct rexp_node ** subexps, int start, int end, struct rx_context_rules * rules, const unsigned char * str) +#else +struct rx_solutions * +rx_basic_make_solutions (regs, expression, subexps, start, end, rules, str) + struct rx_registers * regs; + struct rexp_node * expression; + struct rexp_node ** subexps; + int start; + int end; + struct rx_context_rules * rules; + const unsigned char * str; +#endif +{ + struct rx_str_closure * closure; + if (init_basic_once ()) + return 0; /* bogus but rare */ + if ( expression + && (expression->len >= 0) + && (expression->len != (end - start))) + return &rx_no_solutions; + if (silly_hack) + { + closure = (struct rx_str_closure *)silly_hack; + silly_hack = 0; + } + else + closure = (struct rx_str_closure *)malloc (sizeof (*closure)); + if (!closure) + return 0; + closure->str = str; + closure->len = end; + closure->rules = *rules; + return rx_make_solutions (regs, rx_basic_uv, expression, subexps, 256, + start, end, rx_str_vmfn, rx_str_contextfn, + (void *)closure); +} + + + +#ifdef __STDC__ +void +rx_basic_free_solutions (struct rx_solutions * solns) +#else + void + rx_basic_free_solutions (solns) + struct rx_solutions * solns; +#endif +{ + if (solns == &rx_no_solutions) + return; + + if (!silly_hack) + silly_hack = (char *)solns->closure; + else + free (solns->closure); + solns->closure = 0; + rx_free_solutions (solns); +} diff --git a/rx/rxbasic.h b/rx/rxbasic.h new file mode 100644 index 00000000..876381ed --- /dev/null +++ b/rx/rxbasic.h @@ -0,0 +1,60 @@ +/* classes: h_files */ + +#ifndef RXBASICH +#define RXBASICH +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "rxcontext.h" +#include "rxnode.h" +#include "rxspencer.h" +#include "rxunfa.h" + + + +#ifndef RX_DEFAULT_NFA_DELAY +/* This value bounds the number of NFAs kept in a cache of recent NFAs. + * This value is used whenever the rx_basic_* entry points are used, + * for example, when the Posix entry points are invoked. + */ +#define RX_DEFAULT_NFA_DELAY 64 +#endif + + +#ifdef __STDC__ +extern struct rx_unfaniverse * rx_basic_unfaniverse (void); +extern struct rx_solutions * rx_basic_make_solutions (struct rx_registers * regs, struct rexp_node * expression, struct rexp_node ** subexps, int start, int end, struct rx_context_rules * rules, const unsigned char * str); +extern void rx_basic_free_solutions (struct rx_solutions * solns); + +#else /* STDC */ +extern struct rx_unfaniverse * rx_basic_unfaniverse (); +extern struct rx_solutions * rx_basic_make_solutions (); +extern void rx_basic_free_solutions (); + +#endif /* STDC */ + + + + + + + + +#endif /* RXBASICH */ diff --git a/rx/rxbitset.c b/rx/rxbitset.c new file mode 100644 index 00000000..a34b8b33 --- /dev/null +++ b/rx/rxbitset.c @@ -0,0 +1,364 @@ +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +/* + * Tom Lord (lord@cygnus.com, lord@gnu.ai.mit.edu) + */ + + +#include "rxall.h" +#include "rxbitset.h" + + +#ifdef __STDC__ +int +rx_bitset_is_equal (int size, rx_Bitset a, rx_Bitset b) +#else +int +rx_bitset_is_equal (size, a, b) + int size; + rx_Bitset a; + rx_Bitset b; +#endif +{ + int x; + RX_subset s; + + if (size == 0) + return 1; + + s = b[0]; + b[0] = ~a[0]; + + for (x = rx_bitset_numb_subsets(size) - 1; a[x] == b[x]; --x) + ; + + b[0] = s; + return !x && (s == a[0]); +} + + +#ifdef __STDC__ +int +rx_bitset_is_subset (int size, rx_Bitset a, rx_Bitset b) +#else +int +rx_bitset_is_subset (size, a, b) + int size; + rx_Bitset a; + rx_Bitset b; +#endif +{ + int x; + x = rx_bitset_numb_subsets(size) - 1; + while (x-- && ((a[x] & b[x]) == a[x])); + return x == -1; +} + + +#ifdef __STDC__ +int +rx_bitset_empty (int size, rx_Bitset set) +#else +int +rx_bitset_empty (size, set) + int size; + rx_Bitset set; +#endif +{ + int x; + RX_subset s; + s = set[0]; + set[0] = 1; + for (x = rx_bitset_numb_subsets(size) - 1; !set[x]; --x) + ; + set[0] = s; + return !s; +} + +#ifdef __STDC__ +void +rx_bitset_null (int size, rx_Bitset b) +#else +void +rx_bitset_null (size, b) + int size; + rx_Bitset b; +#endif +{ + rx_bzero ((char *)b, rx_sizeof_bitset(size)); +} + + +#ifdef __STDC__ +void +rx_bitset_universe (int size, rx_Bitset b) +#else +void +rx_bitset_universe (size, b) + int size; + rx_Bitset b; +#endif +{ + int x = rx_bitset_numb_subsets (size); + while (x--) + *b++ = ~(RX_subset)0; +} + + +#ifdef __STDC__ +void +rx_bitset_complement (int size, rx_Bitset b) +#else +void +rx_bitset_complement (size, b) + int size; + rx_Bitset b; +#endif +{ + int x = rx_bitset_numb_subsets (size); + while (x--) + { + *b = ~*b; + ++b; + } +} + + +#ifdef __STDC__ +void +rx_bitset_assign (int size, rx_Bitset a, rx_Bitset b) +#else +void +rx_bitset_assign (size, a, b) + int size; + rx_Bitset a; + rx_Bitset b; +#endif +{ + int x; + for (x = rx_bitset_numb_subsets(size) - 1; x >=0; --x) + a[x] = b[x]; +} + + +#ifdef __STDC__ +void +rx_bitset_union (int size, rx_Bitset a, rx_Bitset b) +#else +void +rx_bitset_union (size, a, b) + int size; + rx_Bitset a; + rx_Bitset b; +#endif +{ + int x; + for (x = rx_bitset_numb_subsets(size) - 1; x >=0; --x) + a[x] |= b[x]; +} + + +#ifdef __STDC__ +void +rx_bitset_intersection (int size, + rx_Bitset a, rx_Bitset b) +#else +void +rx_bitset_intersection (size, a, b) + int size; + rx_Bitset a; + rx_Bitset b; +#endif +{ + int x; + for (x = rx_bitset_numb_subsets(size) - 1; x >=0; --x) + a[x] &= b[x]; +} + + +#ifdef __STDC__ +void +rx_bitset_difference (int size, rx_Bitset a, rx_Bitset b) +#else +void +rx_bitset_difference (size, a, b) + int size; + rx_Bitset a; + rx_Bitset b; +#endif +{ + int x; + for (x = rx_bitset_numb_subsets(size) - 1; x >=0; --x) + a[x] &= ~ b[x]; +} + + +#ifdef __STDC__ +void +rx_bitset_revdifference (int size, + rx_Bitset a, rx_Bitset b) +#else +void +rx_bitset_revdifference (size, a, b) + int size; + rx_Bitset a; + rx_Bitset b; +#endif +{ + int x; + for (x = rx_bitset_numb_subsets(size) - 1; x >=0; --x) + a[x] = ~a[x] & b[x]; +} + +#ifdef __STDC__ +void +rx_bitset_xor (int size, rx_Bitset a, rx_Bitset b) +#else +void +rx_bitset_xor (size, a, b) + int size; + rx_Bitset a; + rx_Bitset b; +#endif +{ + int x; + for (x = rx_bitset_numb_subsets(size) - 1; x >=0; --x) + a[x] ^= b[x]; +} + + +#ifdef __STDC__ +unsigned long +rx_bitset_hash (int size, rx_Bitset b) +#else +unsigned long +rx_bitset_hash (size, b) + int size; + rx_Bitset b; +#endif +{ + int x; + unsigned long answer; + + answer = 0; + + for (x = 0; x < size; ++x) + { + if (RX_bitset_member (b, x)) + answer += (answer << 3) + x; + } + return answer; +} + + +RX_subset rx_subset_singletons [RX_subset_bits] = +{ + 0x1, + 0x2, + 0x4, + 0x8, + 0x10, + 0x20, + 0x40, + 0x80, + 0x100, + 0x200, + 0x400, + 0x800, + 0x1000, + 0x2000, + 0x4000, + 0x8000, + 0x10000, + 0x20000, + 0x40000, + 0x80000, + 0x100000, + 0x200000, + 0x400000, + 0x800000, + 0x1000000, + 0x2000000, + 0x4000000, + 0x8000000, + 0x10000000, + 0x20000000, + 0x40000000, + 0x80000000 +}; + + +/* + * (define l (let loop ((x 0) (l '())) (if (eq? x 256) l (loop (+ x 1) (cons x l))))) + * (define lb (map (lambda (n) (number->string n 2)) l)) + * (define lc (map string->list lb)) + * (define ln (map (lambda (l) (map (lambda (c) (if (eq? c #\1) 1 0)) l)) lc)) + * (define lt (map (lambda (l) (apply + l)) ln)) + */ + +static int char_pops[256] = +{ + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 +}; + +#define RX_char_population(C) (char_pops[C]) + +#ifdef __STDC__ +int +rx_bitset_population (int size, rx_Bitset a) +#else +int +rx_bitset_population (size, a) + int size; + rx_Bitset a; +#endif +{ + int x; + int total; + unsigned char s; + + if (size == 0) + return 0; + + total = 0; + x = sizeof (RX_subset) * rx_bitset_numb_subsets(size) - 1; + while (x >= 0) + { + s = ((unsigned char *)a)[x]; + --x; + total = total + RX_char_population (s); + } + return total; +} diff --git a/rx/rxbitset.h b/rx/rxbitset.h new file mode 100644 index 00000000..9ddb0f08 --- /dev/null +++ b/rx/rxbitset.h @@ -0,0 +1,109 @@ +/* classes: h_files */ + +#ifndef RXBITSETH +#define RXBITSETH + +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + + +typedef unsigned long RX_subset; +#define RX_subset_bits (8 * sizeof (RX_subset)) +#define RX_subset_mask (RX_subset_bits - 1) +typedef RX_subset * rx_Bitset; + +#ifdef __STDC__ +typedef void (*rx_bitset_iterator) (rx_Bitset, int member_index); +#else +typedef void (*rx_bitset_iterator) (); +#endif + +/* Return the index of the word containing the Nth bit. + */ +#define rx_bitset_subset(N) ((N) / RX_subset_bits) + +/* Return the conveniently-sized supset containing the Nth bit. + */ +#define rx_bitset_subset_val(B,N) ((B)[rx_bitset_subset(N)]) + + +/* Genericly combine the word containing the Nth bit with a 1 bit mask + * of the Nth bit position within that word. + */ +#define RX_bitset_access(B,N,OP) \ + ((B)[rx_bitset_subset(N)] OP rx_subset_singletons[(N) & RX_subset_mask]) + +#define RX_bitset_member(B,N) RX_bitset_access(B, N, &) +#define RX_bitset_enjoin(B,N) RX_bitset_access(B, N, |=) +#define RX_bitset_remove(B,N) RX_bitset_access(B, N, &= ~) +#define RX_bitset_toggle(B,N) RX_bitset_access(B, N, ^= ) + +/* How many words are needed for N bits? + */ +#define rx_bitset_numb_subsets(N) (((N) + RX_subset_bits - 1) / RX_subset_bits) + +/* How much memory should be allocated for a bitset with N bits? + */ +#define rx_sizeof_bitset(N) (rx_bitset_numb_subsets(N) * sizeof(RX_subset)) + + +extern RX_subset rx_subset_singletons[]; + + + +#ifdef __STDC__ +extern int rx_bitset_is_equal (int size, rx_Bitset a, rx_Bitset b); +extern int rx_bitset_is_subset (int size, rx_Bitset a, rx_Bitset b); +extern int rx_bitset_empty (int size, rx_Bitset set); +extern void rx_bitset_null (int size, rx_Bitset b); +extern void rx_bitset_universe (int size, rx_Bitset b); +extern void rx_bitset_complement (int size, rx_Bitset b); +extern void rx_bitset_assign (int size, rx_Bitset a, rx_Bitset b); +extern void rx_bitset_union (int size, rx_Bitset a, rx_Bitset b); +extern void rx_bitset_intersection (int size, + rx_Bitset a, rx_Bitset b); +extern void rx_bitset_difference (int size, rx_Bitset a, rx_Bitset b); +extern void rx_bitset_revdifference (int size, + rx_Bitset a, rx_Bitset b); +extern void rx_bitset_xor (int size, rx_Bitset a, rx_Bitset b); +extern unsigned long rx_bitset_hash (int size, rx_Bitset b); +extern int rx_bitset_population (int size, rx_Bitset a); + +#else /* STDC */ +extern int rx_bitset_is_equal (); +extern int rx_bitset_is_subset (); +extern int rx_bitset_empty (); +extern void rx_bitset_null (); +extern void rx_bitset_universe (); +extern void rx_bitset_complement (); +extern void rx_bitset_assign (); +extern void rx_bitset_union (); +extern void rx_bitset_intersection (); +extern void rx_bitset_difference (); +extern void rx_bitset_revdifference (); +extern void rx_bitset_xor (); +extern unsigned long rx_bitset_hash (); +extern int rx_bitset_population (); + +#endif /* STDC */ + + + +#endif /* RXBITSETH */ diff --git a/rx/rxcontext.h b/rx/rxcontext.h new file mode 100644 index 00000000..b72cdfc9 --- /dev/null +++ b/rx/rxcontext.h @@ -0,0 +1,41 @@ +/* classes: h_files */ + +#ifndef RXCONTEXTH +#define RXCONTEXTH +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +struct rx_context_rules +{ + unsigned int newline_anchor:1;/* If true, an anchor at a newline matches.*/ + unsigned int not_bol:1; /* If set, the anchors ('^' and '$') don't */ + unsigned int not_eol:1; /* match at the ends of the string. */ + unsigned int case_indep:1; +}; + + +#ifdef __STDC__ + +#else /* STDC */ + +#endif /* STDC */ + + +#endif /* RXCONTEXTH */ diff --git a/rx/rxcset.c b/rx/rxcset.c new file mode 100644 index 00000000..e8f21df0 --- /dev/null +++ b/rx/rxcset.c @@ -0,0 +1,79 @@ +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +/* + * Tom Lord (lord@cygnus.com, lord@gnu.ai.mit.edu) + */ + + + +#include "rxall.h" +#include "rxcset.h" +/* Utilities for manipulating bitset represntations of characters sets. */ + +#ifdef __STDC__ +rx_Bitset +rx_cset (int size) +#else +rx_Bitset +rx_cset (size) + int size; +#endif +{ + rx_Bitset b; + b = (rx_Bitset) malloc (rx_sizeof_bitset (size)); + if (b) + rx_bitset_null (size, b); + return b; +} + + +#ifdef __STDC__ +rx_Bitset +rx_copy_cset (int size, rx_Bitset a) +#else +rx_Bitset +rx_copy_cset (size, a) + int size; + rx_Bitset a; +#endif +{ + rx_Bitset cs; + cs = rx_cset (size); + + if (cs) + rx_bitset_union (size, cs, a); + + return cs; +} + + +#ifdef __STDC__ +void +rx_free_cset (rx_Bitset c) +#else +void +rx_free_cset (c) + rx_Bitset c; +#endif +{ + if (c) + free ((char *)c); +} + diff --git a/rx/rxcset.h b/rx/rxcset.h new file mode 100644 index 00000000..d1301898 --- /dev/null +++ b/rx/rxcset.h @@ -0,0 +1,44 @@ +/* classes: h_files */ + +#ifndef RXCSETH +#define RXCSETH + +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* lord Sun May 7 12:34:11 1995 */ + + +#include "rxbitset.h" + + +#ifdef __STDC__ +extern rx_Bitset rx_cset (int size); +extern rx_Bitset rx_copy_cset (int size, rx_Bitset a); +extern void rx_free_cset (rx_Bitset c); + +#else /* STDC */ +extern rx_Bitset rx_cset (); +extern rx_Bitset rx_copy_cset (); +extern void rx_free_cset (); + +#endif /* STDC */ + + + +#endif /* RXCSETH */ diff --git a/rx/rxgnucomp.c b/rx/rxgnucomp.c new file mode 100644 index 00000000..18d40087 --- /dev/null +++ b/rx/rxgnucomp.c @@ -0,0 +1,1665 @@ +/* Copyright (C) 1992, 1993, 1994, 1995 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#include <sys/types.h> +#include "rxall.h" +#include "rxgnucomp.h" +#include "inst-rxposix.h" + + +/* {A Syntax Table} + */ + +/* Define the syntax basics for \<, \>, etc. + */ + +#ifndef emacs +#define CHARBITS 8 +#define CHAR_SET_SIZE (1 << CHARBITS) +#define Sword 1 +#define SYNTAX(c) re_syntax_table[c] +char re_syntax_table[CHAR_SET_SIZE]; + +#ifdef __STDC__ +static void +init_syntax_once (void) +#else +static void +init_syntax_once () +#endif +{ + register int c; + static int done = 0; + + if (done) + return; + + rx_bzero ((char *)re_syntax_table, sizeof re_syntax_table); + + for (c = 'a'; c <= 'z'; c++) + re_syntax_table[c] = Sword; + + for (c = 'A'; c <= 'Z'; c++) + re_syntax_table[c] = Sword; + + for (c = '0'; c <= '9'; c++) + re_syntax_table[c] = Sword; + + re_syntax_table['_'] = Sword; + + done = 1; +} +#endif /* not emacs */ + + + + + +const char *rx_error_msg[] = +{ + 0, /* REG_NOUT */ + "No match", /* REG_NOMATCH */ + "Invalid regular expression", /* REG_BADPAT */ + "Invalid collation character", /* REG_ECOLLATE */ + "Invalid character class name", /* REG_ECTYPE */ + "Trailing backslash", /* REG_EESCAPE */ + "Invalid back reference", /* REG_ESUBREG */ + "Unmatched [ or [^", /* REG_EBRACK */ + "Unmatched ( or \\(", /* REG_EPAREN */ + "Unmatched \\{", /* REG_EBRACE */ + "Invalid content of \\{\\}", /* REG_BADBR */ + "Invalid range end", /* REG_ERANGE */ + "Memory exhausted", /* REG_ESPACE */ + "Invalid preceding regular expression", /* REG_BADRPT */ + "Premature end of regular expression", /* REG_EEND */ + "Regular expression too big", /* REG_ESIZE */ + "Unmatched ) or \\)", /* REG_ERPAREN */ +}; + + + +/* + * Macros used while compiling patterns. + * + * By convention, PEND points just past the end of the uncompiled pattern, + * P points to the read position in the pattern. `translate' is the name + * of the translation table (`TRANSLATE' is the name of a macro that looks + * things up in `translate'). + */ + + +/* + * Fetch the next character in the uncompiled pattern---translating it + * if necessary. *Also cast from a signed character in the constant + * string passed to us by the user to an unsigned char that we can use + * as an array index (in, e.g., `translate'). + */ +#define PATFETCH(c) \ + do {if (p == pend) return REG_EEND; \ + c = (unsigned char) *p++; \ + c = translate[c]; \ + } while (0) + +/* + * Fetch the next character in the uncompiled pattern, with no + * translation. + */ +#define PATFETCH_RAW(c) \ + do {if (p == pend) return REG_EEND; \ + c = (unsigned char) *p++; \ + } while (0) + +/* Go backwards one character in the pattern. */ +#define PATUNFETCH p-- + + +#define TRANSLATE(d) translate[(unsigned char) (d)] + +typedef int regnum_t; + +/* Since offsets can go either forwards or backwards, this type needs to + * be able to hold values from -(MAX_BUF_SIZE - 1) to MAX_BUF_SIZE - 1. + */ +typedef int pattern_offset_t; + +typedef struct +{ + struct rexp_node ** top_expression; + struct rexp_node ** last_expression; + struct rexp_node ** last_non_regular_expression; + pattern_offset_t inner_group_offset; + regnum_t regnum; +} compile_stack_elt_t; + +typedef struct +{ + compile_stack_elt_t *stack; + unsigned size; + unsigned avail; /* Offset of next open position. */ +} compile_stack_type; + + +#define INIT_COMPILE_STACK_SIZE 32 + +#define COMPILE_STACK_EMPTY (compile_stack.avail == 0) +#define COMPILE_STACK_FULL (compile_stack.avail == compile_stack.size) + +/* The next available element. */ +#define COMPILE_STACK_TOP (compile_stack.stack[compile_stack.avail]) + + +/* Set the bit for character C in a list. */ +#define SET_LIST_BIT(c) \ + (b[((unsigned char) (c)) / CHARBITS] \ + |= 1 << (((unsigned char) c) % CHARBITS)) + +/* Get the next unsigned number in the uncompiled pattern. */ +#define GET_UNSIGNED_NUMBER(num) \ + { if (p != pend) \ + { \ + PATFETCH (c); \ + while (isdigit (c)) \ + { \ + if (num < 0) \ + num = 0; \ + num = num * 10 + c - '0'; \ + if (p == pend) \ + break; \ + PATFETCH (c); \ + } \ + } \ + } + +#define CHAR_CLASS_MAX_LENGTH 64 + +#define IS_CHAR_CLASS(string) \ + (!strcmp (string, "alpha") || !strcmp (string, "upper") \ + || !strcmp (string, "lower") || !strcmp (string, "digit") \ + || !strcmp (string, "alnum") || !strcmp (string, "xdigit") \ + || !strcmp (string, "space") || !strcmp (string, "print") \ + || !strcmp (string, "punct") || !strcmp (string, "graph") \ + || !strcmp (string, "cntrl") || !strcmp (string, "blank")) + + +/* These predicates are used in regex_compile. */ + +/* P points to just after a ^ in PATTERN. Return true if that ^ comes + * after an alternative or a begin-subexpression. We assume there is at + * least one character before the ^. + */ + +#ifdef __STDC__ +static int +at_begline_loc_p (const char *pattern, const char * p, unsigned long syntax) +#else +static int +at_begline_loc_p (pattern, p, syntax) + const char *pattern; + const char * p; + unsigned long syntax; +#endif +{ + const char *prev = p - 2; + int prev_prev_backslash = ((prev > pattern) && (prev[-1] == '\\')); + + return + + (/* After a subexpression? */ + ((*prev == '(') && ((syntax & RE_NO_BK_PARENS) || prev_prev_backslash)) + || + /* After an alternative? */ + ((*prev == '|') && ((syntax & RE_NO_BK_VBAR) || prev_prev_backslash)) + ); +} + +/* The dual of at_begline_loc_p. This one is for $. We assume there is + * at least one character after the $, i.e., `P < PEND'. + */ + +#ifdef __STDC__ +static int +at_endline_loc_p (const char *p, const char *pend, int syntax) +#else +static int +at_endline_loc_p (p, pend, syntax) + const char *p; + const char *pend; + int syntax; +#endif +{ + const char *next = p; + int next_backslash = (*next == '\\'); + const char *next_next = (p + 1 < pend) ? (p + 1) : 0; + + return + ( + /* Before a subexpression? */ + ((syntax & RE_NO_BK_PARENS) + ? (*next == ')') + : (next_backslash && next_next && (*next_next == ')'))) + || + /* Before an alternative? */ + ((syntax & RE_NO_BK_VBAR) + ? (*next == '|') + : (next_backslash && next_next && (*next_next == '|'))) + ); +} + + +unsigned char rx_id_translation[256] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, + + 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, + 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, + 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, + 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, + 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, + + 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, + 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, + 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, + 250, 251, 252, 253, 254, 255 +}; + +/* The compiler keeps an inverted translation table. + * This looks up/inititalize elements. + * VALID is an array of booleans that validate CACHE. + */ + +#ifdef __STDC__ +static rx_Bitset +inverse_translation (int * n_members, int cset_size, char * valid, rx_Bitset cache, unsigned char * translate, int c) +#else +static rx_Bitset +inverse_translation (n_members, cset_size, valid, cache, translate, c) + int * n_members; + int cset_size; + char * valid; + rx_Bitset cache; + unsigned char * translate; + int c; +#endif +{ + rx_Bitset cs; + cs = cache + c * rx_bitset_numb_subsets (cset_size); + + if (!valid[c]) + { + int x; + int c_tr; + int membs; + + c_tr = TRANSLATE(c); + rx_bitset_null (cset_size, cs); + membs = 0; + for (x = 0; x < 256; ++x) + if (TRANSLATE(x) == c_tr) + { + RX_bitset_enjoin (cs, x); + membs++; + } + valid[c] = 1; + n_members[c] = membs; + } + return cs; +} + + + + +/* More subroutine declarations and macros for regex_compile. */ + +/* Returns true if REGNUM is in one of COMPILE_STACK's elements and + * false if it's not. + */ + +#ifdef __STDC__ +static int +group_in_compile_stack (compile_stack_type compile_stack, regnum_t regnum) +#else +static int +group_in_compile_stack (compile_stack, regnum) + compile_stack_type compile_stack; + regnum_t regnum; +#endif +{ + int this_element; + + for (this_element = compile_stack.avail - 1; + this_element >= 0; + this_element--) + if (compile_stack.stack[this_element].regnum == regnum) + return 1; + + return 0; +} + + +/* + * Read the ending character of a range (in a bracket expression) from the + * uncompiled pattern *P_PTR (which ends at PEND). We assume the + * starting character is in `P[-2]'. (`P[-1]' is the character `-'.) + * Then we set the translation of all bits between the starting and + * ending characters (inclusive) in the compiled pattern B. + * + * Return an error code. + * + * We use these short variable names so we can use the same macros as + * `regex_compile' itself. + */ + +#ifdef __STDC__ +static int +compile_range (int * n_members, int cset_size, rx_Bitset cs, const char ** p_ptr, const char * pend, unsigned char * translate, unsigned long syntax, rx_Bitset inv_tr, char * valid_inv_tr) +#else +static int +compile_range (n_members, cset_size, cs, p_ptr, pend, translate, syntax, inv_tr, valid_inv_tr) + int * n_members; + int cset_size; + rx_Bitset cs; + const char ** p_ptr; + const char * pend; + unsigned char * translate; + unsigned long syntax; + rx_Bitset inv_tr; + char * valid_inv_tr; +#endif +{ + unsigned this_char; + + const char *p = *p_ptr; + + unsigned char range_end; + unsigned char range_start = TRANSLATE(p[-2]); + + if (p == pend) + return REG_ERANGE; + + PATFETCH (range_end); + + (*p_ptr)++; + + if (range_start > range_end) + return syntax & RE_NO_EMPTY_RANGES ? REG_ERANGE : REG_NOERROR; + + for (this_char = range_start; this_char <= range_end; this_char++) + { + rx_Bitset it = + inverse_translation (n_members, cset_size, valid_inv_tr, inv_tr, translate, this_char); + rx_bitset_union (cset_size, cs, it); + } + + return REG_NOERROR; +} + + +#ifdef __STDC__ +static int +pointless_if_repeated (struct rexp_node * node) +#else +static int +pointless_if_repeated (node) + struct rexp_node * node; +#endif +{ + if (!node) + return 1; + switch (node->type) + { + case r_cset: + case r_string: + case r_cut: + return 0; + case r_concat: + case r_alternate: + return (pointless_if_repeated (node->params.pair.left) + && pointless_if_repeated (node->params.pair.right)); + case r_opt: + case r_star: + case r_interval: + case r_parens: + return pointless_if_repeated (node->params.pair.left); + case r_context: + switch (node->params.intval) + { + case '=': + case '<': + case '^': + case 'b': + case 'B': + case '`': + case '\'': + return 1; + default: + return 0; + } + default: + return 0; + } +} + + + +#ifdef __STDC__ +static int +factor_string (struct rexp_node *** lastp, int cset_size) +#else +static int +factor_string (lastp, cset_size) + struct rexp_node *** lastp; + int cset_size; +#endif +{ + struct rexp_node ** expp; + struct rexp_node * exp; + rx_Bitset cs; + struct rexp_node * cset_node; + + expp = *lastp; + exp = *expp; /* presumed r_string */ + + cs = rx_cset (cset_size); + if (!cs) + return -1; + RX_bitset_enjoin (cs, exp->params.cstr.contents[exp->params.cstr.len - 1]); + cset_node = rx_mk_r_cset (r_cset, cset_size, cs); + if (!cset_node) + { + rx_free_cset (cs); + return -1; + } + if (exp->params.cstr.len == 1) + { + rx_free_rexp (exp); + *expp = cset_node; + /* lastp remains the same */ + return 0; + } + else + { + struct rexp_node * concat_node; + exp->params.cstr.len--; + concat_node = rx_mk_r_binop (r_concat, exp, cset_node); + if (!concat_node) + { + rx_free_rexp (cset_node); + return -1; + } + *expp = concat_node; + *lastp = &concat_node->params.pair.right; + return 0; + } +} + + + +#define isa_blank(C) (((C) == ' ') || ((C) == '\t')) + +#ifdef __STDC__ +int +rx_parse (struct rexp_node ** rexp_p, + const char *pattern, + int size, + unsigned long syntax, + int cset_size, + unsigned char *translate) +#else +int +rx_parse (rexp_p, pattern, size, syntax, cset_size, translate) + struct rexp_node ** rexp_p; + const char *pattern; + int size; + unsigned long syntax; + int cset_size; + unsigned char *translate; +#endif +{ + int compile_error; + RX_subset + inverse_translate [CHAR_SET_SIZE * rx_bitset_numb_subsets(CHAR_SET_SIZE)]; + char validate_inv_tr [CHAR_SET_SIZE]; + int n_members [CHAR_SET_SIZE]; + + /* We fetch characters from PATTERN here. Even though PATTERN is + * `char *' (i.e., signed), we declare these variables as unsigned, so + * they can be reliably used as array indices. + */ + register unsigned char c; + register unsigned char c1; + + /* A random tempory spot in PATTERN. */ + const char *p1; + + /* Keeps track of unclosed groups. */ + compile_stack_type compile_stack; + + /* Points to the current (ending) position in the pattern. */ + const char *p; + const char *pend; + + /* When parsing is done, this will hold the expression tree. */ + struct rexp_node * rexp; + + /* This and top_expression are saved on the compile stack. */ + struct rexp_node ** top_expression; + struct rexp_node ** last_non_regular_expression; + struct rexp_node ** last_expression; + + /* Parameter to `goto append_node' */ + struct rexp_node * append; + + /* Counts open-groups as they are encountered. This is the index of the + * innermost group being compiled. + */ + regnum_t regnum; + + /* True iff the sub-expression just started + * is purely syntactic. Otherwise, a regmatch data + * slot is allocated for the subexpression. + */ + int syntax_only_parens; + + /* Place in the uncompiled pattern (i.e., the {) to + * which to go back if the interval is invalid. + */ + const char *beg_interval; + + int side; + + + + if (!translate) + translate = rx_id_translation; + + /* Points to the current (ending) position in the pattern. */ + p = pattern; + pend = pattern + size; + + /* When parsing is done, this will hold the expression tree. */ + rexp = 0; + + /* In the midst of compilation, this holds onto the regexp + * first parst while rexp goes on to aquire additional constructs. + */ + top_expression = &rexp; + last_non_regular_expression = top_expression; + last_expression = top_expression; + + /* Counts open-groups as they are encountered. This is the index of the + * innermost group being compiled. + */ + regnum = 0; + + rx_bzero ((char *)validate_inv_tr, sizeof (validate_inv_tr)); + + + /* Initialize the compile stack. */ + compile_stack.stack = (( compile_stack_elt_t *) malloc ((INIT_COMPILE_STACK_SIZE) * sizeof ( compile_stack_elt_t))); + if (compile_stack.stack == 0) + return REG_ESPACE; + + compile_stack.size = INIT_COMPILE_STACK_SIZE; + compile_stack.avail = 0; + +#if !defined (emacs) && !defined (SYNTAX_TABLE) + /* Initialize the syntax table. */ + init_syntax_once (); +#endif + + /* Loop through the uncompiled pattern until we're at the end. */ + while (p != pend) + { + PATFETCH (c); + + switch (c) + { + case '^': + { + if ( /* If at start of pattern, it's an operator. */ + p == pattern + 1 + /* If context independent, it's an operator. */ + || syntax & RE_CONTEXT_INDEP_ANCHORS + /* Otherwise, depends on what's come before. */ + || at_begline_loc_p (pattern, p, syntax)) + { + struct rexp_node * n + = rx_mk_r_int (r_context, '^'); + if (!n) + goto space_error; + append = n; + goto append_node; + } + else + goto normal_char; + } + break; + + + case '$': + { + if ( /* If at end of pattern, it's an operator. */ + p == pend + /* If context independent, it's an operator. */ + || syntax & RE_CONTEXT_INDEP_ANCHORS + /* Otherwise, depends on what's next. */ + || at_endline_loc_p (p, pend, syntax)) + { + struct rexp_node * n + = rx_mk_r_int (r_context, '$'); + if (!n) + goto space_error; + append = n; + goto append_node; + } + else + goto normal_char; + } + break; + + + case '+': + case '?': + if ((syntax & RE_BK_PLUS_QM) + || (syntax & RE_LIMITED_OPS)) + goto normal_char; + + handle_plus: + case '*': + /* If there is no previous pattern... */ + if (pointless_if_repeated (*last_expression)) + { + if (syntax & RE_CONTEXT_INVALID_OPS) + { + compile_error = REG_BADRPT; + goto error_return; + } + else if (!(syntax & RE_CONTEXT_INDEP_OPS)) + goto normal_char; + } + + { + /* 1 means zero (many) matches is allowed. */ + char zero_times_ok = 0, many_times_ok = 0; + + /* If there is a sequence of repetition chars, collapse it + down to just one (the right one). We can't combine + interval operators with these because of, e.g., `a{2}*', + which should only match an even number of `a's. */ + + for (;;) + { + zero_times_ok |= c != '+'; + many_times_ok |= c != '?'; + + if (p == pend) + break; + + PATFETCH (c); + + if (c == '*' + || (!(syntax & RE_BK_PLUS_QM) && (c == '+' || c == '?'))) + ; + + else if (syntax & RE_BK_PLUS_QM && c == '\\') + { + if (p == pend) + { + compile_error = REG_EESCAPE; + goto error_return; + } + + PATFETCH (c1); + if (!(c1 == '+' || c1 == '?')) + { + PATUNFETCH; + PATUNFETCH; + break; + } + + c = c1; + } + else + { + PATUNFETCH; + break; + } + + /* If we get here, we found another repeat character. */ + } + + /* Now we know whether or not zero matches is allowed + * and also whether or not two or more matches is allowed. + */ + + { + struct rexp_node * inner_exp; + struct rexp_node * star; + + if (*last_expression && ((*last_expression)->type == r_string)) + if (factor_string (&last_expression, cset_size)) + goto space_error; + inner_exp = *last_expression; + star = rx_mk_r_monop ((many_times_ok + ? (zero_times_ok ? r_star : r_plus) + : r_opt), + inner_exp); + if (!star) + goto space_error; + *last_expression = star; + } + } + break; + + + case '.': + { + rx_Bitset cs; + struct rexp_node * n; + cs = rx_cset (cset_size); + if (!cs) + goto space_error; + n = rx_mk_r_cset (r_cset, cset_size, cs); + if (!n) + { + rx_free_cset (cs); + goto space_error; + } + rx_bitset_universe (cset_size, cs); + if (!(syntax & RE_DOT_NEWLINE)) + RX_bitset_remove (cs, '\n'); + if (syntax & RE_DOT_NOT_NULL) + RX_bitset_remove (cs, 0); + + append = n; + goto append_node; + break; + } + + + case '[': + if (p == pend) + { + compile_error = REG_EBRACK; + goto error_return; + } + { + int had_char_class; + rx_Bitset cs; + struct rexp_node * node; + int is_inverted; + + had_char_class = 0; + is_inverted = *p == '^'; + cs = rx_cset (cset_size); + if (!cs) + goto space_error; + node = rx_mk_r_cset (r_cset, cset_size ,cs); + if (!node) + { + rx_free_cset (cs); + goto space_error; + } + + /* This branch of the switch is normally exited with + *`goto append_node' + */ + append = node; + + if (is_inverted) + p++; + + /* Remember the first position in the bracket expression. */ + p1 = p; + + /* Read in characters and ranges, setting map bits. */ + for (;;) + { + if (p == pend) + { + compile_error = REG_EBRACK; + goto error_return; + } + + PATFETCH (c); + + /* \ might escape characters inside [...] and [^...]. */ + if ((syntax & RE_BACKSLASH_ESCAPE_IN_LISTS) && c == '\\') + { + if (p == pend) + { + compile_error = REG_EESCAPE; + goto error_return; + } + + PATFETCH (c1); + { + rx_Bitset it = inverse_translation (n_members, + cset_size, + validate_inv_tr, + inverse_translate, + translate, + c1); + rx_bitset_union (cset_size, cs, it); + } + continue; + } + + /* Could be the end of the bracket expression. If it's + not (i.e., when the bracket expression is `[]' so + far), the ']' character bit gets set way below. */ + if (c == ']' && p != p1 + 1) + goto finalize_class_and_append; + + /* Look ahead to see if it's a range when the last thing + was a character class. */ + if (had_char_class && c == '-' && *p != ']') + { + compile_error = REG_ERANGE; + goto error_return; + } + + /* Look ahead to see if it's a range when the last thing + was a character: if this is a hyphen not at the + beginning or the end of a list, then it's the range + operator. */ + if (c == '-' + && !(p - 2 >= pattern && p[-2] == '[') + && !(p - 3 >= pattern && p[-3] == '[' && p[-2] == '^') + && *p != ']') + { + int ret + = compile_range (n_members, cset_size, cs, &p, pend, translate, syntax, + inverse_translate, validate_inv_tr); + if (ret != REG_NOERROR) + { + compile_error = ret; + goto error_return; + } + } + + else if (p[0] == '-' && p[1] != ']') + { /* This handles ranges made up of characters only. */ + int ret; + + /* Move past the `-'. */ + PATFETCH (c1); + + ret = compile_range (n_members, cset_size, cs, &p, pend, translate, syntax, + inverse_translate, validate_inv_tr); + if (ret != REG_NOERROR) + { + compile_error = ret; + goto error_return; + } + } + + /* See if we're at the beginning of a possible character + class. */ + + else if ((syntax & RE_CHAR_CLASSES) + && (c == '[') && (*p == ':')) + { + char str[CHAR_CLASS_MAX_LENGTH + 1]; + + PATFETCH (c); + c1 = 0; + + /* If pattern is `[[:'. */ + if (p == pend) + { + compile_error = REG_EBRACK; + goto error_return; + } + + for (;;) + { + PATFETCH (c); + if (c == ':' || c == ']' || p == pend + || c1 == CHAR_CLASS_MAX_LENGTH) + break; + str[c1++] = c; + } + str[c1] = '\0'; + + /* If isn't a word bracketed by `[:' and:`]': + undo the ending character, the letters, and leave + the leading `:' and `[' (but set bits for them). */ + if (c == ':' && *p == ']') + { + if (!strncmp (str, "cut", 3)) + { + int val; + if (1 != sscanf (str + 3, " %d", &val)) + { + compile_error = REG_ECTYPE; + goto error_return; + } + /* Throw away the ]] */ + PATFETCH (c); + PATFETCH (c); + { + struct rexp_node * cut; + cut = rx_mk_r_int (r_cut, val); + append = cut; + goto append_node; + } + } + else if (!strncmp (str, "(", 1)) + { + /* Throw away the ]] */ + PATFETCH (c); + PATFETCH (c); + syntax_only_parens = 1; + goto handle_open; + } + else if (!strncmp (str, ")", 1)) + { + /* Throw away the ]] */ + PATFETCH (c); + PATFETCH (c); + syntax_only_parens = 1; + goto handle_close; + } + else + { + int ch; + int is_alnum = !strcmp (str, "alnum"); + int is_alpha = !strcmp (str, "alpha"); + int is_blank = !strcmp (str, "blank"); + int is_cntrl = !strcmp (str, "cntrl"); + int is_digit = !strcmp (str, "digit"); + int is_graph = !strcmp (str, "graph"); + int is_lower = !strcmp (str, "lower"); + int is_print = !strcmp (str, "print"); + int is_punct = !strcmp (str, "punct"); + int is_space = !strcmp (str, "space"); + int is_upper = !strcmp (str, "upper"); + int is_xdigit = !strcmp (str, "xdigit"); + + if (!IS_CHAR_CLASS (str)) + { + compile_error = REG_ECTYPE; + goto error_return; + } + + /* Throw away the ] at the end of the character + class. */ + PATFETCH (c); + + if (p == pend) { compile_error = REG_EBRACK; goto error_return; } + + for (ch = 0; ch < 1 << CHARBITS; ch++) + { + if ( (is_alnum && isalnum (ch)) + || (is_alpha && isalpha (ch)) + || (is_blank && isa_blank (ch)) + || (is_cntrl && iscntrl (ch)) + || (is_digit && isdigit (ch)) + || (is_graph && isgraph (ch)) + || (is_lower && islower (ch)) + || (is_print && isprint (ch)) + || (is_punct && ispunct (ch)) + || (is_space && isspace (ch)) + || (is_upper && isupper (ch)) + || (is_xdigit && isxdigit (ch))) + { + rx_Bitset it = + inverse_translation (n_members, + cset_size, + validate_inv_tr, + inverse_translate, + translate, + ch); + rx_bitset_union (cset_size, + cs, it); + } + } + had_char_class = 1; + } + } + else + { + c1++; + while (c1--) + PATUNFETCH; + { + rx_Bitset it = + inverse_translation (n_members, + cset_size, + validate_inv_tr, + inverse_translate, + translate, + '['); + rx_bitset_union (cset_size, + cs, it); + } + { + rx_Bitset it = + inverse_translation (n_members, + cset_size, + validate_inv_tr, + inverse_translate, + translate, + ':'); + rx_bitset_union (cset_size, + cs, it); + } + had_char_class = 0; + } + } + else + { + had_char_class = 0; + { + rx_Bitset it = inverse_translation (n_members, + cset_size, + validate_inv_tr, + inverse_translate, + translate, + c); + rx_bitset_union (cset_size, cs, it); + } + } + } + + finalize_class_and_append: + if (is_inverted) + { + rx_bitset_complement (cset_size, cs); + if (syntax & RE_HAT_LISTS_NOT_NEWLINE) + RX_bitset_remove (cs, '\n'); + } + goto append_node; + } + break; + + + case '(': + if (syntax & RE_NO_BK_PARENS) + { + syntax_only_parens = 0; + goto handle_open; + } + else + goto normal_char; + + + case ')': + if (syntax & RE_NO_BK_PARENS) + { + syntax_only_parens = 0; + goto handle_close; + } + else + goto normal_char; + + + case '\n': + if (syntax & RE_NEWLINE_ALT) + goto handle_alt; + else + goto normal_char; + + + case '|': + if (syntax & RE_NO_BK_VBAR) + goto handle_alt; + else + goto normal_char; + + + case '{': + if ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES)) + goto handle_interval; + else + goto normal_char; + + + case '\\': + if (p == pend) { compile_error = REG_EESCAPE; goto error_return; } + + /* Do not translate the character after the \, so that we can + distinguish, e.g., \B from \b, even if we normally would + translate, e.g., B to b. */ + PATFETCH_RAW (c); + + switch (c) + { + case '(': + if (syntax & RE_NO_BK_PARENS) + goto normal_backslash; + + syntax_only_parens = 0; + + handle_open: + if (!syntax_only_parens) + regnum++; + + if (COMPILE_STACK_FULL) + { + compile_stack.stack + = ((compile_stack_elt_t *) + realloc (compile_stack.stack, + (compile_stack.size << 1) * sizeof (compile_stack_elt_t))); + if (compile_stack.stack == 0) + goto space_error; + compile_stack.size <<= 1; + } + + if (*last_non_regular_expression) + { + struct rexp_node * concat; + concat = rx_mk_r_binop (r_concat, *last_non_regular_expression, 0); + if (!concat) + goto space_error; + *last_non_regular_expression = concat; + last_non_regular_expression = &concat->params.pair.right; + last_expression = last_non_regular_expression; + } + + /* + * These are the values to restore when we hit end of this + * group. + */ + COMPILE_STACK_TOP.top_expression = top_expression; + COMPILE_STACK_TOP.last_expression = last_expression; + COMPILE_STACK_TOP.last_non_regular_expression = last_non_regular_expression; + + if (syntax_only_parens) + COMPILE_STACK_TOP.regnum = -1; + else + COMPILE_STACK_TOP.regnum = regnum; + + compile_stack.avail++; + + top_expression = last_non_regular_expression; + break; + + + case ')': + if (syntax & RE_NO_BK_PARENS) goto normal_backslash; + syntax_only_parens = 0; + + handle_close: + /* See similar code for backslashed left paren above. */ + if (COMPILE_STACK_EMPTY) + if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD) + goto normal_char; + else + { compile_error = REG_ERPAREN; goto error_return; } + + /* Since we just checked for an empty stack above, this + * ``can't happen''. + */ + + { + /* We don't just want to restore into `regnum', because + * later groups should continue to be numbered higher, + * as in `(ab)c(de)' -- the second group is #2. + */ + regnum_t this_group_regnum; + struct rexp_node ** inner; + struct rexp_node * parens; + + inner = top_expression; + compile_stack.avail--; + + if (!!syntax_only_parens != (COMPILE_STACK_TOP.regnum == -1)) + { compile_error = REG_ERPAREN; goto error_return; } + + top_expression = COMPILE_STACK_TOP.top_expression; + last_expression = COMPILE_STACK_TOP.last_expression; + last_non_regular_expression = COMPILE_STACK_TOP.last_non_regular_expression; + this_group_regnum = COMPILE_STACK_TOP.regnum; + + { + parens = rx_mk_r_monop (r_parens, *inner); + if (!parens) + goto space_error; + parens->params.intval = this_group_regnum; + *inner = parens; + break; + } + } + + case '|': /* `\|'. */ + if ((syntax & RE_LIMITED_OPS) || (syntax & RE_NO_BK_VBAR)) + goto normal_backslash; + handle_alt: + if (syntax & RE_LIMITED_OPS) + goto normal_char; + + { + struct rexp_node * alt; + + alt = rx_mk_r_binop (r_alternate, *top_expression, 0); + if (!alt) + goto space_error; + *top_expression = alt; + last_expression = &alt->params.pair.right; + last_non_regular_expression = &alt->params.pair.right; + } + break; + + + case '{': + /* If \{ is a literal. */ + if (!(syntax & RE_INTERVALS) + /* If we're at `\{' and it's not the open-interval + operator. */ + || ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES)) + || (p - 2 == pattern && p == pend)) + goto normal_backslash; + + handle_interval: + { + /* If got here, then the syntax allows intervals. + */ + + /* At least (most) this many matches must be made. + */ + int lower_bound; + int upper_bound; + + lower_bound = -1; + upper_bound = -1; + + /* We're about to parse the bounds of the interval. + * It may turn out that this isn't an interval after + * all, in which case these same characters will have + * to be reparsed as literals. This remembers + * the backtrack point in the parse: + */ + beg_interval = p - 1; + + if (p == pend) + { + if (syntax & RE_NO_BK_BRACES) + goto unfetch_interval; + else + { compile_error = REG_EBRACE; goto error_return; } + } + + GET_UNSIGNED_NUMBER (lower_bound); + + if (c == ',') + { + GET_UNSIGNED_NUMBER (upper_bound); + if (upper_bound < 0) upper_bound = RE_DUP_MAX; + } + else + /* Interval such as `{n}' => match exactly n times. + */ + upper_bound = lower_bound; + + if (lower_bound < 0 + || upper_bound > RE_DUP_MAX + || lower_bound > upper_bound) + { + if (syntax & RE_NO_BK_BRACES) + goto unfetch_interval; + else + { compile_error = REG_BADBR; goto error_return; } + } + + if (!(syntax & RE_NO_BK_BRACES)) + { + if (c != '\\') { compile_error = REG_EBRACE; goto error_return; } + PATFETCH (c); + } + + if (c != '}') + { + if (syntax & RE_NO_BK_BRACES) + goto unfetch_interval; + else + { compile_error = REG_BADBR; goto error_return; } + } + + /* We just parsed a valid interval. + * lower_bound and upper_bound are set. + */ + + /* If it's invalid to have no preceding re. + */ + if (pointless_if_repeated (*last_expression)) + { + if (syntax & RE_CONTEXT_INVALID_OPS) + { compile_error = REG_BADRPT; goto error_return; } + else if (!(syntax & RE_CONTEXT_INDEP_OPS)) + /* treat the interval spec as literal chars. */ + goto unfetch_interval; + } + + { + struct rexp_node * interval; + + if (*last_expression && ((*last_expression)->type == r_string)) + if (factor_string (&last_expression, cset_size)) + goto space_error; + interval = rx_mk_r_monop (r_interval, *last_expression); + if (!interval) + goto space_error; + interval->params.intval = lower_bound; + interval->params.intval2 = upper_bound; + *last_expression = interval; + last_non_regular_expression = last_expression; + } + beg_interval = 0; + } + break; + + unfetch_interval: + /* If an invalid interval, match the characters as literals. */ + p = beg_interval; + beg_interval = 0; + + /* normal_char and normal_backslash need `c'. */ + PATFETCH (c); + + if (!(syntax & RE_NO_BK_BRACES)) + { + if ((p > pattern) && (p[-1] == '\\')) + goto normal_backslash; + } + goto normal_char; + +#ifdef emacs + /* There is no way to specify the before_dot and after_dot + * operators. rms says this is ok. --karl + */ + case '=': + side = '='; + goto add_side_effect; + break; + + case 's': + case 'S': + { + rx_Bitset cs; + struct rexp_node * set; + + cs = rx_cset (&cset_size); + if (!cs) + goto space_error; + set = rx_mk_r_cset (r_cset, cset_size, cs); + if (!set) + { + rx_free_cset (cs); + goto space_error; + } + if (c == 'S') + rx_bitset_universe (cset_size, cs); + + PATFETCH (c); + { + int x; + enum syntaxcode code = syntax_spec_code [c]; + for (x = 0; x < 256; ++x) + { + + if (SYNTAX (x) == code) + { + rx_Bitset it = + inverse_translation (n_members, + cset_size, validate_inv_tr, + inverse_translate, + translate, x); + rx_bitset_xor (cset_size, cs, it); + } + } + } + append = set; + goto append_node; + } + break; +#endif /* emacs */ + + + case 'w': + case 'W': + { + rx_Bitset cs; + struct rexp_node * n; + + cs = rx_cset (cset_size); + n = (cs ? rx_mk_r_cset (r_cset, cset_size, cs) : 0); + if (!(cs && n)) + { + if (cs) + rx_free_cset (cs); + goto space_error; + } + if (c == 'W') + rx_bitset_universe (cset_size ,cs); + { + int x; + for (x = cset_size - 1; x > 0; --x) + if (SYNTAX(x) & Sword) + RX_bitset_toggle (cs, x); + } + append = n; + goto append_node; + } + break; + + case '<': + side = '<'; + goto add_side_effect; + break; + + case '>': + side = '>'; + goto add_side_effect; + break; + + case 'b': + side = 'b'; + goto add_side_effect; + break; + + case 'B': + side = 'B'; + goto add_side_effect; + break; + + case '`': + side = '`'; + goto add_side_effect; + break; + + case '\'': + side = '\''; + goto add_side_effect; + break; + + add_side_effect: + { + struct rexp_node * se; + se = rx_mk_r_int (r_context, side); + if (!se) + goto space_error; + append = se; + goto append_node; + } + break; + + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + if (syntax & RE_NO_BK_REFS) + goto normal_char; + + c1 = c - '0'; + + /* Can't back reference to a subexpression if inside of it. */ + if (group_in_compile_stack (compile_stack, c1)) + goto normal_char; + + if (c1 > regnum) + { compile_error = REG_ESUBREG; goto error_return; } + + side = c; + goto add_side_effect; + break; + + case '+': + case '?': + if (syntax & RE_BK_PLUS_QM) + goto handle_plus; + else + goto normal_backslash; + + default: + normal_backslash: + /* You might think it would be useful for \ to mean + * not to translate; but if we don't translate it + * it will never match anything. + */ + c = TRANSLATE (c); + goto normal_char; + } + break; + + + default: + /* Expects the character in `c'. */ + normal_char: + { + rx_Bitset cs; + struct rexp_node * match; + rx_Bitset it; + + it = inverse_translation (n_members, + cset_size, validate_inv_tr, + inverse_translate, translate, c); + + if (1 != n_members[c]) + { + cs = rx_cset (cset_size); + match = (cs ? rx_mk_r_cset (r_cset, cset_size, cs) : 0); + if (!(cs && match)) + { + if (cs) + rx_free_cset (cs); + goto space_error; + } + rx_bitset_union (CHAR_SET_SIZE, cs, it); + append = match; + goto append_node; + } + else + { + if (*last_expression && (*last_expression)->type == r_string) + { + if (rx_adjoin_string (&((*last_expression)->params.cstr), c)) + goto space_error; + break; + } + else + { + append = rx_mk_r_str (r_string, c); + if(!append) + goto space_error; + goto append_node; + } + } + break; + + append_node: + /* This genericly appends the rexp APPEND to *LAST_EXPRESSION + * and then parses the next character normally. + */ + if (RX_regular_node_type (append->type)) + { + if (!*last_expression) + *last_expression = append; + else + { + struct rexp_node * concat; + concat = rx_mk_r_binop (r_concat, + *last_expression, append); + if (!concat) + goto space_error; + *last_expression = concat; + last_expression = &concat->params.pair.right; + } + } + else + { + if (!*last_non_regular_expression) + { + *last_non_regular_expression = append; + last_expression = last_non_regular_expression; + } + else + { + struct rexp_node * concat; + concat = rx_mk_r_binop (r_concat, + *last_non_regular_expression, append); + if (!concat) + goto space_error; + *last_non_regular_expression = concat; + last_non_regular_expression = &concat->params.pair.right; + last_expression = last_non_regular_expression; + } + } + } + } /* switch (c) */ + } /* while p != pend */ + + + /* Through the pattern now. */ + + if (!COMPILE_STACK_EMPTY) + { compile_error = REG_EPAREN; goto error_return; } + free (compile_stack.stack); + + + *rexp_p = rexp; + return REG_NOERROR; + + space_error: + compile_error = REG_ESPACE; + + error_return: + free (compile_stack.stack); + /* Free expressions pushed onto the compile stack! */ + if (rexp) + rx_free_rexp (rexp); + return compile_error; +} + + diff --git a/rx/rxgnucomp.h b/rx/rxgnucomp.h new file mode 100644 index 00000000..d572ece3 --- /dev/null +++ b/rx/rxgnucomp.h @@ -0,0 +1,210 @@ +/* classes: h_files */ + +#ifndef RXGNUCOMPH +#define RXGNUCOMPH +/* Copyright (C) 1992, 1993, 1994, 1995 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "rxcset.h" +#include "rxnode.h" + + + + +/* This is an array of error messages corresponding to the error codes. + */ +extern const char *rx_error_msg[]; + + + +/* {Syntax Bits} + */ + +/* The following bits are used to determine the regexp syntax we + recognize. The set/not-set meanings are chosen so that Emacs syntax + remains the value 0. The bits are given in alphabetical order, and + the definitions shifted by one from the previous bit; thus, when we + add or remove a bit, only one other definition need change. */ + +enum RE_SYNTAX_BITS +{ +/* If this bit is not set, then \ inside a bracket expression is literal. + If set, then such a \ quotes the following character. */ + RE_BACKSLASH_ESCAPE_IN_LISTS = (1), + +/* If this bit is not set, then + and ? are operators, and \+ and \? are + literals. + If set, then \+ and \? are operators and + and ? are literals. */ + RE_BK_PLUS_QM = (RE_BACKSLASH_ESCAPE_IN_LISTS << 1), + +/* If this bit is set, then character classes are supported. They are: + [:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:], + [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:]. + If not set, then character classes are not supported. */ + RE_CHAR_CLASSES = (RE_BK_PLUS_QM << 1), + +/* If this bit is set, then ^ and $ are always anchors (outside bracket + expressions, of course). + If this bit is not set, then it depends: + ^ is an anchor if it is at the beginning of a regular + expression or after an open-group or an alternation operator; + $ is an anchor if it is at the end of a regular expression, or + before a close-group or an alternation operator. + + This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because + POSIX draft 11.2 says that * etc. in leading positions is undefined. + We already implemented a previous draft which made those constructs + invalid, though, so we haven't changed the code back. */ + RE_CONTEXT_INDEP_ANCHORS = (RE_CHAR_CLASSES << 1), + +/* If this bit is set, then special characters are always special + regardless of where they are in the pattern. + If this bit is not set, then special characters are special only in + some contexts; otherwise they are ordinary. Specifically, + * + ? and intervals are only special when not after the beginning, + open-group, or alternation operator. */ + RE_CONTEXT_INDEP_OPS = (RE_CONTEXT_INDEP_ANCHORS << 1), + +/* If this bit is set, then *, +, ?, and { cannot be first in an re or + immediately after an alternation or begin-group operator. */ + RE_CONTEXT_INVALID_OPS = (RE_CONTEXT_INDEP_OPS << 1), + +/* If this bit is set, then . matches newline. + If not set, then it doesn't. */ + RE_DOT_NEWLINE = (RE_CONTEXT_INVALID_OPS << 1), + +/* If this bit is set, then . doesn't match NUL. + If not set, then it does. */ + RE_DOT_NOT_NULL = (RE_DOT_NEWLINE << 1), + +/* If this bit is set, nonmatching lists [^...] do not match newline. + If not set, they do. */ + RE_HAT_LISTS_NOT_NEWLINE = (RE_DOT_NOT_NULL << 1), + +/* If this bit is set, either \{...\} or {...} defines an + interval, depending on RE_NO_BK_BRACES. + If not set, \{, \}, {, and } are literals. */ + RE_INTERVALS = (RE_HAT_LISTS_NOT_NEWLINE << 1), + +/* If this bit is set, +, ? and | aren't recognized as operators. + If not set, they are. */ + RE_LIMITED_OPS = (RE_INTERVALS << 1), + +/* If this bit is set, newline is an alternation operator. + If not set, newline is literal. */ + RE_NEWLINE_ALT = (RE_LIMITED_OPS << 1), + +/* If this bit is set, then `{...}' defines an interval, and \{ and \} + are literals. + If not set, then `\{...\}' defines an interval. */ + RE_NO_BK_BRACES = (RE_NEWLINE_ALT << 1), + +/* If this bit is set, (...) defines a group, and \( and \) are literals. + If not set, \(...\) defines a group, and ( and ) are literals. */ + RE_NO_BK_PARENS = (RE_NO_BK_BRACES << 1), + +/* If this bit is set, then \<digit> matches <digit>. + If not set, then \<digit> is a back-reference. */ + RE_NO_BK_REFS = (RE_NO_BK_PARENS << 1), + +/* If this bit is set, then | is an alternation operator, and \| is literal. + If not set, then \| is an alternation operator, and | is literal. */ + RE_NO_BK_VBAR = (RE_NO_BK_REFS << 1), + +/* If this bit is set, then an ending range point collating higher + than the starting range point, as in [z-a], is invalid. + If not set, then when ending range point collates higher than the + starting range point, the range is ignored. */ + RE_NO_EMPTY_RANGES = (RE_NO_BK_VBAR << 1), + +/* If this bit is set, then an unmatched ) is ordinary. + If not set, then an unmatched ) is invalid. */ + RE_UNMATCHED_RIGHT_PAREN_ORD = (RE_NO_EMPTY_RANGES << 1), + + RE_SYNTAX_EMACS = 0, + + RE_SYNTAX_AWK = (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL + | RE_NO_BK_PARENS | RE_NO_BK_REFS + | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES + | RE_UNMATCHED_RIGHT_PAREN_ORD), + + RE_SYNTAX_GREP = (RE_BK_PLUS_QM | RE_CHAR_CLASSES + | RE_HAT_LISTS_NOT_NEWLINE | RE_INTERVALS + | RE_NEWLINE_ALT), + + RE_SYNTAX_EGREP = (RE_CHAR_CLASSES | RE_CONTEXT_INDEP_ANCHORS + | RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE + | RE_NEWLINE_ALT | RE_NO_BK_PARENS + | RE_NO_BK_VBAR), + + RE_SYNTAX_POSIX_EGREP = (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES), + + /* Syntax bits common to both basic and extended POSIX regex syntax. */ + _RE_SYNTAX_POSIX_COMMON = (RE_CHAR_CLASSES | RE_DOT_NEWLINE | RE_DOT_NOT_NULL + | RE_INTERVALS | RE_NO_EMPTY_RANGES), + + RE_SYNTAX_POSIX_BASIC = (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM), + + /* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes + RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. */ + + RE_SYNTAX_POSIX_MINIMAL_BASIC = (_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS), + + RE_SYNTAX_POSIX_EXTENDED = (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS + | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES + | RE_NO_BK_PARENS | RE_NO_BK_VBAR + | RE_UNMATCHED_RIGHT_PAREN_ORD), + + /* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INVALID_OPS + replaces RE_CONTEXT_INDEP_OPS and RE_NO_BK_REFS is added. */ + RE_SYNTAX_POSIX_MINIMAL_EXTENDED = (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS + | RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES + | RE_NO_BK_PARENS | RE_NO_BK_REFS + | RE_NO_BK_VBAR | RE_UNMATCHED_RIGHT_PAREN_ORD), + + RE_SYNTAX_SED = RE_SYNTAX_POSIX_BASIC, + + RE_SYNTAX_POSIX_AWK = (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS) +}; + + +/* Maximum number of duplicates an interval can allow. Some systems + (erroneously) define this in other header files, but we want our + value, so remove any previous define. */ +#undef RE_DUP_MAX +#define RE_DUP_MAX ((1 << 15) - 1) + + + +#ifdef __STDC__ +extern int rx_parse (struct rexp_node ** rexp_p, + const char *pattern, + int size, + unsigned long syntax, + int cset_size, + unsigned char *translate); + +#else /* STDC */ +extern int rx_parse (); + +#endif /* STDC */ + + +#endif /* RXGNUCOMPH */ diff --git a/rx/rxhash.c b/rx/rxhash.c new file mode 100644 index 00000000..4e959737 --- /dev/null +++ b/rx/rxhash.c @@ -0,0 +1,394 @@ +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +/* + * Tom Lord (lord@cygnus.com, lord@gnu.ai.mit.edu) + */ + + +#include "rxall.h" +#include "rxhash.h" + + + +#ifdef __STDC__ +static struct rx_hash * +default_hash_alloc (struct rx_hash_rules * rules) +#else +static struct rx_hash * +default_hash_alloc (rules) + struct rx_hash_rules * rules; +#endif +{ + return (struct rx_hash *)malloc (sizeof (struct rx_hash)); +} + + +#ifdef __STDC__ +static struct rx_hash_item * +default_hash_item_alloc (struct rx_hash_rules * rules, void * value) +#else +static struct rx_hash_item * +default_hash_item_alloc (rules, value) + struct rx_hash_rules * rules; + void * value; +#endif +{ + struct rx_hash_item * it; + it = (struct rx_hash_item *)malloc (sizeof (*it)); + if (it) + { + it->data = value; + it->binding = 0; + } + return it; +} + + +#ifdef __STDC__ +static void +default_free_hash (struct rx_hash * tab, + struct rx_hash_rules * rules) +#else +static void +default_free_hash (tab, rules) + struct rx_hash * tab; + struct rx_hash_rules * rules; +#endif +{ + free ((char *)tab); +} + + +#ifdef __STDC__ +static void +default_free_hash_item (struct rx_hash_item * item, + struct rx_hash_rules * rules) +#else +static void +default_free_hash_item (item, rules) + struct rx_hash_item * item; + struct rx_hash_rules * rules; +#endif +{ + free ((char *)item); +} + +#ifdef __STDC__ +static int +default_eq (void * va, void * vb) +#else +static int +default_eq (va, vb) + void * va; + void * vb; +#endif +{ + return va == vb; +} + + + +#define EQ(rules) ((rules && rules->eq) ? rules->eq : default_eq) +#define HASH_ALLOC(rules) ((rules && rules->hash_alloc) ? rules->hash_alloc : default_hash_alloc) +#define FREE_HASH(rules) ((rules && rules->free_hash) ? rules->free_hash : default_free_hash) +#define ITEM_ALLOC(rules) ((rules && rules->hash_item_alloc) ? rules->hash_item_alloc : default_hash_item_alloc) +#define FREE_HASH_ITEM(rules) ((rules && rules->free_hash_item) ? rules->free_hash_item : default_free_hash_item) + + +static unsigned long rx_hash_masks[4] = +{ + 0x12488421, + 0x96699669, + 0xbe7dd7eb, + 0xffffffff +}; + +/* hash to bucket */ +#define JOIN_BYTE(H, B) (((H) + ((H) << 3) + (B)) & 0xf) + +#define H2B(X) JOIN_BYTE (JOIN_BYTE (JOIN_BYTE ((X & 0xf), ((X>>8) & 0xf)), ((X>>16) & 0xf)), ((X>>24) & 0xf)) + +#define BKTS 16 + +/* Hash tables */ +#ifdef __STDC__ +struct rx_hash_item * +rx_hash_find (struct rx_hash * table, + unsigned long hash, + void * value, + struct rx_hash_rules * rules) +#else +struct rx_hash_item * +rx_hash_find (table, hash, value, rules) + struct rx_hash * table; + unsigned long hash; + void * value; + struct rx_hash_rules * rules; +#endif +{ + rx_hash_eq eq = EQ (rules); + int maskc = 0; + long mask = rx_hash_masks [0]; + int bucket = H2B(hash & mask); + + while (RX_bitset_member (&table->nested_p, bucket)) + { + table = (struct rx_hash *)(table->children [bucket]); + ++maskc; + mask = rx_hash_masks[maskc]; + bucket = H2B (hash & mask); + } + + { + struct rx_hash_item * it; + it = (struct rx_hash_item *)(table->children[bucket]); + while (it) + if (eq (it->data, value)) + return it; + else + it = it->next_same_hash; + } + + return 0; +} + + +#ifdef __STDC__ +static int +listlen (struct rx_hash_item * bucket) +#else +static int +listlen (bucket) + struct rx_hash_item * bucket; +#endif +{ + int i; + for (i = 0; bucket; ++i, bucket = bucket->next_same_hash) + ; + return i; +} + +#ifdef __STDC__ +static int +overflows (struct rx_hash_item * bucket) +#else +static int +overflows (bucket) + struct rx_hash_item * bucket; +#endif +{ + return !( bucket + && bucket->next_same_hash + && bucket->next_same_hash->next_same_hash + && bucket->next_same_hash->next_same_hash->next_same_hash); +} + + +#ifdef __STDC__ +struct rx_hash_item * +rx_hash_store (struct rx_hash * table, + unsigned long hash, + void * value, + struct rx_hash_rules * rules) +#else +struct rx_hash_item * +rx_hash_store (table, hash, value, rules) + struct rx_hash * table; + unsigned long hash; + void * value; + struct rx_hash_rules * rules; +#endif +{ + rx_hash_eq eq = EQ (rules); + int maskc = 0; + long mask = rx_hash_masks [0]; + int bucket = H2B(hash & mask); + int depth = 0; + + while (RX_bitset_member (&table->nested_p, bucket)) + { + table = (struct rx_hash *)(table->children [bucket]); + ++maskc; + mask = rx_hash_masks[maskc]; + bucket = H2B(hash & mask); + ++depth; + } + + { + struct rx_hash_item * it; + it = (struct rx_hash_item *)(table->children[bucket]); + while (it) + if (eq (it->data, value)) + return it; + else + it = it->next_same_hash; + } + + { + if ( (depth < 3) + && (overflows ((struct rx_hash_item *)table->children [bucket]))) + { + struct rx_hash * newtab; + newtab = (struct rx_hash *) HASH_ALLOC(rules) (rules); + if (!newtab) + goto add_to_bucket; + rx_bzero ((char *)newtab, sizeof (*newtab)); + newtab->parent = table; + { + struct rx_hash_item * them; + unsigned long newmask; + them = (struct rx_hash_item *)table->children[bucket]; + newmask = rx_hash_masks[maskc + 1]; + while (them) + { + struct rx_hash_item * save = them->next_same_hash; + int new_buck = H2B(them->hash & newmask); + them->next_same_hash = ((struct rx_hash_item *) + newtab->children[new_buck]); + ((struct rx_hash_item **)newtab->children)[new_buck] = them; + them->table = newtab; + them = save; + ++newtab->refs; + --table->refs; + } + ((struct rx_hash **)table->children)[bucket] = newtab; + RX_bitset_enjoin (&table->nested_p, bucket); + ++table->refs; + table = newtab; + bucket = H2B(hash & newmask); + } + } + } + add_to_bucket: + { + struct rx_hash_item * it = ((struct rx_hash_item *) + ITEM_ALLOC(rules) (rules, value)); + if (!it) + return 0; + it->hash = hash; + it->table = table; + /* DATA and BINDING are to be set in hash_item_alloc */ + it->next_same_hash = (struct rx_hash_item *)table->children [bucket]; + ((struct rx_hash_item **)table->children)[bucket] = it; + ++table->refs; + return it; + } +} + + +#ifdef __STDC__ +void +rx_hash_free (struct rx_hash_item * it, struct rx_hash_rules * rules) +#else +void +rx_hash_free (it, rules) + struct rx_hash_item * it; + struct rx_hash_rules * rules; +#endif +{ + if (it) + { + struct rx_hash * table = it->table; + unsigned long hash = it->hash; + int depth = (table->parent + ? (table->parent->parent + ? (table->parent->parent->parent + ? 3 + : 2) + : 1) + : 0); + int bucket = H2B (hash & rx_hash_masks [depth]); + struct rx_hash_item ** pos + = (struct rx_hash_item **)&table->children [bucket]; + + while (*pos != it) + pos = &(*pos)->next_same_hash; + *pos = it->next_same_hash; + FREE_HASH_ITEM(rules) (it, rules); + --table->refs; + while (!table->refs && depth) + { + struct rx_hash * save = table; + table = table->parent; + --depth; + bucket = H2B(hash & rx_hash_masks [depth]); + --table->refs; + table->children[bucket] = 0; + RX_bitset_remove (&table->nested_p, bucket); + FREE_HASH (rules) (save, rules); + } + } +} + +#ifdef __STDC__ +void +rx_free_hash_table (struct rx_hash * tab, rx_hash_freefn freefn, + struct rx_hash_rules * rules) +#else +void +rx_free_hash_table (tab, freefn, rules) + struct rx_hash * tab; + rx_hash_freefn freefn; + struct rx_hash_rules * rules; +#endif +{ + int x; + + for (x = 0; x < BKTS; ++x) + if (RX_bitset_member (&tab->nested_p, x)) + { + rx_free_hash_table ((struct rx_hash *)tab->children[x], + freefn, rules); + FREE_HASH (rules) ((struct rx_hash *)tab->children[x], rules); + } + else + { + struct rx_hash_item * them = (struct rx_hash_item *)tab->children[x]; + while (them) + { + struct rx_hash_item * that = them; + them = that->next_same_hash; + freefn (that); + FREE_HASH_ITEM (rules) (that, rules); + } + } +} + + + +#ifdef __STDC__ +int +rx_count_hash_nodes (struct rx_hash * st) +#else +int +rx_count_hash_nodes (st) + struct rx_hash * st; +#endif +{ + int x; + int count = 0; + for (x = 0; x < BKTS; ++x) + count += ((RX_bitset_member (&st->nested_p, x)) + ? rx_count_hash_nodes ((struct rx_hash *)st->children[x]) + : listlen ((struct rx_hash_item *)(st->children[x]))); + + return count; +} + diff --git a/rx/rxhash.h b/rx/rxhash.h new file mode 100644 index 00000000..9763fdf8 --- /dev/null +++ b/rx/rxhash.h @@ -0,0 +1,112 @@ +/* classes: h_files */ + +#ifndef RXHASHH +#define RXHASHH +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +/* + * Tom Lord (lord@cygnus.com, lord@gnu.ai.mit.edu) + */ + + +#include "rxbitset.h" + +/* giant inflatable hash trees */ + +struct rx_hash_item +{ + struct rx_hash_item * next_same_hash; + struct rx_hash * table; + unsigned long hash; + void * data; + void * binding; +}; + +struct rx_hash +{ + struct rx_hash * parent; + int refs; + RX_subset nested_p; + void ** children[16]; +}; + +struct rx_hash_rules; + +/* rx_hash_eq should work like the == operator. */ + +#ifdef __STDC__ +typedef int (*rx_hash_eq)(void *, void *); +typedef struct rx_hash * (*rx_alloc_hash)(struct rx_hash_rules *); +typedef void (*rx_free_hash)(struct rx_hash *, + struct rx_hash_rules *); +typedef struct rx_hash_item * (*rx_alloc_hash_item)(struct rx_hash_rules *, + void *); +typedef void (*rx_free_hash_item)(struct rx_hash_item *, + struct rx_hash_rules *); +typedef void (*rx_hash_freefn) (struct rx_hash_item * it); +#else +typedef int (*rx_hash_eq)(); +typedef struct rx_hash * (*rx_alloc_hash)(); +typedef void (*rx_free_hash)(); +typedef struct rx_hash_item * (*rx_alloc_hash_item)(); +typedef void (*rx_free_hash_item)(); +typedef void (*rx_hash_freefn) (); +#endif + +struct rx_hash_rules +{ + rx_hash_eq eq; + rx_alloc_hash hash_alloc; + rx_free_hash free_hash; + rx_alloc_hash_item hash_item_alloc; + rx_free_hash_item free_hash_item; +}; + + +#ifdef __STDC__ +extern struct rx_hash_item * rx_hash_find (struct rx_hash * table, + unsigned long hash, + void * value, + struct rx_hash_rules * rules); +extern struct rx_hash_item * rx_hash_store (struct rx_hash * table, + unsigned long hash, + void * value, + struct rx_hash_rules * rules); +extern void rx_hash_free (struct rx_hash_item * it, struct rx_hash_rules * rules); +extern void rx_free_hash_table (struct rx_hash * tab, rx_hash_freefn freefn, + struct rx_hash_rules * rules); +extern int rx_count_hash_nodes (struct rx_hash * st); + +#else /* STDC */ +extern struct rx_hash_item * rx_hash_find (); +extern struct rx_hash_item * rx_hash_store (); +extern void rx_hash_free (); +extern void rx_free_hash_table (); +extern int rx_count_hash_nodes (); + +#endif /* STDC */ + + + + + +#endif /* RXHASHH */ + diff --git a/rx/rxnfa.c b/rx/rxnfa.c new file mode 100644 index 00000000..d67dd0ec --- /dev/null +++ b/rx/rxnfa.c @@ -0,0 +1,853 @@ +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +/* + * Tom Lord (lord@cygnus.com, lord@gnu.ai.mit.edu) + */ + + +#include "rxall.h" +#include "rxnfa.h" + + + +#define remalloc(M, S) (M ? realloc (M, S) : malloc (S)) + + +/* {Low Level Data Structure Code} + */ + +/* Constructs a new nfa node. */ +#ifdef __STDC__ +struct rx_nfa_state * +rx_nfa_state (struct rx *rx) +#else +struct rx_nfa_state * +rx_nfa_state (rx) + struct rx *rx; +#endif +{ + struct rx_nfa_state * n = (struct rx_nfa_state *)malloc (sizeof (*n)); + if (!n) + return 0; + rx_bzero ((char *)n, sizeof (*n)); + n->next = rx->nfa_states; + rx->nfa_states = n; + return n; +} + + +#ifdef __STDC__ +static void +rx_free_nfa_state (struct rx_nfa_state * n) +#else +static void +rx_free_nfa_state (n) + struct rx_nfa_state * n; +#endif +{ + free ((char *)n); +} + + +/* This adds an edge between two nodes, but doesn't initialize the + * edge label. + */ + +#ifdef __STDC__ +struct rx_nfa_edge * +rx_nfa_edge (struct rx *rx, + enum rx_nfa_etype type, + struct rx_nfa_state *start, + struct rx_nfa_state *dest) +#else +struct rx_nfa_edge * +rx_nfa_edge (rx, type, start, dest) + struct rx *rx; + enum rx_nfa_etype type; + struct rx_nfa_state *start; + struct rx_nfa_state *dest; +#endif +{ + struct rx_nfa_edge *e; + e = (struct rx_nfa_edge *)malloc (sizeof (*e)); + if (!e) + return 0; + e->next = start->edges; + start->edges = e; + e->type = type; + e->dest = dest; + return e; +} + + +#ifdef __STDC__ +static void +rx_free_nfa_edge (struct rx_nfa_edge * e) +#else +static void +rx_free_nfa_edge (e) + struct rx_nfa_edge * e; +#endif +{ + free ((char *)e); +} + + +/* This constructs a POSSIBLE_FUTURE, which is a kind epsilon-closure + * of an NFA. These are added to an nfa automaticly by eclose_nfa. + */ + +#ifdef __STDC__ +static struct rx_possible_future * +rx_possible_future (struct rx * rx, + struct rx_se_list * effects) +#else +static struct rx_possible_future * +rx_possible_future (rx, effects) + struct rx * rx; + struct rx_se_list * effects; +#endif +{ + struct rx_possible_future *ec; + ec = (struct rx_possible_future *) malloc (sizeof (*ec)); + if (!ec) + return 0; + ec->destset = 0; + ec->next = 0; + ec->effects = effects; + return ec; +} + + +#ifdef __STDC__ +static void +rx_free_possible_future (struct rx_possible_future * pf) +#else +static void +rx_free_possible_future (pf) + struct rx_possible_future * pf; +#endif +{ + free ((char *)pf); +} + + +#ifdef __STDC__ +static void +rx_free_nfa_graph (struct rx *rx) +#else +static void +rx_free_nfa_graph (rx) + struct rx *rx; +#endif +{ + while (rx->nfa_states) + { + while (rx->nfa_states->edges) + { + switch (rx->nfa_states->edges->type) + { + case ne_cset: + rx_free_cset (rx->nfa_states->edges->params.cset); + break; + default: + break; + } + { + struct rx_nfa_edge * e; + e = rx->nfa_states->edges; + rx->nfa_states->edges = rx->nfa_states->edges->next; + rx_free_nfa_edge (e); + } + } /* while (rx->nfa_states->edges) */ + { + /* Iterate over the partial epsilon closures of rx->nfa_states */ + struct rx_possible_future * pf = rx->nfa_states->futures; + while (pf) + { + struct rx_possible_future * pft = pf; + pf = pf->next; + rx_free_possible_future (pft); + } + } + { + struct rx_nfa_state *n; + n = rx->nfa_states; + rx->nfa_states = rx->nfa_states->next; + rx_free_nfa_state (n); + } + } +} + + + +/* {Translating a Syntax Tree into an NFA} + * + */ + + +/* This is the Thompson regexp->nfa algorithm. + * It is modified to allow for `side-effect epsilons.' Those are + * edges that are taken whenever a similarly placed epsilon edge + * would be, but which also imply that some side effect occurs + * when the edge is taken. + * + * Side effects are used to model parts of the pattern langauge + * that are not regular. + */ + +#ifdef __STDC__ +int +rx_build_nfa (struct rx *rx, + struct rexp_node *rexp, + struct rx_nfa_state **start, + struct rx_nfa_state **end) +#else +int +rx_build_nfa (rx, rexp, start, end) + struct rx *rx; + struct rexp_node *rexp; + struct rx_nfa_state **start; + struct rx_nfa_state **end; +#endif +{ + struct rx_nfa_edge *edge; + + /* Start & end nodes may have been allocated by the caller. */ + *start = *start ? *start : rx_nfa_state (rx); + + if (!*start) + return 0; + + if (!rexp) + { + *end = *start; + return 1; + } + + *end = *end ? *end : rx_nfa_state (rx); + + if (!*end) + { + rx_free_nfa_state (*start); + return 0; + } + + switch (rexp->type) + { + case r_cset: + edge = rx_nfa_edge (rx, ne_cset, *start, *end); + (*start)->has_cset_edges = 1; + if (!edge) + return 0; + edge->params.cset = rx_copy_cset (rx->local_cset_size, + rexp->params.cset); + if (!edge->params.cset) + { + rx_free_nfa_edge (edge); + return 0; + } + return 1; + + case r_string: + { + if (rexp->params.cstr.len == 1) + { + edge = rx_nfa_edge (rx, ne_cset, *start, *end); + (*start)->has_cset_edges = 1; + if (!edge) + return 0; + edge->params.cset = rx_cset (rx->local_cset_size); + if (!edge->params.cset) + { + rx_free_nfa_edge (edge); + return 0; + } + RX_bitset_enjoin (edge->params.cset, rexp->params.cstr.contents[0]); + return 1; + } + else + { + struct rexp_node copied; + struct rx_nfa_state * shared; + + copied = *rexp; + shared = 0; + copied.params.cstr.len--; + copied.params.cstr.contents++; + if (!rx_build_nfa (rx, &copied, &shared, end)) + return 0; + copied.params.cstr.len = 1; + copied.params.cstr.contents--; + return rx_build_nfa (rx, &copied, start, &shared); + } + } + + case r_opt: + return (rx_build_nfa (rx, rexp->params.pair.left, start, end) + && rx_nfa_edge (rx, ne_epsilon, *start, *end)); + + case r_plus: + { + struct rx_nfa_state * star_start = 0; + struct rx_nfa_state * star_end = 0; + struct rx_nfa_state * shared; + + shared = 0; + if (!rx_build_nfa (rx, rexp->params.pair.left, start, &shared)) + return 0; + return (rx_build_nfa (rx, rexp->params.pair.left, + &star_start, &star_end) + && star_start + && star_end + && rx_nfa_edge (rx, ne_epsilon, star_start, star_end) + && rx_nfa_edge (rx, ne_epsilon, shared, star_start) + && rx_nfa_edge (rx, ne_epsilon, star_end, *end) + && rx_nfa_edge (rx, ne_epsilon, star_end, star_start)); + } + + case r_interval: + case r_star: + { + struct rx_nfa_state * star_start = 0; + struct rx_nfa_state * star_end = 0; + return (rx_build_nfa (rx, rexp->params.pair.left, + &star_start, &star_end) + && star_start + && star_end + && rx_nfa_edge (rx, ne_epsilon, star_start, star_end) + && rx_nfa_edge (rx, ne_epsilon, *start, star_start) + && rx_nfa_edge (rx, ne_epsilon, star_end, *end) + + && rx_nfa_edge (rx, ne_epsilon, star_end, star_start)); + } + + case r_cut: + { + struct rx_nfa_state * cut_end = 0; + + cut_end = rx_nfa_state (rx); + if (!(cut_end && rx_nfa_edge (rx, ne_epsilon, *start, cut_end))) + { + rx_free_nfa_state (*start); + rx_free_nfa_state (*end); + if (cut_end) + rx_free_nfa_state (cut_end); + return 0; + } + cut_end->is_final = rexp->params.intval; + return 1; + } + + case r_parens: + return rx_build_nfa (rx, rexp->params.pair.left, start, end); + + case r_concat: + { + struct rx_nfa_state *shared = 0; + return + (rx_build_nfa (rx, rexp->params.pair.left, start, &shared) + && rx_build_nfa (rx, rexp->params.pair.right, &shared, end)); + } + + case r_alternate: + { + struct rx_nfa_state *ls = 0; + struct rx_nfa_state *le = 0; + struct rx_nfa_state *rs = 0; + struct rx_nfa_state *re = 0; + return (rx_build_nfa (rx, rexp->params.pair.left, &ls, &le) + && rx_build_nfa (rx, rexp->params.pair.right, &rs, &re) + && rx_nfa_edge (rx, ne_epsilon, *start, ls) + && rx_nfa_edge (rx, ne_epsilon, *start, rs) + && rx_nfa_edge (rx, ne_epsilon, le, *end) + && rx_nfa_edge (rx, ne_epsilon, re, *end)); + } + + case r_context: + edge = rx_nfa_edge (rx, ne_side_effect, *start, *end); + if (!edge) + return 0; + edge->params.side_effect = (void *)rexp->params.intval; + return 1; + } + + /* this should never happen */ + return 0; +} + + +/* {Low Level Data Structures for the Static NFA->SuperNFA Analysis} + * + * There are side effect lists -- lists of side effects occuring + * along an uninterrupted, acyclic path of side-effect epsilon edges. + * Such paths are collapsed to single edges in the course of computing + * epsilon closures. The resulting single edges are labled with a list + * of all the side effects from the original multi-edge path. Equivalent + * lists of side effects are made == by the constructors below. + * + * There are also nfa state sets. These are used to hold a list of all + * states reachable from a starting state for a given type of transition + * and side effect list. These are also hash-consed. + */ + + + +/* The next several functions compare, construct, etc. lists of side + * effects. See ECLOSE_NFA (below) for details. + */ + +/* Ordering of rx_se_list + * (-1, 0, 1 return value convention). + */ + +#ifdef __STDC__ +static int +se_list_cmp (void * va, void * vb) +#else +static int +se_list_cmp (va, vb) + void * va; + void * vb; +#endif +{ + struct rx_se_list * a = (struct rx_se_list *)va; + struct rx_se_list * b = (struct rx_se_list *)vb; + + return ((va == vb) + ? 0 + : (!va + ? -1 + : (!vb + ? 1 + : ((long)a->car < (long)b->car + ? 1 + : ((long)a->car > (long)b->car + ? -1 + : se_list_cmp ((void *)a->cdr, (void *)b->cdr)))))); +} + + +#ifdef __STDC__ +static int +se_list_equal (void * va, void * vb) +#else +static int +se_list_equal (va, vb) + void * va; + void * vb; +#endif +{ + return !(se_list_cmp (va, vb)); +} + +static struct rx_hash_rules se_list_hash_rules = { se_list_equal, 0, 0, 0, 0 }; + + +#ifdef __STDC__ +static struct rx_se_list * +side_effect_cons (struct rx * rx, + void * se, struct rx_se_list * list) +#else +static struct rx_se_list * +side_effect_cons (rx, se, list) + struct rx * rx; + void * se; + struct rx_se_list * list; +#endif +{ + struct rx_se_list * l; + l = ((struct rx_se_list *) malloc (sizeof (*l))); + if (!l) + return 0; + l->car = se; + l->cdr = list; + return l; +} + + +#ifdef __STDC__ +static struct rx_se_list * +hash_cons_se_prog (struct rx * rx, + struct rx_hash * memo, + void * car, struct rx_se_list * cdr) +#else +static struct rx_se_list * +hash_cons_se_prog (rx, memo, car, cdr) + struct rx * rx; + struct rx_hash * memo; + void * car; + struct rx_se_list * cdr; +#endif +{ + long hash = (long)car ^ (long)cdr; + struct rx_se_list template; + + template.car = car; + template.cdr = cdr; + { + struct rx_hash_item * it = rx_hash_store (memo, hash, + (void *)&template, + &se_list_hash_rules); + if (!it) + return 0; + if (it->data == (void *)&template) + { + struct rx_se_list * consed; + consed = (struct rx_se_list *) malloc (sizeof (*consed)); + *consed = template; + it->data = (void *)consed; + } + return (struct rx_se_list *)it->data; + } +} + + +#ifdef __STDC__ +static struct rx_se_list * +hash_se_prog (struct rx * rx, struct rx_hash * memo, struct rx_se_list * prog) +#else +static struct rx_se_list * +hash_se_prog (rx, memo, prog) + struct rx * rx; + struct rx_hash * memo; + struct rx_se_list * prog; +#endif +{ + struct rx_se_list * answer = 0; + while (prog) + { + answer = hash_cons_se_prog (rx, memo, prog->car, answer); + if (!answer) + return 0; + prog = prog->cdr; + } + return answer; +} + + + +/* {Constructors, etc. for NFA State Sets} + */ + +#ifdef __STDC__ +static int +nfa_set_cmp (void * va, void * vb) +#else +static int +nfa_set_cmp (va, vb) + void * va; + void * vb; +#endif +{ + struct rx_nfa_state_set * a = (struct rx_nfa_state_set *)va; + struct rx_nfa_state_set * b = (struct rx_nfa_state_set *)vb; + + return ((va == vb) + ? 0 + : (!va + ? -1 + : (!vb + ? 1 + : (a->car->id < b->car->id + ? 1 + : (a->car->id > b->car->id + ? -1 + : nfa_set_cmp ((void *)a->cdr, (void *)b->cdr)))))); +} + +#ifdef __STDC__ +static int +nfa_set_equal (void * va, void * vb) +#else +static int +nfa_set_equal (va, vb) + void * va; + void * vb; +#endif +{ + return !nfa_set_cmp (va, vb); +} + +static struct rx_hash_rules nfa_set_hash_rules = { nfa_set_equal, 0, 0, 0, 0 }; + + +#ifdef __STDC__ +static struct rx_nfa_state_set * +nfa_set_cons (struct rx * rx, + struct rx_hash * memo, struct rx_nfa_state * state, + struct rx_nfa_state_set * set) +#else +static struct rx_nfa_state_set * +nfa_set_cons (rx, memo, state, set) + struct rx * rx; + struct rx_hash * memo; + struct rx_nfa_state * state; + struct rx_nfa_state_set * set; +#endif +{ + struct rx_nfa_state_set template; + struct rx_hash_item * node; + template.car = state; + template.cdr = set; + node = rx_hash_store (memo, + (((long)state) >> 8) ^ (long)set, + &template, &nfa_set_hash_rules); + if (!node) + return 0; + if (node->data == &template) + { + struct rx_nfa_state_set * l; + l = (struct rx_nfa_state_set *) malloc (sizeof (*l)); + node->data = (void *) l; + if (!l) + return 0; + *l = template; + } + return (struct rx_nfa_state_set *)node->data; +} + + +#ifdef __STDC__ +static struct rx_nfa_state_set * +nfa_set_enjoin (struct rx * rx, + struct rx_hash * memo, struct rx_nfa_state * state, + struct rx_nfa_state_set * set) +#else +static struct rx_nfa_state_set * +nfa_set_enjoin (rx, memo, state, set) + struct rx * rx; + struct rx_hash * memo; + struct rx_nfa_state * state; + struct rx_nfa_state_set * set; +#endif +{ + if (!set || (state->id < set->car->id)) + return nfa_set_cons (rx, memo, state, set); + if (state->id == set->car->id) + return set; + else + { + struct rx_nfa_state_set * newcdr + = nfa_set_enjoin (rx, memo, state, set->cdr); + if (newcdr != set->cdr) + set = nfa_set_cons (rx, memo, set->car, newcdr); + return set; + } +} + + +/* {Computing Epsilon/Side-effect Closures.} + */ + +struct eclose_frame +{ + struct rx_se_list *prog_backwards; +}; + + +/* This is called while computing closures for "outnode". + * The current node in the traversal is "node". + * "frame" contains a list of a all side effects between + * "outnode" and "node" from most to least recent. + * + * Explores forward from "node", adding new possible + * futures to outnode. + * + * Returns 0 on allocation failure. + */ + +#ifdef __STDC__ +static int +eclose_node (struct rx *rx, struct rx_nfa_state *outnode, + struct rx_nfa_state *node, struct eclose_frame *frame) +#else +static int +eclose_node (rx, outnode, node, frame) + struct rx *rx; + struct rx_nfa_state *outnode; + struct rx_nfa_state *node; + struct eclose_frame *frame; +#endif +{ + struct rx_nfa_edge *e = node->edges; + + /* For each node, we follow all epsilon paths to build the closure. + * The closure omits nodes that have only epsilon edges. + * The closure is split into partial closures -- all the states in + * a partial closure are reached by crossing the same list of + * of side effects (though not necessarily the same path). + */ + if (node->mark) + return 1; + node->mark = 1; + + /* If "node" has more than just epsilon and + * and side-effect transitions (id >= 0), or is final, + * then it has to be added to the possible futures + * of "outnode". + */ + if (node->id >= 0 || node->is_final) + { + struct rx_possible_future **ec; + struct rx_se_list * prog_in_order; + int cmp; + + prog_in_order = ((struct rx_se_list *)hash_se_prog (rx, + &rx->se_list_memo, + frame->prog_backwards)); + + ec = &outnode->futures; + + while (*ec) + { + cmp = se_list_cmp ((void *)(*ec)->effects, (void *)prog_in_order); + if (cmp <= 0) + break; + ec = &(*ec)->next; + } + + if (!*ec || (cmp < 0)) + { + struct rx_possible_future * pf; + pf = rx_possible_future (rx, prog_in_order); + if (!pf) + return 0; + pf->next = *ec; + *ec = pf; + } + if (node->id >= 0) + { + (*ec)->destset = nfa_set_enjoin (rx, &rx->set_list_memo, + node, (*ec)->destset); + if (!(*ec)->destset) + return 0; + } + } + + /* Recurse on outgoing epsilon and side effect nodes. + */ + while (e) + { + switch (e->type) + { + case ne_epsilon: + if (!eclose_node (rx, outnode, e->dest, frame)) + return 0; + break; + case ne_side_effect: + { + frame->prog_backwards = side_effect_cons (rx, + e->params.side_effect, + frame->prog_backwards); + if (!frame->prog_backwards) + return 0; + if (!eclose_node (rx, outnode, e->dest, frame)) + return 0; + { + struct rx_se_list * dying = frame->prog_backwards; + frame->prog_backwards = frame->prog_backwards->cdr; + free ((char *)dying); + } + break; + } + default: + break; + } + e = e->next; + } + node->mark = 0; + return 1; +} + + +#ifdef __STDC__ +struct rx_possible_future * +rx_state_possible_futures (struct rx * rx, struct rx_nfa_state * n) +#else +struct rx_possible_future * +rx_state_possible_futures (rx, n) + struct rx * rx; + struct rx_nfa_state * n; +#endif +{ + if (n->futures_computed) + return n->futures; + else + { + struct eclose_frame frame; + frame.prog_backwards = 0; + if (!eclose_node (rx, n, n, &frame)) + return 0; + else + { + n->futures_computed = 1; + return n->futures; + } + } +} + + + +/* {Storing the NFA in a Contiguous Region of Memory} + */ + + + +#ifdef __STDC__ +static void +se_memo_freer (struct rx_hash_item * node) +#else +static void +se_memo_freer (node) + struct rx_hash_item * node; +#endif +{ + free ((char *)node->data); +} + + +#ifdef __STDC__ +static void +nfa_set_freer (struct rx_hash_item * node) +#else +static void +nfa_set_freer (node) + struct rx_hash_item * node; +#endif +{ + free ((char *)node->data); +} + +#ifdef __STDC__ +void +rx_free_nfa (struct rx *rx) +#else +void +rx_free_nfa (rx) + struct rx *rx; +#endif +{ + rx_free_hash_table (&rx->se_list_memo, se_memo_freer, &se_list_hash_rules); + rx_bzero ((char *)&rx->se_list_memo, sizeof (rx->se_list_memo)); + rx_free_hash_table (&rx->set_list_memo, nfa_set_freer, &nfa_set_hash_rules); + rx_bzero ((char *)&rx->set_list_memo, sizeof (rx->set_list_memo)); + rx_free_nfa_graph (rx); +} diff --git a/rx/rxnfa.h b/rx/rxnfa.h new file mode 100644 index 00000000..82a135b0 --- /dev/null +++ b/rx/rxnfa.h @@ -0,0 +1,232 @@ +/* classes: h_files */ + +#ifndef RXNFAH +#define RXNFAH +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +/* + * Tom Lord (lord@cygnus.com, lord@gnu.ai.mit.edu) + */ + +#include "_rx.h" +#include "rxnode.h" + + +/* NFA + * + * A syntax tree is compiled into an NFA. This page defines the structure + * of that NFA. + */ + +struct rx_nfa_state +{ + /* These are kept in a list as the NFA is being built. + * Here is the link. + */ + struct rx_nfa_state *next; + + /* After the NFA is built, states are given integer id's. + * States whose outgoing transitions are all either epsilon or + * side effect edges are given ids less than 0. Other states + * are given successive non-negative ids starting from 0. + * + * Here is the id for this state: + */ + int id; + + /* The list of NFA edges that go from this state to some other. */ + struct rx_nfa_edge *edges; + + /* If you land in this state, then you implicitly land + * in all other states reachable by only epsilon translations. + * Call the set of maximal loop-less paths to such states the + * epsilon closure of this state. + * + * There may be other states that are reachable by a mixture of + * epsilon and side effect edges. Consider the set of maximal loop-less + * paths of that sort from this state. Call it the epsilon-side-effect + * closure of the state. + * + * The epsilon closure of the state is a subset of the epsilon-side- + * effect closure. It consists of all the paths that contain + * no side effects -- only epsilon edges. + * + * The paths in the epsilon-side-effect closure can be partitioned + * into equivalance sets. Two paths are equivalant if they have the + * same set of side effects, in the same order. The epsilon-closure + * is one of these equivalance sets. Let's call these equivalance + * sets: observably equivalant path sets. That name is chosen + * because equivalance of two paths means they cause the same side + * effects -- so they lead to the same subsequent observations other + * than that they may wind up in different target states. + * + * The superstate nfa, which is derived from this nfa, is based on + * the observation that all of the paths in an observably equivalant + * path set can be explored at the same time, provided that the + * matcher keeps track not of a single nfa state, but of a set of + * states. In particular, after following all the paths in an + * observably equivalant set, you wind up at a set of target states. + * That set of target states corresponds to one state in the + * superstate NFA. + * + * Staticly, before matching begins, it is convenient to analyze the + * nfa. Each state is labeled with a list of the observably + * equivalant path sets who's union covers all the + * epsilon-side-effect paths beginning in this state. This list is + * called the possible futures of the state. + * + * A trivial example is this NFA: + * s1 + * A ---> B + * + * s2 + * ---> C + * + * epsilon s1 + * ---------> D ------> E + * + * + * In this example, A has two possible futures. + * One invokes the side effect `s1' and contains two paths, + * one ending in state B, the other in state E. + * The other invokes the side effect `s2' and contains only + * one path, landing in state C. + * + * Here is a list of the possible futures of this state: + */ + struct rx_possible_future *futures; + int futures_computed:1; + + + /* There is exactly one distinguished starting state in every NFA: */ + unsigned int is_start:1; + + int has_cset_edges:1; + + /* There may be many final states if the "cut" operator was used. + * each will have a different non-0 value for this field: + */ + int is_final; + + + /* These are used internally during NFA construction... */ + unsigned int eclosure_needed:1; + unsigned int mark:1; +}; + + +/* An edge in an NFA is typed: + */ +enum rx_nfa_etype +{ + /* A cset edge is labled with a set of characters one of which + * must be matched for the edge to be taken. + */ + ne_cset, + + /* An epsilon edge is taken whenever its starting state is + * reached. + */ + ne_epsilon, + + /* A side effect edge is taken whenever its starting state is + * reached. Side effects may cause the match to fail or the + * position of the matcher to advance. + */ + ne_side_effect +}; + +struct rx_nfa_edge +{ + struct rx_nfa_edge *next; + enum rx_nfa_etype type; + struct rx_nfa_state *dest; + union + { + rx_Bitset cset; + void * side_effect; + } params; +}; + + + +/* A possible future consists of a list of side effects + * and a set of destination states. Below are their + * representations. These structures are hash-consed so + * that lists with the same elements share a representation + * (their addresses are ==). + */ + +struct rx_nfa_state_set +{ + struct rx_nfa_state * car; + struct rx_nfa_state_set * cdr; +}; + +struct rx_se_list +{ + void * car; + struct rx_se_list * cdr; +}; + +struct rx_possible_future +{ + struct rx_possible_future *next; + struct rx_se_list * effects; + struct rx_nfa_state_set * destset; +}; + + + + +#ifdef __STDC__ +extern struct rx_nfa_state * rx_nfa_state (struct rx *rx); +extern struct rx_nfa_edge * rx_nfa_edge (struct rx *rx, + enum rx_nfa_etype type, + struct rx_nfa_state *start, + struct rx_nfa_state *dest); +extern int rx_build_nfa (struct rx *rx, + struct rexp_node *rexp, + struct rx_nfa_state **start, + struct rx_nfa_state **end); +extern struct rx_possible_future * rx_state_possible_futures (struct rx * rx, struct rx_nfa_state * n); +extern void rx_free_nfa (struct rx *rx); + +#else /* STDC */ +extern struct rx_nfa_state * rx_nfa_state (); +extern struct rx_nfa_edge * rx_nfa_edge (); +extern int rx_build_nfa (); +extern struct rx_possible_future * rx_state_possible_futures (); +extern void rx_free_nfa (); + +#endif /* STDC */ + + + + + + + + + + + +#endif /* RXNFAH */ diff --git a/rx/rxnode.c b/rx/rxnode.c new file mode 100644 index 00000000..a4e1cca8 --- /dev/null +++ b/rx/rxnode.c @@ -0,0 +1,545 @@ +/* classes: src_files */ + +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "rxall.h" +#include "rxnode.h" + + + +#define INITSIZE 8 +#define EXPANDSIZE 8 + +#ifdef __STDC__ +static int +rx_init_string(struct rx_string *thisone, char first) +#else +static int +rx_init_string(thisone, first) + struct rx_string *thisone; + char first; +#endif +{ + char *tmp; + + tmp = (char *) malloc (INITSIZE); + + if(!tmp) + return -1; + + thisone->contents = tmp; + thisone->contents[0] = first; + thisone->reallen = INITSIZE; + thisone->len = 1; + return 0; +} + +#ifdef __STDC__ +static void +rx_free_string (struct rx_string *junk) +#else +static void +rx_free_string (junk) + struct rx_string *junk; +#endif +{ + free (junk->contents); + junk->len = junk->reallen = 0; + junk->contents = 0; +} + + +#ifdef __STDC__ +int +rx_adjoin_string (struct rx_string *str, char c) +#else +int +rx_adjoin_string (str, c) + struct rx_string *str; + char c; +#endif +{ + if (!str->contents) + return rx_init_string (str, c); + + if (str->len == str->reallen) + { + char *temp; + temp = (char *) realloc (str->contents, str->reallen + EXPANDSIZE); + + if(!temp) + return -1; + + str->contents = temp; + str->reallen += EXPANDSIZE; + } + + str->contents[str->len++] = c; + return 0; +} + + +#ifdef __STDC__ +static int +rx_copy_string (struct rx_string *to, struct rx_string *from) +#else +static int +rx_copy_string (to, from) + struct rx_string *to; + struct rx_string *from; +#endif +{ + char *tmp; + + if (from->len) + { + tmp = (char *) malloc (from->reallen); + + if (!tmp) + return -1; + } + + rx_free_string (to); + to->len = from->len; + to->reallen = from->reallen; + to->contents = tmp; + + memcpy (to->contents, from->contents, from->reallen); + + return 0; +} + + +#ifdef __STDC__ +static int +rx_compare_rx_strings (struct rx_string *a, struct rx_string *b) +#else +static int +rx_compare_rx_strings (a, b) + struct rx_string *a; + struct rx_string *b; +#endif +{ + if (a->len != b->len) + return 0; + + if (a->len) + return !memcmp (a->contents, b->contents, a->len); + else + return 1; /* trivial case: "" == "" */ +} + + +#ifdef __STDC__ +static unsigned long +rx_string_hash (unsigned long seed, struct rx_string *str) +#else +static unsigned long +rx_string_hash (seed, str) + unsigned long seed; + struct rx_string *str; +#endif +{ + /* From Tcl: */ + unsigned long result; + int c; + char * string; + int len; + + string = str->contents; + len = str->len; + result = seed; + + while (len--) + { + c = *string; + string++; + result += (result<<3) + c; + } + return result; +} + + + + +#ifdef __STDC__ +struct rexp_node * +rexp_node (int type) +#else +struct rexp_node * +rexp_node (type) + int type; +#endif +{ + struct rexp_node *n; + + n = (struct rexp_node *) malloc (sizeof (*n)); + rx_bzero ((char *)n, sizeof (*n)); + if (n) + { + n->type = type; + n->id = -1; + n->refs = 1; + } + return n; +} + + +/* free_rexp_node assumes that the bitset passed to rx_mk_r_cset + * can be freed using rx_free_cset. + */ + +#ifdef __STDC__ +struct rexp_node * +rx_mk_r_cset (int type, int size, rx_Bitset b) +#else +struct rexp_node * +rx_mk_r_cset (type, size, b) + int type; + int size; + rx_Bitset b; +#endif +{ + struct rexp_node * n; + n = rexp_node (type); + if (n) + { + n->params.cset = b; + n->params.cset_size = size; + } + return n; +} + + +#ifdef __STDC__ +struct rexp_node * +rx_mk_r_int (int type, int intval) +#else +struct rexp_node * +rx_mk_r_int (type, intval) + int type; + int intval; +#endif +{ + struct rexp_node * n; + n = rexp_node (type); + if (n) + n->params.intval = intval; + return n; +} + + +#ifdef __STDC__ +struct rexp_node * +rx_mk_r_str (int type, char c) +#else +struct rexp_node * +rx_mk_r_str (type, c) + int type; + char c; +#endif +{ + struct rexp_node *n; + n = rexp_node (type); + if (n) + rx_init_string (&(n->params.cstr), c); + return n; +} + + +#ifdef __STDC__ +struct rexp_node * +rx_mk_r_binop (int type, struct rexp_node * a, struct rexp_node * b) +#else +struct rexp_node * +rx_mk_r_binop (type, a, b) + int type; + struct rexp_node * a; + struct rexp_node * b; +#endif +{ + struct rexp_node * n = rexp_node (type); + if (n) + { + n->params.pair.left = a; + n->params.pair.right = b; + } + return n; +} + + +#ifdef __STDC__ +struct rexp_node * +rx_mk_r_monop (int type, struct rexp_node * a) +#else +struct rexp_node * +rx_mk_r_monop (type, a) + int type; + struct rexp_node * a; +#endif +{ + return rx_mk_r_binop (type, a, 0); +} + + +#ifdef __STDC__ +void +rx_free_rexp (struct rexp_node * node) +#else +void +rx_free_rexp (node) + struct rexp_node * node; +#endif +{ + if (node && !--node->refs) + { + if (node->params.cset) + rx_free_cset (node->params.cset); + if (node->params.cstr.reallen) + rx_free_string (&(node->params.cstr)); + rx_free_rexp (node->params.pair.left); + rx_free_rexp (node->params.pair.right); + rx_free_rexp (node->simplified); + free ((char *)node); + } +} + +#ifdef __STDC__ +void +rx_save_rexp (struct rexp_node * node) +#else +void +rx_save_rexp (node) + struct rexp_node * node; +#endif +{ + if (node) + ++node->refs; +} + + +#ifdef __STDC__ +struct rexp_node * +rx_copy_rexp (int cset_size, struct rexp_node *node) +#else +struct rexp_node * +rx_copy_rexp (cset_size, node) + int cset_size; + struct rexp_node *node; +#endif +{ + if (!node) + return 0; + else + { + struct rexp_node *n; + n = rexp_node (node->type); + if (!n) + return 0; + + if (node->params.cset) + { + n->params.cset = rx_copy_cset (cset_size, + node->params.cset); + if (!n->params.cset) + { + rx_free_rexp (n); + return 0; + } + } + + if (node->params.cstr.reallen) + if (rx_copy_string (&(n->params.cstr), &(node->params.cstr))) + { + rx_free_rexp(n); + return 0; + } + + n->params.intval = node->params.intval; + n->params.intval2 = node->params.intval2; + n->params.pair.left = rx_copy_rexp (cset_size, node->params.pair.left); + n->params.pair.right = rx_copy_rexp (cset_size, node->params.pair.right); + if ( (node->params.pair.left && !n->params.pair.left) + || (node->params.pair.right && !n->params.pair.right)) + { + rx_free_rexp (n); + return 0; + } + n->id = node->id; + n->len = node->len; + n->observed = node->observed; + return n; + } +} + + + +#ifdef __STDC__ +struct rexp_node * +rx_shallow_copy_rexp (int cset_size, struct rexp_node *node) +#else +struct rexp_node * +rx_shallow_copy_rexp (cset_size, node) + int cset_size; + struct rexp_node *node; +#endif +{ + if (!node) + return 0; + else + { + struct rexp_node *n; + n = rexp_node (node->type); + if (!n) + return 0; + + if (node->params.cset) + { + n->params.cset = rx_copy_cset (cset_size, + node->params.cset); + if (!n->params.cset) + { + rx_free_rexp (n); + return 0; + } + } + + if (node->params.cstr.reallen) + if (rx_copy_string (&(n->params.cstr), &(node->params.cstr))) + { + rx_free_rexp(n); + return 0; + } + + n->params.intval = node->params.intval; + n->params.intval2 = node->params.intval2; + n->params.pair.left = node->params.pair.left; + rx_save_rexp (node->params.pair.left); + n->params.pair.right = node->params.pair.right; + rx_save_rexp (node->params.pair.right); + n->id = node->id; + n->len = node->len; + n->observed = node->observed; + return n; + } +} + + + + +#ifdef __STDC__ +int +rx_rexp_equal (struct rexp_node * a, struct rexp_node * b) +#else +int +rx_rexp_equal (a, b) + struct rexp_node * a; + struct rexp_node * b; +#endif +{ + int ret; + + if (a == b) + return 1; + + if ((a == 0) || (b == 0)) + return 0; + + if (a->type != b->type) + return 0; + + switch (a->type) + { + case r_cset: + ret = ( (a->params.cset_size == b->params.cset_size) + && rx_bitset_is_equal (a->params.cset_size, + a->params.cset, + b->params.cset)); + break; + + case r_string: + ret = rx_compare_rx_strings (&(a->params.cstr), &(b->params.cstr)); + break; + + case r_cut: + ret = (a->params.intval == b->params.intval); + break; + + case r_concat: + case r_alternate: + ret = ( rx_rexp_equal (a->params.pair.left, b->params.pair.left) + && rx_rexp_equal (a->params.pair.right, b->params.pair.right)); + break; + case r_opt: + case r_star: + case r_plus: + ret = rx_rexp_equal (a->params.pair.left, b->params.pair.left); + break; + case r_interval: + ret = ( (a->params.intval == b->params.intval) + && (a->params.intval2 == b->params.intval2) + && rx_rexp_equal (a->params.pair.left, b->params.pair.left)); + break; + case r_parens: + ret = ( (a->params.intval == b->params.intval) + && rx_rexp_equal (a->params.pair.left, b->params.pair.left)); + break; + + case r_context: + ret = (a->params.intval == b->params.intval); + break; + default: + return 0; + } + return ret; +} + + + + + +#ifdef __STDC__ +unsigned long +rx_rexp_hash (struct rexp_node * node, unsigned long seed) +#else + unsigned long + rx_rexp_hash (node, seed) + struct rexp_node * node; + unsigned long seed; +#endif +{ + if (!node) + return seed; + + seed = rx_rexp_hash (node->params.pair.left, seed); + seed = rx_rexp_hash (node->params.pair.right, seed); + seed = rx_bitset_hash (node->params.cset_size, node->params.cset); + seed = rx_string_hash (seed, &(node->params.cstr)); + seed += (seed << 3) + node->params.intval; + seed += (seed << 3) + node->params.intval2; + seed += (seed << 3) + node->type; + seed += (seed << 3) + node->id; +#if 0 + seed += (seed << 3) + node->len; + seed += (seed << 3) + node->observed; +#endif + return seed; +} diff --git a/rx/rxnode.h b/rx/rxnode.h new file mode 100644 index 00000000..ea3a1fcb --- /dev/null +++ b/rx/rxnode.h @@ -0,0 +1,125 @@ +/* classes: h_files */ + +#ifndef RXNODEH +#define RXNODEH +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +/* + * Tom Lord (lord@cygnus.com, lord@gnu.ai.mit.edu) + */ + + +#include "rxbitset.h" +#include "rxcset.h" + + + +enum rexp_node_type +{ + r_cset = 0, /* Match from a character set. `a' or `[a-z]'*/ + r_concat = 1, /* Concat two subexpressions. `ab' */ + r_alternate = 2, /* Choose one of two subexpressions. `a\|b' */ + r_opt = 3, /* Optional subexpression. `a?' */ + r_star = 4, /* Repeated subexpression. `a*' */ + r_plus = 5, /* Nontrivially repeated subexpression. `a+' */ + r_string = 6, /* Shorthand for a concatenation of characters */ + r_cut = 7, /* Generates a tagged, final nfa state. */ + + /* see RX_regular_node_type */ + + r_interval = 8, /* Counted subexpression. `a{4, 1000}' */ + r_parens = 9, /* Parenthesized subexpression */ + r_context = 10 /* Context-sensative operator such as "^" */ +}; + +#define RX_regular_node_type(T) ((T) <= r_interval) + + + +struct rx_string +{ + unsigned long len; + unsigned long reallen; + unsigned char *contents; +}; + +struct rexp_node +{ + int refs; + enum rexp_node_type type; + struct + { + int cset_size; + rx_Bitset cset; + int intval; + int intval2; + struct + { + struct rexp_node *left; + struct rexp_node *right; + } pair; + struct rx_string cstr; + } params; + int id; + int len; + int observed; + struct rexp_node * simplified; + struct rx_cached_rexp * cr; +}; + + + +#ifdef __STDC__ +extern int rx_adjoin_string (struct rx_string *str, char c); +extern struct rexp_node * rexp_node (int type); +extern struct rexp_node * rx_mk_r_cset (int type, int size, rx_Bitset b); +extern struct rexp_node * rx_mk_r_int (int type, int intval); +extern struct rexp_node * rx_mk_r_str (int type, char c); +extern struct rexp_node * rx_mk_r_binop (int type, struct rexp_node * a, struct rexp_node * b); +extern struct rexp_node * rx_mk_r_monop (int type, struct rexp_node * a); +extern void rx_free_rexp (struct rexp_node * node); +extern void rx_save_rexp (struct rexp_node * node); +extern struct rexp_node * rx_copy_rexp (int cset_size, struct rexp_node *node); +extern struct rexp_node * rx_shallow_copy_rexp (int cset_size, struct rexp_node *node); +extern int rx_rexp_equal (struct rexp_node * a, struct rexp_node * b); +extern unsigned long rx_rexp_hash (struct rexp_node * node, unsigned long seed); + +#else /* STDC */ +extern int rx_adjoin_string (); +extern struct rexp_node * rexp_node (); +extern struct rexp_node * rx_mk_r_cset (); +extern struct rexp_node * rx_mk_r_int (); +extern struct rexp_node * rx_mk_r_str (); +extern struct rexp_node * rx_mk_r_binop (); +extern struct rexp_node * rx_mk_r_monop (); +extern void rx_free_rexp (); +extern void rx_save_rexp (); +extern struct rexp_node * rx_copy_rexp (); +extern struct rexp_node * rx_shallow_copy_rexp (); +extern int rx_rexp_equal (); +extern unsigned long rx_rexp_hash (); + +#endif /* STDC */ + + + + +#endif /* RXNODEH */ diff --git a/rx/rxposix.c b/rx/rxposix.c new file mode 100644 index 00000000..17a7e108 --- /dev/null +++ b/rx/rxposix.c @@ -0,0 +1,484 @@ +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "rxall.h" +#include "rxposix.h" +#include "rxgnucomp.h" +#include "rxbasic.h" +#include "rxsimp.h" + +/* regcomp takes a regular expression as a string and compiles it. + * + * PATTERN is the address of the pattern string. + * + * CFLAGS is a series of bits which affect compilation. + * + * If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we + * use POSIX basic syntax. + * + * If REG_NEWLINE is set, then . and [^...] don't match newline. + * Also, regexec will try a match beginning after every newline. + * + * If REG_ICASE is set, then we considers upper- and lowercase + * versions of letters to be equivalent when matching. + * + * If REG_NOSUB is set, then when PREG is passed to regexec, that + * routine will report only success or failure, and nothing about the + * registers. + * + * It returns 0 if it succeeds, nonzero if it doesn't. (See regex.h for + * the return codes and their meanings.) + */ + + +#ifdef __STDC__ +int +regncomp (regex_t * preg, const char * pattern, int len, int cflags) +#else +int +regncomp (preg, pattern, len, cflags) + regex_t * preg; + const char * pattern; + int len; + int cflags; +#endif +{ + int ret; + unsigned int syntax; + + rx_bzero ((char *)preg, sizeof (*preg)); + syntax = ((cflags & REG_EXTENDED) + ? RE_SYNTAX_POSIX_EXTENDED + : RE_SYNTAX_POSIX_BASIC); + + if (!(cflags & REG_ICASE)) + preg->translate = 0; + else + { + unsigned i; + + preg->translate = (unsigned char *) malloc (256); + if (!preg->translate) + return (int) REG_ESPACE; + + /* Map uppercase characters to corresponding lowercase ones. */ + for (i = 0; i < CHAR_SET_SIZE; i++) + preg->translate[i] = isupper (i) ? tolower (i) : i; + } + + + /* If REG_NEWLINE is set, newlines are treated differently. */ + if (!(cflags & REG_NEWLINE)) + preg->newline_anchor = 0; + else + { + /* REG_NEWLINE implies neither . nor [^...] match newline. */ + syntax &= ~RE_DOT_NEWLINE; + syntax |= RE_HAT_LISTS_NOT_NEWLINE; + /* It also changes the matching behavior. */ + preg->newline_anchor = 1; + } + + preg->no_sub = !!(cflags & REG_NOSUB); + + ret = rx_parse (&preg->pattern, + pattern, len, + syntax, + 256, + preg->translate); + + /* POSIX doesn't distinguish between an unmatched open-group and an + * unmatched close-group: both are REG_EPAREN. + */ + if (ret == REG_ERPAREN) + ret = REG_EPAREN; + + if (!ret) + { + preg->re_nsub = 1; + preg->subexps = 0; + rx_posix_analyze_rexp (&preg->subexps, + &preg->re_nsub, + preg->pattern, + 0); + preg->is_nullable = rx_fill_in_fastmap (256, + preg->fastmap, + preg->pattern); + + preg->is_anchored = rx_is_anchored_p (preg->pattern); + } + + return (int) ret; +} + + +#ifdef __STDC__ +int +regcomp (regex_t * preg, const char * pattern, int cflags) +#else +int +regcomp (preg, pattern, cflags) + regex_t * preg; + const char * pattern; + int cflags; +#endif +{ + /* POSIX says a null character in the pattern terminates it, so we + * can use strlen here in compiling the pattern. + */ + + return regncomp (preg, pattern, strlen (pattern), cflags); +} + + + + +/* Returns a message corresponding to an error code, ERRCODE, returned + from either regcomp or regexec. */ + +#ifdef __STDC__ +size_t +regerror (int errcode, const regex_t *preg, + char *errbuf, size_t errbuf_size) +#else +size_t +regerror (errcode, preg, errbuf, errbuf_size) + int errcode; + const regex_t *preg; + char *errbuf; + size_t errbuf_size; +#endif +{ + const char *msg; + size_t msg_size; + + msg = rx_error_msg[errcode] == 0 ? "Success" : rx_error_msg[errcode]; + msg_size = strlen (msg) + 1; /* Includes the 0. */ + if (errbuf_size != 0) + { + if (msg_size > errbuf_size) + { + strncpy (errbuf, msg, errbuf_size - 1); + errbuf[errbuf_size - 1] = 0; + } + else + strcpy (errbuf, msg); + } + return msg_size; +} + + + +#ifdef __STDC__ +int +rx_regmatch (regmatch_t pmatch[], const regex_t *preg, struct rx_context_rules * rules, int start, int end, const char *string) +#else +int +rx_regmatch (pmatch, preg, rules, start, end, string) + regmatch_t pmatch[]; + const regex_t *preg; + struct rx_context_rules * rules; + int start; + int end; + const char *string; +#endif +{ + struct rx_solutions * solutions; + enum rx_answers answer; + struct rx_context_rules local_rules; + int orig_end; + int end_lower_bound; + int end_upper_bound; + + local_rules = *rules; + orig_end = end; + + if (!preg->pattern) + { + end_lower_bound = start; + end_upper_bound = start; + } + else if (preg->pattern->len >= 0) + { + end_lower_bound = start + preg->pattern->len; + end_upper_bound = start + preg->pattern->len; + } + else + { + end_lower_bound = start; + end_upper_bound = end; + } + end = end_upper_bound; + while (end >= end_lower_bound) + { + local_rules.not_eol = (rules->not_eol + ? ( (end == orig_end) + || !local_rules.newline_anchor + || (string[end] != '\n')) + : ( (end != orig_end) + && (!local_rules.newline_anchor + || (string[end] != '\n')))); + solutions = rx_basic_make_solutions (pmatch, preg->pattern, preg->subexps, + start, end, &local_rules, string); + if (!solutions) + return REG_ESPACE; + + answer = rx_next_solution (solutions); + + if (answer == rx_yes) + { + if (pmatch) + { + pmatch[0].rm_so = start; + pmatch[0].rm_eo = end; + pmatch[0].final_tag = solutions->final_tag; + } + rx_basic_free_solutions (solutions); + return 0; + } + else + rx_basic_free_solutions (solutions); + + --end; + } + + switch (answer) + { + default: + case rx_bogus: + return REG_ESPACE; + + case rx_no: + return REG_NOMATCH; + } +} + + +#ifdef __STDC__ +int +rx_regexec (regmatch_t pmatch[], const regex_t *preg, struct rx_context_rules * rules, int start, int end, const char *string) +#else +int +rx_regexec (pmatch, preg, rules, start, end, string) + regmatch_t pmatch[]; + const regex_t *preg; + struct rx_context_rules * rules; + int start; + int end; + const char *string; +#endif +{ + int x; + int stat; + int anchored; + struct rexp_node * simplified; + struct rx_unfa * unfa; + struct rx_classical_system machine; + + anchored = preg->is_anchored; + + unfa = 0; + if ((end - start) > RX_MANY_CASES) + { + if (0 > rx_simple_rexp (&simplified, 256, preg->pattern, preg->subexps)) + return REG_ESPACE; + unfa = rx_unfa (rx_basic_unfaniverse (), simplified, 256); + if (!unfa) + { + rx_free_rexp (simplified); + return REG_ESPACE; + } + rx_init_system (&machine, unfa->nfa); + rx_free_rexp (simplified); + } + + for (x = start; x <= end; ++x) + { + if (preg->is_nullable + || ((x < end) + && (preg->fastmap[((unsigned char *)string)[x]]))) + { + if ((end - start) > RX_MANY_CASES) + { + int amt; + if (rx_start_superstate (&machine) != rx_yes) + { + rx_free_unfa (unfa); + return REG_ESPACE; + } + amt = rx_advance_to_final (&machine, string + x, end - start - x); + if (!machine.final_tag && (amt < (end - start - x))) + goto nomatch; + } + stat = rx_regmatch (pmatch, preg, rules, x, end, string); + if (!stat || (stat != REG_NOMATCH)) + { + rx_free_unfa (unfa); + return stat; + } + } + nomatch: + if (anchored) + if (!preg->newline_anchor) + { + rx_free_unfa (unfa); + return REG_NOMATCH; + } + else + while (x < end) + if (string[x] == '\n') + break; + else + ++x; + } + rx_free_unfa (unfa); + return REG_NOMATCH; +} + + + +/* regexec searches for a given pattern, specified by PREG, in the + * string STRING. + * + * If NMATCH is zero or REG_NOSUB was set in the cflags argument to + * `regcomp', we ignore PMATCH. Otherwise, we assume PMATCH has at + * least NMATCH elements, and we set them to the offsets of the + * corresponding matched substrings. + * + * EFLAGS specifies `execution flags' which affect matching: if + * REG_NOTBOL is set, then ^ does not match at the beginning of the + * string; if REG_NOTEOL is set, then $ does not match at the end. + * + * We return 0 if we find a match and REG_NOMATCH if not. + */ + +#ifdef __STDC__ +int +regnexec (const regex_t *preg, const char *string, int len, size_t nmatch, regmatch_t **pmatch, int eflags) +#else +int +regnexec (preg, string, len, nmatch, pmatch, eflags) + const regex_t *preg; + const char *string; + int len; + size_t nmatch; + regmatch_t **pmatch; + int eflags; +#endif +{ + int want_reg_info; + struct rx_context_rules rules; + regmatch_t * regs; + size_t nregs; + int stat; + + want_reg_info = (!preg->no_sub && (nmatch > 0)); + + rules.newline_anchor = preg->newline_anchor; + rules.not_bol = !!(eflags & REG_NOTBOL); + rules.not_eol = !!(eflags & REG_NOTEOL); + rules.case_indep = !!(eflags & REG_ICASE); + + if (nmatch >= preg->re_nsub) + { + regs = *pmatch; + nregs = nmatch; + } + else + { + regs = (regmatch_t *)malloc (preg->re_nsub * sizeof (*regs)); + if (!regs) + return REG_ESPACE; + nregs = preg->re_nsub; + } + + { + int x; + for (x = 0; x < nregs; ++x) + regs[x].rm_so = regs[x].rm_eo = -1; + } + + + stat = rx_regexec (regs, preg, &rules, 0, len, string); + + if (!stat && want_reg_info && pmatch && (regs != *pmatch)) + { + size_t x; + for (x = 0; x < nmatch; ++x) + (*pmatch)[x] = regs[x]; + } + + if (!stat && (eflags & REG_ALLOC_REGS)) + *pmatch = regs; + else if (regs && (!pmatch || (regs != *pmatch))) + free (regs); + + return stat; +} + +#ifdef __STDC__ +int +regexec (const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags) +#else +int +regexec (preg, string, nmatch, pmatch, eflags) + const regex_t *preg; + const char *string; + size_t nmatch; + regmatch_t pmatch[]; + int eflags; +#endif +{ + return regnexec (preg, + string, + strlen (string), + nmatch, + &pmatch, + (eflags & ~REG_ALLOC_REGS)); +} + + +/* Free dynamically allocated space used by PREG. */ + +#ifdef __STDC__ +void +regfree (regex_t *preg) +#else +void +regfree (preg) + regex_t *preg; +#endif +{ + if (preg->pattern) + { + rx_free_rexp (preg->pattern); + preg->pattern = 0; + } + if (preg->subexps) + { + free (preg->subexps); + preg->subexps = 0; + } + if (preg->translate != 0) + { + free (preg->translate); + preg->translate = 0; + } +} diff --git a/rx/rxposix.h b/rx/rxposix.h new file mode 100644 index 00000000..9edcfb5d --- /dev/null +++ b/rx/rxposix.h @@ -0,0 +1,51 @@ +/* classes: h_files */ + +#ifndef RXPOSIXH +#define RXPOSIXH +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#include "rxspencer.h" +#include "rxcontext.h" +#include "inst-rxposix.h" + +#ifdef __STDC__ +extern int regncomp (regex_t * preg, const char * pattern, int len, int cflags); +extern int regcomp (regex_t * preg, const char * pattern, int cflags); +extern size_t regerror (int errcode, const regex_t *preg, + char *errbuf, size_t errbuf_size); +extern int rx_regmatch (regmatch_t pmatch[], const regex_t *preg, struct rx_context_rules * rules, int start, int end, const char *string); +extern int rx_regexec (regmatch_t pmatch[], const regex_t *preg, struct rx_context_rules * rules, int start, int end, const char *string); +extern int regnexec (const regex_t *preg, const char *string, int len, size_t nmatch, regmatch_t **pmatch, int eflags); +extern int regexec (const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags); +extern void regfree (regex_t *preg); + +#else /* STDC */ +extern int regncomp (); +extern int regcomp (); +extern size_t regerror (); +extern int rx_regmatch (); +extern int rx_regexec (); +extern int regnexec (); +extern int regexec (); +extern void regfree (); + +#endif /* STDC */ + +#endif /* RXPOSIXH */ diff --git a/rx/rxproto.h b/rx/rxproto.h new file mode 100644 index 00000000..25f5b576 --- /dev/null +++ b/rx/rxproto.h @@ -0,0 +1,41 @@ +/* classes: h_files */ + +#ifndef RXPROTOH +#define RXPROTOH +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + + +#ifdef __STDC__ +#define P(X) X +#else +#define P(X) () +#endif + + + +#ifdef __STDC__ + +#else /* STDC */ + +#endif /* STDC */ + + +#endif /* RXPROTOH */ diff --git a/rx/rxsimp.c b/rx/rxsimp.c new file mode 100644 index 00000000..5041c354 --- /dev/null +++ b/rx/rxsimp.c @@ -0,0 +1,147 @@ +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "rxall.h" +#include "rxsimp.h" + + + + +/* Could reasonably hashcons instead of in rxunfa.c */ + +#ifdef __STDC__ +int +rx_simple_rexp (struct rexp_node ** answer, + int cset_size, + struct rexp_node *node, + struct rexp_node ** subexps) +#else +int +rx_simple_rexp (answer, cset_size, node, subexps) + struct rexp_node ** answer; + int cset_size; + struct rexp_node *node; + struct rexp_node ** subexps; +#endif +{ + int stat; + + if (!node) + { + *answer = 0; + return 0; + } + + if (!node->observed) + { + rx_save_rexp (node); + *answer = node; + return 0; + } + + if (node->simplified) + { + rx_save_rexp (node->simplified); + *answer = node->simplified; + return 0; + } + + switch (node->type) + { + default: + case r_cset: + case r_string: + case r_cut: + return -2; /* an internal error, really */ + + case r_parens: + stat = rx_simple_rexp (answer, cset_size, + node->params.pair.left, + subexps); + break; + + case r_context: + if (isdigit (node->params.intval)) + stat = rx_simple_rexp (answer, cset_size, + subexps [node->params.intval - '0'], + subexps); + else + { + *answer = 0; + stat = 0; + } + break; + + case r_concat: + case r_alternate: + case r_opt: + case r_star: + case r_plus: + case r_interval: + { + struct rexp_node *n; + n = rexp_node (node->type); + if (!n) + return -1; + + if (node->params.cset) + { + n->params.cset = rx_copy_cset (cset_size, + node->params.cset); + if (!n->params.cset) + { + rx_free_rexp (n); + return -1; + } + } + n->params.intval = node->params.intval; + n->params.intval2 = node->params.intval2; + { + int s; + + s = rx_simple_rexp (&n->params.pair.left, cset_size, + node->params.pair.left, subexps); + if (!s) + s = rx_simple_rexp (&n->params.pair.right, cset_size, + node->params.pair.right, subexps); + if (!s) + { + *answer = n; + stat = 0; + } + else + { + rx_free_rexp (n); + stat = s; + } + } + } + break; + } + + if (!stat) + { + node->simplified = *answer; + rx_save_rexp (node->simplified); + } + return stat; +} + + diff --git a/rx/rxsimp.h b/rx/rxsimp.h new file mode 100644 index 00000000..2b167371 --- /dev/null +++ b/rx/rxsimp.h @@ -0,0 +1,44 @@ +/* classes: h_files */ + +#ifndef RXSIMPH +#define RXSIMPH +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "rxcset.h" +#include "rxnode.h" + + +#ifdef __STDC__ +extern int rx_simple_rexp (struct rexp_node ** answer, + int cset_size, + struct rexp_node *node, + struct rexp_node ** subexps); + +#else /* STDC */ +extern int rx_simple_rexp (); + +#endif /* STDC */ + + + + + +#endif /* RXSIMPH */ diff --git a/rx/rxspencer.c b/rx/rxspencer.c new file mode 100644 index 00000000..0e2805c6 --- /dev/null +++ b/rx/rxspencer.c @@ -0,0 +1,1191 @@ +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include <stdio.h> +#include "rxall.h" +#include "rxspencer.h" +#include "rxsimp.h" + + + +static char * silly_hack_2 = 0; + +struct rx_solutions rx_no_solutions; + +#ifdef __STDC__ +struct rx_solutions * +rx_make_solutions (struct rx_registers * regs, struct rx_unfaniverse * verse, struct rexp_node * expression, struct rexp_node ** subexps, int cset_size, int start, int end, rx_vmfn vmfn, rx_contextfn contextfn, void * closure) +#else +struct rx_solutions * +rx_make_solutions (regs, verse, expression, subexps, cset_size, + start, end, vmfn, contextfn, closure) + struct rx_registers * regs; + struct rx_unfaniverse * verse; + struct rexp_node * expression; + struct rexp_node ** subexps; + int cset_size; + int start; + int end; + rx_vmfn vmfn; + rx_contextfn contextfn; + void * closure; +#endif +{ + struct rx_solutions * solns; + + if ( expression + && (expression->len >= 0) + && (expression->len != (end - start))) + return &rx_no_solutions; + + if (silly_hack_2) + { + solns = (struct rx_solutions *)silly_hack_2; + silly_hack_2 = 0; + } + else + solns = (struct rx_solutions *)malloc (sizeof (*solns)); + if (!solns) + return 0; + rx_bzero ((char *)solns, sizeof (*solns)); + + solns->step = 0; + solns->cset_size = cset_size; + solns->subexps = subexps; + solns->exp = expression; + rx_save_rexp (expression); + solns->verse = verse; + solns->regs = regs; + solns->start = start; + solns->end = end; + solns->vmfn = vmfn; + solns->contextfn = contextfn; + solns->closure = closure; + + if (!solns->exp || !solns->exp->observed) + { + solns->dfa = rx_unfa (verse, expression, cset_size); + if (!solns->dfa) + goto err_return; + rx_init_system (&solns->match_engine, solns->dfa->nfa); + + if (rx_yes != rx_start_superstate (&solns->match_engine)) + goto err_return; + } + else + { + struct rexp_node * simplified; + int status; + status = rx_simple_rexp (&simplified, cset_size, solns->exp, subexps); + if (status) + goto err_return; + solns->dfa = rx_unfa (verse, simplified, cset_size); + if (!solns->dfa) + { + rx_free_rexp (simplified); + goto err_return; + } + rx_init_system (&solns->match_engine, solns->dfa->nfa); + if (rx_yes != rx_start_superstate (&solns->match_engine)) + goto err_return; + rx_free_rexp (simplified); + } + + if (expression && ( (expression->type == r_concat) + || (expression->type == r_plus) + || (expression->type == r_star) + || (expression->type == r_interval))) + { + struct rexp_node * subexp; + + subexp = solns->exp->params.pair.left; + + if (!subexp || !subexp->observed) + { + solns->left_dfa = rx_unfa (solns->verse, subexp, solns->cset_size); + } + else + { + struct rexp_node * simplified; + int status; + status = rx_simple_rexp (&simplified, solns->cset_size, subexp, solns->subexps); + if (status) + goto err_return; + solns->left_dfa = rx_unfa (solns->verse, simplified, solns->cset_size); + rx_free_rexp (simplified); + } + + if (!solns->left_dfa) + goto err_return; + rx_bzero ((char *)&solns->left_match_engine, sizeof (solns->left_match_engine)); + rx_init_system (&solns->left_match_engine, solns->left_dfa->nfa); + } + + return solns; + + err_return: + rx_free_rexp (solns->exp); + free (solns); + return 0; +} + + + +#ifdef __STDC__ +void +rx_free_solutions (struct rx_solutions * solns) +#else +void +rx_free_solutions (solns) + struct rx_solutions * solns; +#endif +{ + if (!solns) + return; + + if (solns == &rx_no_solutions) + return; + + if (solns->left) + { + rx_free_solutions (solns->left); + solns->left = 0; + } + + if (solns->right) + { + rx_free_solutions (solns->right); + solns->right = 0; + } + + if (solns->dfa) + { + rx_free_unfa (solns->dfa); + solns->dfa = 0; + } + if (solns->left_dfa) + { + rx_terminate_system (&solns->left_match_engine); + rx_free_unfa (solns->left_dfa); + solns->left_dfa = 0; + } + + rx_terminate_system (&solns->match_engine); + + if (solns->exp) + { + rx_free_rexp (solns->exp); + solns->exp = 0; + } + + if (!silly_hack_2) + silly_hack_2 = (char *)solns; + else + free (solns); +} + + +#ifdef __STDC__ +static enum rx_answers +rx_solution_fit_p (struct rx_solutions * solns) +#else +static enum rx_answers +rx_solution_fit_p (solns) + struct rx_solutions * solns; +#endif +{ + unsigned const char * burst; + int burst_addr; + int burst_len; + int burst_end_addr; + int rel_pos_in_burst; + enum rx_answers vmstat; + int current_pos; + + current_pos = solns->start; + next_burst: + vmstat = solns->vmfn (solns->closure, + &burst, &burst_len, &burst_addr, + current_pos, solns->end, + current_pos); + + if (vmstat != rx_yes) + return vmstat; + + rel_pos_in_burst = current_pos - burst_addr; + burst_end_addr = burst_addr + burst_len; + + if (burst_end_addr >= solns->end) + { + enum rx_answers fit_status; + fit_status = rx_fit_p (&solns->match_engine, + burst + rel_pos_in_burst, + solns->end - current_pos); + return fit_status; + } + else + { + enum rx_answers fit_status; + fit_status = rx_advance (&solns->match_engine, + burst + rel_pos_in_burst, + burst_len - rel_pos_in_burst); + if (fit_status != rx_yes) + { + return fit_status; + } + else + { + current_pos += burst_len - rel_pos_in_burst; + goto next_burst; + } + } +} + + +#ifdef __STDC__ +static enum rx_answers +rx_solution_fit_str_p (struct rx_solutions * solns) +#else +static enum rx_answers +rx_solution_fit_str_p (solns) + struct rx_solutions * solns; +#endif +{ + int current_pos; + unsigned const char * burst; + int burst_addr; + int burst_len; + int burst_end_addr; + int rel_pos_in_burst; + enum rx_answers vmstat; + int count; + unsigned char * key; + + + current_pos = solns->start; + count = solns->exp->params.cstr.len; + key = (unsigned char *)solns->exp->params.cstr.contents; + + next_burst: + vmstat = solns->vmfn (solns->closure, + &burst, &burst_len, &burst_addr, + current_pos, solns->end, + current_pos); + + if (vmstat != rx_yes) + return vmstat; + + rel_pos_in_burst = current_pos - burst_addr; + burst_end_addr = burst_addr + burst_len; + + { + unsigned const char * pos; + + pos = burst + rel_pos_in_burst; + + if (burst_end_addr >= solns->end) + { + while (count) + { + if (*pos != *key) + return rx_no; + ++pos; + ++key; + --count; + } + return rx_yes; + } + else + { + int part_count; + int part_count_init; + + part_count_init = burst_len - rel_pos_in_burst; + part_count = part_count_init; + while (part_count) + { + if (*pos != *key) + return rx_no; + ++pos; + ++key; + --part_count; + } + count -= part_count_init; + current_pos += burst_len - rel_pos_in_burst; + goto next_burst; + } + } +} + + + + +#if 0 +#ifdef __STDC__ +int +rx_best_end_guess (struct rx_solutions * solns, struct rexp_node * exp, int bound) +#else +int +rx_best_end_guess (solns, exp, bound) + struct rx_solutions * solns; + struct rexp_node * exp; + int bound; +#endif +{ + int current_pos; + unsigned const char * burst; + int burst_addr; + int burst_len; + int burst_end_addr; + int rel_pos_in_burst; + int best_guess; + enum rx_answers vmstat; + +#if 0 + unparse_print_rexp (256, solns->exp); + printf ("\n"); + unparse_print_rexp (256, exp); + printf ("\nbound %d \n", bound); +#endif + + if (rx_yes != rx_start_superstate (&solns->left_match_engine)) + { + return bound - 1; + } + best_guess = current_pos = solns->start; + + next_burst: +#if 0 + printf (" best_guess %d\n", best_guess); +#endif + vmstat = solns->vmfn (solns->closure, + &burst, &burst_len, &burst_addr, + current_pos, bound, + current_pos); +#if 0 + printf (" str>%s\n", burst); +#endif + if (vmstat != rx_yes) + { + return bound - 1; + } + + rel_pos_in_burst = current_pos - burst_addr; + burst_end_addr = burst_addr + burst_len; + + if (burst_end_addr > bound) + { + burst_end_addr = bound; + burst_len = bound - burst_addr; + } + + { + int amt_advanced; + +#if 0 + printf (" rel_pos_in_burst %d burst_len %d\n", rel_pos_in_burst, burst_len); +#endif + while (rel_pos_in_burst < burst_len) + { + amt_advanced= rx_advance_to_final (&solns->left_match_engine, + burst + rel_pos_in_burst, + burst_len - rel_pos_in_burst); +#if 0 + printf (" amt_advanced %d", amt_advanced); +#endif + if (amt_advanced < 0) + { + return bound - 1; + } + current_pos += amt_advanced; + rel_pos_in_burst += amt_advanced; + if (solns->left_match_engine.final_tag) + best_guess = current_pos; +#if 0 + printf (" best_guess %d\n", best_guess); + printf (" current_pos %d\n", current_pos); +#endif + if (amt_advanced == 0) + { + return best_guess; + } + } + if (current_pos == bound) + { + return best_guess; + } + goto next_burst; + } +} +#endif + + + +#ifdef __STDC__ +enum rx_answers +rx_next_solution (struct rx_solutions * solns) +#else +enum rx_answers +rx_next_solution (solns) + struct rx_solutions * solns; +#endif +{ + if (!solns) + return rx_bogus; + + if (solns == &rx_no_solutions) + { + return rx_no; + } + + if (!solns->exp) + { + if (solns->step != 0) + { + return rx_no; + } + else + { + solns->step = 1; + solns->final_tag = 1; + return (solns->start == solns->end + ? rx_yes + : rx_no); + } + } + else if ( (solns->exp->len >= 0) + && (solns->exp->len != (solns->end - solns->start))) + { + return rx_no; + } + else if (!solns->exp->observed) + { + if (solns->step != 0) + { + return rx_no; + } + else if (solns->exp->type == r_string) + { + enum rx_answers ans; + ans = rx_solution_fit_str_p (solns); + solns->final_tag = 1; + solns->step = -1; + return ans; + } + else + { + enum rx_answers ans; + ans = rx_solution_fit_p (solns); + solns->final_tag = solns->match_engine.final_tag; + solns->step = -1; + return ans; + } + } + else if (solns->exp->observed) + { + enum rx_answers fit_p; + switch (solns->step) + { + case -2: + if (solns->exp->params.intval) + { + solns->regs[solns->exp->params.intval].rm_so = solns->saved_rm_so; + solns->regs[solns->exp->params.intval].rm_eo = solns->saved_rm_eo; + } + return rx_no; + + case -1: + return rx_no; + + case 0: + fit_p = rx_solution_fit_p (solns); + /* Set final_tag here because this rough fit test + * may be all the matching that gets done. + * For example, consider a paren node containing + * a true regular expression ending with a cut + * operator. + */ + solns->final_tag = solns->match_engine.final_tag; + switch (fit_p) + { + case rx_no: + solns->step = -1; + return rx_no; + case rx_yes: + solns->step = 1; + goto resolve_fit; + case rx_bogus: + default: + solns->step = -1; + return fit_p; + } + + default: + resolve_fit: + switch (solns->exp->type) + { + case r_cset: + case r_string: + case r_cut: + solns->step = -1; + return rx_bogus; + + case r_parens: + { + enum rx_answers paren_stat; + switch (solns->step) + { + case 1: + if (solns->exp->params.intval) + { + solns->saved_rm_so = solns->regs[solns->exp->params.intval].rm_so; + solns->saved_rm_eo = solns->regs[solns->exp->params.intval].rm_eo; + } + + if ( !solns->exp->params.pair.left + || !solns->exp->params.pair.left->observed) + { + if (solns->exp->params.intval) + { + solns->regs[solns->exp->params.intval].rm_so = solns->start; + solns->regs[solns->exp->params.intval].rm_eo = solns->end; + } + solns->step = -2; + /* Keep the final_tag from the fit_p test. */ + return rx_yes; + } + else + { + solns->left = rx_make_solutions (solns->regs, + solns->verse, + solns->exp->params.pair.left, + solns->subexps, + solns->cset_size, + solns->start, + solns->end, + solns->vmfn, + solns->contextfn, + solns->closure); + if (!solns->left) + { + solns->step = -1; + return rx_bogus; + } + } + solns->step = 2; + /* fall through */ + + case 2: + if (solns->exp->params.intval) + { + solns->regs[solns->exp->params.intval].rm_so = solns->saved_rm_so; + solns->regs[solns->exp->params.intval].rm_eo = solns->saved_rm_eo; + } + + paren_stat = rx_next_solution (solns->left); + if (paren_stat == rx_yes) + { + if (solns->exp->params.intval) + { + solns->regs[solns->exp->params.intval].rm_so = solns->start; + solns->regs[solns->exp->params.intval].rm_eo = solns->end; + } + solns->final_tag = solns->left->final_tag; + return rx_yes; + } + else + { + solns->step = -1; + rx_free_solutions (solns->left); + solns->left = 0; + if (solns->exp->params.intval) + { + solns->regs[solns->exp->params.intval].rm_so = solns->saved_rm_so; + solns->regs[solns->exp->params.intval].rm_eo = solns->saved_rm_eo; + } + return paren_stat; + } + } + } + + + case r_opt: + { + enum rx_answers opt_stat; + switch (solns->step) + { + case 1: + solns->left = rx_make_solutions (solns->regs, + solns->verse, + solns->exp->params.pair.left, + solns->subexps, + solns->cset_size, + solns->start, + solns->end, + solns->vmfn, + solns->contextfn, + solns->closure); + if (!solns->left) + { + solns->step = -1; + return rx_bogus; + } + solns->step = 2; + /* fall through */ + + case 2: + opt_stat = rx_next_solution (solns->left); + if (opt_stat == rx_yes) + { + solns->final_tag = solns->left->final_tag; + return rx_yes; + } + else + { + solns->step = -1; + rx_free_solutions (solns->left); + solns->left = 0; + return ((solns->start == solns->end) + ? rx_yes + : rx_no); + } + + } + } + + case r_alternate: + { + enum rx_answers alt_stat; + switch (solns->step) + { + case 1: + solns->left = rx_make_solutions (solns->regs, + solns->verse, + solns->exp->params.pair.left, + solns->subexps, + solns->cset_size, + solns->start, + solns->end, + solns->vmfn, + solns->contextfn, + solns->closure); + if (!solns->left) + { + solns->step = -1; + return rx_bogus; + } + solns->step = 2; + /* fall through */ + + case 2: + alt_stat = rx_next_solution (solns->left); + + if (alt_stat == rx_yes) + { + solns->final_tag = solns->left->final_tag; + return alt_stat; + } + else + { + solns->step = 3; + rx_free_solutions (solns->left); + solns->left = 0; + /* fall through */ + } + + case 3: + solns->right = rx_make_solutions (solns->regs, + solns->verse, + solns->exp->params.pair.right, + solns->subexps, + solns->cset_size, + solns->start, + solns->end, + solns->vmfn, + solns->contextfn, + solns->closure); + if (!solns->right) + { + solns->step = -1; + return rx_bogus; + } + solns->step = 4; + /* fall through */ + + case 4: + alt_stat = rx_next_solution (solns->right); + + if (alt_stat == rx_yes) + { + solns->final_tag = solns->right->final_tag; + return alt_stat; + } + else + { + solns->step = -1; + rx_free_solutions (solns->right); + solns->right = 0; + return alt_stat; + } + } + } + + case r_concat: + { + switch (solns->step) + { + enum rx_answers concat_stat; + case 1: + solns->split_guess = solns->end; +#if 0 + solns->split_guess = ((solns->end - solns->start) > RX_MANY_CASES + ? rx_best_end_guess (solns, + solns->exp->params.pair.left, solns->end) + : solns->end); +#endif + + concat_split_guess_loop: + solns->left = rx_make_solutions (solns->regs, + solns->verse, + solns->exp->params.pair.left, + solns->subexps, + solns->cset_size, + solns->start, + solns->split_guess, + solns->vmfn, + solns->contextfn, + solns->closure); + if (!solns->left) + { + solns->step = -1; + return rx_bogus; + } + solns->step = 2; + + case 2: + concat_try_next_left_match: + + concat_stat = rx_next_solution (solns->left); + if (concat_stat != rx_yes) + { + rx_free_solutions (solns->left); + rx_free_solutions (solns->right); + solns->left = solns->right = 0; + solns->split_guess = solns->split_guess - 1; +#if 0 + solns->split_guess = ((solns->split_guess - solns->start) > RX_MANY_CASES + ? rx_best_end_guess (solns, + solns->exp->params.pair.left, + solns->split_guess - 1) + : solns->split_guess - 1); +#endif + if (solns->split_guess >= solns->start) + goto concat_split_guess_loop; + else + { + solns->step = -1; + return concat_stat; + } + } + else + { + solns->step = 3; + /* fall through */ + } + + case 3: + solns->right = rx_make_solutions (solns->regs, + solns->verse, + solns->exp->params.pair.right, + solns->subexps, + solns->cset_size, + solns->split_guess, + solns->end, + solns->vmfn, + solns->contextfn, + solns->closure); + if (!solns->right) + { + rx_free_solutions (solns->left); + solns->left = 0; + solns->step = -1; + return rx_bogus; + } + + solns->step = 4; + /* fall through */ + + case 4: + /* concat_try_next_right_match: */ + + concat_stat = rx_next_solution (solns->right); + if (concat_stat == rx_yes) + { + solns->final_tag = solns->right->final_tag; + return concat_stat; + } + else if (concat_stat == rx_no) + { + rx_free_solutions (solns->right); + solns->right = 0; + solns->step = 2; + goto concat_try_next_left_match; + } + else /* concat_stat == rx_bogus */ + { + rx_free_solutions (solns->left); + solns->left = 0; + rx_free_solutions (solns->right); + solns->right = 0; + solns->step = -1; + return concat_stat; + } + } + } + + + case r_plus: + case r_star: + { + switch (solns->step) + { + enum rx_answers star_stat; + case 1: + solns->split_guess = solns->end; +#if 0 + solns->split_guess = ((solns->end - solns->start) > RX_MANY_CASES + ? rx_best_end_guess (solns, + solns->exp->params.pair.left, solns->end) + : solns->end); +#endif + + star_split_guess_loop: + solns->left = rx_make_solutions (solns->regs, + solns->verse, + solns->exp->params.pair.left, + solns->subexps, + solns->cset_size, + solns->start, + solns->split_guess, + solns->vmfn, + solns->contextfn, + solns->closure); + if (!solns->left) + { + solns->step = -1; + return rx_bogus; + } + solns->step = 2; + + case 2: + star_try_next_left_match: + + star_stat = rx_next_solution (solns->left); + if (star_stat != rx_yes) + { + rx_free_solutions (solns->left); + rx_free_solutions (solns->right); + solns->left = solns->right = 0; + solns->split_guess = solns->split_guess - 1; +#if 0 + solns->split_guess = ((solns->split_guess - solns->start) > RX_MANY_CASES + ? rx_best_end_guess (solns, + solns->exp->params.pair.left, + solns->split_guess - 1) + : solns->split_guess - 1); +#endif + if (solns->split_guess >= solns->start) + goto star_split_guess_loop; + else + { + solns->step = -1; + + if ( (solns->exp->type == r_star) + && (solns->start == solns->end) + && (star_stat == rx_no)) + { + solns->final_tag = 1; + return rx_yes; + } + else + return star_stat; + } + } + else + { + solns->step = 3; + /* fall through */ + } + + + if (solns->split_guess == solns->end) + { + solns->final_tag = solns->left->final_tag; + return rx_yes; + } + + case 3: + solns->right = rx_make_solutions (solns->regs, + solns->verse, + solns->exp, + solns->subexps, + solns->cset_size, + solns->split_guess, + solns->end, + solns->vmfn, + solns->contextfn, + solns->closure); + if (!solns->right) + { + rx_free_solutions (solns->left); + solns->left = 0; + solns->step = -1; + return rx_bogus; + } + + solns->step = 4; + /* fall through */ + + case 4: + /* star_try_next_right_match: */ + + star_stat = rx_next_solution (solns->right); + if (star_stat == rx_yes) + { + solns->final_tag = solns->right->final_tag; + return star_stat; + } + else if (star_stat == rx_no) + { + rx_free_solutions (solns->right); + solns->right = 0; + solns->step = 2; + goto star_try_next_left_match; + } + else /* star_stat == rx_bogus */ + { + rx_free_solutions (solns->left); + solns->left = 0; + rx_free_solutions (solns->right); + solns->right = 0; + solns->step = -1; + return star_stat; + } + } + } + + case r_interval: + { + switch (solns->step) + { + enum rx_answers interval_stat; + + case 1: + /* If the interval permits nothing, + * return immediately. + */ + if (solns->exp->params.intval2 < solns->interval_x) + { + solns->step = -1; + return rx_no; + } + + /* If the interval permits only 0 iterations, + * return immediately. Success depends on the + * emptiness of the match. + */ + if ( (solns->exp->params.intval2 == solns->interval_x) + && (solns->exp->params.intval <= solns->interval_x)) + { + solns->step = -1; + solns->final_tag = 1; + return ((solns->start == solns->end) + ? rx_yes + : rx_no); + } + + /* The interval permits at most 0 iterations, + * but also requires more. A bug. + */ + if (solns->exp->params.intval2 == solns->interval_x) + { + /* indicates a regexp compilation error, actually */ + solns->step = -1; + return rx_bogus; + } + + solns->split_guess = solns->end; +#if 0 + solns->split_guess = ((solns->end - solns->start) > RX_MANY_CASES + ? rx_best_end_guess (solns, + solns->exp->params.pair.left, solns->end) + : solns->end); +#endif + + /* The interval permits more than 0 iterations. + * If it permits 0 and the match is to be empty, + * the trivial match is the most preferred answer. + */ + if (solns->exp->params.intval <= solns->interval_x) + { + solns->step = 2; + if (solns->start == solns->end) + { + solns->final_tag = 1; + return rx_yes; + } + /* If this isn't a trivial match, or if the trivial match + * is rejected, look harder. + */ + } + + case 2: + interval_split_guess_loop: + /* The match requires at least one iteration, either because + * there are characters to match, or because the interval starts + * above 0. + * + * Look for the first iteration: + */ + solns->left = rx_make_solutions (solns->regs, + solns->verse, + solns->exp->params.pair.left, + solns->subexps, + solns->cset_size, + solns->start, + solns->split_guess, + solns->vmfn, + solns->contextfn, + solns->closure); + if (!solns->left) + { + solns->step = -1; + return rx_bogus; + } + solns->step = 3; + + case 3: + interval_try_next_left_match: + + interval_stat = rx_next_solution (solns->left); + if (interval_stat != rx_yes) + { + rx_free_solutions (solns->left); + rx_free_solutions (solns->right); + solns->left = solns->right = 0; + solns->split_guess = solns->split_guess - 1; +#if 0 + solns->split_guess = ((solns->split_guess - solns->start) > RX_MANY_CASES + ? rx_best_end_guess (solns, + solns->exp->params.pair.left, + solns->split_guess - 1) + : solns->split_guess - 1); +#endif + if (solns->split_guess >= solns->start) + goto interval_split_guess_loop; + else + { + solns->step = -1; + return interval_stat; + } + } + else + { + solns->step = 4; + /* fall through */ + } + + case 4: + { + /* After matching one required iteration, construct a smaller + * interval and try to match that against the rest. + * + * To avoid thwarting unfa caching, instead of building a new + * rexp node with different interval extents, we keep interval_x + * in each solns structure to keep track of the number of + * iterations matched so far. + */ + solns->right = rx_make_solutions (solns->regs, + solns->verse, + solns->exp, + solns->subexps, + solns->cset_size, + solns->split_guess, + solns->end, + solns->vmfn, + solns->contextfn, + solns->closure); + solns->right->interval_x = solns->interval_x + 1; + } + if (!solns->right) + { + rx_free_solutions (solns->left); + solns->left = 0; + solns->step = -1; + return rx_bogus; + } + + solns->step = 5; + /* fall through */ + + case 5: + /* interval_try_next_right_match: */ + + interval_stat = rx_next_solution (solns->right); + if (interval_stat == rx_yes) + { + solns->final_tag = solns->right->final_tag; + return interval_stat; + } + else if (interval_stat == rx_no) + { + rx_free_solutions (solns->right); + solns->right = 0; + solns->step = 2; + goto interval_try_next_left_match; + } + else /* interval_stat == rx_bogus */ + { + rx_free_solutions (solns->left); + solns->left = 0; + rx_free_solutions (solns->right); + solns->right = 0; + solns->step = -1; + return interval_stat; + } + } + } + + case r_context: + { + solns->step = -1; + solns->final_tag = 1; + return solns->contextfn (solns->closure, + solns->exp, + solns->start, solns->end, + solns->regs); + } + + + } + } + return rx_bogus; + } +} diff --git a/rx/rxspencer.h b/rx/rxspencer.h new file mode 100644 index 00000000..0c4a46bc --- /dev/null +++ b/rx/rxspencer.h @@ -0,0 +1,99 @@ +/* classes: h_files */ + +#ifndef RXSPENCERH +#define RXSPENCERH +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "rxproto.h" +#include "rxnode.h" +#include "rxunfa.h" +#include "rxanal.h" +#include "inst-rxposix.h" + + + +#define RX_MANY_CASES 30 + + +typedef enum rx_answers (*rx_vmfn) + P((void * closure, + unsigned const char ** burst, int * len, int * offset, + int start, int end, int need)); + +typedef enum rx_answers (*rx_contextfn) + P((void * closure, + struct rexp_node * node, + int start, int end, + struct rx_registers * regs)); + + +struct rx_solutions +{ + int step; + + int cset_size; + struct rexp_node * exp; + struct rexp_node ** subexps; + struct rx_registers * regs; + + int start; + int end; + + rx_vmfn vmfn; + rx_contextfn contextfn; + void * closure; + + struct rx_unfaniverse * verse; + struct rx_unfa * dfa; + struct rx_classical_system match_engine; + struct rx_unfa * left_dfa; + struct rx_classical_system left_match_engine; + + int split_guess; + struct rx_solutions * left; + struct rx_solutions * right; + + int interval_x; + + int saved_rm_so; + int saved_rm_eo; + + int final_tag; +}; + +extern struct rx_solutions rx_no_solutions; + + +#ifdef __STDC__ +extern struct rx_solutions * rx_make_solutions (struct rx_registers * regs, struct rx_unfaniverse * verse, struct rexp_node * expression, struct rexp_node ** subexps, int cset_size, int start, int end, rx_vmfn vmfn, rx_contextfn contextfn, void * closure); +extern void rx_free_solutions (struct rx_solutions * solns); +extern int rx_best_end_guess (struct rx_solutions * solns, struct rexp_node * exp, int bound); +extern enum rx_answers rx_next_solution (struct rx_solutions * solns); + +#else /* STDC */ +extern struct rx_solutions * rx_make_solutions (); +extern void rx_free_solutions (); +extern int rx_best_end_guess (); +extern enum rx_answers rx_next_solution (); + +#endif /* STDC */ + +#endif /* RXSPENCERH */ diff --git a/rx/rxstr.c b/rx/rxstr.c new file mode 100644 index 00000000..c3ccb036 --- /dev/null +++ b/rx/rxstr.c @@ -0,0 +1,130 @@ +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "rxall.h" +#include "rxstr.h" + + + +#ifdef __STDC__ +enum rx_answers +rx_str_vmfn (void * closure, unsigned const char ** burstp, int * lenp, int * offsetp, int start, int end, int need) +#else +enum rx_answers +rx_str_vmfn (closure, burstp, lenp, offsetp, start, end, need) + void * closure; + unsigned const char ** burstp; + int * lenp; + int * offsetp; + int start; + int end; + int need; +#endif +{ + struct rx_str_closure * strc; + strc = (struct rx_str_closure *)closure; + + if ( (need < 0) + || (need > strc->len)) + return rx_no; + + *burstp = strc->str; + *lenp = strc->len; + *offsetp = 0; + return rx_yes; +} + +#ifdef __STDC__ +enum rx_answers +rx_str_contextfn (void * closure, struct rexp_node * node, int start, int end, struct rx_registers * regs) +#else +enum rx_answers +rx_str_contextfn (closure, node, start, end, regs) + void * closure; + struct rexp_node * node; + int start; + int end; + struct rx_registers * regs; +#endif +{ + struct rx_str_closure * strc; + + strc = (struct rx_str_closure *)closure; + switch (node->params.intval) + { + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + { + int cmp; + int regn; + regn = node->params.intval - '0'; + if ( (regs[regn].rm_so == -1) + || ((end - start) != (regs[regn].rm_eo - regs[regn].rm_so))) + return rx_no; + else + { + if (strc->rules.case_indep) + cmp = strncasecmp (strc->str + start, + strc->str + regs[regn].rm_so, + end - start); + else + cmp = strncmp (strc->str + start, + strc->str + regs[regn].rm_so, + end - start); + + return (!cmp + ? rx_yes + : rx_no); + } + } + + case '^': + { + return (( (start == end) + && ( ((start == 0) && !strc->rules.not_bol) + || ( (start > 0) + && strc->rules.newline_anchor + && (strc->str[start - 1] == '\n')))) + ? rx_yes + : rx_no); + } + + case '$': + { + return (( (start == end) + && ( ((start == strc->len) && !strc->rules.not_eol) + || ( (start < strc->len) + && strc->rules.newline_anchor + && (strc->str[start] == '\n')))) + ? rx_yes + : rx_no); + } + + case '<': + case '>': + + case 'B': + case 'b': + + + default: + return rx_bogus; + } +} diff --git a/rx/rxstr.h b/rx/rxstr.h new file mode 100644 index 00000000..991f53b8 --- /dev/null +++ b/rx/rxstr.h @@ -0,0 +1,57 @@ +/* classes: h_files */ + +#ifndef RXSTRH +#define RXSTRH +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + + +#include "rxspencer.h" +#include "rxcontext.h" + + + + + +struct rx_str_closure +{ + struct rx_context_rules rules; + const unsigned char * str; + int len; +}; + + + +#ifdef __STDC__ +extern enum rx_answers rx_str_vmfn (void * closure, unsigned const char ** burstp, int * lenp, int * offsetp, int start, int end, int need); +extern enum rx_answers rx_str_contextfn (void * closure, struct rexp_node * node, int start, int end, struct rx_registers * regs); + +#else /* STDC */ +extern enum rx_answers rx_str_vmfn (); +extern enum rx_answers rx_str_contextfn (); + +#endif /* STDC */ + + + + + + +#endif /* RXSTRH */ diff --git a/rx/rxsuper.c b/rx/rxsuper.c new file mode 100644 index 00000000..46e745ab --- /dev/null +++ b/rx/rxsuper.c @@ -0,0 +1,1416 @@ + + +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "rxall.h" +#include "rxsuper.h" + + +/* The functions in the next several pages define the lazy-NFA-conversion used + * by matchers. The input to this construction is an NFA such as + * is built by compactify_nfa (rx.c). The output is the superNFA. + */ + +/* Match engines can use arbitrary values for opcodes. So, the parse tree + * is built using instructions names (enum rx_opcode), but the superstate + * nfa is populated with mystery opcodes (void *). + * + * For convenience, here is an id table. The opcodes are == to their inxs + * + * The lables in re_search_2 would make good values for instructions. + */ + +void * rx_id_instruction_table[rx_num_instructions] = +{ + (void *) rx_backtrack_point, + (void *) rx_do_side_effects, + (void *) rx_cache_miss, + (void *) rx_next_char, + (void *) rx_backtrack, + (void *) rx_error_inx +}; + + + + +/* The partially instantiated superstate graph has a transition + * table at every node. There is one entry for every character. + * This fills in the transition for a set. + */ +#ifdef __STDC__ +static void +install_transition (struct rx_superstate *super, + struct rx_inx *answer, rx_Bitset trcset) +#else +static void +install_transition (super, answer, trcset) + struct rx_superstate *super; + struct rx_inx *answer; + rx_Bitset trcset; +#endif +{ + struct rx_inx * transitions = super->transitions; + int chr; + for (chr = 0; chr < 256; ) + if (!*trcset) + { + ++trcset; + chr += 32; + } + else + { + RX_subset sub = *trcset; + RX_subset mask = 1; + int bound = chr + 32; + while (chr < bound) + { + if (sub & mask) + transitions [chr] = *answer; + ++chr; + mask <<= 1; + } + ++trcset; + } +} + + +#ifdef __STDC__ +static int +qlen (struct rx_superstate * q) +#else +static int +qlen (q) + struct rx_superstate * q; +#endif +{ + int count = 1; + struct rx_superstate * it; + if (!q) + return 0; + for (it = q->next_recyclable; it != q; it = it->next_recyclable) + ++count; + return count; +} + +#ifdef __STDC__ +static void +check_cache (struct rx_cache * cache) +#else +static void +check_cache (cache) + struct rx_cache * cache; +#endif +{ + struct rx_cache * you_fucked_up = 0; + int total = cache->superstates; + int semi = cache->semifree_superstates; + if (semi != qlen (cache->semifree_superstate)) + check_cache (you_fucked_up); + if ((total - semi) != qlen (cache->lru_superstate)) + check_cache (you_fucked_up); +} +#ifdef __STDC__ +char * +rx_cache_malloc (struct rx_cache * cache, int size) +#else +char * +rx_cache_malloc (cache, size) + struct rx_cache * cache; + int size; +#endif +{ + char * answer; + answer = (char *)malloc (size); + if (answer) + cache->bytes_used += size; + return answer; +} + + +#ifdef __STDC__ +void +rx_cache_free (struct rx_cache * cache, int size, char * mem) +#else +void +rx_cache_free (cache, size, mem) + struct rx_cache * cache; + int size; + char * mem; +#endif +{ + free (mem); + cache->bytes_used -= size; +} + + + +/* When a superstate is old and neglected, it can enter a + * semi-free state. A semi-free state is slated to die. + * Incoming transitions to a semi-free state are re-written + * to cause an (interpreted) fault when they are taken. + * The fault handler revives the semi-free state, patches + * incoming transitions back to normal, and continues. + * + * The idea is basicly to free in two stages, aborting + * between the two if the state turns out to be useful again. + * When a free is aborted, the rescued superstate is placed + * in the most-favored slot to maximize the time until it + * is next semi-freed. + * + * Overall, this approximates truly freeing superstates in + * lru order, but does not involve the cost of updating perfectly + * accurate timestamps every time a superstate is touched. + */ + +#ifdef __STDC__ +static void +semifree_superstate (struct rx_cache * cache) +#else +static void +semifree_superstate (cache) + struct rx_cache * cache; +#endif +{ + int disqualified = cache->semifree_superstates; + if (disqualified == cache->superstates) + return; + while (cache->lru_superstate->locks) + { + cache->lru_superstate = cache->lru_superstate->next_recyclable; + ++disqualified; + if (disqualified == cache->superstates) + return; + } + { + struct rx_superstate * it = cache->lru_superstate; + it->next_recyclable->prev_recyclable = it->prev_recyclable; + it->prev_recyclable->next_recyclable = it->next_recyclable; + cache->lru_superstate = (it == it->next_recyclable + ? 0 + : it->next_recyclable); + if (!cache->semifree_superstate) + { + cache->semifree_superstate = it; + it->next_recyclable = it; + it->prev_recyclable = it; + } + else + { + it->prev_recyclable = cache->semifree_superstate->prev_recyclable; + it->next_recyclable = cache->semifree_superstate; + it->prev_recyclable->next_recyclable = it; + it->next_recyclable->prev_recyclable = it; + } + { + struct rx_distinct_future *df; + it->is_semifree = 1; + ++cache->semifree_superstates; + df = it->transition_refs; + if (df) + { + df->prev_same_dest->next_same_dest = 0; + for (df = it->transition_refs; df; df = df->next_same_dest) + { + df->future_frame.inx = cache->instruction_table[rx_cache_miss]; + df->future_frame.data = 0; + df->future_frame.data_2 = (void *) df; + /* If there are any NEXT-CHAR instruction frames that + * refer to this state, we convert them to CACHE-MISS frames. + */ + if (!df->effects + && (df->edge->options->next_same_super_edge[0] + == df->edge->options)) + install_transition (df->present, &df->future_frame, + df->edge->cset); + } + df = it->transition_refs; + df->prev_same_dest->next_same_dest = df; + } + } + } +} + + +#ifdef __STDC__ +static void +refresh_semifree_superstate (struct rx_cache * cache, + struct rx_superstate * super) +#else +static void +refresh_semifree_superstate (cache, super) + struct rx_cache * cache; + struct rx_superstate * super; +#endif +{ + struct rx_distinct_future *df; + + if (super->transition_refs) + { + super->transition_refs->prev_same_dest->next_same_dest = 0; + for (df = super->transition_refs; df; df = df->next_same_dest) + { + df->future_frame.inx = cache->instruction_table[rx_next_char]; + df->future_frame.data = (void *) super->transitions; + df->future_frame.data_2 = (void *)(super->contents->is_final); + /* CACHE-MISS instruction frames that refer to this state, + * must be converted to NEXT-CHAR frames. + */ + if (!df->effects + && (df->edge->options->next_same_super_edge[0] + == df->edge->options)) + install_transition (df->present, &df->future_frame, + df->edge->cset); + } + super->transition_refs->prev_same_dest->next_same_dest + = super->transition_refs; + } + if (cache->semifree_superstate == super) + cache->semifree_superstate = (super->prev_recyclable == super + ? 0 + : super->prev_recyclable); + super->next_recyclable->prev_recyclable = super->prev_recyclable; + super->prev_recyclable->next_recyclable = super->next_recyclable; + + if (!cache->lru_superstate) + (cache->lru_superstate + = super->next_recyclable + = super->prev_recyclable + = super); + else + { + super->next_recyclable = cache->lru_superstate; + super->prev_recyclable = cache->lru_superstate->prev_recyclable; + super->next_recyclable->prev_recyclable = super; + super->prev_recyclable->next_recyclable = super; + } + super->is_semifree = 0; + --cache->semifree_superstates; +} + +#ifdef __STDC__ +void +rx_refresh_this_superstate (struct rx_cache * cache, + struct rx_superstate * superstate) +#else +void +rx_refresh_this_superstate (cache, superstate) + struct rx_cache * cache; + struct rx_superstate * superstate; +#endif +{ + if (superstate->is_semifree) + refresh_semifree_superstate (cache, superstate); + else if (cache->lru_superstate == superstate) + cache->lru_superstate = superstate->next_recyclable; + else if (superstate != cache->lru_superstate->prev_recyclable) + { + superstate->next_recyclable->prev_recyclable + = superstate->prev_recyclable; + superstate->prev_recyclable->next_recyclable + = superstate->next_recyclable; + superstate->next_recyclable = cache->lru_superstate; + superstate->prev_recyclable = cache->lru_superstate->prev_recyclable; + superstate->next_recyclable->prev_recyclable = superstate; + superstate->prev_recyclable->next_recyclable = superstate; + } +} + +#ifdef __STDC__ +static void +release_superset_low (struct rx_cache * cache, + struct rx_superset *set) +#else +static void +release_superset_low (cache, set) + struct rx_cache * cache; + struct rx_superset *set; +#endif +{ + if (!--set->refs) + { + if (set->starts_for) + set->starts_for->start_set = 0; + + if (set->cdr) + release_superset_low (cache, set->cdr); + + rx_hash_free (&set->hash_item, &cache->superset_hash_rules); + rx_cache_free (cache, + sizeof (struct rx_superset), + (char *)set); + } +} + +#ifdef __STDC__ +void +rx_release_superset (struct rx *rx, + struct rx_superset *set) +#else +void +rx_release_superset (rx, set) + struct rx *rx; + struct rx_superset *set; +#endif +{ + release_superset_low (rx->cache, set); +} + +/* This tries to add a new superstate to the superstate freelist. + * It might, as a result, free some edge pieces or hash tables. + * If nothing can be freed because too many locks are being held, fail. + */ + +#ifdef __STDC__ +static int +rx_really_free_superstate (struct rx_cache * cache) +#else +static int +rx_really_free_superstate (cache) + struct rx_cache * cache; +#endif +{ + int locked_superstates = 0; + struct rx_superstate * it; + + if (!cache->superstates) + return 0; + + /* scale these */ + while ((cache->hits + cache->misses) > cache->superstates) + { + cache->hits >>= 1; + cache->misses >>= 1; + } + + /* semifree superstates faster than they are freed + * so popular states can be rescued. + */ + semifree_superstate (cache); + semifree_superstate (cache); + semifree_superstate (cache); + +#if 0 + Redundant because semifree_superstate already + makes this check; + + + while (cache->semifree_superstate && cache->semifree_superstate->locks) + { + refresh_semifree_superstate (cache, cache->semifree_superstate); + ++locked_superstates; + if (locked_superstates == cache->superstates) + return 0; + } + + + if (cache->semifree_superstate) + insert the "it =" block which follows this "if 0" code; + else + { + while (cache->lru_superstate->locks) + { + cache->lru_superstate = cache->lru_superstate->next_recyclable; + ++locked_superstates; + if (locked_superstates == cache->superstates) + return 0; + } + it = cache->lru_superstate; + it->next_recyclable->prev_recyclable = it->prev_recyclable; + it->prev_recyclable->next_recyclable = it->next_recyclable; + cache->lru_superstate = ((it == it->next_recyclable) + ? 0 + : it->next_recyclable); + } +#endif + + if (!cache->semifree_superstate) + return 0; + + { + it = cache->semifree_superstate; + it->next_recyclable->prev_recyclable = it->prev_recyclable; + it->prev_recyclable->next_recyclable = it->next_recyclable; + cache->semifree_superstate = ((it == it->next_recyclable) + ? 0 + : it->next_recyclable); + --cache->semifree_superstates; + } + + + if (it->transition_refs) + { + struct rx_distinct_future *df; + for (df = it->transition_refs, + df->prev_same_dest->next_same_dest = 0; + df; + df = df->next_same_dest) + { + df->future_frame.inx = cache->instruction_table[rx_cache_miss]; + df->future_frame.data = 0; + df->future_frame.data_2 = (void *) df; + df->future = 0; + } + it->transition_refs->prev_same_dest->next_same_dest = + it->transition_refs; + } + { + struct rx_super_edge *tc = it->edges; + while (tc) + { + struct rx_distinct_future * df; + struct rx_super_edge *tct = tc->next; + df = tc->options; + df->next_same_super_edge[1]->next_same_super_edge[0] = 0; + while (df) + { + struct rx_distinct_future *dft = df; + df = df->next_same_super_edge[0]; + + + if (dft->future && dft->future->transition_refs == dft) + { + dft->future->transition_refs = dft->next_same_dest; + if (dft->future->transition_refs == dft) + dft->future->transition_refs = 0; + } + dft->next_same_dest->prev_same_dest = dft->prev_same_dest; + dft->prev_same_dest->next_same_dest = dft->next_same_dest; + rx_cache_free (cache, + sizeof (struct rx_distinct_future), + (char *)dft); + } + rx_cache_free (cache, + sizeof (struct rx_super_edge), + (char *)tc); + tc = tct; + } + } + + if (it->contents->superstate == it) + it->contents->superstate = 0; + release_superset_low (cache, it->contents); + rx_cache_free (cache, + (sizeof (struct rx_superstate) + + cache->local_cset_size * sizeof (struct rx_inx)), + (char *)it); + --cache->superstates; + return 1; +} + + +#ifdef __STDC__ +static char * +rx_cache_malloc_or_get (struct rx_cache * cache, int size) +#else +static char * +rx_cache_malloc_or_get (cache, size) + struct rx_cache * cache; + int size; +#endif +{ + while ( (cache->bytes_used + size > cache->bytes_allowed) + && rx_really_free_superstate (cache)) + ; + + return rx_cache_malloc (cache, size); +} + + + +#ifdef __STDC__ +static int +supersetcmp (void * va, void * vb) +#else +static int +supersetcmp (va, vb) + void * va; + void * vb; +#endif +{ + struct rx_superset * a = (struct rx_superset *)va; + struct rx_superset * b = (struct rx_superset *)vb; + return ( (a == b) + || (a && b && (a->id == b->id) && (a->car == b->car) && (a->cdr == b->cdr))); +} + +#define rx_abs(A) (((A) > 0) ? (A) : -(A)) + + +#ifdef __STDC__ +static struct rx_hash_item * +superset_allocator (struct rx_hash_rules * rules, void * val) +#else +static struct rx_hash_item * +superset_allocator (rules, val) + struct rx_hash_rules * rules; + void * val; +#endif +{ + struct rx_cache * cache; + struct rx_superset * template; + struct rx_superset * newset; + + cache = ((struct rx_cache *) + ((char *)rules + - (unsigned long)(&((struct rx_cache *)0)->superset_hash_rules))); + template = (struct rx_superset *)val; + newset = ((struct rx_superset *) + rx_cache_malloc (cache, sizeof (*template))); + if (!newset) + return 0; + { + int cdrfinal; + int cdredges; + + cdrfinal = (template->cdr + ? template->cdr->is_final + : 0); + cdredges = (template->cdr + ? template->cdr->has_cset_edges + : 0); + + newset->is_final = (rx_abs (template->car->is_final) > rx_abs(cdrfinal) + ? template->car->is_final + : template->cdr->is_final); + newset->has_cset_edges = (template->car->has_cset_edges || cdredges); + } + newset->refs = 0; + newset->id = template->id; + newset->car = template->car; + newset->cdr = template->cdr; + rx_protect_superset (rx, template->cdr); + newset->superstate = 0; + newset->starts_for = 0; + newset->hash_item.data = (void *)newset; + newset->hash_item.binding = 0; + return &newset->hash_item; +} + +#ifdef __STDC__ +static struct rx_hash * +super_hash_allocator (struct rx_hash_rules * rules) +#else +static struct rx_hash * +super_hash_allocator (rules) + struct rx_hash_rules * rules; +#endif +{ + struct rx_cache * cache; + cache = ((struct rx_cache *) + ((char *)rules + - (unsigned long)(&((struct rx_cache *)0)->superset_hash_rules))); + return ((struct rx_hash *) + rx_cache_malloc (cache, sizeof (struct rx_hash))); +} + + +#ifdef __STDC__ +static void +super_hash_liberator (struct rx_hash * hash, struct rx_hash_rules * rules) +#else +static void +super_hash_liberator (hash, rules) + struct rx_hash * hash; + struct rx_hash_rules * rules; +#endif +{ + struct rx_cache * cache + = ((struct rx_cache *) + (char *)rules - (long)(&((struct rx_cache *)0)->superset_hash_rules)); + rx_cache_free (cache, sizeof (struct rx_hash), (char *)hash); +} + +#ifdef __STDC__ +static void +superset_hash_item_liberator (struct rx_hash_item * it, + struct rx_hash_rules * rules) +#else +static void +superset_hash_item_liberator (it, rules) + struct rx_hash_item * it; + struct rx_hash_rules * rules; +#endif +{ +} + +int rx_cache_bound = 3; +static int rx_default_cache_got = 0; + +static struct rx_cache default_cache = +{ + { + supersetcmp, + super_hash_allocator, + super_hash_liberator, + superset_allocator, + superset_hash_item_liberator, + }, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + RX_DEFAULT_DFA_CACHE_SIZE, + 0, + 256, + rx_id_instruction_table, + { + 0, + 0, + 0, + 0, + 0 + } +}; +struct rx_cache * rx_default_cache = &default_cache; + +/* This adds an element to a superstate set. These sets are lists, such + * that lists with == elements are ==. The empty set is returned by + * superset_cons (rx, 0, 0) and is NOT equivelent to + * (struct rx_superset)0. + */ + +#ifdef __STDC__ +struct rx_superset * +rx_superset_cons (struct rx * rx, + struct rx_nfa_state *car, struct rx_superset *cdr) +#else +struct rx_superset * +rx_superset_cons (rx, car, cdr) + struct rx * rx; + struct rx_nfa_state *car; + struct rx_superset *cdr; +#endif +{ + struct rx_cache * cache = rx->cache; + if (!car && !cdr) + { + if (!cache->empty_superset) + { + cache->empty_superset + = ((struct rx_superset *) + rx_cache_malloc (cache, sizeof (struct rx_superset))); + if (!cache->empty_superset) + return 0; + rx_bzero ((char *)cache->empty_superset, sizeof (struct rx_superset)); + cache->empty_superset->refs = 1000; + } + return cache->empty_superset; + } + { + struct rx_superset template; + struct rx_hash_item * hit; + template.car = car; + template.cdr = cdr; + template.id = rx->rx_id; + rx_protect_superset (rx, template.cdr); + hit = rx_hash_store (&cache->superset_table, + (unsigned long)car ^ car->id ^ (unsigned long)cdr, + (void *)&template, + &cache->superset_hash_rules); + rx_protect_superset (rx, template.cdr); + return (hit + ? (struct rx_superset *)hit->data + : 0); + } +} + +/* This computes a union of two NFA state sets. The sets do not have the + * same representation though. One is a RX_SUPERSET structure (part + * of the superstate NFA) and the other is an NFA_STATE_SET (part of the NFA). + */ + +#ifdef __STDC__ +struct rx_superset * +rx_superstate_eclosure_union (struct rx * rx, struct rx_superset *set, struct rx_nfa_state_set *ecl) +#else +struct rx_superset * +rx_superstate_eclosure_union (rx, set, ecl) + struct rx * rx; + struct rx_superset *set; + struct rx_nfa_state_set *ecl; +#endif +{ + if (!ecl) + return set; + + if (!set->car) + return rx_superset_cons (rx, ecl->car, + rx_superstate_eclosure_union (rx, set, ecl->cdr)); + if (set->car == ecl->car) + return rx_superstate_eclosure_union (rx, set, ecl->cdr); + + { + struct rx_superset * tail; + struct rx_nfa_state * first; + + if (set->car->id < ecl->car->id) + { + tail = rx_superstate_eclosure_union (rx, set->cdr, ecl); + first = set->car; + } + else + { + tail = rx_superstate_eclosure_union (rx, set, ecl->cdr); + first = ecl->car; + } + if (!tail) + return 0; + else + { + struct rx_superset * answer; + answer = rx_superset_cons (rx, first, tail); + if (!answer) + { + rx_protect_superset (rx, tail); + rx_release_superset (rx, tail); + return 0; + } + else + return answer; + } + } +} + + + + +/* + * This makes sure that a list of rx_distinct_futures contains + * a future for each possible set of side effects in the eclosure + * of a given state. This is some of the work of filling in a + * superstate transition. + */ + +#ifdef __STDC__ +static struct rx_distinct_future * +include_futures (struct rx *rx, + struct rx_distinct_future *df, struct rx_nfa_state + *state, struct rx_superstate *superstate) +#else +static struct rx_distinct_future * +include_futures (rx, df, state, superstate) + struct rx *rx; + struct rx_distinct_future *df; + struct rx_nfa_state *state; + struct rx_superstate *superstate; +#endif +{ + struct rx_possible_future *future; + struct rx_cache * cache = rx->cache; + for (future = rx_state_possible_futures (rx, state); future; future = future->next) + { + struct rx_distinct_future *dfp; + struct rx_distinct_future *insert_before = 0; + if (df) + df->next_same_super_edge[1]->next_same_super_edge[0] = 0; + for (dfp = df; dfp; dfp = dfp->next_same_super_edge[0]) + if (dfp->effects == future->effects) + break; + else + { + int order = rx->se_list_cmp (rx, dfp->effects, future->effects); + if (order > 0) + { + insert_before = dfp; + dfp = 0; + break; + } + } + if (df) + df->next_same_super_edge[1]->next_same_super_edge[0] = df; + if (!dfp) + { + dfp + = ((struct rx_distinct_future *) + rx_cache_malloc (cache, + sizeof (struct rx_distinct_future))); + if (!dfp) + return 0; + if (!df) + { + df = insert_before = dfp; + df->next_same_super_edge[0] = df->next_same_super_edge[1] = df; + } + else if (!insert_before) + insert_before = df; + else if (insert_before == df) + df = dfp; + + dfp->next_same_super_edge[0] = insert_before; + dfp->next_same_super_edge[1] + = insert_before->next_same_super_edge[1]; + dfp->next_same_super_edge[1]->next_same_super_edge[0] = dfp; + dfp->next_same_super_edge[0]->next_same_super_edge[1] = dfp; + dfp->next_same_dest = dfp->prev_same_dest = dfp; + dfp->future = 0; + dfp->present = superstate; + dfp->future_frame.inx = rx->instruction_table[rx_cache_miss]; + dfp->future_frame.data = 0; + dfp->future_frame.data_2 = (void *) dfp; + dfp->side_effects_frame.inx + = rx->instruction_table[rx_do_side_effects]; + dfp->side_effects_frame.data = 0; + dfp->side_effects_frame.data_2 = (void *) dfp; + dfp->effects = future->effects; + } + } + return df; +} + + + +/* This constructs a new superstate from its state set. The only + * complexity here is memory management. + */ +#ifdef __STDC__ +struct rx_superstate * +rx_superstate (struct rx *rx, + struct rx_superset *set) +#else +struct rx_superstate * +rx_superstate (rx, set) + struct rx *rx; + struct rx_superset *set; +#endif +{ + struct rx_cache * cache = rx->cache; + struct rx_superstate * superstate = 0; + + /* Does the superstate already exist in the cache? */ + if (set->superstate) + { + if (set->superstate->rx_id != rx->rx_id) + { + /* Aha. It is in the cache, but belongs to a superstate + * that refers to an NFA that no longer exists. + * (We know it no longer exists because it was evidently + * stored in the same region of memory as the current nfa + * yet it has a different id.) + */ + superstate = set->superstate; + if (!superstate->is_semifree) + { + if (cache->lru_superstate == superstate) + { + cache->lru_superstate = superstate->next_recyclable; + if (cache->lru_superstate == superstate) + cache->lru_superstate = 0; + } + { + superstate->next_recyclable->prev_recyclable + = superstate->prev_recyclable; + superstate->prev_recyclable->next_recyclable + = superstate->next_recyclable; + if (!cache->semifree_superstate) + { + (cache->semifree_superstate + = superstate->next_recyclable + = superstate->prev_recyclable + = superstate); + } + else + { + superstate->next_recyclable = cache->semifree_superstate; + superstate->prev_recyclable + = cache->semifree_superstate->prev_recyclable; + superstate->next_recyclable->prev_recyclable + = superstate; + superstate->prev_recyclable->next_recyclable + = superstate; + cache->semifree_superstate = superstate; + } + ++cache->semifree_superstates; + } + } + set->superstate = 0; + goto handle_cache_miss; + } + ++cache->hits; + superstate = set->superstate; + + rx_refresh_this_superstate (cache, superstate); + return superstate; + } + + handle_cache_miss: + + /* This point reached only for cache misses. */ + ++cache->misses; +#if RX_DEBUG + if (rx_debug_trace > 1) + { + struct rx_superset * setp = set; + fprintf (stderr, "Building a superstet %d(%d): ", rx->rx_id, set); + while (setp) + { + fprintf (stderr, "%d ", setp->id); + setp = setp->cdr; + } + fprintf (stderr, "(%d)\n", set); + } +#endif + + { + int superstate_size; + superstate_size = ( sizeof (*superstate) + + ( sizeof (struct rx_inx) + * rx->local_cset_size)); + superstate = ((struct rx_superstate *) + rx_cache_malloc_or_get (cache, superstate_size)); + ++cache->superstates; + } + + if (!superstate) + return 0; + + if (!cache->lru_superstate) + (cache->lru_superstate + = superstate->next_recyclable + = superstate->prev_recyclable + = superstate); + else + { + superstate->next_recyclable = cache->lru_superstate; + superstate->prev_recyclable = cache->lru_superstate->prev_recyclable; + ( superstate->prev_recyclable->next_recyclable + = superstate->next_recyclable->prev_recyclable + = superstate); + } + superstate->rx_id = rx->rx_id; + superstate->transition_refs = 0; + superstate->locks = 0; + superstate->is_semifree = 0; + set->superstate = superstate; + superstate->contents = set; + rx_protect_superset (rx, set); + superstate->edges = 0; + { + int x; + /* None of the transitions from this superstate are known yet. */ + for (x = 0; x < rx->local_cset_size; ++x) + { + struct rx_inx * ifr = &superstate->transitions[x]; + ifr->inx = rx->instruction_table [rx_cache_miss]; + ifr->data = ifr->data_2 = 0; + } + } + return superstate; +} + + +/* This computes the destination set of one edge of the superstate NFA. + * Note that a RX_DISTINCT_FUTURE is a superstate edge. + * Returns 0 on an allocation failure. + */ + +#ifdef __STDC__ +static int +solve_destination (struct rx *rx, struct rx_distinct_future *df) +#else +static int +solve_destination (rx, df) + struct rx *rx; + struct rx_distinct_future *df; +#endif +{ + struct rx_super_edge *tc = df->edge; + struct rx_superset *nfa_state; + struct rx_superset *nil_set = rx_superset_cons (rx, 0, 0); + struct rx_superset *solution = nil_set; + struct rx_superstate *dest; + + rx_protect_superset (rx, solution); + /* Iterate over all NFA states in the state set of this superstate. */ + for (nfa_state = df->present->contents; + nfa_state->car; + nfa_state = nfa_state->cdr) + { + struct rx_nfa_edge *e; + /* Iterate over all edges of each NFA state. */ + for (e = nfa_state->car->edges; e; e = e->next) + /* If we find an edge that is labeled with + * the characters we are solving for..... + */ + if ((e->type == ne_cset) + && rx_bitset_is_subset (rx->local_cset_size, + tc->cset, + e->params.cset)) + { + struct rx_nfa_state *n = e->dest; + struct rx_possible_future *pf; + /* ....search the partial epsilon closures of the destination + * of that edge for a path that involves the same set of + * side effects we are solving for. + * If we find such a RX_POSSIBLE_FUTURE, we add members to the + * stateset we are computing. + */ + for (pf = rx_state_possible_futures (rx, n); pf; pf = pf->next) + if (pf->effects == df->effects) + { + struct rx_superset * old_sol; + old_sol = solution; + solution = rx_superstate_eclosure_union (rx, solution, + pf->destset); + if (!solution) + return 0; + rx_protect_superset (rx, solution); + rx_release_superset (rx, old_sol); + } + } + } + /* It is possible that the RX_DISTINCT_FUTURE we are working on has + * the empty set of NFA states as its definition. In that case, this + * is a failure point. + */ + if (solution == nil_set) + { + df->future_frame.inx = (void *) rx_backtrack; + df->future_frame.data = 0; + df->future_frame.data_2 = 0; + return 1; + } + dest = rx_superstate (rx, solution); + rx_release_superset (rx, solution); + if (!dest) + return 0; + + { + struct rx_distinct_future *dft; + dft = df; + df->prev_same_dest->next_same_dest = 0; + while (dft) + { + dft->future = dest; + dft->future_frame.inx = rx->instruction_table[rx_next_char]; + dft->future_frame.data = (void *) dest->transitions; + dft->future_frame.data_2 = (void *) dest->contents->is_final; + dft = dft->next_same_dest; + } + df->prev_same_dest->next_same_dest = df; + } + if (!dest->transition_refs) + dest->transition_refs = df; + else + { + struct rx_distinct_future *dft; + dft = dest->transition_refs->next_same_dest; + dest->transition_refs->next_same_dest = df->next_same_dest; + df->next_same_dest->prev_same_dest = dest->transition_refs; + df->next_same_dest = dft; + dft->prev_same_dest = df; + } + return 1; +} + + +/* This takes a superstate and a character, and computes some edges + * from the superstate NFA. In particular, this computes all edges + * that lead from SUPERSTATE given CHR. This function also + * computes the set of characters that share this edge set. + * This returns 0 on allocation error. + * The character set and list of edges are returned through + * the paramters CSETOUT and DFOUT. + */ + +#ifdef __STDC__ +static int +compute_super_edge (struct rx *rx, struct rx_distinct_future **dfout, + rx_Bitset csetout, struct rx_superstate *superstate, + unsigned char chr) +#else +static int +compute_super_edge (rx, dfout, csetout, superstate, chr) + struct rx *rx; + struct rx_distinct_future **dfout; + rx_Bitset csetout; + struct rx_superstate *superstate; + unsigned char chr; +#endif +{ + struct rx_superset *stateset = superstate->contents; + + /* To compute the set of characters that share edges with CHR, + * we start with the full character set, and subtract. + */ + rx_bitset_universe (rx->local_cset_size, csetout); + *dfout = 0; + + /* Iterate over the NFA states in the superstate state-set. */ + while (stateset->car) + { + struct rx_nfa_edge *e; + for (e = stateset->car->edges; e; e = e->next) + if (e->type == ne_cset) + { + if (!RX_bitset_member (e->params.cset, chr)) + /* An edge that doesn't apply at least tells us some characters + * that don't share the same edge set as CHR. + */ + rx_bitset_difference (rx->local_cset_size, csetout, e->params.cset); + else + { + /* If we find an NFA edge that applies, we make sure there + * are corresponding edges in the superstate NFA. + */ + { + struct rx_distinct_future * saved; + saved = *dfout; + *dfout = include_futures (rx, *dfout, e->dest, superstate); + if (!*dfout) + { + struct rx_distinct_future * df; + df = saved; + if (df) + df->next_same_super_edge[1]->next_same_super_edge[0] = 0; + while (df) + { + struct rx_distinct_future *dft; + dft = df; + df = df->next_same_super_edge[0]; + + if (dft->future && dft->future->transition_refs == dft) + { + dft->future->transition_refs = dft->next_same_dest; + if (dft->future->transition_refs == dft) + dft->future->transition_refs = 0; + } + dft->next_same_dest->prev_same_dest = dft->prev_same_dest; + dft->prev_same_dest->next_same_dest = dft->next_same_dest; + rx_cache_free (rx->cache, sizeof (*dft), (char *)dft); + } + return 0; + } + } + /* We also trim the character set a bit. */ + rx_bitset_intersection (rx->local_cset_size, + csetout, e->params.cset); + } + } + stateset = stateset->cdr; + } + return 1; +} + + +/* This is a constructor for RX_SUPER_EDGE structures. These are + * wrappers for lists of superstate NFA edges that share character sets labels. + * If a transition class contains more than one rx_distinct_future (superstate + * edge), then it represents a non-determinism in the superstate NFA. + */ + +#ifdef __STDC__ +static struct rx_super_edge * +rx_super_edge (struct rx *rx, + struct rx_superstate *super, rx_Bitset cset, + struct rx_distinct_future *df) +#else +static struct rx_super_edge * +rx_super_edge (rx, super, cset, df) + struct rx *rx; + struct rx_superstate *super; + rx_Bitset cset; + struct rx_distinct_future *df; +#endif +{ + struct rx_super_edge *tc; + int tc_size; + + tc_size = ( sizeof (struct rx_super_edge) + + rx_sizeof_bitset (rx->local_cset_size)); + + tc = ((struct rx_super_edge *) + rx_cache_malloc (rx->cache, tc_size)); + + if (!tc) + return 0; + + tc->next = super->edges; + super->edges = tc; + tc->rx_backtrack_frame.inx = rx->instruction_table[rx_backtrack_point]; + tc->rx_backtrack_frame.data = 0; + tc->rx_backtrack_frame.data_2 = (void *) tc; + tc->options = df; + tc->cset = (rx_Bitset) ((char *) tc + sizeof (*tc)); + rx_bitset_assign (rx->local_cset_size, tc->cset, cset); + if (df) + { + struct rx_distinct_future * dfp = df; + df->next_same_super_edge[1]->next_same_super_edge[0] = 0; + while (dfp) + { + dfp->edge = tc; + dfp = dfp->next_same_super_edge[0]; + } + df->next_same_super_edge[1]->next_same_super_edge[0] = df; + } + return tc; +} + + +/* There are three kinds of cache miss. The first occurs when a + * transition is taken that has never been computed during the + * lifetime of the source superstate. That cache miss is handled by + * calling COMPUTE_SUPER_EDGE. The second kind of cache miss + * occurs when the destination superstate of a transition doesn't + * exist. SOLVE_DESTINATION is used to construct the destination superstate. + * Finally, the third kind of cache miss occurs when the destination + * superstate of a transition is in a `semi-free state'. That case is + * handled by UNFREE_SUPERSTATE. + * + * The function of HANDLE_CACHE_MISS is to figure out which of these + * cases applies. + */ + +#ifdef __STDC__ +static void +install_partial_transition (struct rx_superstate *super, + struct rx_inx *answer, + RX_subset set, int offset) +#else +static void +install_partial_transition (super, answer, set, offset) + struct rx_superstate *super; + struct rx_inx *answer; + RX_subset set; + int offset; +#endif +{ + int start = offset; + int end = start + 32; + RX_subset pos = 1; + struct rx_inx * transitions = super->transitions; + + while (start < end) + { + if (set & pos) + transitions[start] = *answer; + pos <<= 1; + ++start; + } +} + + +#ifdef __STDC__ +struct rx_inx * +rx_handle_cache_miss (struct rx *rx, struct rx_superstate *super, unsigned char chr, void *data) +#else +struct rx_inx * +rx_handle_cache_miss (rx, super, chr, data) + struct rx *rx; + struct rx_superstate *super; + unsigned char chr; + void *data; +#endif +{ + int offset = chr / RX_subset_bits; + struct rx_distinct_future *df = data; + + if (!df) /* must be the shared_cache_miss_frame */ + { + /* Perhaps this is just a transition waiting to be filled. */ + struct rx_super_edge *tc; + RX_subset mask = rx_subset_singletons [chr % RX_subset_bits]; + + for (tc = super->edges; tc; tc = tc->next) + if (tc->cset[offset] & mask) + { + struct rx_inx * answer; + df = tc->options; + answer = ((tc->options->next_same_super_edge[0] != tc->options) + ? &tc->rx_backtrack_frame + : (df->effects + ? &df->side_effects_frame + : &df->future_frame)); + install_partial_transition (super, answer, + tc->cset [offset], offset * 32); + return answer; + } + /* Otherwise, it's a flushed or newly encountered edge. */ + { + char cset_space[1024]; /* this limit is far from unreasonable */ + rx_Bitset trcset; + struct rx_inx *answer; + + if (rx_sizeof_bitset (rx->local_cset_size) > sizeof (cset_space)) + return 0; /* If the arbitrary limit is hit, always fail */ + /* cleanly. */ + trcset = (rx_Bitset)cset_space; + rx_lock_superstate (rx, super); + if (!compute_super_edge (rx, &df, trcset, super, chr)) + { + rx_unlock_superstate (rx, super); + return 0; + } + if (!df) /* We just computed the fail transition. */ + { + static struct rx_inx + shared_fail_frame = { 0, 0, (void *)rx_backtrack, 0 }; + answer = &shared_fail_frame; + } + else + { + tc = rx_super_edge (rx, super, trcset, df); + if (!tc) + { + rx_unlock_superstate (rx, super); + return 0; + } + answer = ((tc->options->next_same_super_edge[0] != tc->options) + ? &tc->rx_backtrack_frame + : (df->effects + ? &df->side_effects_frame + : &df->future_frame)); + } + install_partial_transition (super, answer, + trcset[offset], offset * 32); + rx_unlock_superstate (rx, super); + return answer; + } + } + else if (df->future) /* A cache miss on an edge with a future? Must be + * a semi-free destination. */ + { + if (df->future->is_semifree) + refresh_semifree_superstate (rx->cache, df->future); + return &df->future_frame; + } + else + /* no future superstate on an existing edge */ + { + rx_lock_superstate (rx, super); + if (!solve_destination (rx, df)) + { + rx_unlock_superstate (rx, super); + return 0; + } + if (!df->effects + && (df->edge->options->next_same_super_edge[0] == df->edge->options)) + install_partial_transition (super, &df->future_frame, + df->edge->cset[offset], offset * 32); + rx_unlock_superstate (rx, super); + return &df->future_frame; + } +} + + diff --git a/rx/rxsuper.h b/rx/rxsuper.h new file mode 100644 index 00000000..59e1ed94 --- /dev/null +++ b/rx/rxsuper.h @@ -0,0 +1,446 @@ +/* classes: h_files */ + +#ifndef RXSUPERH +#define RXSUPERH + +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* lord Sun May 7 12:40:17 1995 */ + + + +#include "rxnfa.h" + + + +/* This begins the description of the superstate NFA. + * + * The superstate NFA corresponds to the NFA in these ways: + * + * Superstate states correspond to sets of NFA states (nfa_states(SUPER)), + * + * Superstate edges correspond to NFA paths. + * + * The superstate has no epsilon transitions; + * every edge has a character label, and a (possibly empty) side + * effect label. The side effect label corresponds to a list of + * side effects that occur in the NFA. These parts are referred + * to as: superedge_character(EDGE) and superedge_sides(EDGE). + * + * For a superstate edge EDGE starting in some superstate SUPER, + * the following is true (in pseudo-notation :-): + * + * exists DEST in nfa_states s.t. + * exists nfaEDGE in nfa_edges s.t. + * origin (nfaEDGE) == DEST + * && origin (nfaEDGE) is a member of nfa_states(SUPER) + * && exists PF in possible_futures(dest(nfaEDGE)) s.t. + * sides_of_possible_future (PF) == superedge_sides (EDGE) + * + * also: + * + * let SUPER2 := superedge_destination(EDGE) + * nfa_states(SUPER2) + * == union of all nfa state sets S s.t. + * exists PF in possible_futures(dest(nfaEDGE)) s.t. + * sides_of_possible_future (PF) == superedge_sides (EDGE) + * && S == dests_of_possible_future (PF) } + * + * Or in english, every superstate is a set of nfa states. A given + * character and a superstate implies many transitions in the NFA -- + * those that begin with an edge labeled with that character from a + * state in the set corresponding to the superstate. + * + * The destinations of those transitions each have a set of possible + * futures. A possible future is a list of side effects and a set of + * destination NFA states. Two sets of possible futures can be + * `merged' by combining all pairs of possible futures that have the + * same side effects. A pair is combined by creating a new future + * with the same side effect but the union of the two destination sets. + * In this way, all the possible futures suggested by a superstate + * and a character can be merged into a set of possible futures where + * no two elements of the set have the same set of side effects. + * + * The destination of a possible future, being a set of NFA states, + * corresponds to a supernfa state. So, the merged set of possible + * futures we just created can serve as a set of edges in the + * supernfa. + * + * The representation of the superstate nfa and the nfa is critical. + * The nfa has to be compact, but has to facilitate the rapid + * computation of missing superstates. The superstate nfa has to + * be fast to interpret, lazilly constructed, and bounded in space. + * + * To facilitate interpretation, the superstate data structures are + * peppered with `instruction frames'. There is an instruction set + * defined below which matchers using the supernfa must be able to + * interpret. + * + * We'd like to make it possible but not mandatory to use code + * addresses to represent instructions (c.f. gcc's computed goto). + * Therefore, we define an enumerated type of opcodes, and when + * writing one of these instructions into a data structure, use + * the opcode as an index into a table of instruction values. + * + * Below are the opcodes that occur in the superstate nfa. + * + * The descriptions of the opcodes refer to data structures that are + * described further below. + */ + +enum rx_opcode +{ + /* + * BACKTRACK_POINT is invoked when a character transition in + * a superstate leads to more than one edge. In that case, + * the edges have to be explored independently using a backtracking + * strategy. + * + * A BACKTRACK_POINT instruction is stored in a superstate's + * transition table for some character when it is known that that + * character crosses more than one edge. On encountering this + * instruction, the matcher saves enough state to backtrack to this + * point later in the match. + */ + rx_backtrack_point = 0, /* data is (struct transition_class *) */ + + /* + * RX_DO_SIDE_EFFECTS evaluates the side effects of an epsilon path. + * There is one occurence of this instruction per rx_distinct_future. + * This instruction is skipped if a rx_distinct_future has no side effects. + */ + rx_do_side_effects = rx_backtrack_point + 1, + + /* data is (struct rx_distinct_future *) */ + + /* + * RX_CACHE_MISS instructions are stored in rx_distinct_futures whose + * destination superstate has been reclaimed (or was never built). + * It recomputes the destination superstate. + * RX_CACHE_MISS is also stored in a superstate transition table before + * any of its edges have been built. + */ + rx_cache_miss = rx_do_side_effects + 1, + /* data is (struct rx_distinct_future *) */ + + /* + * RX_NEXT_CHAR is called to consume the next character and take the + * corresponding transition. This is the only instruction that uses + * the DATA field of the instruction frame instead of DATA_2. + * The comments about rx_inx explain this further. + */ + rx_next_char = rx_cache_miss + 1, /* data is (struct superstate *) */ + + /* RX_BACKTRACK indicates that a transition fails. Don't + * confuse this with rx_backtrack_point. + */ + rx_backtrack = rx_next_char + 1, /* no data */ + + /* + * RX_ERROR_INX is stored only in places that should never be executed. + */ + rx_error_inx = rx_backtrack + 1, /* Not supposed to occur. */ + + rx_num_instructions = rx_error_inx + 1 +}; + +/* The heart of the matcher is a `word-code-interpreter' + * (like a byte-code interpreter, except that instructions + * are a full word wide). + * + * Instructions are not stored in a vector of code, instead, + * they are scattered throughout the data structures built + * by the regexp compiler and the matcher. One word-code instruction, + * together with the arguments to that instruction, constitute + * an instruction frame (struct rx_inx). + * + * This structure type is padded by hand to a power of 2 because + * in one of the dominant cases, we dispatch by indexing a table + * of instruction frames. If that indexing can be accomplished + * by just a shift of the index, we're happy. + * + * Instructions take at most one argument, but there are two + * slots in an instruction frame that might hold that argument. + * These are called data and data_2. The data slot is only + * used for one instruction (RX_NEXT_CHAR). For all other + * instructions, data should be set to 0. + * + * RX_NEXT_CHAR is the most important instruction by far. + * By reserving the data field for its exclusive use, + * instruction dispatch is sped up in that case. There is + * no need to fetch both the instruction and the data, + * only the data is needed. In other words, a `cycle' begins + * by fetching the field data. If that is non-0, then it must + * be the destination state of a next_char transition, so + * make that value the current state, advance the match position + * by one character, and start a new cycle. On the other hand, + * if data is 0, fetch the instruction and do a more complicated + * dispatch on that. + */ + +struct rx_inx +{ + void * data; + void * data_2; + void * inx; + void * fnord; +}; + +#ifndef RX_TAIL_ARRAY +#define RX_TAIL_ARRAY 1 +#endif + +/* A superstate corresponds to a set of nfa states. Those sets are + * represented by STRUCT RX_SUPERSET. The constructors + * guarantee that only one (shared) structure is created for a given set. + */ +struct rx_superset +{ + int refs; /* This is a reference counted structure. */ + + /* We keep these sets in a cache because (in an unpredictable way), + * the same set is often created again and again. + * + * When an NFA is destroyed, some of the supersets for that NFA + * may still exist. This can lead to false cache hits -- an apparent cache + * hit on a superset that properly belongs to an already free NFA. + * + * When a cache hit appears to occur, we will have in hand the + * nfa for which it may have happened. Every nfa is given + * its own sequence number. The cache is validated + * by comparing the nfa sequence number to this field: + */ + int id; + + struct rx_nfa_state * car; /* May or may not be a valid addr. */ + struct rx_superset * cdr; + + /* If the corresponding superstate exists: */ + struct rx_superstate * superstate; + + /* That is_final field of the constiuent nfa states which has the greatest magnitude. */ + int is_final; + + /* The OR of the corresponding fields of the constiuent nfa states. */ + int has_cset_edges; + + + /* There is another bookkeeping problem. It is expensive to + * compute the starting nfa state set for an nfa. So, once computed, + * it is cached in the `struct rx'. + * + * But, the state set can be flushed from the superstate cache. + * When that happens, the cached value in the `struct rx' has + * to be flushed. + */ + struct rx * starts_for; + + /* This is used to link into a hash bucket so these objects can + * be `hash-consed'. + */ + struct rx_hash_item hash_item; +}; + +#define rx_protect_superset(RX,CON) (++(CON)->refs) + +/* The terminology may be confusing (rename this structure?). + * Every character occurs in at most one rx_super_edge per super-state. + * But, that structure might have more than one option, indicating a point + * of non-determinism. + * + * In other words, this structure holds a list of superstate edges + * sharing a common starting state and character label. The edges + * are in the field OPTIONS. All superstate edges sharing the same + * starting state and character are in this list. + */ +struct rx_super_edge +{ + struct rx_super_edge *next; + struct rx_inx rx_backtrack_frame; + int cset_size; + rx_Bitset cset; + struct rx_distinct_future *options; +}; + +/* A superstate is a set of nfa states (RX_SUPERSET) along + * with a transition table. Superstates are built on demand and reclaimed + * without warning. To protect a superstate from this ghastly fate, + * use LOCK_SUPERSTATE. + */ +struct rx_superstate +{ + int rx_id; /* c.f. the id field of rx_superset */ + int locks; /* protection from reclamation */ + + /* Within a superstate cache, all the superstates are kept in a big + * queue. The tail of the queue is the state most likely to be + * reclaimed. The *recyclable fields hold the queue position of + * this state. + */ + struct rx_superstate * next_recyclable; + struct rx_superstate * prev_recyclable; + + /* The supernfa edges that exist in the cache and that have + * this state as their destination are kept in this list: + */ + struct rx_distinct_future * transition_refs; + + /* The list of nfa states corresponding to this superstate: */ + struct rx_superset * contents; + + /* The list of edges in the cache beginning from this state. */ + struct rx_super_edge * edges; + + /* A tail of the recyclable queue is marked as semifree. A semifree + * state has no incoming next_char transitions -- any transition + * into a semifree state causes a complex dispatch with the side + * effect of rescuing the state from its semifree state into a + * fully free state at the head of the queue. + * + * An alternative to this might be to make next_char more expensive, + * and to move a state to the head of the recyclable queue whenever + * it is entered. That way, popular states would never be recycled. + * + * But unilaterally making next_char more expensive actually loses. + * So, incoming transitions are only made expensive for states near + * the tail of the recyclable queue. The more cache contention + * there is, the more frequently a state will have to prove itself + * and be moved back to the front of the queue. If there is less + * contention, then popular states just aggregate in the front of + * the queue and stay there. + */ + int is_semifree; + + + /* This keeps track of the size of the transition table for this + * state. There is a half-hearted attempt to support variable sized + * superstates. + */ + int trans_size; + + /* Indexed by characters... */ + struct rx_inx transitions[RX_TAIL_ARRAY]; +}; + + +/* A list of distinct futures define the edges that leave from a + * given superstate on a given character. c.f. rx_super_edge. + */ +struct rx_distinct_future +{ + struct rx_distinct_future * next_same_super_edge[2]; + struct rx_distinct_future * next_same_dest; + struct rx_distinct_future * prev_same_dest; + struct rx_superstate * present; /* source state */ + struct rx_superstate * future; /* destination state */ + struct rx_super_edge * edge; + + + /* The future_frame holds the instruction that should be executed + * after all the side effects are done, when it is time to complete + * the transition to the next state. + * + * Normally this is a next_char instruction, but it may be a + * cache_miss instruction as well, depending on whether or not + * the superstate is in the cache and semifree. + * + * If this is the only future for a given superstate/char, and + * if there are no side effects to be performed, this frame is + * not used (directly) at all. Instead, its contents are copied + * into the transition table of the starting state of this dist. future + * (a sort of goto elimination). + */ + struct rx_inx future_frame; + + struct rx_inx side_effects_frame; + struct rx_se_list * effects; +}; + +#define rx_lock_superstate(R,S) ((S)->locks++) +#define rx_unlock_superstate(R,S) (--(S)->locks) + +struct rx_cache; + +#ifdef __STDC__ +typedef void (*rx_morecore_fn)(struct rx_cache *); +#else +typedef void (*rx_morecore_fn)(); +#endif + +struct rx_cache +{ + struct rx_hash_rules superset_hash_rules; + + struct rx_superstate * lru_superstate; + struct rx_superstate * semifree_superstate; + + struct rx_superset * empty_superset; + + int superstates; + int semifree_superstates; + int hits; + int misses; + + int bytes_allowed; + int bytes_used; + + int local_cset_size; + void ** instruction_table; + + struct rx_hash superset_table; +}; + +#ifndef RX_DEFAULT_DFA_CACHE_SIZE +/* This is an upper bound on the number of bytes that may (normally) + * be allocated for DFA states. If this threshold would be exceeded, + * Rx tries to flush some DFA states from the cache. + * + * This value is used whenever the rx_default_cache is used (for example, + * with the Posix entry points). + */ +#define RX_DEFAULT_DFA_CACHE_SIZE (1 << 19) +#endif + +extern struct rx_cache * rx_default_cache; + + +#ifdef __STDC__ +extern char * rx_cache_malloc (struct rx_cache * cache, int size); +extern void rx_cache_free (struct rx_cache * cache, int size, char * mem); +extern void rx_release_superset (struct rx *rx, + struct rx_superset *set); +extern struct rx_superset * rx_superset_cons (struct rx * rx, + struct rx_nfa_state *car, struct rx_superset *cdr); +extern struct rx_superset * rx_superstate_eclosure_union (struct rx * rx, struct rx_superset *set, struct rx_nfa_state_set *ecl) ; +extern struct rx_superstate * rx_superstate (struct rx *rx, + struct rx_superset *set); +extern struct rx_inx * rx_handle_cache_miss (struct rx *rx, struct rx_superstate *super, unsigned char chr, void *data) ; + +#else /* STDC */ +extern char * rx_cache_malloc (); +extern void rx_cache_free (); +extern void rx_release_superset (); +extern struct rx_superset * rx_superset_cons (); +extern struct rx_superset * rx_superstate_eclosure_union (); +extern struct rx_superstate * rx_superstate (); +extern struct rx_inx * rx_handle_cache_miss (); + +#endif /* STDC */ + +#endif /* RXSUPERH */ diff --git a/rx/rxunfa.c b/rx/rxunfa.c new file mode 100644 index 00000000..f43181d9 --- /dev/null +++ b/rx/rxunfa.c @@ -0,0 +1,269 @@ +/* classes: src_files */ + +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "rxall.h" +#include "rx.h" +#include "rxunfa.h" +#include "rxnfa.h" + + +#ifdef __STDC__ +static int +unfa_equal (void * va, void * vb) +#else +static int +unfa_equal (va, vb) + void * va; + void * vb; +#endif +{ + return rx_rexp_equal ((struct rexp_node *)va, (struct rexp_node *)vb); +} + + +struct rx_hash_rules unfa_rules = { unfa_equal, 0, 0, 0, 0 }; + + +#ifdef __STDC__ +static struct rx_cached_rexp * +canonical_unfa (struct rx_hash * table, struct rexp_node * rexp, int cset_size) +#else +static struct rx_cached_rexp * +canonical_unfa (table, rexp, cset_size) + struct rx_hash * table; + struct rexp_node * rexp; + int cset_size; +#endif +{ + struct rx_hash_item * it; + it = rx_hash_store (table, rx_rexp_hash (rexp, 0), rexp, &unfa_rules); + + if (it->binding == 0) + { + struct rx_cached_rexp * cr; + + if (it->data == (void *)rexp) + rx_save_rexp (rexp); + + cr = (struct rx_cached_rexp *)malloc (sizeof (*cr)); + rx_bzero ((char *)cr, sizeof (*cr)); + if (!cr) + return 0; + it->binding = (void *)cr; + cr->unfa.nfa = 0; + cr->unfa.exp = rexp; + cr->hash_item = it; + rx_save_rexp (rexp); + } + return (struct rx_cached_rexp *)it->binding; +} + + +#ifdef __STDC__ +static struct rx * +rx_unfa_rx (struct rx_cached_rexp * cr, struct rexp_node * exp, int cset_size) +#else +static struct rx * +rx_unfa_rx (cr, exp, cset_size) + struct rx_cached_rexp * cr; + struct rexp_node * exp; + int cset_size; +#endif +{ + struct rx * new_rx; + + if (cr->unfa.nfa) + return cr->unfa.nfa; + + new_rx = rx_make_rx (cset_size); + if (!new_rx) + return 0; + { + struct rx_nfa_state * start; + struct rx_nfa_state * end; + start = end = 0; + if (!rx_build_nfa (new_rx, exp, &start, &end)) + { + free (new_rx); + return 0; + } + new_rx->start_nfa_states = start; + end->is_final = 1; + start->is_start = 1; + { + struct rx_nfa_state * s; + int x; + x = 0; + for (s = new_rx->nfa_states; s; s = s->next) + s->id = x++; + } + } + cr->unfa.nfa = new_rx; + return new_rx; +} + + + + +#ifdef __STDC__ +struct rx_unfaniverse * +rx_make_unfaniverse (int delay) +#else +struct rx_unfaniverse * +rx_make_unfaniverse (delay) + int delay; +#endif +{ + struct rx_unfaniverse * it; + it = (struct rx_unfaniverse *)malloc (sizeof (*it)); + if (!it) return 0; + rx_bzero ((char *)it, sizeof (*it)); + it->delay = delay; + return it; +} + + +#ifdef __STDC__ +void +rx_free_unfaniverse (struct rx_unfaniverse * it) +#else +void +rx_free_unfaniverse (it) + struct rx_unfaniverse * it; +#endif +{ +} + + + + + +#ifdef __STDC__ +struct rx_unfa * +rx_unfa (struct rx_unfaniverse * unfaniverse, struct rexp_node * exp, int cset_size) +#else +struct rx_unfa * +rx_unfa (unfaniverse, exp, cset_size) + struct rx_unfaniverse * unfaniverse; + struct rexp_node * exp; + int cset_size; +#endif +{ + struct rx_cached_rexp * cr; + if (exp && exp->cr) + cr = exp->cr; + else + { + cr = canonical_unfa (&unfaniverse->table, exp, cset_size); + if (exp) + exp->cr = cr; + } + if (!cr) + return 0; + if (cr->next) + { + if (unfaniverse->free_queue == cr) + { + unfaniverse->free_queue = cr->next; + if (unfaniverse->free_queue == cr) + unfaniverse->free_queue = 0; + } + cr->next->prev = cr->prev; + cr->prev->next = cr->next; + cr->next = 0; + cr->prev = 0; + --unfaniverse->delayed; + } + ++cr->unfa.refs; + cr->unfa.cset_size = cset_size; + cr->unfa.verse = unfaniverse; + rx_unfa_rx (cr, exp, cset_size); + return &cr->unfa; +} + + +#ifdef __STDC__ +void +rx_free_unfa (struct rx_unfa * unfa) +#else +void +rx_free_unfa (unfa) + struct rx_unfa * unfa; +#endif +{ + struct rx_cached_rexp * cr; + cr = (struct rx_cached_rexp *)unfa; + if (!cr) + return; + if (!--cr->unfa.refs) + { + if (!unfa->verse->free_queue) + { + unfa->verse->free_queue = cr; + cr->next = cr->prev = cr; + } + else + { + cr->next = unfa->verse->free_queue; + cr->prev = unfa->verse->free_queue->prev; + cr->next->prev = cr; + cr->prev->next = cr; + } + + ++unfa->verse->delayed; + while (unfa->verse->delayed > unfa->verse->delay) + { + struct rx_cached_rexp * it; + it = unfa->verse->free_queue; + unfa->verse->free_queue = it->next; + if (!--unfa->verse->delayed) + unfa->verse->free_queue = 0; + it->prev->next = it->next; + it->next->prev = it->prev; + if (it->unfa.exp) + it->unfa.exp->cr = 0; + rx_free_rexp ((struct rexp_node *)it->hash_item->data); + rx_hash_free (it->hash_item, &unfa_rules); + rx_free_rx (it->unfa.nfa); + rx_free_rexp (it->unfa.exp); + free (it); + if (it == cr) + break; + } + } + else + return; +} + + +#ifdef __STDC__ +void +rx_save_unfa (struct rx_unfa * unfa) +#else +void +rx_save_unfa (unfa) + struct rx_unfa * unfa; +#endif +{ + ++(unfa->refs); +} + diff --git a/rx/rxunfa.h b/rx/rxunfa.h new file mode 100644 index 00000000..aeb589ad --- /dev/null +++ b/rx/rxunfa.h @@ -0,0 +1,70 @@ +#ifndef RXUNFAH +#define RXUNFAH + +/* Copyright (C) 1995, 1996 Tom Lord + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2, 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +#include "_rx.h" + + +struct rx_unfaniverse +{ + int delay; + int delayed; + struct rx_hash table; + struct rx_cached_rexp * free_queue; +}; + + +struct rx_unfa +{ + int refs; + struct rexp_node * exp; + struct rx * nfa; + int cset_size; + struct rx_unfaniverse * verse; +}; + +struct rx_cached_rexp +{ + struct rx_unfa unfa; + struct rx_cached_rexp * next; + struct rx_cached_rexp * prev; + struct rx_hash_item * hash_item; +}; + + + +#ifdef __STDC__ +extern struct rx_unfaniverse * rx_make_unfaniverse (int delay); +extern void rx_free_unfaniverse (struct rx_unfaniverse * it); +extern struct rx_unfa * rx_unfa (struct rx_unfaniverse * unfaniverse, struct rexp_node * exp, int cset_size); +extern void rx_free_unfa (struct rx_unfa * unfa); +extern void rx_save_unfa (struct rx_unfa * unfa); + +#else /* STDC */ +extern struct rx_unfaniverse * rx_make_unfaniverse (); +extern void rx_free_unfaniverse (); +extern struct rx_unfa * rx_unfa (); +extern void rx_free_unfa (); +extern void rx_save_unfa (); + +#endif /* STDC */ +#endif /* RXUNFAH */ diff --git a/sample.mailcap b/sample.mailcap new file mode 100644 index 00000000..6cba2b67 --- /dev/null +++ b/sample.mailcap @@ -0,0 +1,4 @@ +text/html; netscape -remote 'openURL(%s)' +image/gif; xv %s +image/jpg; xv %s +application/pgp-keys; pgp -f < %s ; copiousoutput diff --git a/sample.muttrc b/sample.muttrc new file mode 100644 index 00000000..f0bff294 --- /dev/null +++ b/sample.muttrc @@ -0,0 +1,333 @@ +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# ME's personal .muttrc (Mutt 0.92.5) +# +# The format of this file is one command per line. Everything after a pound +# sign (#) is a comment, unless a backward slash (\) precedes it +# + +# Note: $folder should be set _before_ any other path vars where `+' or `=' +# is used because paths are expanded when parsed +# +#set folder=~/Mail # where i keep my mailboxes + +#set abort_unmodified=yes # automatically abort replies if I don't + # change the message +set alias_file=~/.mail_aliases # where I keep my aliases +#set allow_8bit # never do Q-P encoding on legal 8-bit chars +set arrow_cursor # use -> instead of hiliting the whole line +#set ascii_chars # use ASCII instead of ACS chars for threads +#set askbcc +#set askcc +#set attribution="On %d, %n wrote:" # how to attribute replies +set autoedit # go to the editor right away when composing +#set auto_tag # always operate on tagged messages +#set charset="iso-8859-1" # character set for your terminal +set noconfirmappend # don't ask me if i want to append to mailboxes +#set confirmcreate # prompt when creating new files +set copy=yes # always save a copy of outgoing messages +set delete=yes # purge deleted messages without asking +set edit_headers # let me edit the message header when composing +#set editor="emacs -nw" # editor to use when composing messages +#set fast_reply # skip initial prompts when replying +#set fcc_attach # keep attachments in copies of sent messages? +#set force_name # fcc by recipient, create if mailbox doesn't exist +#set forward_decode # weed and MIME decode forwaded messages +#set forward_format="[%a: %s]" # subject to use when forwarding messages +#set forward_quote # quote the header and body of forward msgs +#set hdr_format="%4C %Z %{%m/%d} [%2N] %-15.15F (%4c) %s" +set hdr_format="%4C %Z %{%m/%d} %-15.15F (%4c) %s" # format of the index +#set hdrs # include `my_hdr' lines in outgoing messages +#set header # include message header when replying +set help # show the help lines +#set history=20 # number of lines of history to remember +#set hostname="cs.hmc.edu" # my DNS domain +set include # always include messages when replying +#set indent_string="> " # how to quote replied text +#set locale="C" # locale to use for printing time +#set mailcap_path="~/.mailcap:/usr/local/share/mailcap" +set nomark_old # i don't care about whether a message is old +set mail_check=10 # how often to poll for new mail +set mbox=+mbox # where to store read messages +#set menu_scroll # no implicit next-page/prev-page +#set metoo # remove my address when replying +set mime_forward # use message/rfc822 type to forward messages +set move=yes # don't ask about moving messages, just do it +#set pager=less # some people prefer an external pager +#set pager_context=3 # no. of lines of context to give when scrolling +#set pager_format="-%S- %-20.20f %s" # format of the pager status bar +set pager_index_lines=6 # how many index lines to show in the pager +#set pager_stop # don't move to the next message on next-page +#set pgp_strict_enc # use Q-P encoding when needed for PGP +set postponed=+postponed # mailbox to store postponed messages in +#set post_indent_string='---end quoted text---' +#set print=ask-yes # ask me if I really want to print messages +set print_command=/bin/false # how to print things (I like to save trees) +set noprompt_after # ask me for a command after the external pager exits +#set quote_regexp="^ *[a-zA-Z]*[>:#}]" # how to catch quoted text +set read_inc=25 # show progress when reading a mailbox +#set recall # prompt to recall postponed messages +set record=+outbox # default location to save outgoing mail +set reply_to # always use reply-to if present +#set reply_regexp="^(re:[ \t]*)+"# how to identify replies in the subject: +#set resolve # move to the next message when an action is performed +#set reverse_alias # attempt to look up my names for people +set reverse_name # use my address as it appears in the message + # i am replying to +set nosave_empty # remove files when no messages are left +#set save_name # save outgoing messages by recipient, if the +#set sendmail="/usr/lib/sendmail -oi -oem" # how to deliver mail +#set shell="/bin/zsh" # program to use for shell escapes +#set signature="~/.signature" # file which contains my signature + +# I subscribe to a lot of mailing lists, so this is _very_ useful. This +# groups messages on the same subject to make it easier to follow a +# discussion. Mutt will draw a nice tree showing how the discussion flows. +set sort=threads # primary sorting method + +#set sort_aux=reverse-date-received # how to sort subthreads +#set sort_aux=last-date # date of the last message in thread +set sort_browser=reverse-date # how to sort files in the dir browser +set spoolfile='~/mailbox' # where my new mail is located +#set status_format="-%r-Mutt: %f [Msgs:%?M?%M/?%m%?n? New:%n?%?d? Del:%d?%?F? Flag:%F?%?t? Tag:%t?%?p? Post:%p?%?b? Inc:%b? %l]---(%s)-%>-(%P)---" +#set status_on_top # some people prefer the status bar on top +#set strict_threads # don't thread by subject +set tilde # virtual lines to pad blank lines in the pager +#set timeout=0 # timeout for prompt in the index menu +#set tmpdir=~/tmp # where to store temp files +#set to_chars=" +TCF" +#set use_8bitmime # enable the -B8BITMIME sendmail flag +set nouse_domain # don't qualify local addresses with $domain +#set use_from # always generate the `From:' header field +set use_mailcap=yes # always use a mailcap entry when found +set pgp_verify_sig=no # don't automatically verify message signatures +#set visual=vim # editor invoked by ~v in the builtin editor +#set nowait_key # prompt when a pipe returns normal status +set write_inc=25 # show progress while writing mailboxes + +# only enable the following IFF you have sendmail 8.8.x or you will not +# be able to send mail!!! +#set dsn_notify='failure,delay' # when to return an error message +#set dsn_return=hdrs # what to return in the error message + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# Header fields I don't normally want to see +# +ignore * # this means "ignore all lines by default" + +# I do want to see these fields, though! +unignore from: subject to cc mail-followup-to \ + date x-mailer x-url # this shows how nicely wrap long lines + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# Color definitions +# + +#color normal white default +color hdrdefault red default +color quoted brightblue default +color signature red default +color indicator brightyellow red +color error brightred default +color status yellow blue +color tree magenta default # the thread tree in the index menu +color tilde magenta default +color message brightcyan default +color markers brightcyan default +color attachment brightmagenta default +color search default green # how to hilite search patterns in the pager + +color header brightred default ^(From|Subject): +color body magenta default "(ftp|http)://[^ ]+" # point out URLs +color body magenta default [-a-z_0-9.]+@[-a-z_0-9.]+ # e-mail addresses +color underline brightgreen default + +# attributes when using a mono terminal +#mono header underline ^(From|Subject): +mono quoted bold + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# Key bindings +# +# maps: +# alias alias menu +# attach attachment menu +# browser directory browser +# compose compose menu +# index message index +# pgp pgp menu +# postpone postponed message recall menu +# generic generic keymap for all of the above +# editor line editor +# pager text viewer +# + +bind generic "\e<" first-entry # emacs-like bindings for moving to top/bottom +bind generic \e> last-entry +bind generic { top-page +bind generic } bottom-page +bind generic \177 last-entry + +macro index \cb |urlview\n # simulate the old browse-url function + +macro index S s+spam\n +macro pager S s+spam\n + +#macro index \# /bug^M # search for bugs +#macro index "\"" ":set realname=\"real hairy macro\"^M:set ?realname^M" # and a comment to boot! +#macro index f1 :woohoo! + +bind pager G bottom # just like vi and less +#macro pager \Ck "|pgp -kaf^M" # a comment is valid here +#macro pager X "|morepgp^M" # pipe PGP message to a script + +#bind editor \cy eol # make ^Y jump to the end of the line + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# User Defined Headers +# + +#my_hdr X-Useless-Header: Look ma, it's a \# sign! # real comment +#my_hdr X-Operating-System: `uname -a` + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# Specify default filename when saving messages +# +# save-hook [!]<pattern> <mailbox> +# +# <mailbox> is provided as default when saving messages from <pattern> + +#save-hook mutt- =mutt-mail +#save-hook aol\.com +spam +save-hook ^judge +diplomacy + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# Multiple spool mailboxes +# +# mbox-hook [!]<pattern> <mbox-mailbox> +# +# Read mail in <pattern> is moved to <mbox-mailbox> when <pattern> is +# closed. + +#mbox-hook =mutt-users.in =mutt-users +#mbox-hook +TEST +inbox + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# Change settings based upon message recipient +# +# send-hook [!]<pattern> <command> +# +# <command> is executed when sending mail to an address matching <pattern> + +#send-hook mutt- 'set signature=~/.sigmutt; my_hdr From: Mutt User <user@example.com>' + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# Specify where to save composed messages +# +# fcc-hook [!]<pattern> <mailbox> +# +# <pattern> is recipient(s), <mailbox> is where to save a copy + +#fcc-hook joe +joe +#fcc-hook bob +bob + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# Change settings based on mailbox +# +# folder-hook [!]<pattern> <command> +# +# <command> is executed when opening a mailbox matching <pattern> + +#folder-hook . 'set sort=date-sent' +#folder-hook mutt 'set hdr_format="%4C %Z %02m/%02N %-20.20F (%4l) %s"' +#folder-hook =mutt my_hdr Revolution: \#9 # real comment + +#folder-hook . 'set reply_regexp="^re:[ \t]*"' + +# this mailing list prepends "[WM]" to all non reply subjects, so set +# $reply_regexp to ignore it +#folder-hook +wmaker 'set reply_regexp="^(re:[ \t]*)?\[WM\][ \t]*"' + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# Aliases +# +# alias <name> <address> [ , <address> ... ] + +#alias exam "\# to annoy michael" <user@host> +#alias me Michael Elkins <michael> # me! +alias mutt-dev Mutt Development List <mutt-dev@cs.hmc.edu> # power users +alias mutt-users Mutt User List <mutt-users@cs.hmc.edu> +alias mutt-announce Mutt Announcement List <mutt-announce@cs.hmc.edu> +alias wmaker WindowMaker Mailing List <wmaker@eosys.com> + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# Mailboxes to watch for new mail +# +# mailboxes <path1> [ <path2> ... ] +# + +mailboxes ! +mutt-dev +mutt-users +open-pgp +wmaker +hurricane +vim +ietf \ + +drums +#mailboxes `echo $HOME/Mail/*` + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# Specify the order of the headers to appear when displaying a message +# +# hdr_order <hdr1> [ <hdr2> ... ] +# + +hdr_order date from subject to cc + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# Identify mailing lists I subscribe to +# +# lists <list-name> [ <list-name> ... ] + +lists mutt-dev mutt-users + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# Automatically use entries from ~/.mailcap to view these MIME types +# +# auto_view <type> [ <type> ... ] + +auto_view application/x-gunzip +auto_view application/x-gzip + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# Scoring +# +# score <pattern> <value> +# +# 9999 and -9999 are special values which cause processing of hooks to stop +# at that entry. If you prefix the score with an equal sign (=), the score +# is assigned to the message and processing stops. + +#score '~f ^me@cs\.hmc\.edu$' 1000 +#score '~t mutt | ~c mutt' =500 +#score '~f aol\.com$' -9999 + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# I use Mutt on several different machines, so I put local config commands +# in a separate file so I can have the rest of the settings the same on all +# machines. +# + +source ~/.muttrc-local # config commands local to this site + +# EOF diff --git a/score.c b/score.c new file mode 100644 index 00000000..4fa11c15 --- /dev/null +++ b/score.c @@ -0,0 +1,174 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "sort.h" +#include <string.h> +#include <stdlib.h> + +typedef struct score_t +{ + char *str; + pattern_t *pat; + int val; + int exact; /* if this rule matches, don't evaluate any more */ + struct score_t *next; +} SCORE; + +SCORE *Score = NULL; + +void mutt_check_rescore (CONTEXT *ctx) +{ + int i; + + if (option (OPTNEEDRESCORE)) + { + if ((Sort & SORT_MASK) == SORT_SCORE || + (SortAux & SORT_MASK) == SORT_SCORE) + { + set_option (OPTNEEDRESORT); + if ((Sort & SORT_MASK) == SORT_THREADS) + set_option (OPTSORTSUBTHREADS); + } + + /* must redraw the index since the user might have %N in it */ + set_option (OPTFORCEREDRAWINDEX); + set_option (OPTFORCEREDRAWPAGER); + + for (i = 0; i < ctx->msgcount; i++) + mutt_score_message (ctx->hdrs[i]); + + mutt_cache_index_colors (ctx); + + unset_option (OPTNEEDRESCORE); + } +} + +int mutt_parse_score (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + SCORE *ptr, *last; + char *pattern, *pc; + struct pattern_t *pat; + + mutt_extract_token (buf, s, 0); + if (!MoreArgs (s)) + { + strfcpy (err->data, "score: too few arguments", err->dsize); + return (-1); + } + pattern = buf->data; + memset (buf, 0, sizeof (BUFFER)); + mutt_extract_token (buf, s, 0); + if (MoreArgs (s)) + { + FREE (&pattern); + strfcpy (err->data, "score: too many arguments", err->dsize); + return (-1); + } + + /* look for an existing entry and update the value, else add it to the end + of the list */ + for (ptr = Score, last = NULL; ptr; last = ptr, ptr = ptr->next) + if (strcmp (pattern, ptr->str) == 0) + break; + if (!ptr) + { + if ((pat = mutt_pattern_comp (pattern, 0, err)) == NULL) + { + FREE (&pattern); + return (-1); + } + ptr = safe_calloc (1, sizeof (SCORE)); + if (last) + last->next = ptr; + else + Score = ptr; + ptr->pat = pat; + ptr->str = pattern; + } + pc = buf->data; + if (*pc == '=') + { + ptr->exact = 1; + pc++; + } + ptr->val = atoi (pc); + set_option (OPTNEEDRESCORE); + return 0; +} + +void mutt_score_message (HEADER *hdr) +{ + SCORE *tmp; + + hdr->score = 0; /* in case of re-scoring */ + for (tmp = Score; tmp; tmp = tmp->next) + { + if (mutt_pattern_exec (tmp->pat, 0, NULL, hdr) > 0) + { + if (tmp->exact || tmp->val == 9999 || tmp->val == -9999) + { + hdr->score = tmp->val; + break; + } + hdr->score += tmp->val; + } + } + if (hdr->score < 0) + hdr->score = 0; +} + +int mutt_parse_unscore (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + SCORE *tmp, *last = NULL; + + while (MoreArgs (s)) + { + mutt_extract_token (buf, s, 0); + if (!strcmp ("*", buf->data)) + { + for (tmp = Score; tmp; ) + { + last = tmp; + tmp = tmp->next; + mutt_pattern_free (&last->pat); + safe_free ((void **) &last); + } + Score = NULL; + } + else + { + for (tmp = Score; tmp; last = tmp, tmp = tmp->next) + { + if (!strcmp (buf->data, tmp->str)) + { + if (last) + last->next = tmp->next; + else + Score = tmp->next; + mutt_pattern_free (&tmp->pat); + safe_free ((void **) &tmp); + /* there should only be one score per pattern, so we can stop here */ + break; + } + } + } + } + set_option (OPTNEEDRESCORE); + return 0; +} diff --git a/send.c b/send.c new file mode 100644 index 00000000..5cfa8471 --- /dev/null +++ b/send.c @@ -0,0 +1,1250 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "keymap.h" +#include "mime.h" +#include "mailbox.h" +#include "copy.h" +#include "mx.h" + +#include <ctype.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <dirent.h> + +extern char RFC822Specials[]; + + + + +#ifdef _PGPPATH +#include "pgp.h" +#endif + + + +static void append_signature (ENVELOPE *env, FILE *f) +{ + FILE *tmpfp; + pid_t thepid; + + if (Signature && (tmpfp = mutt_open_read (Signature, &thepid))) + { + if (option (OPTSIGDASHES)) + fputs ("\n-- \n", f); + mutt_copy_stream (tmpfp, f); + fclose (tmpfp); + if (thepid != -1) + mutt_wait_filter (thepid); + } +} + +/* compare two e-mail addresses and return 1 if they are equivalent */ +static int mutt_addrcmp (ADDRESS *a, ADDRESS *b) +{ + if (!a->mailbox || !b->mailbox) + return 0; + if (strcasecmp (a->mailbox, b->mailbox)) + return 0; + return 1; +} + +/* search an e-mail address in a list */ +static int mutt_addrsrc (ADDRESS *a, ADDRESS *lst) +{ + for (; lst; lst = lst->next) + { + if (mutt_addrcmp (a, lst)) + return (1); + } + return (0); +} + +/* removes addresses from "b" which are contained in "a" */ +static ADDRESS *mutt_remove_xrefs (ADDRESS *a, ADDRESS *b) +{ + ADDRESS *top, *p, *prev = NULL; + + top = b; + while (b) + { + for (p = a; p; p = p->next) + { + if (mutt_addrcmp (p, b)) + break; + } + if (p) + { + if (prev) + { + prev->next = b->next; + b->next = NULL; + rfc822_free_address (&b); + b = prev; + } + else + { + top = top->next; + b->next = NULL; + rfc822_free_address (&b); + b = top; + } + } + else + { + prev = b; + b = b->next; + } + } + return top; +} + +/* remove any address which matches the current user. if `leave_only' is + * nonzero, don't remove the user's address if it is the only one in the list + */ +static ADDRESS *remove_user (ADDRESS *a, int leave_only) +{ + ADDRESS *top = NULL, *last = NULL; + + while (a) + { + if (!mutt_addr_is_user (a)) + { + if (top) + { + last->next = a; + last = last->next; + } + else + last = top = a; + a = a->next; + last->next = NULL; + } + else + { + ADDRESS *tmp = a; + + a = a->next; + if (!leave_only || a || last) + { + tmp->next = NULL; + rfc822_free_address (&tmp); + } + else + last = top = tmp; + } + } + return top; +} + +static ADDRESS *find_mailing_lists (ADDRESS *t, ADDRESS *c) +{ + ADDRESS *top = NULL, *ptr = NULL; + + for (; t || c; t = c, c = NULL) + { + for (; t; t = t->next) + { + if (mutt_is_mail_list (t)) + { + if (top) + { + ptr->next = rfc822_cpy_adr_real (t); + ptr = ptr->next; + } + else + ptr = top = rfc822_cpy_adr_real (t); + } + } + } + return top; +} + +static int edit_address (ADDRESS **a, /* const */ char *field) +{ + char buf[HUGE_STRING]; + + buf[0] = 0; + rfc822_write_address (buf, sizeof (buf), *a); + if (mutt_get_field (field, buf, sizeof (buf), M_ALIAS) != 0) + return (-1); + rfc822_free_address (a); + *a = mutt_expand_aliases (mutt_parse_adrlist (NULL, buf)); + return 0; +} + +static int edit_envelope (ENVELOPE *en) +{ + char buf[HUGE_STRING]; + LIST *uh = UserHeader; + + if (edit_address (&en->to, "To: ") == -1 || en->to == NULL) + return (-1); + if (option (OPTASKCC) && edit_address (&en->cc, "Cc: ") == -1) + return (-1); + if (option (OPTASKBCC) && edit_address (&en->bcc, "Bcc: ") == -1) + return (-1); + + if (en->subject) + { + if (option (OPTFASTREPLY)) + return (0); + else + strfcpy (buf, en->subject, sizeof (buf)); + } + else + { + char *p; + + buf[0] = 0; + for (; uh; uh = uh->next) + { + if (strncasecmp ("subject:", uh->data, 8) == 0) + { + p = uh->data + 8; + SKIPWS (p); + strncpy (buf, p, sizeof (buf)); + } + } + } + + if (mutt_get_field ("Subject: ", buf, sizeof (buf), 0) != 0 || + (!buf[0] && query_quadoption (OPT_SUBJECT, "No subject, abort?") != 0)) + { + mutt_message ("No subject, aborting."); + return (-1); + } + safe_free ((void **) &en->subject); + en->subject = safe_strdup (buf); + + return 0; +} + +static void process_user_recips (ENVELOPE *env) +{ + LIST *uh = UserHeader; + + for (; uh; uh = uh->next) + { + if (strncasecmp ("to:", uh->data, 3) == 0) + env->to = rfc822_parse_adrlist (env->to, uh->data + 3); + else if (strncasecmp ("cc:", uh->data, 3) == 0) + env->cc = rfc822_parse_adrlist (env->cc, uh->data + 3); + else if (strncasecmp ("bcc:", uh->data, 4) == 0) + env->bcc = rfc822_parse_adrlist (env->bcc, uh->data + 4); + } +} + +static void process_user_header (ENVELOPE *env) +{ + LIST *uh = UserHeader; + LIST *last = env->userhdrs; + + if (last) + while (last->next) + last = last->next; + + for (; uh; uh = uh->next) + { + if (strncasecmp ("from:", uh->data, 5) == 0) + { + /* User has specified a default From: address. Remove default address */ + rfc822_free_address (&env->from); + env->from = rfc822_parse_adrlist (env->from, uh->data + 5); + } + else if (strncasecmp ("reply-to:", uh->data, 9) == 0) + { + rfc822_free_address (&env->reply_to); + env->reply_to = rfc822_parse_adrlist (env->reply_to, uh->data + 9); + } + else if (strncasecmp ("to:", uh->data, 3) != 0 && + strncasecmp ("cc:", uh->data, 3) != 0 && + strncasecmp ("bcc:", uh->data, 4) != 0 && + strncasecmp ("subject:", uh->data, 8) != 0) + { + if (last) + { + last->next = mutt_new_list (); + last = last->next; + } + else + last = env->userhdrs = mutt_new_list (); + last->data = safe_strdup (uh->data); + } + } +} + +LIST *mutt_copy_list (LIST *p) +{ + LIST *t, *r=NULL, *l=NULL; + + for (; p; p = p->next) + { + t = (LIST *) safe_malloc (sizeof (LIST)); + t->data = safe_strdup (p->data); + t->next = NULL; + if (l) + { + r->next = t; + r = r->next; + } + else + l = r = t; + } + return (l); +} + +static int include_forward (CONTEXT *ctx, HEADER *cur, FILE *out) +{ + char buffer[STRING]; + int chflags = CH_DECODE, cmflags = 0; + + + +#ifdef _PGPPATH + if (cur->pgp) + { + if (cur->pgp & PGPENCRYPT) + { + /* make sure we have the user's passphrase before proceeding... */ + pgp_valid_passphrase (); + } + } +#endif /* _PGPPATH */ + + + + fputs ("----- Forwarded message from ", out); + buffer[0] = 0; + rfc822_write_address (buffer, sizeof (buffer), cur->env->from); + fputs (buffer, out); + fputs (" -----\n\n", out); + if (option (OPTFORWDECODE)) + { + cmflags |= M_CM_DECODE; + chflags |= CH_WEED; + } + if (option (OPTFORWQUOTE)) + cmflags |= M_CM_PREFIX; + mutt_parse_mime_message (ctx, cur); + mutt_copy_message (out, ctx, cur, cmflags, chflags); + fputs ("\n----- End forwarded message -----\n", out); + return 0; +} + +static int include_reply (CONTEXT *ctx, HEADER *cur, FILE *out) +{ + char buffer[STRING]; + int flags = M_CM_PREFIX | M_CM_DECODE; + + + +#ifdef _PGPPATH + if (cur->pgp) + { + if (cur->pgp & PGPENCRYPT) + { + /* make sure we have the user's passphrase before proceeding... */ + pgp_valid_passphrase (); + } + } +#endif /* _PGPPATH */ + + + + if (Attribution) + { + mutt_make_string (buffer, sizeof (buffer), Attribution, cur); + fputs (buffer, out); + fputc ('\n', out); + } + if (!option (OPTHEADER)) + flags |= M_CM_NOHEADER; + mutt_parse_mime_message (ctx, cur); + mutt_copy_message (out, ctx, cur, flags, CH_DECODE); + if (PostIndentString) + { + mutt_make_string (buffer, sizeof (buffer), PostIndentString, cur); + fputs (buffer, out); + fputc ('\n', out); + } + return 0; +} + +static BODY *make_forward (CONTEXT *ctx, HEADER *hdr) +{ + char buffer[LONG_STRING]; + BODY *body; + FILE *fpout; + + mutt_mktemp (buffer); + if ((fpout = safe_fopen (buffer, "w")) == NULL) + return NULL; + + body = mutt_new_body (); + body->type = TYPEMESSAGE; + body->subtype = safe_strdup ("rfc822"); + body->filename = safe_strdup (buffer); + body->unlink = 1; + body->use_disp = 0; + + /* this MUST come after setting ->filename because we reuse buffer[] */ + strfcpy (buffer, "Forwarded message from ", sizeof (buffer)); + rfc822_write_address (buffer + 23, sizeof (buffer) - 23, hdr->env->from); + body->description = safe_strdup (buffer); + + mutt_parse_mime_message (ctx, hdr); + mutt_copy_message (fpout, ctx, hdr, + option (OPTMIMEFORWDECODE) ? M_CM_DECODE : 0, + CH_XMIT | (option (OPTMIMEFORWDECODE) ? (CH_MIME | CH_TXTPLAIN ) : 0)); + + fclose (fpout); + mutt_update_encoding (body); + return (body); +} + +static int default_to (ADDRESS **to, ENVELOPE *env, int group) +{ + char prompt[STRING]; + int i = 0; + + if (group) + { + if (env->mail_followup_to) + { + rfc822_append (to, env->mail_followup_to); + return 0; + } + if (mutt_addr_is_user (env->from)) + return 0; + } + + if (!group && mutt_addr_is_user (env->from)) + { + /* mail is from the user, assume replying to recipients */ + rfc822_append (to, env->to); + } + else if (env->reply_to) + { + if (option (OPTIGNORELISTREPLYTO) && + mutt_is_mail_list (env->reply_to) && + (mutt_addrsrc (env->reply_to, env->to) || + mutt_addrsrc (env->reply_to, env->cc))) + { + /* If the Reply-To: address is a mailing list, assume that it was + * put there by the mailing list, and use the From: address + */ + rfc822_append (to, env->from); + } + else if (!mutt_addrcmp (env->from, env->reply_to) && + quadoption (OPT_REPLYTO) != M_YES) + { + /* There are quite a few mailing lists which set the Reply-To: + * header field to the list address, which makes it quite impossible + * to send a message to only the sender of the message. This + * provides a way to do that. + */ + snprintf (prompt, sizeof (prompt), "Reply to %s?", env->reply_to->mailbox); + if ((i = query_quadoption (OPT_REPLYTO, prompt)) == M_YES) + rfc822_append (to, env->reply_to); + else if (i == M_NO) + rfc822_append (to, env->from); + else + return (-1); /* abort */ + } + else + rfc822_append (to, env->reply_to); + } + else + rfc822_append (to, env->from); + + return (0); +} + +static int fetch_recips (ENVELOPE *out, ENVELOPE *in, int flags) +{ + ADDRESS *tmp; + if (flags & SENDLISTREPLY) + { + tmp = find_mailing_lists (in->to, in->cc); + rfc822_append (&out->to, tmp); + rfc822_free_address (&tmp); + } + else + { + if (default_to (&out->to, in, flags & SENDGROUPREPLY) == -1) + return (-1); /* abort */ + + if ((flags & SENDGROUPREPLY) && !in->mail_followup_to) + { + rfc822_append (&out->to, in->to); + rfc822_append (&out->cc, in->cc); + } + } + return 0; +} + +static int +envelope_defaults (ENVELOPE *env, CONTEXT *ctx, HEADER *cur, int flags) +{ + ENVELOPE *curenv = NULL; + LIST *tmp; + char buffer[STRING]; + int i = 0, tag = 0; + + if (!cur) + { + tag = 1; + for (i = 0; i < ctx->vcount; i++) + if (ctx->hdrs[ctx->v2r[i]]->tagged) + { + cur = ctx->hdrs[ctx->v2r[i]]; + curenv = cur->env; + break; + } + + if (!cur) + { + /* This could happen if the user tagged some messages and then did + * a limit such that none of the tagged message are visible. + */ + mutt_error ("No tagged messages are visible!"); + return (-1); + } + } + else + curenv = cur->env; + + if (flags & SENDREPLY) + { + if (tag) + { + HEADER *h; + + for (i = 0; i < ctx->vcount; i++) + { + h = ctx->hdrs[ctx->v2r[i]]; + if (h->tagged && fetch_recips (env, h->env, flags) == -1) + return -1; + } + } + else if (fetch_recips (env, curenv, flags) == -1) + return -1; + + if ((flags & SENDLISTREPLY) && !env->to) + { + mutt_error ("No mailing lists found!"); + return (-1); + } + + if (! option (OPTMETOO)) + { + /* the order is important here. do the CC: first so that if the + * the user is the only recipient, it ends up on the TO: field + */ + env->cc = remove_user (env->cc, (env->to == NULL)); + env->to = remove_user (env->to, (env->cc == NULL)); + } + + /* the CC field can get cluttered, especially with lists */ + env->to = mutt_remove_duplicates (env->to); + env->cc = mutt_remove_duplicates (env->cc); + env->cc = mutt_remove_xrefs (env->to, env->cc); + + if (curenv->real_subj) + { + env->subject = safe_malloc (strlen (curenv->real_subj) + 5); + sprintf (env->subject, "Re: %s", curenv->real_subj); + } + else + env->subject = safe_strdup ("Re: your mail"); + + /* add the In-Reply-To field */ + if (InReplyTo) + { + strfcpy (buffer, "In-Reply-To: ", sizeof (buffer)); + mutt_make_string (buffer + 13, sizeof (buffer) - 13, InReplyTo, cur); + tmp = env->userhdrs; + while (tmp && tmp->next) + tmp = tmp->next; + if (tmp) + { + tmp->next = mutt_new_list (); + tmp = tmp->next; + } + else + tmp = env->userhdrs = mutt_new_list (); + tmp->data = safe_strdup (buffer); + } + + env->references = mutt_copy_list (curenv->references); + + if (curenv->message_id) + { + LIST *t; + + t = mutt_new_list (); + t->data = safe_strdup (curenv->message_id); + t->next = env->references; + env->references = t; + } + } + else if (flags & SENDFORWARD) + { + /* set the default subject for the message. */ + mutt_make_string (buffer, sizeof (buffer), ForwFmt, cur); + env->subject = safe_strdup (buffer); + } + + return (0); +} + +static int +generate_body (FILE *tempfp, /* stream for outgoing message */ + HEADER *msg, /* header for outgoing message */ + int flags, /* compose mode */ + CONTEXT *ctx, /* current mailbox */ + HEADER *cur) /* current message */ +{ + int i; + HEADER *h; + BODY *tmp; + + if (flags & SENDREPLY) + { + if ((i = query_quadoption (OPT_INCLUDE, "Include message in reply?")) == -1) + return (-1); + + if (i == M_YES) + { + if (!cur) + { + for (i = 0; i < ctx->vcount; i++) + { + h = ctx->hdrs[ctx->v2r[i]]; + if (h->tagged) + { + if (include_reply (ctx, h, tempfp) == -1) + { + mutt_error ("Could not include all requested messages!"); + return (-1); + } + fputc ('\n', tempfp); + } + } + } + else + include_reply (ctx, cur, tempfp); + } + } + else if (flags & SENDFORWARD) + { + if (query_quadoption (OPT_MIMEFWD, "Forward MIME encapsulated?")) + { + BODY *last = msg->content; + + while (last && last->next) + last = last->next; + + if (cur) + { + tmp = make_forward (ctx, cur); + if (last) + last->next = tmp; + else + msg->content = tmp; + } + else + { + for (i = 0; i < ctx->vcount; i++) + { + if (ctx->hdrs[ctx->v2r[i]]->tagged) + { + tmp = make_forward (ctx, ctx->hdrs[ctx->v2r[i]]); + if (last) + { + last->next = tmp; + last = tmp; + } + else + last = msg->content = tmp; + } + } + } + } + else + { + if (cur) + include_forward (ctx, cur, tempfp); + else + for (i=0; i < ctx->vcount; i++) + if (ctx->hdrs[ctx->v2r[i]]->tagged) + include_forward (ctx, ctx->hdrs[ctx->v2r[i]], tempfp); + } + } + + + +#ifdef _PGPPATH + else if (flags & SENDKEY) + { + BODY *tmp; + if ((tmp = pgp_make_key_attachment (NULL)) == NULL) + return -1; + + tmp->next = msg->content; + msg->content = tmp; + } +#endif + + + + return (0); +} + +static void mutt_set_followup_to (ENVELOPE *e) +{ + ADDRESS *t = NULL; + + /* only generate the Mail-Followup-To if the user has requested it, and + it hasn't already been set */ + if (option (OPTFOLLOWUPTO) && !e->mail_followup_to) + { + if (mutt_is_list_recipient (e->to) || mutt_is_list_recipient (e->cc)) + { + /* i am a list recipient, so set the Mail-Followup-To: field so that + * i don't end up getting multiple copies of responses to my mail + */ + t = rfc822_append (&e->mail_followup_to, e->to); + rfc822_append (&t, e->cc); + /* the following is needed if $metoo is set, because the ->to and ->cc + may contain the user's private address(es) */ + e->mail_followup_to = remove_user (e->mail_followup_to, 0); + } + } +} + +/* remove the multipart body if it exists */ +static BODY *remove_multipart (BODY *b) +{ + BODY *t; + + if (b->parts) + { + t = b; + b = b->parts; + t->parts = NULL; + mutt_free_body (&t); + } + return b; +} + +/* look through the recipients of the message we are replying to, and if + we find an address that matches $alternates, we use that as the default + from field */ +static ADDRESS *set_reverse_name (ENVELOPE *env) +{ + ADDRESS *tmp; + + for (tmp = env->to; tmp; tmp = tmp->next) + { + if (mutt_addr_is_user (tmp)) + break; + } + if (!tmp) + { + for (tmp = env->cc; tmp; tmp = tmp->next) + { + if (mutt_addr_is_user (tmp)) + break; + } + } + if (!tmp && mutt_addr_is_user (env->from)) + tmp = env->from; + if (tmp) + { + tmp = rfc822_cpy_adr_real (tmp); + if (!tmp->personal) + tmp->personal = safe_strdup (Realname); + } + return (tmp); +} + +static ADDRESS *mutt_default_from (void) +{ + ADDRESS *adr = rfc822_new_address (); + + /* don't set realname here, it will be set later */ + + if (option (OPTUSEDOMAIN)) + { + adr->mailbox = safe_malloc (strlen (Username) + strlen (Fqdn) + 2); + sprintf (adr->mailbox, "%s@%s", Username, Fqdn); + } + else + adr->mailbox = safe_strdup (Username); + return (adr); +} + +void +ci_send_message (int flags, /* send mode */ + HEADER *msg, /* template to use for new message */ + char *tempfile, /* file specified by -i or -H */ + CONTEXT *ctx, /* current mailbox */ + HEADER *cur) /* current message */ +{ + char buffer[LONG_STRING]; + char fcc[_POSIX_PATH_MAX] = ""; /* where to copy this message */ + FILE *tempfp = NULL; + BODY *pbody; + int i, killfrom = 0; + + + +#ifdef _PGPPATH + /* save current value of "pgp_sign_as" */ + char *signas = NULL; + char *signmic = NULL; + if (flags == SENDPOSTPONED) + { + signas = safe_strdup(PgpSignAs); + signmic = safe_strdup(PgpSignMicalg); + } +#endif /* _PGPPATH */ + + + + if (msg) + { + msg->env->to = mutt_expand_aliases (msg->env->to); + msg->env->cc = mutt_expand_aliases (msg->env->cc); + msg->env->bcc = mutt_expand_aliases (msg->env->bcc); + } + else + { + msg = mutt_new_header (); + + if (flags == SENDPOSTPONED) + { + if ((flags = mutt_get_postponed (ctx, msg, &cur)) < 0) + goto cleanup; + } + else if (!flags && quadoption (OPT_RECALL) != M_NO && mutt_num_postponed ()) + { + /* If the user is composing a new message, check to see if there + * are any postponed messages first. + */ + if ((i = query_quadoption (OPT_RECALL, "Recall postponed message?")) == -1) + goto cleanup; + + if (i == M_YES) + { + if ((flags = mutt_get_postponed (ctx, msg, &cur)) < 0) + flags = 0; + } + } + + if (flags & SENDPOSTPONED) + { + if ((tempfp = safe_fopen (msg->content->filename, "a+")) == NULL) + { + mutt_perror (msg->content->filename); + goto cleanup; + } + } + + if (!msg->env) + msg->env = mutt_new_envelope (); + } + + if (! (flags & (SENDKEY | SENDPOSTPONED))) + { + pbody = mutt_new_body (); + pbody->next = msg->content; /* don't kill command-line attachments */ + msg->content = pbody; + + msg->content->type = TYPETEXT; + msg->content->subtype = safe_strdup ("plain"); + msg->content->unlink = 1; + msg->content->use_disp = 0; + + if (!tempfile) + { + mutt_mktemp (buffer); + tempfp = safe_fopen (buffer, "w+"); + msg->content->filename = safe_strdup (buffer); + } + else + { + tempfp = safe_fopen (tempfile, "a+"); + msg->content->filename = safe_strdup (tempfile); + } + + if (!tempfp) + { + dprint(1,(debugfile, "newsend_message: can't create tempfile %s (errno=%d)\n", tempfile, errno)); + mutt_perror (tempfile); + goto cleanup; + } + } + + /* this is handled here so that the user can match ~f in send-hook */ + if (cur && option (OPTREVNAME)) + { + /* we shouldn't have to worry about freeing `msg->env->from' before + setting it here since this code will only execute when doing some + sort of reply. the pointer will only be set when using the -H command + line option */ + msg->env->from = set_reverse_name (cur->env); + } + + if (!msg->env->from && option (OPTUSEFROM)) + msg->env->from = mutt_default_from (); + + if (flags & SENDBATCH) + { + mutt_copy_stream (stdin, tempfp); + if (option (OPTHDRS)) + { + process_user_recips (msg->env); + process_user_header (msg->env); + } + } + else if (! (flags & SENDPOSTPONED)) + { + if ((flags & (SENDREPLY | SENDFORWARD)) && + envelope_defaults (msg->env, ctx, cur, flags) == -1) + goto cleanup; + + if (option (OPTHDRS)) + process_user_recips (msg->env); + + if (! (flags & SENDMAILX) && + ! (option (OPTAUTOEDIT) && option (OPTEDITHDRS)) && + ! ((flags & SENDREPLY) && option (OPTFASTREPLY))) + { + if (edit_envelope (msg->env) == -1) + goto cleanup; + } + + /* the from address must be set here regardless of whether or not + $use_from is set so that the `~P' (from you) operator in send-hook + patterns will work. if $use_from is unset, the from address is killed + after send-hooks are evaulated */ + if (!msg->env->from) + { + msg->env->from = mutt_default_from (); + killfrom = 1; + } + + /* change settings based upon recipients */ + mutt_send_hook (msg); + + if (killfrom) + { + rfc822_free_address (&msg->env->from); + killfrom = 0; + } + + if (option (OPTHDRS)) + process_user_header (msg->env); + + + +#ifdef _PGPPATH + if (! (flags & SENDMAILX)) + { + if (option (OPTPGPAUTOSIGN)) + msg->pgp |= PGPSIGN; + if (option (OPTPGPAUTOENCRYPT)) + msg->pgp |= PGPENCRYPT; + if (option (OPTPGPREPLYENCRYPT) && cur && cur->pgp & PGPENCRYPT) + msg->pgp |= PGPENCRYPT; + if (option (OPTPGPREPLYSIGN) && cur && cur->pgp & PGPSIGN) + msg->pgp |= PGPSIGN; + } +#endif /* _PGPPATH */ + + + + /* include replies/forwarded messages */ + if (generate_body (tempfp, msg, flags, ctx, cur) == -1) + goto cleanup; + + if (! (flags & (SENDMAILX | SENDKEY)) && strcmp (Editor, "builtin") != 0) + append_signature (msg->env, tempfp); + } + /* wait until now to set the real name portion of our return address so + that $realname can be set in a send-hook */ + if (msg->env->from && !msg->env->from->personal) + msg->env->from->personal = safe_strdup (Realname); + + + +#ifdef _PGPPATH + + if (! (flags & SENDKEY)) + { + +#endif + + + + fclose (tempfp); + tempfp = NULL; + + + +#ifdef _PGPPATH + + } + +#endif + + + + if (flags & SENDMAILX) + { + if (mutt_builtin_editor (msg->content->filename, msg, cur) == -1) + goto cleanup; + } + else if (! (flags & SENDBATCH)) + { + struct stat st; + time_t mtime; + + stat (msg->content->filename, &st); + mtime = st.st_mtime; + + mutt_update_encoding (msg->content); + + /* If the this isn't a text message, look for a mailcap edit command */ + if(! (flags & SENDKEY)) + { + if (mutt_needs_mailcap (msg->content)) + mutt_edit_attachment (msg->content, 0); + else if (strcmp ("builtin", Editor) == 0) + mutt_builtin_editor (msg->content->filename, msg, cur); + else if (option (OPTEDITHDRS)) + mutt_edit_headers (Editor, msg->content->filename, msg, fcc, sizeof (fcc)); + else + mutt_edit_file (Editor, msg->content->filename); + } + + if (! (flags & (SENDPOSTPONED | SENDFORWARD | SENDKEY))) + { + if (stat (msg->content->filename, &st) == 0) + { + /* if the file was not modified, bail out now */ + if (mtime == st.st_mtime && + query_quadoption (OPT_ABORT, "Abort unmodified message?") == M_YES) + { + mutt_message ("Aborted unmodified message."); + goto cleanup; + } + } + else + mutt_perror (msg->content->filename); + } + } + + /* specify a default fcc. if we are in batchmode, only save a copy of + the message if the value of $copy is yes or ask-yes */ + if (!fcc[0] && (!(flags & SENDBATCH) || (quadoption (OPT_COPY) & 0x1))) + { + /* set the default FCC */ + if (!msg->env->from) + { + msg->env->from = mutt_default_from (); + killfrom = 1; /* no need to check $use_from because if the user specified + a from address it would have already been set by now */ + } + mutt_select_fcc (fcc, sizeof (fcc), msg); + if (killfrom) + { + rfc822_free_address (&msg->env->from); + killfrom = 0; + } + } + + mutt_update_encoding (msg->content); + + if (! (flags & (SENDMAILX | SENDBATCH))) + { +main_loop: + + if ((i = mutt_send_menu (msg, fcc, sizeof (fcc), cur)) == -1) + { + /* abort */ + mutt_message ("Mail not sent."); + goto cleanup; + } + else if (i == 1) + { + /* postpone the message until later. */ + if (msg->content->next) + msg->content = mutt_make_multipart (msg->content); + if (mutt_write_fcc (NONULL (Postponed), msg, (cur && (flags & SENDREPLY)) ? cur->env->message_id : NULL, 1) < 0) + { + msg->content = remove_multipart (msg->content); + goto main_loop; + } + mutt_message ("Message postponed."); + goto cleanup; + } + } + + if (!msg->env->to && !msg->env->cc && !msg->env->bcc) + { + if (! (flags & SENDBATCH)) + { + mutt_error ("No recipients are specified!"); + goto main_loop; + } + else + { + puts ("No recipients were specified."); + goto cleanup; + } + } + + if (!msg->env->subject && ! (flags & SENDBATCH) && + (i = query_quadoption (OPT_SUBJECT, "No subject, abort sending?")) != M_NO) + { + /* if the abort is automatic, print an error message */ + if (quadoption (OPT_SUBJECT) == M_YES) + mutt_error ("No subject specified."); + goto main_loop; + } + + if (msg->content->next) + msg->content = mutt_make_multipart (msg->content); + + + +#ifdef _PGPPATH + if (msg->pgp) + { + if (pgp_protect (msg) != 0) + { + if (msg->content->parts) + { + /* remove the toplevel multipart structure */ + pbody = msg->content; + msg->content = msg->content->parts; + pbody->parts = NULL; + mutt_free_body (&pbody); + } + goto main_loop; + } + } +#endif /* _PGPPATH */ + + + + mutt_expand_path (fcc, sizeof (fcc)); + + if (!option (OPTNOCURSES) && ! (flags & SENDMAILX)) + mutt_message ("Sending message..."); + + if (msg->env->bcc && ! (msg->env->to || msg->env->cc)) + { + /* some MTA's will put an Apparently-To: header field showing the Bcc: + * recipients if there is no To: or Cc: field, so attempt to suppress + * it by using an empty To: field. + */ + msg->env->to = rfc822_new_address (); + msg->env->to->mailbox = safe_strdup (EmptyTo); + msg->env->to->group = 1; + msg->env->to->next = rfc822_new_address (); + + buffer[0] = 0; + rfc822_cat (buffer, sizeof (buffer), msg->env->to->mailbox, RFC822Specials); + msg->env->to->mailbox = safe_strdup (buffer); + } + + mutt_set_followup_to (msg->env); + + if (mutt_send_message (msg, fcc) == -1) + { + msg->content = remove_multipart (msg->content); + goto main_loop; + } + + if (!option (OPTNOCURSES) && ! (flags & SENDMAILX)) + mutt_message ("Mail sent."); + + /* now free up the memory used to generate this message. */ + mutt_free_header (&msg); + + if (flags & SENDREPLY) + { + if (cur) + mutt_set_flag (ctx, cur, M_REPLIED, 1); + else if (!(flags & SENDPOSTPONED) && ctx && ctx->tagged) + { + for (i = 0; i < ctx->vcount; i++) + if (ctx->hdrs[ctx->v2r[i]]->tagged) + mutt_set_flag (ctx, ctx->hdrs[ctx->v2r[i]], M_REPLIED, 1); + } + } + + + +#ifdef _PGPPATH + if (flags == SENDPOSTPONED) + { + safe_free((void **) &PgpSignAs); + safe_free((void **) &PgpSignMicalg); + + PgpSignAs = signas; + PgpSignMicalg = signmic; + } +#endif /* _PGPPATH */ + + + + return; /* all done */ + +cleanup: + + + +#ifdef _PGPPATH + if (flags == SENDPOSTPONED) + { + safe_free((void **) &PgpSignAs); + safe_free((void **) &PgpSignMicalg); + + PgpSignAs = signas; + PgpSignMicalg = signmic; + } +#endif /* _PGPPATH */ + + + + if (tempfp) + fclose (tempfp); + mutt_free_header (&msg); +} diff --git a/sendlib.c b/sendlib.c new file mode 100644 index 00000000..b6eaac64 --- /dev/null +++ b/sendlib.c @@ -0,0 +1,1788 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "rfc2047.h" +#include "mx.h" +#include "mime.h" +#include "mailbox.h" +#include "copy.h" + +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <ctype.h> +#include <sys/stat.h> +#include <signal.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <sysexits.h> + + + +#ifdef _PGPPATH +#include "pgp.h" +#endif /* _PGPPATH */ + + + +#define DISPOSITION(X) X==DISPATTACH?"attachment":"inline" + +const char MimeSpecials[] = "@.,;<>[]\\\"()?/="; + +char B64Chars[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', + 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '+', '/' +}; + +static char MsgIdPfx = 'A'; + +static void transform_to_7bit (BODY *a, FILE *fpin); + +static void encode_quoted (FILE * fin, FILE *fout, int istext) +{ + int c, linelen = 0; + char line[77], savechar; + + while ((c = fgetc (fin)) != EOF) + { + /* Escape lines that begin with "the message separator". */ + if (linelen == 5 && !strncmp ("From ", line, 5)) + { + strfcpy (line, "=46rom ", sizeof (line)); + linelen = 7; + } + else if (linelen == 1 && line[0] == '.') + { + strfcpy (line, "=2E", sizeof (line)); + linelen = 3; + } + + /* Wrap the line if needed. */ + if (linelen == 76 && ((istext && c != '\n') || !istext)) + { + /* If the last character is "quoted", then be sure to move all three + * characters to the next line. Otherwise, just move the last + * character... + */ + if (line[linelen-3] == '=') + { + line[linelen-3] = 0; + fputs (line, fout); + fputs ("=\n", fout); + line[linelen] = 0; + line[0] = '='; + line[1] = line[linelen-2]; + line[2] = line[linelen-1]; + linelen = 3; + } + else + { + savechar = line[linelen-1]; + line[linelen-1] = '='; + line[linelen] = 0; + fputs (line, fout); + fputc ('\n', fout); + line[0] = savechar; + linelen = 1; + } + } + + if (c == '\n' && istext) + { + /* Check to make sure there is no trailing space on this line. */ + if (line[linelen-1] == ' ' || line[linelen-1] == '\t') + { + if (linelen < 74) + { + sprintf (line+linelen-1, "=%2.2X", line[linelen-1]); + fputs (line, fout); + } + else + { + int savechar = line[linelen-1]; + + line[linelen-1] = '='; + line[linelen] = 0; + fputs (line, fout); + fprintf (fout, "\n=%2.2X", savechar); + } + } + else + { + line[linelen] = 0; + fputs (line, fout); + } + fputc ('\n', fout); + linelen = 0; + } + else if (c != 9 && (c < 32 || c > 126 || c == '=')) + { + /* Check to make sure there is enough room for the quoted character. + * If not, wrap to the next line. + */ + if (linelen > 73) + { + line[linelen++] = '='; + line[linelen] = 0; + fputs (line, fout); + fputc ('\n', fout); + linelen = 0; + } + sprintf (line+linelen,"=%2.2X", c); + linelen += 3; + } + else + { + /* Don't worry about wrapping the line here. That will happen during + * the next iteration when I'll also know what the next character is. + */ + line[linelen++] = c; + } + } + + /* Take care of anything left in the buffer */ + if (linelen > 0) + { + if (line[linelen-1] == ' ' || line[linelen-1] == '\t') + { + /* take care of trailing whitespace */ + if (linelen < 74) + sprintf (line+linelen-1, "=%2.2X", line[linelen-1]); + else + { + savechar = line[linelen-1]; + line[linelen-1] = '='; + line[linelen] = 0; + fputs (line, fout); + fputc ('\n', fout); + sprintf (line, "=%2.2X", savechar); + } + } + else + line[linelen] = 0; + fputs (line, fout); + } +} + +static void encode_base64 (FILE * fin, FILE *fout, int istext) +{ + int c1, c2, c3, ch; + int insert_newline = 0; + int linelen = 0; + + FOREVER + { + if (istext) + { + if (insert_newline) + { + c1 = '\n'; + insert_newline = 0; + + c2 = fgetc(fin); + if (c2 == '\n') + { + c2 = '\r'; + c3 = '\n'; + } + else + { + c3 = fgetc(fin); + if (c3 == '\n') + { + c3 = '\r'; + insert_newline = 1; + } + } + } + else + { + c1 = fgetc(fin); + if (c1 == '\n') + { + c1 = '\r'; + c2 = '\n'; + c3 = fgetc(fin); + if (c3 == '\n') + { + c3 = '\r'; + insert_newline = 1; + } + } + else + { + c2 = fgetc(fin); + if (c2 == '\n') + { + c2 = '\r'; + c3 = '\n'; + } + else + { + c3 = fgetc(fin); + if (c3 == '\n') + { + c3 = '\r'; + insert_newline = 1; + } + } + } + } + } + else /* !istext */ + { + if ((c1 = fgetc(fin)) == EOF) + break; + c2 = fgetc(fin); + c3 = fgetc(fin); + } + + if (linelen + 4 >= 76) + { + fputc('\n', fout); + linelen = 0; + } + + ch = c1 >> 2; + fputc (B64Chars[ch], fout); + + if (c2 != EOF) + { + ch = ((c1 & 0x3) << 4) | (c2 >> 4); + fputc (B64Chars[ch], fout); + } + else + { + ch = (c1 & 0x3) << 4; + fputc (B64Chars[ch], fout); + fputs("==", fout); + break; + } + + if (c3 != EOF) + { + ch = ((c2 & 0xf) << 2) | (c3 >> 6); + fputc (B64Chars[ch], fout); + } + else + { + ch = (c2 & 0xf) << 2; + fputc(B64Chars[ch], fout); + fputc('=', fout); + break; + } + + ch = c3 & 0x3f; + fputc(B64Chars[ch], fout); + + linelen += 4; + } + + fputc('\n', fout); +} + +int mutt_write_mime_header (BODY *a, FILE *f) +{ + PARAMETER *p; + char buffer[STRING]; + char *t; + char *fn; + int len; + int tmplen; + + fprintf (f, "Content-Type: %s/%s", TYPE (a->type), a->subtype); + + if (a->parameter) + { + len = 25 + strlen (a->subtype); /* approximate len. of content-type */ + + p = a->parameter; + while (p) + { + fputc (';', f); + + buffer[0] = 0; + rfc822_cat (buffer, sizeof (buffer), p->value, MimeSpecials); + + tmplen = strlen (buffer) + strlen (p->attribute) + 1; + + if (len + tmplen + 2 > 76) + { + fputs ("\n\t", f); + len = tmplen + 8; + } + else + { + fputc (' ', f); + len += tmplen + 1; + } + + fprintf (f, "%s=%s", p->attribute, buffer); + + p = p->next; + } + } + + fputc ('\n', f); + + if (a->encoding != ENC7BIT) + fprintf(f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding)); + + if (a->description) + fprintf(f, "Content-Description: %s\n", a->description); + + if (a->use_disp && (a->disposition == DISPATTACH || a->filename || a->d_filename)) + { + fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition)); + + if(!(fn = a->d_filename)) + fn = a->filename; + + if (fn) + { + /* Strip off the leading path... */ + if ((t = strrchr (fn, '/'))) + t++; + else + t = fn; + + buffer[0] = 0; + rfc822_cat (buffer, sizeof (buffer), t, MimeSpecials); + fprintf (f, "; filename=%s", buffer); + } + + fputc ('\n', f); + } + + /* Do NOT add the terminator here!!! */ + return (ferror (f) ? -1 : 0); +} + +int mutt_write_mime_body (BODY *a, FILE *f) +{ + char *p, boundary[SHORT_STRING]; + FILE *fpin; + BODY *t; + + if (a->type == TYPEMULTIPART) + { + /* First, find the boundary to use */ + if (!(p = mutt_get_parameter ("boundary", a->parameter))) + { + dprint (1, (debugfile, "mutt_write_mime_body(): no boundary parameter found!\n")); + return (-1); + } + strfcpy (boundary, p, sizeof (boundary)); + + for (t = a->parts; t ; t = t->next) + { + fprintf (f, "\n--%s\n", boundary); + if (mutt_write_mime_header (t, f) == -1) + return -1; + fputc ('\n', f); + if (mutt_write_mime_body (t, f) == -1) + return -1; + } + fprintf (f, "\n--%s--\n", boundary); + return (ferror (f) ? -1 : 0); + } + + + +#ifdef _PGPPATH + /* This is pretty gross, but it's the best solution for now... */ + if (a->type == TYPEAPPLICATION && strcmp (a->subtype, "pgp-encrypted") == 0) + { + fputs ("Version: 1\n", f); + return 0; + } +#endif /* _PGPPATH */ + + + + if ((fpin = fopen (a->filename, "r")) == NULL) + { + dprint(1,(debugfile, "write_mime_body: %s no longer exists!\n",a->filename)); + return -1; + } + + if (a->encoding == ENCQUOTEDPRINTABLE) + encode_quoted (fpin, f, mutt_is_text_type (a->type, a->subtype)); + else if (a->encoding == ENCBASE64) + encode_base64 (fpin, f, mutt_is_text_type (a->type, a->subtype)); + else + mutt_copy_stream (fpin, f); + fclose (fpin); + + return (ferror (f) ? -1 : 0); +} + +#define BOUNDARYLEN 16 +char *mutt_generate_boundary (void) +{ + char *rs = (char *)safe_malloc (BOUNDARYLEN + 1); + char *p = rs; + int i; + + rs[BOUNDARYLEN] = 0; + for (i=0;i<BOUNDARYLEN;i++) *p++ = B64Chars[LRAND() % sizeof (B64Chars)]; + *p = 0; + return (rs); +} + +/* analyze the contents of a file to determine which MIME encoding to use */ +static CONTENT *mutt_get_content_info (const char *fname) +{ + CONTENT *info; + FILE *fp; + int ch, from=0, whitespace=0, dot=0, linelen=0; + + if ((fp = fopen (fname, "r")) == NULL) + { + dprint (1, (debugfile, "mutt_get_content_info: %s: %s (errno %d).\n", + fname, strerror (errno), errno)); + return (NULL); + } + + info = safe_calloc (1, sizeof (CONTENT)); + while ((ch = fgetc (fp)) != EOF) + { + linelen++; + if (ch == '\n') + { + if (whitespace) info->space = 1; + if (dot) info->dot = 1; + if (linelen > info->linemax) info->linemax = linelen; + whitespace = 0; + linelen = 0; + dot = 0; + } + else if (ch == '\r') + { + if ((ch = fgetc (fp)) == EOF) + { + info->binary = 1; + break; + } + else if (ch != '\n') + { + info->binary = 1; + ungetc (ch, fp); + continue; + } + else + { + if (whitespace) info->space = 1; + if (dot) info->dot = 1; + if (linelen > info->linemax) info->linemax = linelen; + whitespace = 0; + dot = 0; + linelen = 0; + } + } + else if (ch & 0x80) + info->hibin++; + else if (ch == '\t' || ch == '\f') + { + info->ascii++; + whitespace++; + } + else if (ch < 32 || ch == 127) + info->lobin++; + else + { + if (linelen == 1) + { + if (ch == 'F') + from = 1; + else + from = 0; + if (ch == '.') + dot = 1; + else + dot = 0; + } + else if (from) + { + if (linelen == 2 && ch != 'r') from = 0; + else if (linelen == 3 && ch != 'o') from = 0; + else if (linelen == 4 && ch != 'm') from = 0; + else if (linelen == 5) + { + if (ch == ' ') info->from = 1; + from = 0; + } + } + if (ch == ' ') whitespace++; + info->ascii++; + } + if (linelen > 1) dot = 0; + if (ch != ' ' && ch != '\t') whitespace = 0; + } + fclose (fp); + return (info); +} + +/* Given a file with path ``s'', see if there is a registered MIME type. + * returns the major MIME type, and copies the subtype to ``d''. First look + * for ~/.mime.types, then look in a system mime.types if we can find one. + * The longest match is used so that we can match `ps.gz' when `gz' also + * exists. + */ + +static int lookup_mime_type (char *d, const char *s) +{ + FILE *f; + char *p, *ct, + buf[LONG_STRING]; + int count; + int szf, sze, cur_n, cur_sze; + + *d = 0; + cur_n = TYPEOTHER; + cur_sze = 0; + szf = strlen (s); + + for (count = 0 ; count < 2 ; count++) + { + /* + * can't use strtok() because we use it in an inner loop below, so use + * a switch statement here instead. + */ + switch (count) + { + case 0: + snprintf (buf, sizeof (buf), "%s/.mime.types", Homedir); + break; + case 1: + strfcpy (buf, SHAREDIR"/mime.types", sizeof (buf)); + break; + default: + return (cur_n); + } + + if ((f = fopen (buf, "r")) != NULL) + { + while (fgets (buf, sizeof (buf) - 1, f) != NULL) + { + /* weed out any comments */ + if ((p = strchr (buf, '#'))) + *p = 0; + + /* remove any leading space. */ + ct = buf; + SKIPWS (ct); + + /* position on the next field in this line */ + if ((p = strpbrk (ct, " \t")) == NULL) + continue; + *p++ = 0; + SKIPWS (p); + + /* cycle through the file extensions */ + while ((p = strtok (p, " \t\n"))) + { + sze = strlen (p); + if ((sze > cur_sze) && (szf >= sze) && + strcasecmp (s + szf - sze, p) == 0 && + (szf == sze || s[szf - sze - 1] == '.')) + { + char *dc; + + /* get the content-type */ + + if ((p = strchr (ct, '/')) == NULL) + { + /* malformed line, just skip it. */ + break; + } + *p++ = 0; + + dc = d; + while (*p && !ISSPACE (*p)) + *dc++ = *p++; + *dc = 0; + + cur_n = mutt_check_mime_type (ct); + cur_sze = sze; + } + p = NULL; + } + } + fclose (f); + } + } + return (cur_n); +} + +static char *set_text_charset (CONTENT *info) +{ + if (strcasecmp (NONULL (Charset), "us-ascii") == 0) + { + if (info->hibin != 0) + return "unknown-8bit"; + } + else if (info->hibin == 0) + return "us-ascii"; + + /* if no charset is given, provide a reasonable default */ + return (Charset ? Charset : "us-ascii"); +} + +void mutt_message_to_7bit (BODY *a, FILE *fp) +{ + char temp[_POSIX_PATH_MAX]; + size_t linelen = 0; + char *line = NULL; + FILE *fpin = NULL; + FILE *fpout = NULL; + struct stat sb; + + if (!a->filename && fp) + fpin = fp; + else if (!a->filename || !(fpin = fopen (a->filename, "r"))) + { + mutt_error ("Could not open %s", a->filename ? a->filename : "(null)"); + return; + } + else + { + a->offset = 0; + if (stat (a->filename, &sb) == -1) + { + mutt_perror ("stat"); + fclose (fpin); + } + a->length = sb.st_size; + } + + mutt_mktemp (temp); + if (!(fpout = safe_fopen (temp, "w+"))) + { + mutt_perror ("fopen"); + goto cleanup; + } + + fseek (fpin, a->offset, 0); + a->parts = mutt_parse_messageRFC822 (fpin, a); + + transform_to_7bit (a->parts, fpin); + + mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length, + CH_MIME | CH_NONEWLINE | CH_XMIT, NULL); + + fputs ("Mime-Version: 1.0\n", fpout); + mutt_write_mime_header (a->parts, fpout); + fputc ('\n', fpout); + mutt_write_mime_body (a->parts, fpout); + + cleanup: + safe_free ((void **) &line); + linelen = 0; + + if (fpin && !fp) + fclose (fpin); + if (fpout) + fclose (fpout); + else + return; + + a->encoding = ENC7BIT; + a->d_filename = a->filename; + if (a->filename && a->unlink) + unlink (a->filename); + a->filename = safe_strdup (temp); + a->unlink = 1; + if(stat (a->filename, &sb) == -1) + { + mutt_perror ("stat"); + return; + } + a->length = sb.st_size; + mutt_free_body (&a->parts); +} + +static void transform_to_7bit (BODY *a, FILE *fpin) +{ + char buff[_POSIX_PATH_MAX]; + STATE s; + struct stat sb; + + memset (&s, 0, sizeof (s)); + for (; a; a = a->next) + { + if (a->type == TYPEMULTIPART) + { + if (a->encoding != ENC7BIT) + a->encoding = ENC7BIT; + + transform_to_7bit (a->parts, fpin); + } + else if (a->type == TYPEMESSAGE && strcasecmp (a->subtype, "delivery-status")) + { + mutt_message_to_7bit (a, fpin); + } + else + { + mutt_mktemp (buff); + if ((s.fpout = safe_fopen (buff, "w")) == NULL) + { + mutt_perror ("fopen"); + return; + } + s.fpin = fpin; + mutt_decode_attachment (a, &s); + fclose (s.fpout); + a->d_filename = a->filename; + a->filename = safe_strdup (buff); + a->unlink = 1; + if (stat (a->filename, &sb) == -1) + { + mutt_perror ("stat"); + return; + } + a->length = sb.st_size; + + mutt_update_encoding (a); + if (a->encoding == ENC8BIT) + a->encoding = ENCQUOTEDPRINTABLE; + else if(a->encoding == ENCBINARY) + a->encoding = ENCBASE64; + } + } +} + +/* determine which Content-Transfer-Encoding to use */ +static void mutt_set_encoding (BODY *b, CONTENT *info) +{ + if (b->type == TYPETEXT) + { + if (info->lobin) + b->encoding = ENCQUOTEDPRINTABLE; + else if (info->hibin) + b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE; + else + b->encoding = ENC7BIT; + } + else if (b->type == TYPEMESSAGE) + { + if (info->lobin || info->hibin) + { + if (option (OPTALLOW8BIT) && !info->lobin) + b->encoding = ENC8BIT; + else + mutt_message_to_7bit (b, NULL); + } + else + b->encoding = ENC7BIT; + } + else if (info->lobin || info->hibin || info->binary || info->linemax > 990) + { + /* Determine which encoding is smaller */ + if (1.33 * (float)(info->lobin+info->hibin+info->ascii) < 3.0 * (float) (info->lobin + info->hibin) + (float)info->ascii) + b->encoding = ENCBASE64; + else + b->encoding = ENCQUOTEDPRINTABLE; + } + else + b->encoding = ENC7BIT; +} + +/* Assumes called from send mode where BODY->filename points to actual file */ +void mutt_update_encoding (BODY *a) +{ + CONTENT *info; + + if ((info = mutt_get_content_info (a->filename)) == NULL) + return; + + mutt_set_encoding (a, info); + + if (a->type == TYPETEXT) + { + /* make sure the charset is valid */ + if (a->parameter) + safe_free ((void **) &a->parameter->value); + else + { + a->parameter = mutt_new_parameter (); + a->parameter->attribute = safe_strdup ("charset"); + } + a->parameter->value = safe_strdup (set_text_charset (info)); + } + + + +#ifdef _PGPPATH + /* save the info in case this message is signed. we will want to do Q-P + * encoding if any lines begin with "From " so the signature won't be munged, + * for example. + */ + safe_free ((void **) &a->content); + a->content = info; + info = NULL; +#endif + + + + safe_free ((void **) &info); +} + +BODY *mutt_make_attach (const char *path) +{ + BODY *att; + CONTENT *info; + char buf[SHORT_STRING]; + int n; + + if ((info = mutt_get_content_info (path)) == NULL) + return NULL; + + att = mutt_new_body (); + att->filename = safe_strdup (path); + + /* Attempt to determine the appropriate content-type based on the filename + * suffix. + */ + if ((n = lookup_mime_type (buf, path)) != TYPEOTHER) + { + att->type = n; + att->subtype = safe_strdup (buf); + } + + if (!att->subtype) + { + if (info->lobin == 0 || (info->lobin + info->hibin + info->ascii)/ info->lobin >= 10) + { + /* + * Statistically speaking, there should be more than 10% "lobin" + * chars if this is really a binary file... + */ + att->type = TYPETEXT; + att->subtype = safe_strdup ("plain"); + att->parameter = mutt_new_parameter (); + att->parameter->attribute = safe_strdup ("charset"); + att->parameter->value = safe_strdup (set_text_charset (info)); + } + else + { + att->type = TYPEAPPLICATION; + att->subtype = safe_strdup ("octet-stream"); + } + } + + mutt_set_encoding (att, info); + + + +#ifdef _PGPPATH + /* + * save the info in case this message is signed. we will want to do Q-P + * encoding if any lines begin with "From " so the signature won't be munged, + * for example. + */ + att->content = info; + info = NULL; +#endif + + + + safe_free ((void **) &info); + + return (att); +} + +static int get_toplevel_encoding (BODY *a) +{ + int e = ENC7BIT; + + for (; a; a = a->next) + { + if (a->encoding == ENCBINARY) + return (ENCBINARY); + else if (a->encoding == ENC8BIT) + e = ENC8BIT; + } + + return (e); +} + +BODY *mutt_make_multipart (BODY *b) +{ + BODY *new; + + new = mutt_new_body (); + new->type = TYPEMULTIPART; + new->subtype = safe_strdup ("mixed"); + new->encoding = get_toplevel_encoding (b); + new->parameter = mutt_new_parameter (); + new->parameter->attribute = safe_strdup ("boundary"); + new->parameter->value = mutt_generate_boundary (); + new->use_disp = 0; + new->parts = b; + + return new; +} + +static char *mutt_make_date (char *s) +{ + time_t t = time (NULL); + struct tm *l = gmtime(&t); + int yday = l->tm_yday; + int tz = l->tm_hour * 60 + l->tm_min; + + l = localtime(&t); + tz = l->tm_hour * 60 + l->tm_min - tz; + yday = l->tm_yday - yday; + + if (yday != 0) + tz += yday * 24 * 60; /* GMT is next or previous day! */ + + sprintf (s, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n", + Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon], l->tm_year+1900, + l->tm_hour, l->tm_min, l->tm_sec, tz/60, abs(tz) % 60); + return (s); +} + +/* wrapper around mutt_write_address() so we can handle very large + recipient lists without needing a huge temporary buffer in memory */ +void mutt_write_address_list (ADDRESS *adr, FILE *fp, int linelen) +{ + ADDRESS *tmp; + char buf[LONG_STRING]; + int count = 0; + int len; + + while (adr) + { + tmp = adr->next; + adr->next = NULL; + buf[0] = 0; + rfc822_write_address (buf, sizeof (buf), adr); + len = strlen (buf); + if (count && linelen + len > 74) + { + if (count) + { + fputs ("\n\t", fp); + linelen = len + 8; /* tab is usually about 8 spaces... */ + } + } + else + { + if (count && adr->mailbox) + { + fputc (' ', fp); + linelen++; + } + linelen += len; + } + fputs (buf, fp); + adr->next = tmp; + if (!adr->group && adr->next && adr->next->mailbox) + { + linelen++; + fputc (',', fp); + } + adr = adr->next; + count++; + } + fputc ('\n', fp); +} + +/* arbitrary number of elements to grow the array by */ +#define REF_INC 16 + +#define TrimRef 10 + +/* need to write the list in reverse because they are stored in reverse order + * when parsed to speed up threading + */ +static void write_references (LIST *r, FILE *f) +{ + LIST **ref = NULL; + int refcnt = 0, refmax = 0; + + for ( ; (TrimRef == 0 || refcnt < TrimRef) && r ; r = r->next) + { + if (refcnt == refmax) + safe_realloc ((void **) &ref, (refmax += REF_INC) * sizeof (LIST *)); + ref[refcnt++] = r; + } + + while (refcnt-- > 0) + { + fputc (' ', f); + fputs (ref[refcnt]->data, f); + } + + safe_free ((void **) &ref); +} + +/* Note: all RFC2047 encoding should be done outside of this routine, except + * for the "real name." This will allow this routine to be used more than + * once, if necessary. + * + * mode == 1 => "lite" mode (used for edit_hdrs) + * mode == 0 => normal mode. write full header + MIME headers + * mode == -1 => write just the envelope info (used for postponing messages) + */ + +int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach, int mode) +{ + char buffer[LONG_STRING]; + LIST *tmp = env->userhdrs; + + if (mode == 0) + { + if (env->message_id) + fprintf (fp, "Message-ID: %s\n", env->message_id); + fputs (mutt_make_date (buffer), fp); + } + + /* OPTUSEFROM is not consulted here so that we can still write a From: + * field if the user sets it with the `my_hdr' command + */ + if (env->from) + { + buffer[0] = 0; + rfc822_write_address (buffer, sizeof (buffer), env->from); + fprintf (fp, "From: %s\n", buffer); + } + + if (env->to) + { + fputs ("To: ", fp); + mutt_write_address_list (env->to, fp, 4); + } + else if (mode > 0) + fputs ("To: \n", fp); + + if (env->cc) + { + fputs ("Cc: ", fp); + mutt_write_address_list (env->cc, fp, 4); + } + else if (mode > 0) + fputs ("Cc: \n", fp); + + if (env->bcc) + { + fputs ("Bcc: ", fp); + mutt_write_address_list (env->bcc, fp, 5); + } + else if (mode > 0) + fputs ("Bcc: \n", fp); + + if (env->subject) + fprintf (fp, "Subject: %s\n", env->subject); + else if (mode == 1) + fputs ("Subject: \n", fp); + + if (env->reply_to) + { + fputs ("Reply-To: ", fp); + mutt_write_address_list (env->reply_to, fp, 10); + } + else if (mode > 0) + fputs ("Reply-To: \n", fp); + + if (env->mail_followup_to) + { + fputs ("Mail-Followup-To: ", fp); + mutt_write_address_list (env->mail_followup_to, fp, 18); + } + + if (mode <= 0) + { + if (env->references) + { + fputs ("References:", fp); + write_references (env->references, fp); + fputc('\n', fp); + } + + /* Add the MIME headers */ + fputs ("Mime-Version: 1.0\n", fp); + mutt_write_mime_header (attach, fp); + } + +#ifndef NO_XMAILER + if (mode == 0) + { + /* Add a vanity header */ + fprintf (fp, "X-Mailer: Mutt %s\n", VERSION); + } +#endif + + /* Add any user defined headers */ + for (; tmp; tmp = tmp->next) + { + fputs (tmp->data, fp); + fputc ('\n', fp); + } + + return (ferror (fp) == 0 ? 0 : -1); +} + +static void encode_headers (LIST *h) +{ + char tmp[LONG_STRING]; + char *p; + size_t len; + + for (; h; h = h->next) + { + if ((p = strchr (h->data, ':'))) + { + *p++ = 0; + SKIPWS (p); + snprintf (tmp, sizeof (tmp), "%s: ", h->data); + len = strlen (tmp); + rfc2047_encode_string (tmp + len, sizeof (tmp) - len, (unsigned char *) p); + safe_free ((void **) &h->data); + h->data = safe_strdup (tmp); + } + } +} + +/* rfc2047 encode the content-descriptions */ +static void encode_descriptions (BODY *b) +{ + BODY *t; + char tmp[LONG_STRING]; + + for (t = b; t; t = t->next) + { + if (t->description) + { + rfc2047_encode_string (tmp, sizeof (tmp), (unsigned char *) t->description); + safe_free ((void **) &t->description); + t->description = safe_strdup (tmp); + } + if (t->parts) + encode_descriptions (t->parts); + } +} + +char *mutt_gen_msgid (void) +{ + char buf[SHORT_STRING]; + time_t now; + struct tm *tm; + + now = time (NULL); + tm = localtime (&now); + snprintf (buf, sizeof (buf), "<%d%02d%02d%02d%02d%02d.%c%d@%s>", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, + tm->tm_min, tm->tm_sec, MsgIdPfx, getpid (), + Fqdn[0] != '@' ? Fqdn : Hostname); + MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1; + return (safe_strdup (buf)); +} + +static RETSIGTYPE alarm_handler (int sig) +{ + Signals |= S_ALARM; +} + +/* invoke sendmail in a subshell + path (in) path to program to execute + args (in) arguments to pass to program + msg (in) temp file containing message to send + tempfile (out) if sendmail is put in the background, this points + to the temporary file containing the stdout of the + child process */ +static int +send_msg (const char *path, char **args, const char *msg, char **tempfile) +{ + int fd, st, w = 0, err = 0; + pid_t pid; + struct sigaction act, oldint, oldquit, oldalrm; + + memset (&act, 0, sizeof (struct sigaction)); + sigemptyset (&(act.sa_mask)); + act.sa_handler = SIG_IGN; + sigaction (SIGINT, &act, &oldint); + sigaction (SIGQUIT, &act, &oldquit); + + if (SendmailWait) + { + char tmp[_POSIX_PATH_MAX]; + + mutt_mktemp (tmp); + *tempfile = safe_strdup (tmp); + } + + if ((pid = fork ()) == 0) + { + /* reset signals for the child */ + act.sa_handler = SIG_DFL; + /* we need SA_RESTART for the open() below */ +#ifdef SA_RESTART + act.sa_flags = SA_NOCLDSTOP | SA_RESTART; +#else + act.sa_flags = SA_NOCLDSTOP; +#endif + sigaction (SIGCHLD, &act, NULL); + act.sa_flags = 0; + sigaction (SIGINT, &act, NULL); + sigaction (SIGQUIT, &act, NULL); + + /* if it is possible that we will deliver in the background, then we have + to detach the child from this process group or it will die when the + parent process exists, causing the mail to not get delivered. The + problem here is that any error messages will get lost... */ + if (SendmailWait) + setsid (); + if ((pid = fork ()) == 0) + { + fd = open (msg, O_RDONLY, 0); + if (fd < 0) + _exit (127); + dup2 (fd, 0); + close (fd); + if (SendmailWait) + { + /* write stdout to a tempfile */ + w = open (*tempfile, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (w < 0) + _exit (errno); + dup2 (w, 1); + close (w); + } + sigaction (SIGCHLD, &act, NULL); + execv (path, args); + unlink (msg); + _exit (127); + } + else if (pid == -1) + { + unlink (msg); + _exit (errno); + } + + if (waitpid (pid, &st, 0) > 0) + { + st = WIFEXITED (st) ? WEXITSTATUS (st) : 127; + if (st == (EX_OK & 0xff) && SendmailWait) + unlink (*tempfile); /* no longer needed */ + } + else + { + st = 127; + if (SendmailWait) + unlink (*tempfile); + } + unlink (msg); + _exit (st); + } + /* SendmailWait > 0: SIGALRM will interrupt wait() after alrm seconds + SendmailWait = 0: wait forever + SendmailWait < 0: don't wait */ + if (SendmailWait > 0) + { + Signals &= ~S_ALARM; + act.sa_handler = alarm_handler; +#ifdef SA_INTERRUPT + /* need to make sure the waitpid() is interrupted on SIGALRM */ + act.sa_flags = SA_INTERRUPT; +#else + act.sa_flags = 0; +#endif + sigaction (SIGALRM, &act, &oldalrm); + alarm (SendmailWait); + } + if (SendmailWait >= 0) + { + w = waitpid (pid, &st, 0); + err = errno; /* save error since it might be clobbered by another + system call before we check the value */ + dprint (1, (debugfile, "send_msg(): waitpid returned %d (%s)\n", w, + (w < 0 ? strerror (errno) : "OK"))); + } + if (SendmailWait > 0) + { + alarm (0); + sigaction (SIGALRM, &oldalrm, NULL); + } + if (SendmailWait < 0 || ((Signals & S_ALARM) && w < 0 && err == EINTR)) + { + /* add to list of children pids to reap */ + mutt_add_child_pid (pid); + /* since there is no way for the user to be notified of error in this case, + remove the temp file now */ + unlink (*tempfile); + FREE (tempfile); +#ifdef DEBUG + if (Signals & S_ALARM) + dprint (1, (debugfile, "send_msg(): received SIGALRM\n")); + dprint (1, (debugfile, "send_msg(): putting sendmail in the background\n")); +#endif + st = EX_OK & 0xff; + } + else if (w < 0) + { + /* if err==EINTR, alarm interrupt, child status unknown, + otherwise, there was an error invoking the child */ + st = (err == EINTR) ? (EX_OK & 0xff) : 127; + } + else + { +#ifdef DEBUG + if (WIFEXITED (st)) + dprint (1, (debugfile, "send_msg(): child exited %d\n", WEXITSTATUS (st))); + else + dprint (1, (debugfile, "send_msg(): child did not exit\n")); +#endif /* DEBUG */ + st = WIFEXITED (st) ? WEXITSTATUS (st) : -1; /* return child status */ + } + sigaction (SIGINT, &oldint, NULL); + sigaction (SIGQUIT, &oldquit, NULL); + /* restore errno so that the caller can build an error message */ + errno = err; + return (st); +} + +static char ** +add_args (char **args, size_t *argslen, size_t *argsmax, ADDRESS *addr) +{ + for (; addr; addr = addr->next) + { + /* weed out group mailboxes, since those are for display only */ + if (addr->mailbox && !addr->group) + { + if (*argslen == *argsmax) + safe_realloc ((void **) &args, (*argsmax += 5) * sizeof (char *)); + args[(*argslen)++] = addr->mailbox; + } + } + return (args); +} + +static char ** +add_option (char **args, size_t *argslen, size_t *argsmax, char *s) +{ + if (*argslen == *argsmax) + safe_realloc ((void **) &args, (*argsmax += 5) * sizeof (char *)); + args[(*argslen)++] = s; + return (args); +} + +static int +invoke_sendmail (ADDRESS *to, ADDRESS *cc, ADDRESS *bcc, /* recips */ + const char *msg, /* file containing message */ + int eightbit) /* message contains 8bit chars */ +{ + char *ps = NULL, *path = NULL, *s = safe_strdup (Sendmail), *childout = NULL; + char **args = NULL; + size_t argslen = 0, argsmax = 0; + int i; + + ps = s; + i = 0; + while ((ps = strtok (ps, " "))) + { + if (argslen == argsmax) + safe_realloc ((void **) &args, sizeof (char *) * (argsmax += 5)); + + if (i) + args[argslen++] = ps; + else + { + path = safe_strdup (ps); + ps = strrchr (ps, '/'); + if (ps) + ps++; + else + ps = path; + args[argslen++] = ps; + } + ps = NULL; + i++; + } + if (eightbit && option (OPTUSE8BITMIME)) + args = add_option (args, &argslen, &argsmax, "-B8BITMIME"); + if (DsnNotify) + { + args = add_option (args, &argslen, &argsmax, "-N"); + args = add_option (args, &argslen, &argsmax, DsnNotify); + } + if (DsnReturn) + { + args = add_option (args, &argslen, &argsmax, "-R"); + args = add_option (args, &argslen, &argsmax, DsnReturn); + } + args = add_args (args, &argslen, &argsmax, to); + args = add_args (args, &argslen, &argsmax, cc); + args = add_args (args, &argslen, &argsmax, bcc); + + if (argslen == argsmax) + safe_realloc ((void **) &args, sizeof (char *) * (++argsmax)); + + args[argslen++] = NULL; + + if (!option (OPTNOCURSES)) + endwin (); + if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) + { + char *e = strerror (errno); + + fprintf (stderr, "Error sending message, child exited %d (%s).\n", i, NONULL (e)); + if (childout) + fprintf (stderr, "Saved output of child process to %s.\n", childout); + if (!option (OPTNOCURSES)) + { + mutt_any_key_to_continue (NULL); + mutt_error ("Error sending message."); + } + } + FREE (&childout); + FREE (&path); + FREE (&s); + FREE (&args); + + return (i); +} + +/* appends string 'b' to string 'a', and returns the pointer to the new + string. */ +char *mutt_append_string (char *a, const char *b) +{ + size_t la = strlen (a); + safe_realloc ((void **) &a, la + strlen (b) + 1); + strcpy (a + la, b); + return (a); +} + +/* returns 1 if char `c' needs to be quoted to protect from shell + interpretation when executing commands in a subshell */ +#define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c)) + +/* returns 1 if string `s' contains characters which could cause problems + when used on a command line to execute a command */ +int mutt_needs_quote (const char *s) +{ + while (*s) + { + if (INVALID_CHAR (*s)) + return 1; + s++; + } + return 0; +} + +/* Quote a string to prevent shell escapes when this string is used on the + command line to send mail. */ +char *mutt_quote_string (const char *s) +{ + char *r, *pr; + size_t rlen; + + rlen = strlen (s) + 3; + pr = r = malloc (rlen); + *pr++ = '"'; + while (*s) + { + if (INVALID_CHAR (*s)) + { + size_t o = pr - r; + safe_realloc ((void **) &r, ++rlen); + pr = r + o; + *pr++ = '\\'; + } + *pr++ = *s++; + } + *pr++ = '"'; + *pr = 0; + return (r); +} + +int mutt_send_message (HEADER *msg, const char *fcc) +{ + char tempfile[_POSIX_PATH_MAX], buffer[STRING]; + FILE *tempfp; + int i; + + /* Take care of 8-bit => 7-bit conversion. */ + rfc2047_encode_adrlist (msg->env->to); + rfc2047_encode_adrlist (msg->env->cc); + rfc2047_encode_adrlist (msg->env->from); + rfc2047_encode_adrlist (msg->env->mail_followup_to); + if (msg->env->subject) + { + rfc2047_encode_string (buffer, sizeof (buffer) - 1, + (unsigned char *) msg->env->subject); + safe_free ((void **) &msg->env->subject); + msg->env->subject = safe_strdup (buffer); + } + encode_headers (msg->env->userhdrs); + encode_descriptions (msg->content); + + if (!msg->env->message_id) + msg->env->message_id = mutt_gen_msgid (); + + /* Write out the message in MIME form. */ + mutt_mktemp (tempfile); + if ((tempfp = safe_fopen (tempfile, "w")) == NULL) + return (-1); + + mutt_write_rfc822_header (tempfp, msg->env, msg->content, 0); + fputc ('\n', tempfp); /* tie off the header. */ + + mutt_write_mime_body (msg->content, tempfp); + if (fclose (tempfp) != 0) + { + mutt_perror (tempfile); + unlink (tempfile); + return (-1); + } + + /* save a copy of the message, if necessary. */ + if (*fcc && strcmp ("/dev/null", fcc) != 0) + { + BODY *tmpbody = msg->content; + + /* check to see if the user wants copies of all attachments */ + if (msg->content->type == TYPEMULTIPART && + strcmp (msg->content->subtype, "encrypted") && + strcmp (msg->content->subtype, "signed") && + !option (OPTFCCATTACH)) + msg->content = msg->content->parts; + + mutt_write_fcc (fcc, msg, NULL, 0); + msg->content = tmpbody; + } + + i = invoke_sendmail (msg->env->to, msg->env->cc, msg->env->bcc, + tempfile, + (msg->content->encoding == ENC8BIT)); + return (i ? -1 : 0); +} + +void mutt_bounce_message (HEADER *h, ADDRESS *to) +{ + int i; + FILE *f; + char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX]; + MESSAGE *msg; + + if (!h) + { + for (i=0; i<Context->msgcount; i++) + if (Context->hdrs[i]->tagged) + mutt_bounce_message (Context->hdrs[i], to); + return; + } + + if ((msg = mx_open_message (Context, h->msgno)) != NULL) + { + mutt_mktemp (tempfile); + if ((f = safe_fopen (tempfile, "w")) != NULL) + { + fseek (msg->fp, h->offset, 0); + mutt_copy_header (msg->fp, h, f, CH_XMIT | CH_NONEWLINE, NULL); + fprintf (f, "Resent-From: %s", Username); + if (Fqdn[0] != '@') + fprintf (f, "@%s", Fqdn); + fprintf (f, "\nResent-%s", mutt_make_date (date)); + fputs ("Resent-To: ", f); + mutt_write_address_list (to, f, 11); + fputc ('\n', f); + mutt_copy_bytes (msg->fp, f, h->content->length); + fclose (f); + + invoke_sendmail (to, NULL, NULL, tempfile, h->content->encoding == ENC8BIT); + } + mx_close_message (&msg); + } +} + +/* given a list of addresses, return a list of unique addresses */ +ADDRESS *mutt_remove_duplicates (ADDRESS *addr) +{ + ADDRESS *top = NULL; + ADDRESS *tmp; + + if ((top = addr) == NULL) + return (NULL); + addr = addr->next; + top->next = NULL; + while (addr) + { + tmp = top; + do { + if (addr->mailbox && tmp->mailbox && + !strcasecmp (addr->mailbox, tmp->mailbox)) + { + /* duplicate address, just ignore it */ + tmp = addr; + addr = addr->next; + tmp->next = NULL; + rfc822_free_address (&tmp); + } + else if (!tmp->next) + { + /* unique address. add it to the list */ + tmp->next = addr; + addr = addr->next; + tmp = tmp->next; + tmp->next = NULL; + tmp = NULL; /* so we exit the loop */ + } + else + tmp = tmp->next; + } while (tmp); + } + + return (top); +} + +int mutt_write_fcc (const char *path, HEADER *hdr, const char *msgid, int post) +{ + CONTEXT f; + MESSAGE *msg; + char tempfile[_POSIX_PATH_MAX]; + FILE *tempfp = NULL; + int r; + + if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) + { + dprint (1, (debugfile, "mutt_write_fcc(): unable to open mailbox %s in append-mode, aborting.\n", + path)); + return (-1); + } + + /* We need to add a Content-Length field to avoid problems where a line in + * the message body begins with "From " + */ + if (f.magic == M_MMDF || f.magic == M_MBOX) + { + mutt_mktemp (tempfile); + if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) + { + mutt_perror (tempfile); + mx_close_mailbox (&f); + return (-1); + } + } + + hdr->read = 1; /* make sure to put it in the `cur' directory (maildir) */ + if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) + { + mx_close_mailbox (&f); + return (-1); + } + + mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, (post ? -1 : 0)); + + /* (postponment) if this was a reply of some sort, <msgid> contians the + * Message-ID: of message replied to. Save it using a special X-Mutt- + * header so it can be picked up if the message is recalled at a later + * point in time. This will allow the message to be marked as replied if + * the same mailbox is still open. + */ + if (post && msgid) + fprintf (msg->fp, "X-Mutt-References: %s\n", msgid); + fprintf (msg->fp, "Status: RO\n"); + + + +#ifdef _PGPPATH + /* (postponment) if the mail is to be signed or encrypted, save this info */ + if (post && (hdr->pgp & (PGPENCRYPT | PGPSIGN))) + { + fputs ("Pgp: ", msg->fp); + if (hdr->pgp & PGPENCRYPT) + fputc ('E', msg->fp); + if (hdr->pgp & PGPSIGN) + { + fputc ('S', msg->fp); + if (PgpSignAs && *PgpSignAs) + fprintf (msg->fp, "<%s>", PgpSignAs); + if (*PgpSignMicalg) + fprintf (msg->fp, "M<%s>", PgpSignMicalg); + } + fputc ('\n', msg->fp); + } +#endif /* _PGPPATH */ + + + + if (tempfp) + { + char sasha[LONG_STRING]; + int lines = 0; + + mutt_write_mime_body (hdr->content, tempfp); + + /* make sure the last line ends with a newline. Emacs doesn't ensure + * this will happen, and it can cause problems parsing the mailbox + * later. + */ + fseek (tempfp, -1, 2); + if (fgetc (tempfp) != '\n') + { + fseek (tempfp, 0, 2); + fputc ('\n', tempfp); + } + + fflush (tempfp); + if (ferror (tempfp)) + { + dprint (1, (debugfile, "mutt_write_fcc(): %s: write failed.\n", tempfile)); + fclose (tempfp); + unlink (tempfile); + mx_close_message (&msg); + mx_close_mailbox (&f); + return -1; + } + + /* count the number of lines */ + rewind (tempfp); + while (fgets (sasha, sizeof (sasha), tempfp) != NULL) + lines++; + fprintf (msg->fp, "Content-Length: %ld\n", (long) ftell (tempfp)); + fprintf (msg->fp, "Lines: %d\n\n", lines); + + /* copy the body and clean up */ + rewind (tempfp); + r = mutt_copy_stream (tempfp, msg->fp); + if (fclose (tempfp) != 0) + r = -1; + /* if there was an error, leave the temp version */ + if (!r) + unlink (tempfile); + } + else + { + fputc ('\n', msg->fp); /* finish off the header */ + r = mutt_write_mime_body (hdr->content, msg->fp); + } + + mx_close_message (&msg); + mx_close_mailbox (&f); + + return r; +} diff --git a/sha.h b/sha.h new file mode 100644 index 00000000..9e22fa87 --- /dev/null +++ b/sha.h @@ -0,0 +1,105 @@ +/* crypto/sha/sha.h */ +/* Copyright (C) 1995-1997 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + +#ifndef HEADER_SHA_H +#define HEADER_SHA_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define SHA_CBLOCK 64 +#define SHA_LBLOCK 16 +#define SHA_BLOCK 16 +#define SHA_LAST_BLOCK 56 +#define SHA_LENGTH_BLOCK 8 +#define SHA_DIGEST_LENGTH 20 + +typedef struct SHAstate_st + { + unsigned long h0,h1,h2,h3,h4; + unsigned long Nl,Nh; + unsigned long data[SHA_LBLOCK]; + int num; + } SHA_CTX; + +#ifndef NOPROTO +void SHA_Init(SHA_CTX *c); +void SHA_Update(SHA_CTX *c, unsigned char *data, unsigned long len); +void SHA_Final(unsigned char *md, SHA_CTX *c); +unsigned char *SHA(unsigned char *d, unsigned long n,unsigned char *md); +void SHA1_Init(SHA_CTX *c); +void SHA1_Update(SHA_CTX *c, unsigned char *data, unsigned long len); +void SHA1_Final(unsigned char *md, SHA_CTX *c); +unsigned char *SHA1(unsigned char *d, unsigned long n,unsigned char *md); +#else +void SHA_Init(); +void SHA_Update(); +void SHA_Final(); +unsigned char *SHA(); +void SHA1_Init(); +void SHA1_Update(); +void SHA1_Final(); +unsigned char *SHA1(); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/sha1dgst.c b/sha1dgst.c new file mode 100644 index 00000000..58230acd --- /dev/null +++ b/sha1dgst.c @@ -0,0 +1,373 @@ +/* crypto/sha/sha1dgst.c */ +/* Copyright (C) 1995-1997 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + +#include <stdio.h> +#undef SHA_0 +#define SHA_1 +#include "sha.h" +#include "sha_locl.h" + +char *SHA1_version="SHA1 part of SSLeay 0.8.1 19-Jul-1997"; + +/* Implemented from SHA-1 document - The Secure Hash Algorithm + */ + +#define INIT_DATA_h0 (unsigned long)0x67452301L +#define INIT_DATA_h1 (unsigned long)0xefcdab89L +#define INIT_DATA_h2 (unsigned long)0x98badcfeL +#define INIT_DATA_h3 (unsigned long)0x10325476L +#define INIT_DATA_h4 (unsigned long)0xc3d2e1f0L + +#define K_00_19 0x5a827999L +#define K_20_39 0x6ed9eba1L +#define K_40_59 0x8f1bbcdcL +#define K_60_79 0xca62c1d6L + +#ifndef NOPROTO +static void sha1_block(SHA_CTX *c, register unsigned long *p); +#else +static void sha1_block(); +#endif + +void SHA1_Init(c) +SHA_CTX *c; + { + c->h0=INIT_DATA_h0; + c->h1=INIT_DATA_h1; + c->h2=INIT_DATA_h2; + c->h3=INIT_DATA_h3; + c->h4=INIT_DATA_h4; + c->Nl=0; + c->Nh=0; + c->num=0; + } + +void SHA1_Update(c, data, len) +SHA_CTX *c; +register unsigned char *data; +unsigned long len; + { + register ULONG *p; + int ew,ec,sw,sc; + ULONG l; + + if (len == 0) return; + + l=(c->Nl+(len<<3))&0xffffffff; + if (l < c->Nl) /* overflow */ + c->Nh++; + c->Nh+=(len>>29); + c->Nl=l; + + if (c->num != 0) + { + p=c->data; + sw=c->num>>2; + sc=c->num&0x03; + + if ((c->num+len) >= SHA_CBLOCK) + { + l= p[sw]; + p_c2nl(data,l,sc); + p[sw++]=l; + for (; sw<SHA_LBLOCK; sw++) + { + c2nl(data,l); + p[sw]=l; + } + len-=(SHA_CBLOCK-c->num); + + sha1_block(c,p); + c->num=0; + /* drop through and do the rest */ + } + else + { + c->num+=(int)len; + if ((sc+len) < 4) /* ugly, add char's to a word */ + { + l= p[sw]; + p_c2nl_p(data,l,sc,len); + p[sw]=l; + } + else + { + ew=(c->num>>2); + ec=(c->num&0x03); + l= p[sw]; + p_c2nl(data,l,sc); + p[sw++]=l; + for (; sw < ew; sw++) + { c2nl(data,l); p[sw]=l; } + if (ec) + { + c2nl_p(data,l,ec); + p[sw]=l; + } + } + return; + } + } + /* we now can process the input data in blocks of SHA_CBLOCK + * chars and save the leftovers to c->data. */ + p=c->data; + while (len >= SHA_CBLOCK) + { +#if defined(B_ENDIAN) || defined(L_ENDIAN) + memcpy(p,data,SHA_CBLOCK); + data+=SHA_CBLOCK; +#ifdef L_ENDIAN + for (sw=(SHA_LBLOCK/4); sw; sw--) + { + Endian_Reverse32(p[0]); + Endian_Reverse32(p[1]); + Endian_Reverse32(p[2]); + Endian_Reverse32(p[3]); + p+=4; + } +#endif +#else + for (sw=(SHA_BLOCK/4); sw; sw--) + { + c2nl(data,l); *(p++)=l; + c2nl(data,l); *(p++)=l; + c2nl(data,l); *(p++)=l; + c2nl(data,l); *(p++)=l; + } +#endif + p=c->data; + sha1_block(c,p); + len-=SHA_CBLOCK; + } + ec=(int)len; + c->num=ec; + ew=(ec>>2); + ec&=0x03; + + for (sw=0; sw < ew; sw++) + { c2nl(data,l); p[sw]=l; } + c2nl_p(data,l,ec); + p[sw]=l; + } + +static void sha1_block(c, X) +SHA_CTX *c; +register unsigned long *X; + { + register ULONG A,B,C,D,E,T; + + A=c->h0; + B=c->h1; + C=c->h2; + D=c->h3; + E=c->h4; + + BODY_00_15( 0,A,B,C,D,E,T); + BODY_00_15( 1,T,A,B,C,D,E); + BODY_00_15( 2,E,T,A,B,C,D); + BODY_00_15( 3,D,E,T,A,B,C); + BODY_00_15( 4,C,D,E,T,A,B); + BODY_00_15( 5,B,C,D,E,T,A); + BODY_00_15( 6,A,B,C,D,E,T); + BODY_00_15( 7,T,A,B,C,D,E); + BODY_00_15( 8,E,T,A,B,C,D); + BODY_00_15( 9,D,E,T,A,B,C); + BODY_00_15(10,C,D,E,T,A,B); + BODY_00_15(11,B,C,D,E,T,A); + BODY_00_15(12,A,B,C,D,E,T); + BODY_00_15(13,T,A,B,C,D,E); + BODY_00_15(14,E,T,A,B,C,D); + BODY_00_15(15,D,E,T,A,B,C); + BODY_16_19(16,C,D,E,T,A,B); + BODY_16_19(17,B,C,D,E,T,A); + BODY_16_19(18,A,B,C,D,E,T); + BODY_16_19(19,T,A,B,C,D,E); + + BODY_20_39(20,E,T,A,B,C,D); + BODY_20_39(21,D,E,T,A,B,C); + BODY_20_39(22,C,D,E,T,A,B); + BODY_20_39(23,B,C,D,E,T,A); + BODY_20_39(24,A,B,C,D,E,T); + BODY_20_39(25,T,A,B,C,D,E); + BODY_20_39(26,E,T,A,B,C,D); + BODY_20_39(27,D,E,T,A,B,C); + BODY_20_39(28,C,D,E,T,A,B); + BODY_20_39(29,B,C,D,E,T,A); + BODY_20_39(30,A,B,C,D,E,T); + BODY_20_39(31,T,A,B,C,D,E); + BODY_20_39(32,E,T,A,B,C,D); + BODY_20_39(33,D,E,T,A,B,C); + BODY_20_39(34,C,D,E,T,A,B); + BODY_20_39(35,B,C,D,E,T,A); + BODY_20_39(36,A,B,C,D,E,T); + BODY_20_39(37,T,A,B,C,D,E); + BODY_20_39(38,E,T,A,B,C,D); + BODY_20_39(39,D,E,T,A,B,C); + + BODY_40_59(40,C,D,E,T,A,B); + BODY_40_59(41,B,C,D,E,T,A); + BODY_40_59(42,A,B,C,D,E,T); + BODY_40_59(43,T,A,B,C,D,E); + BODY_40_59(44,E,T,A,B,C,D); + BODY_40_59(45,D,E,T,A,B,C); + BODY_40_59(46,C,D,E,T,A,B); + BODY_40_59(47,B,C,D,E,T,A); + BODY_40_59(48,A,B,C,D,E,T); + BODY_40_59(49,T,A,B,C,D,E); + BODY_40_59(50,E,T,A,B,C,D); + BODY_40_59(51,D,E,T,A,B,C); + BODY_40_59(52,C,D,E,T,A,B); + BODY_40_59(53,B,C,D,E,T,A); + BODY_40_59(54,A,B,C,D,E,T); + BODY_40_59(55,T,A,B,C,D,E); + BODY_40_59(56,E,T,A,B,C,D); + BODY_40_59(57,D,E,T,A,B,C); + BODY_40_59(58,C,D,E,T,A,B); + BODY_40_59(59,B,C,D,E,T,A); + + BODY_60_79(60,A,B,C,D,E,T); + BODY_60_79(61,T,A,B,C,D,E); + BODY_60_79(62,E,T,A,B,C,D); + BODY_60_79(63,D,E,T,A,B,C); + BODY_60_79(64,C,D,E,T,A,B); + BODY_60_79(65,B,C,D,E,T,A); + BODY_60_79(66,A,B,C,D,E,T); + BODY_60_79(67,T,A,B,C,D,E); + BODY_60_79(68,E,T,A,B,C,D); + BODY_60_79(69,D,E,T,A,B,C); + BODY_60_79(70,C,D,E,T,A,B); + BODY_60_79(71,B,C,D,E,T,A); + BODY_60_79(72,A,B,C,D,E,T); + BODY_60_79(73,T,A,B,C,D,E); + BODY_60_79(74,E,T,A,B,C,D); + BODY_60_79(75,D,E,T,A,B,C); + BODY_60_79(76,C,D,E,T,A,B); + BODY_60_79(77,B,C,D,E,T,A); + BODY_60_79(78,A,B,C,D,E,T); + BODY_60_79(79,T,A,B,C,D,E); + + c->h0=(c->h0+E)&0xffffffff; + c->h1=(c->h1+T)&0xffffffff; + c->h2=(c->h2+A)&0xffffffff; + c->h3=(c->h3+B)&0xffffffff; + c->h4=(c->h4+C)&0xffffffff; + } + +void SHA1_Final(md, c) +unsigned char *md; +SHA_CTX *c; + { + register int i,j; + register ULONG l; + register ULONG *p; + static unsigned char end[4]={0x80,0x00,0x00,0x00}; + unsigned char *cp=end; + + /* c->num should definitly have room for at least one more byte. */ + p=c->data; + j=c->num; + i=j>>2; +#ifdef PURIFY + if ((j&0x03) == 0) p[i]=0; +#endif + l=p[i]; + p_c2nl(cp,l,j&0x03); + p[i]=l; + i++; + /* i is the next 'undefined word' */ + if (c->num >= SHA_LAST_BLOCK) + { + for (; i<SHA_LBLOCK; i++) + p[i]=0; + sha1_block(c,p); + i=0; + } + for (; i<(SHA_LBLOCK-2); i++) + p[i]=0; + p[SHA_LBLOCK-2]=c->Nh; + p[SHA_LBLOCK-1]=c->Nl; + sha1_block(c,p); + cp=md; + l=c->h0; nl2c(l,cp); + l=c->h1; nl2c(l,cp); + l=c->h2; nl2c(l,cp); + l=c->h3; nl2c(l,cp); + l=c->h4; nl2c(l,cp); + + /* clear stuff, sha1_block may be leaving some stuff on the stack + * but I'm not worried :-) */ + c->num=0; +/* memset((char *)&c,0,sizeof(c));*/ + } + +#ifdef undef +int printit(l) +unsigned long *l; + { + int i,ii; + + for (i=0; i<2; i++) + { + for (ii=0; ii<8; ii++) + { + fprintf(stderr,"%08lx ",l[i*8+ii]); + } + fprintf(stderr,"\n"); + } + } +#endif diff --git a/sha_locl.h b/sha_locl.h new file mode 100644 index 00000000..0a5cf469 --- /dev/null +++ b/sha_locl.h @@ -0,0 +1,198 @@ +/* crypto/sha/sha_locl.h */ +/* Copyright (C) 1995-1997 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + +#include <stdlib.h> +#include <string.h> + +#ifdef undef +/* one or the other needs to be defined */ +#ifndef SHA_1 /* FIPE 180-1 */ +#define SHA_0 /* FIPS 180 */ +#endif +#endif + +#define ULONG unsigned long +#define UCHAR unsigned char +#define UINT unsigned int + +#ifdef NOCONST +#define const +#endif + +#undef c2nl +#define c2nl(c,l) (l =(((unsigned long)(*((c)++)))<<24), \ + l|=(((unsigned long)(*((c)++)))<<16), \ + l|=(((unsigned long)(*((c)++)))<< 8), \ + l|=(((unsigned long)(*((c)++))) )) + +#undef p_c2nl +#define p_c2nl(c,l,n) { \ + switch (n) { \ + case 0: l =((unsigned long)(*((c)++)))<<24; \ + case 1: l|=((unsigned long)(*((c)++)))<<16; \ + case 2: l|=((unsigned long)(*((c)++)))<< 8; \ + case 3: l|=((unsigned long)(*((c)++))); \ + } \ + } + +#undef c2nl_p +/* NOTE the pointer is not incremented at the end of this */ +#define c2nl_p(c,l,n) { \ + l=0; \ + (c)+=n; \ + switch (n) { \ + case 3: l =((unsigned long)(*(--(c))))<< 8; \ + case 2: l|=((unsigned long)(*(--(c))))<<16; \ + case 1: l|=((unsigned long)(*(--(c))))<<24; \ + } \ + } + +#undef p_c2nl_p +#define p_c2nl_p(c,l,sc,len) { \ + switch (sc) \ + { \ + case 0: l =((unsigned long)(*((c)++)))<<24; \ + if (--len == 0) break; \ + case 1: l|=((unsigned long)(*((c)++)))<<16; \ + if (--len == 0) break; \ + case 2: l|=((unsigned long)(*((c)++)))<< 8; \ + } \ + } + +#undef nl2c +#define nl2c(l,c) (*((c)++)=(unsigned char)(((l)>>24)&0xff), \ + *((c)++)=(unsigned char)(((l)>>16)&0xff), \ + *((c)++)=(unsigned char)(((l)>> 8)&0xff), \ + *((c)++)=(unsigned char)(((l) )&0xff)) + +/* I have taken some of this code from my MD5 implementation */ + +#undef ROTATE +#if defined(WIN32) +#define ROTATE(a,n) _lrotl(a,n) +#else +#define ROTATE(a,n) (((a)<<(n))|(((a)&0xffffffff)>>(32-(n)))) +#endif + +/* A nice byte order reversal from Wei Dai <weidai@eskimo.com> */ +#if defined(WIN32) +/* 5 instructions with rotate instruction, else 9 */ +#define Endian_Reverse32(a) \ + { \ + unsigned long l=(a); \ + (a)=((ROTATE(l,8)&0x00FF00FF)|(ROTATE(l,24)&0xFF00FF00)); \ + } +#else +/* 6 instructions with rotate instruction, else 8 */ +#define Endian_Reverse32(a) \ + { \ + unsigned long l=(a); \ + l=(((l&0xFF00FF00)>>8L)|((l&0x00FF00FF)<<8L)); \ + (a)=ROTATE(l,16L); \ + } +#endif + +/* As pointed out by Wei Dai <weidai@eskimo.com>, F() below can be + * simplified to the code in F_00_19. Wei attributes these optimisations + * to Peter Gutmann's SHS code, and he attributes it to Rich Schroeppel. + * #define F(x,y,z) (((x) & (y)) | ((~(x)) & (z))) + * I've just become aware of another tweak to be made, again from Wei Dai, + * in F_40_59, (x&a)|(y&a) -> (x|y)&a + */ +#define F_00_19(b,c,d) ((((c) ^ (d)) & (b)) ^ (d)) +#define F_20_39(b,c,d) ((b) ^ (c) ^ (d)) +#define F_40_59(b,c,d) (((b) & (c)) | (((b)|(c)) & (d))) +#define F_60_79(b,c,d) F_20_39(b,c,d) + +#ifdef SHA_0 +#undef Xupdate +#define Xupdate(a,i) \ + X[(i)&0x0f]=(a)=\ + (X[(i)&0x0f]^X[((i)+2)&0x0f]^X[((i)+8)&0x0f]^X[((i)+13)&0x0f]); +#endif +#ifdef SHA_1 +#undef Xupdate +#define Xupdate(a,i) \ + (a)=(X[(i)&0x0f]^X[((i)+2)&0x0f]^X[((i)+8)&0x0f]^X[((i)+13)&0x0f]); \ + X[(i)&0x0f]=(a)=ROTATE((a),1); +#endif + +#define BODY_00_15(i,a,b,c,d,e,f) \ + (f)=X[i]+(e)+K_00_19+ROTATE((a),5)+F_00_19((b),(c),(d)); \ + (b)=ROTATE((b),30); + +#define BODY_16_19(i,a,b,c,d,e,f) \ + Xupdate(f,i); \ + (f)+=(e)+K_00_19+ROTATE((a),5)+F_00_19((b),(c),(d)); \ + (b)=ROTATE((b),30); + +#define BODY_20_39(i,a,b,c,d,e,f) \ + Xupdate(f,i); \ + (f)+=(e)+K_20_39+ROTATE((a),5)+F_20_39((b),(c),(d)); \ + (b)=ROTATE((b),30); + +#define BODY_40_59(i,a,b,c,d,e,f) \ + Xupdate(f,i); \ + (f)+=(e)+K_40_59+ROTATE((a),5)+F_40_59((b),(c),(d)); \ + (b)=ROTATE((b),30); + +#define BODY_60_79(i,a,b,c,d,e,f) \ + Xupdate(f,i); \ + (f)=X[(i)&0x0f]+(e)+K_60_79+ROTATE((a),5)+F_60_79((b),(c),(d)); \ + (b)=ROTATE((b),30); + diff --git a/signal.c b/signal.c new file mode 100644 index 00000000..b8e63f72 --- /dev/null +++ b/signal.c @@ -0,0 +1,245 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_curses.h" + +#include <signal.h> +#include <string.h> +#include <sys/wait.h> + +static sigset_t Sigset; +static int IsEndwin = 0; + +static pid_t *PidList = NULL; +static int PidListLen = 0; + +/* Attempt to catch "ordinary" signals and shut down gracefully. */ +RETSIGTYPE mutt_exit_handler (int sig) +{ + curs_set (1); + endwin (); /* just to be safe */ +#if SYS_SIGLIST_DECLARED + printf("Caught %s... Exiting.\n", sys_siglist[sig]); +#else +#if (__sun__ && __svr4__) + printf("Caught %s... Exiting.\n", _sys_siglist[sig]); +#else + printf("Caught signal %d... Exiting.\n", sig); +#endif +#endif + exit (0); +} + +static void reap_children (void) +{ + int i, flag; + + /* at this point we don't know which child died */ + for (i = 0; i < PidListLen; i++) + { + if (PidList[i]) + { + /* try to reap child "i" */ + flag = 0; + while (waitpid (PidList[i], NULL, WNOHANG) > 0) + flag = 1; + /* remove child from list if reaped */ + if (flag) + PidList[i] = 0; + /* don't break here */ + } + } +} + +void mutt_add_child_pid (pid_t pid) +{ + int i; + + for (i = 0; i < PidListLen; i++) + { + if (PidList[i] == 0) + { + PidList[i] = pid; + break; + } + } + if (i >= PidListLen) + { + /* quite a few children around... */ + safe_realloc ((void **) &PidList, (PidListLen += 2) * sizeof (pid_t)); + PidList[i++] = pid; + for (; i < PidListLen; i++) + PidList[i] = 0; + } +} + +RETSIGTYPE sighandler (int sig) +{ + switch (sig) + { + case SIGTSTP: /* user requested a suspend */ + if(!option(OPTSUSPEND)) + break; + IsEndwin = isendwin (); + curs_set (1); + if (!IsEndwin) + endwin (); + kill (0, SIGSTOP); + + case SIGCONT: + if (!IsEndwin) + refresh (); + mutt_curs_set (-1); + break; + +#if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM) + case SIGWINCH: + Signals |= S_SIGWINCH; + break; +#endif + case SIGINT: + Signals |= S_INTERRUPT; + break; + + case SIGCHLD: + reap_children (); + break; + } +} + +#ifdef USE_SLANG_CURSES +int mutt_intr_hook (void) +{ + return (-1); +} +#endif /* USE_SLANG_CURSES */ + +void mutt_signal_init (void) +{ + struct sigaction act; + + memset (&act, 0, sizeof (struct sigaction)); + + act.sa_handler = SIG_IGN; + sigaction (SIGPIPE, &act, NULL); + + act.sa_handler = mutt_exit_handler; + sigaction (SIGTERM, &act, NULL); + sigaction (SIGHUP, &act, NULL); + + act.sa_handler = sighandler; +#ifdef SA_INTERRUPT + /* POSIX.1 uses SA_RESTART, but SunOS 4.x uses this instead */ + act.sa_flags = SA_INTERRUPT; +#endif + sigaction (SIGCONT, &act, NULL); + sigaction (SIGINT, &act, NULL); + + /* SIGTSTP is the one signal in which we want to restart a system call if it + * was interrupted in progress. This is especially important if we are in + * the middle of a system() call, like if the user is editing a message. + * Otherwise, the system() will exit when SIGCONT is received and Mutt will + * resume even though the subprocess may not be finished. + */ +#ifdef SA_RESTART + act.sa_flags = SA_RESTART; +#else + act.sa_flags = 0; +#endif + sigaction (SIGTSTP, &act, NULL); + +#if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM) + sigaction (SIGWINCH, &act, NULL); +#endif + + /* we don't want to mess with stopped children */ + act.sa_flags |= SA_NOCLDSTOP; + sigaction (SIGCHLD, &act, NULL); + +#ifdef USE_SLANG_CURSES + /* This bit of code is required because of the implementation of + * SLcurses_wgetch(). If a signal is received (like SIGWINCH) when we + * are in blocking mode, SLsys_getkey() will not return an error unless + * a handler function is defined and it returns -1. This is needed so + * that if the user resizes the screen while at a prompt, it will just + * abort and go back to the main-menu. + */ + SLang_getkey_intr_hook = mutt_intr_hook; +#endif +} + +/* signals which are important to block while doing critical ops */ +void mutt_block_signals (void) +{ + if (!option (OPTSIGNALSBLOCKED)) + { + sigemptyset (&Sigset); + sigaddset (&Sigset, SIGINT); + sigaddset (&Sigset, SIGWINCH); + sigaddset (&Sigset, SIGHUP); + sigaddset (&Sigset, SIGTERM); + sigaddset (&Sigset, SIGTSTP); + sigprocmask (SIG_BLOCK, &Sigset, 0); + set_option (OPTSIGNALSBLOCKED); + } +} + +/* restore the previous signal mask */ +void mutt_unblock_signals (void) +{ + if (option (OPTSIGNALSBLOCKED)) + { + sigprocmask (SIG_UNBLOCK, &Sigset, 0); + unset_option (OPTSIGNALSBLOCKED); + } +} + +void mutt_block_signals_system (void) +{ + struct sigaction sa; + + if (! option (OPTSIGNALSBLOCKED)) + { + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigaction (SIGINT, &sa, NULL); + sigaction (SIGQUIT, &sa, NULL); + sigemptyset (&Sigset); + sigaddset (&Sigset, SIGCHLD); + sigprocmask (SIG_BLOCK, &Sigset, 0); + set_option (OPTSIGNALSBLOCKED); + } +} + +void mutt_unblock_signals_system (int catch) +{ + struct sigaction sa; + + if (option (OPTSIGNALSBLOCKED)) + { + sa.sa_flags = 0; + sa.sa_handler = mutt_exit_handler; + sigaction (SIGQUIT, &sa, NULL); + if (catch) + sa.sa_handler = sighandler; + sigaction (SIGINT, &sa, NULL); + sigprocmask (SIG_UNBLOCK, &Sigset, NULL); + unset_option (OPTSIGNALSBLOCKED); + } +} diff --git a/snprintf.c b/snprintf.c new file mode 100644 index 00000000..c03aa878 --- /dev/null +++ b/snprintf.c @@ -0,0 +1,787 @@ +/************************************************************** + * Original: + * Patrick Powell Tue Apr 11 09:48:21 PDT 1995 + * A bombproof version of doprnt (dopr) included. + * Sigh. This sort of thing is always nasty do deal with. Note that + * the version here does not include floating point... + * + * snprintf() is used instead of sprintf() as it does limit checks + * for string length. This covers a nasty loophole. + * + * The other functions are there to prevent NULL pointers from + * causing nast effects. + * + * More Recently: + * Brandon Long <blong@fiction.net> 9/15/96 for mutt 0.43 + * This was ugly. It is still ugly. I opted out of floating point + * numbers, but the formatter understands just about everything + * from the normal C string format, at least as far as I can tell from + * the Solaris 2.5 printf(3S) man page. + * + * Brandon Long <blong@fiction.net> 10/22/97 for mutt 0.87.1 + * Ok, added some minimal floating point support, which means this + * probably requires libm on most operating systems. Don't yet + * support the exponent (e,E) and sigfig (g,G). Also, fmtint() + * was pretty badly broken, it just wasn't being exercised in ways + * which showed it, so that's been fixed. Also, formated the code + * to mutt conventions, and removed dead code left over from the + * original. Also, there is now a builtin-test, just compile with: + * gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm + * and run snprintf for results. + * + * Thomas Roessler <roessler@guug.de> 01/27/98 for mutt 0.89i + * The PGP code was using unsigned hexadecimal formats. + * Unfortunately, unsigned formats simply didn't work. + * + * Michael Elkins <me@cs.hmc.edu> 03/05/98 for mutt 0.90.8 + * The original code assumed that both snprintf() and vsnprintf() were + * missing. Some systems only have snprintf() but not vsnprintf(), so + * the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF. + * + **************************************************************/ + +#include "config.h" + +#if !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF) + +#include <ctype.h> +#include <sys/types.h> + +/* Define this as a fall through, HAVE_STDARG_H is probably already set */ + +#define HAVE_VARARGS_H + +/* varargs declarations: */ + +#if defined(HAVE_STDARG_H) +# include <stdarg.h> +# define HAVE_STDARGS /* let's hope that works everywhere (mj) */ +# define VA_LOCAL_DECL va_list ap +# define VA_START(f) va_start(ap, f) +# define VA_SHIFT(v,t) ; /* no-op for ANSI */ +# define VA_END va_end(ap) +#else +# if defined(HAVE_VARARGS_H) +# include <varargs.h> +# undef HAVE_STDARGS +# define VA_LOCAL_DECL va_list ap +# define VA_START(f) va_start(ap) /* f is ignored! */ +# define VA_SHIFT(v,t) v = va_arg(ap,t) +# define VA_END va_end(ap) +# else +/*XX ** NO VARARGS ** XX*/ +# endif +#endif + +/*int snprintf (char *str, size_t count, const char *fmt, ...);*/ +/*int vsnprintf (char *str, size_t count, const char *fmt, va_list arg);*/ + +static void dopr (char *buffer, size_t maxlen, const char *format, + va_list args); +static void fmtstr (char *buffer, size_t *currlen, size_t maxlen, + char *value, int flags, int min, int max); +static void fmtint (char *buffer, size_t *currlen, size_t maxlen, + long value, int base, int min, int max, int flags); +static void fmtfp (char *buffer, size_t *currlen, size_t maxlen, + long double fvalue, int min, int max, int flags); +static void dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c ); + +/* + * dopr(): poor man's version of doprintf + */ + +/* format read states */ +#define DP_S_DEFAULT 0 +#define DP_S_FLAGS 1 +#define DP_S_MIN 2 +#define DP_S_DOT 3 +#define DP_S_MAX 4 +#define DP_S_MOD 5 +#define DP_S_CONV 6 +#define DP_S_DONE 7 + +/* format flags - Bits */ +#define DP_F_MINUS (1 << 0) +#define DP_F_PLUS (1 << 1) +#define DP_F_SPACE (1 << 2) +#define DP_F_NUM (1 << 3) +#define DP_F_ZERO (1 << 4) +#define DP_F_UP (1 << 5) +#define DP_F_UNSIGNED (1 << 6) + +/* Conversion Flags */ +#define DP_C_SHORT 1 +#define DP_C_LONG 2 +#define DP_C_LDOUBLE 3 + +#define char_to_int(p) (p - '0') +#define MAX(p,q) ((p >= q) ? p : q) + +static void dopr (char *buffer, size_t maxlen, const char *format, va_list args) +{ + char ch; + long value; + long double fvalue; + char *strvalue; + int min; + int max; + int state; + int flags; + int cflags; + size_t currlen; + + state = DP_S_DEFAULT; + currlen = flags = cflags = min = 0; + max = -1; + ch = *format++; + + while (state != DP_S_DONE) + { + if ((ch == '\0') || (currlen >= maxlen)) + state = DP_S_DONE; + + switch(state) + { + case DP_S_DEFAULT: + if (ch == '%') + state = DP_S_FLAGS; + else + dopr_outch (buffer, &currlen, maxlen, ch); + ch = *format++; + break; + case DP_S_FLAGS: + switch (ch) + { + case '-': + flags |= DP_F_MINUS; + ch = *format++; + break; + case '+': + flags |= DP_F_PLUS; + ch = *format++; + break; + case ' ': + flags |= DP_F_SPACE; + ch = *format++; + break; + case '#': + flags |= DP_F_NUM; + ch = *format++; + break; + case '0': + flags |= DP_F_ZERO; + ch = *format++; + break; + default: + state = DP_S_MIN; + break; + } + break; + case DP_S_MIN: + if (isdigit(ch)) + { + min = 10*min + char_to_int (ch); + ch = *format++; + } + else if (ch == '*') + { + min = va_arg (args, int); + ch = *format++; + state = DP_S_DOT; + } + else + state = DP_S_DOT; + break; + case DP_S_DOT: + if (ch == '.') + { + state = DP_S_MAX; + ch = *format++; + } + else + state = DP_S_MOD; + break; + case DP_S_MAX: + if (isdigit(ch)) + { + if (max < 0) + max = 0; + max = 10*max + char_to_int (ch); + ch = *format++; + } + else if (ch == '*') + { + max = va_arg (args, int); + ch = *format++; + state = DP_S_MOD; + } + else + state = DP_S_MOD; + break; + case DP_S_MOD: + /* Currently, we don't support Long Long, bummer */ + switch (ch) + { + case 'h': + cflags = DP_C_SHORT; + ch = *format++; + break; + case 'l': + cflags = DP_C_LONG; + ch = *format++; + break; + case 'L': + cflags = DP_C_LDOUBLE; + ch = *format++; + break; + default: + break; + } + state = DP_S_CONV; + break; + case DP_S_CONV: + switch (ch) + { + case 'd': + case 'i': + if (cflags == DP_C_SHORT) + value = va_arg (args, short int); + else if (cflags == DP_C_LONG) + value = va_arg (args, long int); + else + value = va_arg (args, int); + fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags); + break; + case 'o': + flags |= DP_F_UNSIGNED; + if (cflags == DP_C_SHORT) + value = va_arg (args, unsigned short int); + else if (cflags == DP_C_LONG) + value = va_arg (args, unsigned long int); + else + value = va_arg (args, unsigned int); + fmtint (buffer, &currlen, maxlen, value, 8, min, max, flags); + break; + case 'u': + flags |= DP_F_UNSIGNED; + if (cflags == DP_C_SHORT) + value = va_arg (args, unsigned short int); + else if (cflags == DP_C_LONG) + value = va_arg (args, unsigned long int); + else + value = va_arg (args, unsigned int); + fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags); + break; + case 'X': + flags |= DP_F_UP; + case 'x': + flags |= DP_F_UNSIGNED; + if (cflags == DP_C_SHORT) + value = va_arg (args, unsigned short int); + else if (cflags == DP_C_LONG) + value = va_arg (args, unsigned long int); + else + value = va_arg (args, unsigned int); + fmtint (buffer, &currlen, maxlen, value, 16, min, max, flags); + break; + case 'f': + if (cflags == DP_C_LDOUBLE) + fvalue = va_arg (args, long double); + else + fvalue = va_arg (args, double); + /* um, floating point? */ + fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags); + break; + case 'E': + flags |= DP_F_UP; + case 'e': + if (cflags == DP_C_LDOUBLE) + fvalue = va_arg (args, long double); + else + fvalue = va_arg (args, double); + break; + case 'G': + flags |= DP_F_UP; + case 'g': + if (cflags == DP_C_LDOUBLE) + fvalue = va_arg (args, long double); + else + fvalue = va_arg (args, double); + break; + case 'c': + dopr_outch (buffer, &currlen, maxlen, va_arg (args, int)); + break; + case 's': + strvalue = va_arg (args, char *); + if (max < 0) + max = maxlen; /* ie, no max */ + fmtstr (buffer, &currlen, maxlen, strvalue, flags, min, max); + break; + case 'p': + strvalue = va_arg (args, void *); + fmtint (buffer, &currlen, maxlen, (long) strvalue, 16, min, max, flags); + break; + case 'n': + if (cflags == DP_C_SHORT) + { + short int *num; + num = va_arg (args, short int *); + *num = currlen; + } + else if (cflags == DP_C_LONG) + { + long int *num; + num = va_arg (args, long int *); + *num = currlen; + } + else + { + int *num; + num = va_arg (args, int *); + *num = currlen; + } + break; + case '%': + dopr_outch (buffer, &currlen, maxlen, ch); + break; + case 'w': + /* not supported yet, treat as next char */ + ch = *format++; + break; + default: + /* Unknown, skip */ + break; + } + ch = *format++; + state = DP_S_DEFAULT; + flags = cflags = min = 0; + max = -1; + break; + case DP_S_DONE: + break; + default: + /* hmm? */ + break; /* some picky compilers need this */ + } + } + if (currlen < maxlen - 1) + buffer[currlen] = '\0'; + else + buffer[maxlen - 1] = '\0'; +} + +static void fmtstr (char *buffer, size_t *currlen, size_t maxlen, + char *value, int flags, int min, int max) +{ + int padlen, strln; /* amount to pad */ + int cnt = 0; + + if (value == 0) + { + value = "<NULL>"; + } + + for (strln = 0; value[strln]; ++strln); /* strlen */ + padlen = min - strln; + if (padlen < 0) + padlen = 0; + if (flags & DP_F_MINUS) + padlen = -padlen; /* Left Justify */ + + while ((padlen > 0) && (cnt < max)) + { + dopr_outch (buffer, currlen, maxlen, ' '); + --padlen; + ++cnt; + } + while (*value && (cnt < max)) + { + dopr_outch (buffer, currlen, maxlen, *value++); + ++cnt; + } + while ((padlen < 0) && (cnt < max)) + { + dopr_outch (buffer, currlen, maxlen, ' '); + ++padlen; + ++cnt; + } +} + +/* Have to handle DP_F_NUM (ie 0x and 0 alternates) */ + +static void fmtint (char *buffer, size_t *currlen, size_t maxlen, + long value, int base, int min, int max, int flags) +{ + int signvalue = 0; + unsigned long uvalue; + char convert[20]; + int place = 0; + int spadlen = 0; /* amount to space pad */ + int zpadlen = 0; /* amount to zero pad */ + int caps = 0; + + if (max < 0) + max = 0; + + uvalue = value; + + if(!(flags & DP_F_UNSIGNED)) + { + if( value < 0 ) { + signvalue = '-'; + uvalue = -value; + } + else + if (flags & DP_F_PLUS) /* Do a sign (+/i) */ + signvalue = '+'; + else + if (flags & DP_F_SPACE) + signvalue = ' '; + } + + if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */ + + do { + convert[place++] = + (caps? "0123456789ABCDEF":"0123456789abcdef") + [uvalue % (unsigned)base ]; + uvalue = (uvalue / (unsigned)base ); + } while(uvalue && (place < 20)); + if (place == 20) place--; + convert[place] = 0; + + zpadlen = max - place; + spadlen = min - MAX (max, place) - (signvalue ? 1 : 0); + if (zpadlen < 0) zpadlen = 0; + if (spadlen < 0) spadlen = 0; + if (flags & DP_F_ZERO) + { + zpadlen = MAX(zpadlen, spadlen); + spadlen = 0; + } + if (flags & DP_F_MINUS) + spadlen = -spadlen; /* Left Justifty */ + +#ifdef DEBUG_SNPRINTF + dprint (1, (debugfile, "zpad: %d, spad: %d, min: %d, max: %d, place: %d\n", + zpadlen, spadlen, min, max, place)); +#endif + + /* Spaces */ + while (spadlen > 0) + { + dopr_outch (buffer, currlen, maxlen, ' '); + --spadlen; + } + + /* Sign */ + if (signvalue) + dopr_outch (buffer, currlen, maxlen, signvalue); + + /* Zeros */ + if (zpadlen > 0) + { + while (zpadlen > 0) + { + dopr_outch (buffer, currlen, maxlen, '0'); + --zpadlen; + } + } + + /* Digits */ + while (place > 0) + dopr_outch (buffer, currlen, maxlen, convert[--place]); + + /* Left Justified spaces */ + while (spadlen < 0) { + dopr_outch (buffer, currlen, maxlen, ' '); + ++spadlen; + } +} + +static long double abs_val (long double value) +{ + long double result = value; + + if (value < 0) + result = -value; + + return result; +} + +static long double pow10 (int exp) +{ + long double result = 1; + + while (exp) + { + result *= 10; + exp--; + } + + return result; +} + +static long round (long double value) +{ + long intpart; + + intpart = value; + value = value - intpart; + if (value >= 0.5) + intpart++; + + return intpart; +} + +static void fmtfp (char *buffer, size_t *currlen, size_t maxlen, + long double fvalue, int min, int max, int flags) +{ + int signvalue = 0; + long double ufvalue; + char iconvert[20]; + char fconvert[20]; + int iplace = 0; + int fplace = 0; + int padlen = 0; /* amount to pad */ + int zpadlen = 0; + int caps = 0; + long intpart; + long fracpart; + + /* + * AIX manpage says the default is 0, but Solaris says the default + * is 6, and sprintf on AIX defaults to 6 + */ + if (max < 0) + max = 6; + + ufvalue = abs_val (fvalue); + + if (fvalue < 0) + signvalue = '-'; + else + if (flags & DP_F_PLUS) /* Do a sign (+/i) */ + signvalue = '+'; + else + if (flags & DP_F_SPACE) + signvalue = ' '; + +#if 0 + if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */ +#endif + + intpart = ufvalue; + + /* + * Sorry, we only support 9 digits past the decimal because of our + * conversion method + */ + if (max > 9) + max = 9; + + /* We "cheat" by converting the fractional part to integer by + * multiplying by a factor of 10 + */ + fracpart = round ((pow10 (max)) * (ufvalue - intpart)); + + if (fracpart >= pow10 (max)) + { + intpart++; + fracpart -= pow10 (max); + } + +#ifdef DEBUG_SNPRINTF + dprint (1, (debugfile, "fmtfp: %f =? %d.%d\n", fvalue, intpart, fracpart)); +#endif + + /* Convert integer part */ + do { + iconvert[iplace++] = + (caps? "0123456789ABCDEF":"0123456789abcdef")[intpart % 10]; + intpart = (intpart / 10); + } while(intpart && (iplace < 20)); + if (iplace == 20) iplace--; + iconvert[iplace] = 0; + + /* Convert fractional part */ + do { + fconvert[fplace++] = + (caps? "0123456789ABCDEF":"0123456789abcdef")[fracpart % 10]; + fracpart = (fracpart / 10); + } while(fracpart && (fplace < 20)); + if (fplace == 20) fplace--; + fconvert[fplace] = 0; + + /* -1 for decimal point, another -1 if we are printing a sign */ + padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0); + zpadlen = max - fplace; + if (zpadlen < 0) + zpadlen = 0; + if (padlen < 0) + padlen = 0; + if (flags & DP_F_MINUS) + padlen = -padlen; /* Left Justifty */ + + if ((flags & DP_F_ZERO) && (padlen > 0)) + { + if (signvalue) + { + dopr_outch (buffer, currlen, maxlen, signvalue); + --padlen; + signvalue = 0; + } + while (padlen > 0) + { + dopr_outch (buffer, currlen, maxlen, '0'); + --padlen; + } + } + while (padlen > 0) + { + dopr_outch (buffer, currlen, maxlen, ' '); + --padlen; + } + if (signvalue) + dopr_outch (buffer, currlen, maxlen, signvalue); + + while (iplace > 0) + dopr_outch (buffer, currlen, maxlen, iconvert[--iplace]); + + /* + * Decimal point. This should probably use locale to find the correct + * char to print out. + */ + dopr_outch (buffer, currlen, maxlen, '.'); + + while (fplace > 0) + dopr_outch (buffer, currlen, maxlen, fconvert[--fplace]); + + while (zpadlen > 0) + { + dopr_outch (buffer, currlen, maxlen, '0'); + --zpadlen; + } + + while (padlen < 0) + { + dopr_outch (buffer, currlen, maxlen, ' '); + ++padlen; + } +} + +static void dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c) +{ + if (*currlen < maxlen) + buffer[(*currlen)++] = c; +} +#endif /* !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF) */ + +#ifndef HAVE_VSNPRINTF +int vsnprintf (char *str, size_t count, const char *fmt, va_list args) +{ + str[0] = 0; + dopr(str, count, fmt, args); + return(strlen(str)); +} +#endif /* !HAVE_VSNPRINTF */ + +#ifndef HAVE_SNPRINTF +/* VARARGS3 */ +#ifdef HAVE_STDARGS +int snprintf (char *str,size_t count,const char *fmt,...) +#else +int snprintf (va_alist) va_dcl +#endif +{ +#ifndef HAVE_STDARGS + char *str; + size_t count; + char *fmt; +#endif + VA_LOCAL_DECL; + + VA_START (fmt); + VA_SHIFT (str, char *); + VA_SHIFT (count, size_t ); + VA_SHIFT (fmt, char *); + (void) vsnprintf(str, count, fmt, ap); + VA_END; + return(strlen(str)); +} + +#ifdef TEST_SNPRINTF +#ifndef LONG_STRING +#define LONG_STRING 1024 +#endif +int main (void) +{ + char buf1[LONG_STRING]; + char buf2[LONG_STRING]; + char *fp_fmt[] = { + "%-1.5f", + "%1.5f", + "%123.9f", + "%10.5f", + "% 10.5f", + "%+22.9f", + "%+4.9f", + "%01.3f", + "%4f", + "%3.1f", + "%3.2f", + NULL + }; + double fp_nums[] = { -1.5, 134.21, 91340.2, 341.1234, 0203.9, 0.96, 0.996, + 0.9996, 1.996, 4.136, 0}; + char *int_fmt[] = { + "%-1.5d", + "%1.5d", + "%123.9d", + "%5.5d", + "%10.5d", + "% 10.5d", + "%+22.33d", + "%01.3d", + "%4d", + NULL + }; + long int_nums[] = { -1, 134, 91340, 341, 0203, 0}; + int x, y; + int fail = 0; + int num = 0; + + printf ("Testing snprintf format codes against system sprintf...\n"); + + for (x = 0; fp_fmt[x] != NULL ; x++) + for (y = 0; fp_nums[y] != 0 ; y++) + { + snprintf (buf1, sizeof (buf1), fp_fmt[x], fp_nums[y]); + sprintf (buf2, fp_fmt[x], fp_nums[y]); + if (strcmp (buf1, buf2)) + { + printf("snprintf doesn't match Format: %s\n\tsnprintf = %s\n\tsprintf = %s\n", + fp_fmt[x], buf1, buf2); + fail++; + } + num++; + } + + for (x = 0; int_fmt[x] != NULL ; x++) + for (y = 0; int_nums[y] != 0 ; y++) + { + snprintf (buf1, sizeof (buf1), int_fmt[x], int_nums[y]); + sprintf (buf2, int_fmt[x], int_nums[y]); + if (strcmp (buf1, buf2)) + { + printf("snprintf doesn't match Format: %s\n\tsnprintf = %s\n\tsprintf = %s\n", + int_fmt[x], buf1, buf2); + fail++; + } + num++; + } + printf ("%d tests failed out of %d.\n", fail, num); +} +#endif /* SNPRINTF_TEST */ + +#endif /* !HAVE_SNPRINTF */ diff --git a/sort.c b/sort.c new file mode 100644 index 00000000..3ece2f7f --- /dev/null +++ b/sort.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "sort.h" + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> + +#define SORTCODE(x) (Sort & SORT_REVERSE) ? -(x) : x + +/* function to use as discriminator when normal sort method is equal */ +static sort_t *AuxSort = NULL; + +#define AUXSORT(code,a,b) if (!code && AuxSort && !option(OPTAUXSORT)) { \ + set_option(OPTAUXSORT); \ + code = AuxSort(a,b); \ + unset_option(OPTAUXSORT); \ +} + +int compare_score (const void *a, const void *b) +{ + HEADER **pa = (HEADER **) a; + HEADER **pb = (HEADER **) b; + int result = (*pb)->score - (*pa)->score; /* note that this is reverse */ + AUXSORT(result,a,b); + return (SORTCODE (result)); +} + +int compare_size (const void *a, const void *b) +{ + HEADER **pa = (HEADER **) a; + HEADER **pb = (HEADER **) b; + int result = (*pa)->content->length - (*pb)->content->length; + AUXSORT(result,a,b); + return (SORTCODE (result)); +} + +int compare_date_sent (const void *a, const void *b) +{ + HEADER **pa = (HEADER **) a; + HEADER **pb = (HEADER **) b; + int result = (*pa)->date_sent - (*pb)->date_sent; + AUXSORT(result,a,b); + return (SORTCODE (result)); +} + +int compare_subject (const void *a, const void *b) +{ + HEADER **pa = (HEADER **) a; + HEADER **pb = (HEADER **) b; + int rc; + + if (!(*pa)->env->real_subj) + { + if (!(*pb)->env->real_subj) + rc = compare_date_sent (pa, pb); + else + rc = -1; + } + else if (!(*pb)->env->real_subj) + rc = 1; + else + rc = strcasecmp ((*pa)->env->real_subj, (*pb)->env->real_subj); + AUXSORT(rc,a,b); + return (SORTCODE (rc)); +} + +char *mutt_get_name (ADDRESS *a) +{ + ADDRESS *ali; + + if (a) + { + if (option (OPTREVALIAS) && (ali = alias_reverse_lookup (a)) && ali->personal) + return ali->personal; + else if (a->personal) + return a->personal; + else if (a->mailbox) + return (a->mailbox); + } + /* don't return NULL to avoid segfault when printing/comparing */ + return (""); +} + +int compare_to (const void *a, const void *b) +{ + HEADER **ppa = (HEADER **) a; + HEADER **ppb = (HEADER **) b; + char *fa, *fb; + int result; + + fa = mutt_get_name ((*ppa)->env->to); + fb = mutt_get_name ((*ppb)->env->to); + result = strcasecmp (fa, fb); + AUXSORT(result,a,b); + return (SORTCODE (result)); +} + +int compare_from (const void *a, const void *b) +{ + HEADER **ppa = (HEADER **) a; + HEADER **ppb = (HEADER **) b; + char *fa, *fb; + int result; + + fa = mutt_get_name ((*ppa)->env->from); + fb = mutt_get_name ((*ppb)->env->from); + result = strcasecmp (fa, fb); + AUXSORT(result,a,b); + return (SORTCODE (result)); +} + +int compare_date_received (const void *a, const void *b) +{ + HEADER **pa = (HEADER **) a; + HEADER **pb = (HEADER **) b; + int result = (*pa)->received - (*pb)->received; + AUXSORT(result,a,b); + return (SORTCODE (result)); +} + +int compare_order (const void *a, const void *b) +{ + HEADER **ha = (HEADER **) a; + HEADER **hb = (HEADER **) b; + + /* no need to auxsort because you will never have equality here */ + return (SORTCODE ((*ha)->index - (*hb)->index)); +} + +sort_t *mutt_get_sort_func (int method) +{ + switch (method & SORT_MASK) + { + case SORT_RECEIVED: + return (compare_date_received); + case SORT_ORDER: + return (compare_order); + case SORT_DATE: + return (compare_date_sent); + case SORT_SUBJECT: + return (compare_subject); + case SORT_FROM: + return (compare_from); + case SORT_SIZE: + return (compare_size); + case SORT_TO: + return (compare_to); + case SORT_SCORE: + return (compare_score); + default: + return (NULL); + } + /* not reached */ +} + +void mutt_sort_headers (CONTEXT *ctx, int init) +{ + int i; + sort_t *sortfunc; + + unset_option (OPTNEEDRESORT); + + if (!ctx) + return; + + if (!ctx->msgcount) + { + /* this function gets called by mutt_sync_mailbox(), which may have just + * deleted all the messages. the virtual message numbers are not updated + * in that routine, so we must make sure to zero the vcount member. + */ + ctx->vcount = 0; + ctx->tree = 0; + return; /* nothing to do! */ + } + + if (!ctx->quiet) + mutt_message ("Sorting mailbox..."); + + /* threads may be bogus, so clear the links */ + if (init) + mutt_clear_threads (ctx); + + if (option (OPTNEEDRESCORE)) + { + for (i = 0; i < ctx->msgcount; i++) + mutt_score_message (ctx->hdrs[i]); + unset_option (OPTNEEDRESCORE); + } + + if ((Sort & SORT_MASK) == SORT_THREADS) + { + AuxSort = NULL; + /* if $sort_aux changed after the mailbox is sorted, then all the + subthreads need to be resorted */ + if (option (OPTSORTSUBTHREADS)) + { + ctx->tree = mutt_sort_subthreads (ctx->tree, mutt_get_sort_func (SortAux)); + unset_option (OPTSORTSUBTHREADS); + } + mutt_sort_threads (ctx, init); + } + else if ((sortfunc = mutt_get_sort_func (Sort)) == NULL || + (AuxSort = mutt_get_sort_func (SortAux)) == NULL) + { + mutt_error ("Could not find sorting function! [report this bug]"); + sleep (1); + return; + } + else + qsort ((void *) ctx->hdrs, ctx->msgcount, sizeof (HEADER *), sortfunc); + + /* the threading function find_reference() needs to know how the mailbox + * is currently sorted in memory in order to speed things up a bit + */ + ctx->revsort = (Sort & SORT_REVERSE) ? 1 : 0; + + /* adjust the virtual message numbers */ + ctx->vcount = 0; + for (i = 0; i < ctx->msgcount; i++) + { + if (ctx->hdrs[i]->virtual != -1) + { + ctx->hdrs[i]->virtual = ctx->vcount; + ctx->v2r[ctx->vcount] = i; + ctx->vcount++; + } + ctx->hdrs[i]->msgno = i; + } + + mutt_cache_index_colors(ctx); + + if (!ctx->quiet) + mutt_clear_error (); +} diff --git a/sort.h b/sort.h new file mode 100644 index 00000000..75500249 --- /dev/null +++ b/sort.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mapping.h" + +#define SORT_DATE 1 /* the date the mail was sent. */ +#define SORT_SIZE 2 +#define SORT_SUBJECT 3 +#define SORT_FROM 4 +#define SORT_ORDER 5 /* the order the messages appear in the mailbox. */ +#define SORT_THREADS 6 +#define SORT_RECEIVED 7 /* when the message were delivered locally */ +#define SORT_TO 8 +#define SORT_SCORE 9 +#define SORT_ALIAS 10 +#define SORT_ADDRESS 11 +#define SORT_MASK 0xf +#define SORT_REVERSE (1<<4) +#define SORT_LAST (1<<5) + +typedef int sort_t (const void *, const void *); +sort_t *mutt_get_sort_func (int); + +void mutt_clear_threads (CONTEXT *); +void mutt_sort_headers (CONTEXT *, int); +void mutt_sort_threads (CONTEXT *, int); +int mutt_select_sort (int); +HEADER *mutt_sort_subthreads (HEADER *, sort_t *); + +WHERE short BrowserSort INITVAL (SORT_SUBJECT); +WHERE short Sort INITVAL (SORT_DATE); +WHERE short SortAux INITVAL (SORT_DATE); /* auxiallary sorting method */ +WHERE short SortAlias INITVAL (SORT_ALIAS); + +extern const struct mapping_t SortMethods[]; diff --git a/status.c b/status.c new file mode 100644 index 00000000..dd61d757 --- /dev/null +++ b/status.c @@ -0,0 +1,276 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mutt_menu.h" +#include "mutt_curses.h" +#include "sort.h" + +#include <string.h> +#include <ctype.h> +#include <unistd.h> + +static char *get_sort_str (char *buf, size_t buflen, int method) +{ + snprintf (buf, buflen, "%s%s%s", + (method & SORT_REVERSE) ? "reverse-" : "", + (method & SORT_LAST) ? "last-" : "", + mutt_getnamebyvalue (method & SORT_MASK, SortMethods)); + return buf; +} + +/* %b = number of incoming folders with unread messages [option] + * %d = number of deleted messages [option] + * %f = full mailbox path + * %F = number of flagged messages [option] + * %h = hostname + * %l = length of mailbox (in bytes) [option] + * %m = total number of messages [option] + * %M = number of messages shown (virutal message count when limiting) [option] + * %n = number of new messages [option] + * %p = number of postponed messages [option] + * %P = percent of way through index + * %r = readonly/wontwrite/changed flag + * %s = current sorting method ($sort) + * %S = current aux sorting method ($sort_aux) + * %t = # of tagged messages [option] + * %v = Mutt version + * %V = currently active limit pattern [option] */ +static const char * +status_format_str (char *buf, size_t buflen, char op, const char *src, + const char *prefix, const char *ifstring, + const char *elsestring, + unsigned long data, format_flag flags) +{ + char fmt[SHORT_STRING], tmp[SHORT_STRING], *cp; + int count, optional = (flags & M_FORMAT_OPTIONAL); + MUTTMENU *menu = (MUTTMENU *) data; + + *buf = 0; + switch (op) + { + case 'b': + if (!optional) + { + snprintf (fmt, sizeof (fmt), "%%%sd", prefix); + snprintf (buf, buflen, fmt, mutt_buffy_check (0)); + } + else if (!mutt_buffy_check (0)) + optional = 0; + break; + + case 'd': + if (!optional) + { + snprintf (fmt, sizeof (fmt), "%%%sd", prefix); + snprintf (buf, buflen, fmt, Context ? Context->deleted : 0); + } + else if (!Context || !Context->deleted) + optional = 0; + break; + + case 'h': + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + snprintf (buf, buflen, fmt, Hostname); + break; + + case 'f': + snprintf (fmt, sizeof(fmt), "%%%ss", prefix); + if (Context && Context->path) + { + strfcpy (tmp, Context->path, sizeof (tmp)); + mutt_pretty_mailbox (tmp); + } + else + strfcpy (tmp, "(no mailbox)", sizeof (tmp)); + snprintf (buf, buflen, fmt, tmp); + break; + + case 'F': + if (!optional) + { + snprintf (fmt, sizeof (fmt), "%%%sd", prefix); + snprintf (buf, buflen, fmt, Context ? Context->flagged : 0); + } + else if (!Context || !Context->flagged) + optional = 0; + break; + + case 'l': + if (!optional) + { + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + mutt_pretty_size (tmp, sizeof (tmp), Context ? Context->size : 0); + snprintf (buf, buflen, fmt, tmp); + } + else if (!Context || !Context->size) + optional = 0; + break; + + case 'L': + if (!optional) + { + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + mutt_pretty_size (tmp, sizeof (tmp), Context ? Context->vsize: 0); + snprintf (buf, buflen, fmt, tmp); + } + else if (!Context || !Context->pattern) + optional = 0; + break; + + case 'm': + if (!optional) + { + snprintf (fmt, sizeof (fmt), "%%%sd", prefix); + snprintf (buf, buflen, fmt, Context ? Context->msgcount : 0); + } + else if (!Context || !Context->msgcount) + optional = 0; + break; + + case 'M': + if (!optional) + { + snprintf (fmt, sizeof(fmt), "%%%sd", prefix); + snprintf (buf, buflen, fmt, Context ? Context->vcount : 0); + } + else if (!Context || !Context->pattern) + optional = 0; + break; + + case 'n': + if (!optional) + { + snprintf (fmt, sizeof (fmt), "%%%sd", prefix); + snprintf (buf, buflen, fmt, Context ? Context->new : 0); + } + else if (!Context || !Context->new) + optional = 0; + break; + + case 'o': + if (!optional) + { + snprintf (fmt, sizeof (fmt), "%%%sd", prefix); + snprintf (buf, buflen, fmt, Context ? Context->unread - Context->new : 0); + } + else if (!Context || !(Context->unread - Context->new)) + optional = 0; + break; + + case 'p': + count = mutt_num_postponed (); + if (!optional) + { + snprintf (fmt, sizeof (fmt), "%%%sd", prefix); + snprintf (buf, buflen, fmt, count); + } + else if (!count) + optional = 0; + break; + + case 'P': + if (menu->top + menu->pagelen >= menu->max) + cp = menu->top ? "end" : "all"; + else + { + count = (100 * (menu->top + menu->pagelen)) / menu->max; + snprintf (tmp, sizeof (tmp), "%d%%", count); + cp = tmp; + } + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + snprintf (buf, buflen, fmt, cp); + break; + + case 'r': + if (Context) + buf[0] = (Context->readonly || Context->dontwrite) ? StChars[2] : + (Context->changed || Context->deleted) ? StChars[1] : StChars[0]; + else + buf[0] = StChars[0]; + buf[1] = 0; + break; + + case 's': + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + snprintf (buf, buflen, fmt, + get_sort_str (tmp, sizeof (tmp), Sort)); + break; + + case 'S': + snprintf (fmt, sizeof (fmt), "%%%ss", prefix); + snprintf (buf, buflen, fmt, + get_sort_str (tmp, sizeof (tmp), SortAux)); + break; + + case 't': + if (!optional) + { + snprintf (fmt, sizeof (fmt), "%%%sd", prefix); + snprintf (buf, buflen, fmt, Context ? Context->tagged : 0); + } + else if (!Context || !Context->tagged) + optional = 0; + break; + + case 'u': + if (!optional) + { + snprintf (fmt, sizeof (fmt), "%%%sd", prefix); + snprintf (buf, buflen, fmt, Context ? Context->unread : 0); + } + else if (!Context || !Context->unread) + optional = 0; + break; + + case 'v': + snprintf (fmt, sizeof (fmt), "Mutt %%s"); + snprintf (buf, buflen, fmt, VERSION); + break; + + case 'V': + if (!optional) + { + snprintf (fmt, sizeof(fmt), "%%%ss", prefix); + snprintf (buf, buflen, fmt, Context ? Context->pattern : 0); + } + else if (!Context || !Context->pattern) + optional = 0; + break; + + case 0: + *buf = 0; + return (src); + + default: + snprintf (buf, buflen, "%%%s%c", prefix, op); + break; + } + + if (optional) + menu_status_line (buf, buflen, menu, ifstring); + else if (flags & M_FORMAT_OPTIONAL) + menu_status_line (buf, buflen, menu, elsestring); + + return (src); +} + +void menu_status_line (char *buf, size_t buflen, MUTTMENU *menu, const char *p) +{ + mutt_FormatString (buf, buflen, p, status_format_str, (unsigned long) menu, 0); +} diff --git a/strcasecmp.c b/strcasecmp.c new file mode 100644 index 00000000..7578d0c7 --- /dev/null +++ b/strcasecmp.c @@ -0,0 +1,37 @@ +#include <ctype.h> +#include <sys/types.h> + +/* UnixWare doesn't have these functions in its standard C library */ + +int strncasecmp (char *s1, char *s2, size_t n) +{ + register int c1, c2, l = 0; + + while (*s1 && *s2 && l < n) + { + c1 = tolower (*s1); + c2 = tolower (*s2); + if (c1 != c2) + return (c1 - c2); + s1++; + s2++; + l++; + } + return (int) (0); +} + +int strcasecmp (char *s1, char *s2) +{ + register int c1, c2; + + while (*s1 && *s2) + { + c1 = tolower (*s1); + c2 = tolower (*s2); + if (c1 != c2) + return (c1 - c2); + s1++; + s2++; + } + return (int) (*s1 - *s2); +} diff --git a/system.c b/system.c new file mode 100644 index 00000000..eb9a2aad --- /dev/null +++ b/system.c @@ -0,0 +1,111 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" + +#include <stdlib.h> +#include <signal.h> +#include <string.h> +#include <sys/wait.h> +#include <unistd.h> + +int _mutt_system (const char *cmd, int flags) +{ + int rc = -1; + struct sigaction act; + struct sigaction oldcont; + struct sigaction oldtstp; + struct sigaction oldint; + struct sigaction oldquit; + struct sigaction oldchld; + sigset_t set; + pid_t thepid; + + /* must block SIGCHLD and ignore SIGINT and SIGQUIT */ + + act.sa_handler = SIG_IGN; + act.sa_flags = 0; + sigemptyset (&(act.sa_mask)); + sigaction (SIGINT, &act, &oldint); + sigaction (SIGQUIT, &act, &oldquit); + if (flags & M_DETACH_PROCESS) + { + act.sa_flags = SA_NOCLDSTOP; + sigaction (SIGCHLD, &act, &oldchld); + act.sa_flags = 0; + } + else + { + sigemptyset (&set); + sigaddset (&set, SIGCHLD); + sigprocmask (SIG_BLOCK, &set, NULL); + } + + act.sa_handler = SIG_DFL; + sigaction (SIGTSTP, &act, &oldtstp); + sigaction (SIGCONT, &act, &oldcont); + + if ((thepid = fork ()) == 0) + { + /* reset signals for the child */ + sigaction (SIGINT, &act, NULL); + sigaction (SIGQUIT, &act, NULL); + + if (flags & M_DETACH_PROCESS) + { + setsid (); + switch (fork ()) + { + case 0: + sigaction (SIGCHLD, &act, NULL); + break; + + case -1: + _exit (127); + + default: + _exit (0); + } + } + else + sigprocmask (SIG_UNBLOCK, &set, NULL); + + execl (EXECSHELL, "sh", "-c", cmd, NULL); + _exit (127); /* execl error */ + } + else if (thepid != -1) + { + /* wait for the child process to finish */ + waitpid (thepid, &rc, 0); + } + + /* reset SIGINT, SIGQUIT and SIGCHLD */ + sigaction (SIGINT, &oldint, NULL); + sigaction (SIGQUIT, &oldquit, NULL); + if (flags & M_DETACH_PROCESS) + sigaction (SIGCHLD, &oldchld, NULL); + else + sigprocmask (SIG_UNBLOCK, &set, NULL); + + sigaction (SIGCONT, &oldcont, NULL); + sigaction (SIGTSTP, &oldtstp, NULL); + + rc = WIFEXITED (rc) ? WEXITSTATUS (rc) : -1; + + return (rc); +} diff --git a/testmsg b/testmsg new file mode 100644 index 00000000..87ec8f6e --- /dev/null +++ b/testmsg @@ -0,0 +1,45 @@ +From michael Thu Apr 2 17:51:50 PST 1998 +From: michael +To: michael +Subject: complex multipart +Mime-Version: 1.0 +Content-Type: multipart/mixed; boundary=foo + +--foo +Content-Type text/plain + +This is part one. + +--foo +Content-Type text/plain + +This is part two. + +--foo +Content-Type text/plain + +This is part three. + +--foo +Content-Type: message/rfc822 + +From: bob +To: joe +Subject: nested message +Mime-Version: 1.0 +Content-Type: multipart/mixed; boundary=bar + +--bar +Content-Type text/plain + +This is subpart one. + +--bar +Content-Type text/plain + +This is subpart two. + +--bar-- + +--foo-- + diff --git a/thread.c b/thread.c new file mode 100644 index 00000000..2c2570a1 --- /dev/null +++ b/thread.c @@ -0,0 +1,625 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * 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 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "sort.h" + +#include <string.h> +#include <ctype.h> + +/* This function makes use of the fact that Mutt stores message references in + reverse order (i.e., last to first). This is optiminal since we would like + to find the most recent message to which "cur" refers itself. */ +static HEADER *find_reference (HEADER *cur, CONTEXT *ctx) +{ + LIST *refs = cur->env->references; + void *ptr; + + for (; refs; refs = refs->next) + if ((ptr = hash_find (ctx->id_hash, refs->data))) + return ptr; + + return NULL; +} + +/* Determines whether to display a message's subject. */ +static int need_display_subject (HEADER *tree) +{ + HEADER *tmp; + + if (tree->subject_changed) + return (1); + for (tmp = tree->prev; tmp; tmp = tmp->prev) + { + if (tmp->virtual >= 0) + { + if (!tmp->subject_changed) + return (0); + else + break; + } + } + for (tmp = tree->parent; tmp; tmp = tmp->parent) + { + if (tmp->virtual >= 0) + return (0); + else if (tmp->subject_changed) + return (1); + } + return (0); +} + +/* determines whether a later sibling or the child of a later + sibling is displayed. */ +int is_next_displayed (HEADER *tree) +{ + int depth = 0; + + if ((tree = tree->next) == NULL) + return (0); + + FOREVER + { + if (tree->virtual >= 0) + return (1); + + if (tree->child) + { + tree = tree->child; + depth++; + } + else + { + while (!tree->next && depth > 0) + { + tree = tree->parent; + depth--; + } + if ((tree = tree->next) == NULL) + break; + } + } + return (0); +} + + +/* Since the graphics characters have a value >255, I have to resort to + * using escape sequences to pass the information to print_enriched_string(). + * These are the macros M_TREE_* defined in mutt.h. + * + * ncurses should automatically use the default ASCII characters instead of + * graphics chars on terminals which don't support them (see the man page + * for curs_addch). + */ +void mutt_linearize_tree (CONTEXT *ctx, int linearize) +{ + char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL; + char corner = Sort & SORT_REVERSE ? M_TREE_ULCORNER : M_TREE_LLCORNER; + int depth = 0, start_depth = 0, max_depth = 0, max_width = 0; + int nextdisp = 0; + HEADER *tree = ctx->tree; + HEADER **array = ctx->hdrs + (Sort & SORT_REVERSE ? ctx->msgcount - 1 : 0); + + /* A NULL tree should never be passed here, but may occur if there is + a cycle. */ + if (!tree) + return; + + FOREVER + { + if (tree->virtual >= 0) + tree->display_subject = need_display_subject (tree); + + if (depth >= max_depth) + safe_realloc ((void **) &pfx, + (max_depth += 32) * 2 * sizeof (char)); + + if (depth - start_depth >= max_width) + safe_realloc ((void **) &arrow, + (max_width += 16) * 2 * sizeof (char)); + + safe_free ((void **) &tree->tree); + if (!depth) + { + if (tree->virtual >= 0) + tree->tree = safe_strdup (""); + } + else + { + myarrow = arrow + (depth - start_depth - (start_depth ? 0 : 1)) * 2; + nextdisp = is_next_displayed (tree); + + if (depth && start_depth == depth) + myarrow[0] = nextdisp ? M_TREE_LTEE : corner; + else + myarrow[0] = M_TREE_HIDDEN; + myarrow[1] = tree->fake_thread ? M_TREE_STAR : M_TREE_HLINE; + if (tree->virtual >= 0) + { + myarrow[2] = M_TREE_RARROW; + myarrow[3] = 0; + } + + if (tree->virtual >= 0) + { + tree->tree = safe_malloc ((2 + depth * 2) * sizeof (char)); + if (start_depth > 1) + { + strncpy (tree->tree, pfx, (start_depth - 1) * 2); + strfcpy (tree->tree + (start_depth - 1) * 2, + arrow, (2 + depth - start_depth) * 2); + } + else + strfcpy (tree->tree, arrow, 2 + depth * 2); + } + } + + if (linearize) + { + *array = tree; + array += Sort & SORT_REVERSE ? -1 : 1; + } + + if (tree->child) + { + if (depth) + { + mypfx = pfx + (depth - 1) * 2; + mypfx[0] = nextdisp ? M_TREE_VLINE : M_TREE_SPACE; + mypfx[1] = M_TREE_SPACE; + } + depth++; + if (tree->virtual >= 0) + start_depth = depth; + tree = tree->child; + } + else + { + while (!tree->next && tree->parent) + { + if (tree->virtual >= 0) + start_depth = depth; + tree = tree->parent; + if (start_depth == depth) + start_depth--; + depth--; + } + if (tree->virtual >= 0) + start_depth = depth; + if ((tree = tree->next) == NULL) + break; + } + } + + safe_free ((void **) &pfx); + safe_free ((void **) &arrow); +} + +/* inserts `msg' into the list `tree' using an insertion sort. this function + * assumes that `tree' is the first element in the list, and not some + * element in the middle of the list. + */ +static void insert_message (HEADER **tree, HEADER *msg, sort_t *sortFunc) +{ + HEADER *tmp; + + /* NOTE: we do NOT clear the `msg->child' link here because when we do + * the pseudo-threading, we want to preserve any sub-threads. So we clear + * the `msg->child' in the main routine where we know it is safe to do. + */ + + /* if there are no elements in the list, just add it and return */ + if (!*tree) + { + msg->prev = msg->next = NULL; + *tree = msg; + return; + } + + /* check to see if this message belongs at the beginning of the list */ + if (!sortFunc || sortFunc ((void *) &msg, (void *) tree) < 0) + { + (*tree)->prev = msg; + msg->next = *tree; + msg->prev = NULL; + *tree = msg; + return; + } + + /* search for the correct spot in the list to insert */ + for (tmp = *tree; tmp->next; tmp = tmp->next) + if (sortFunc ((void *) &msg, (void *) &tmp->next) < 0) + { + msg->prev = tmp; + msg->next = tmp->next; + tmp->next->prev = msg; + tmp->next = msg; + return; + } + + /* did not insert yet, so add this message to the end of the list */ + tmp->next = msg; + msg->prev = tmp; + msg->next = NULL; +} + +/* returns 1 if `a' is a descendant (child) of thread `b' */ +static int is_descendant (HEADER *a, HEADER *b) +{ + /* find the top parent of the thread */ + while (a->parent) + a = a->parent; + return (a == b); +} + +/* find the best possible match for a parent mesage based upon subject. + if there are multiple matches, the one which was sent the latest, but + before the current message, is used. */ +static HEADER *find_subject (CONTEXT *ctx, HEADER *cur) +{ + struct hash_elem *ptr; + HEADER *tmp, *last = NULL; + ENVELOPE *env = cur->env; + int hash; + + if (env->real_subj && + ((env->real_subj != env->subject) || (!option (OPTSORTRE)))) + { + hash = hash_string ((unsigned char *) env->real_subj, ctx->subj_hash->nelem); + for (ptr = ctx->subj_hash->table[hash]; ptr; ptr = ptr->next) + { + tmp = ptr->data; + if (tmp != cur && /* don't match the same message */ + !tmp->fake_thread && /* don't match pseudo threads */ + tmp->subject_changed && /* only match interesting replies */ + !is_descendant (tmp, cur) && /* don't match in the same thread */ + cur->date_sent >= tmp->date_sent && + (!last || (last->date_sent <= tmp->date_sent)) && + tmp->env->real_subj && + strcmp (env->real_subj, tmp->env->real_subj) == 0) + { + last = tmp; /* best match so far */ + } + } + } + return last; +} + +static void unlink_message (HEADER **top, HEADER *cur) +{ + if (cur->prev) + { + cur->prev->next = cur->next; + + if (cur->next) + cur->next->prev = cur->prev; + } + else + { + if (cur->next) + cur->next->prev = NULL; + *top = cur->next; + } +} + +static void pseudo_threads (CONTEXT *ctx, sort_t *sortFunc) +{ + HEADER *tree = ctx->tree; + HEADER *top = tree, *cur, *tmp, *curchild, *nextchild; + + while (tree) + { + cur = tree; + tree = tree->next; + if ((tmp = find_subject (ctx, cur)) != NULL) + { + /* detach this message from it's current location */ + unlink_message (&top, cur); + + cur->subject_changed = 0; + cur->fake_thread = 1; + cur->parent = tmp; + insert_message (&tmp->child, cur, sortFunc); + + /* if the message we're attaching has pseudo-children, they + need to be attached to its parent, so move them up a level. */ + for (curchild = cur->child; curchild; ) + { + nextchild = curchild->next; + if (curchild->fake_thread) + { + /* detach this message from its current location */ + unlink_message (&cur->child, curchild); + curchild->parent = tmp; + insert_message (&tmp->child, curchild, sortFunc); + } + curchild = nextchild; + } + } + } + ctx->tree = top; +} + +static HEADER *sort_last (HEADER *top) +{ + HEADER *tree; + HEADER *tmp; + HEADER *first; + HEADER *last; + HEADER *nextsearch; + sort_t *usefunc; + + usefunc = mutt_get_sort_func (Sort); + + tree = top; + FOREVER + { + if (tree->child) + tree = tree->child; + else + { + while (!tree->next) + { + first = last = tree; + nextsearch = tree->prev; + first->prev = NULL; + last->next = NULL; + while ((tree = nextsearch) != NULL) + { + tmp = last; + nextsearch = nextsearch->prev; + while (tmp && (*usefunc) ((void *) &tree->last_sort, + (void *) &tmp->last_sort) < 0) + tmp = tmp->prev; + if (tmp) + { + if ((tree->next = tmp->next) != NULL) + tmp->next->prev = tree; + else + last = tree; + tmp->next = tree; + tree->prev = tmp; + } + else + { + tree->next = first; + first->prev = tree; + first = tree; + tree->prev = NULL; + } + } + if (first->parent) + { + first->parent->child = first; + tree = first->parent; + if (Sort & SORT_REVERSE) + { + if ((*usefunc) ((void *) &tree->last_sort, + (void *) &first->last_sort) > 0) + tree->last_sort = first->last_sort; + } + else + { + if ((*usefunc) ((void *) &tree->last_sort, + (void *) &last->last_sort) < 0) + tree->last_sort = last->last_sort; + } + } + else + { + top = first; + tree = last; + break; + } + } + if ((tree = tree->next) == NULL) + break; + } + } + return top; +} + +static void move_descendants (HEADER **tree, HEADER *cur, sort_t *usefunc) +{ + HEADER *ptr, *tmp = *tree; + + while (tmp) + { + /* only need to look at the last reference */ + if (tmp->env->references && + strcmp (tmp->env->references->data, cur->env->message_id) == 0) + { + /* remove message from current location */ + unlink_message (tree, tmp); + + tmp->parent = cur; + if (cur->env->real_subj && tmp->env->real_subj) + tmp->subject_changed = strcmp (tmp->env->real_subj, cur->env->real_subj) ? 1 : 0; + else + tmp->subject_changed = (cur->env->real_subj || tmp->env->real_subj) ? 1 : 0; + tmp->fake_thread = 0; /* real reference */ + + ptr = tmp; + tmp = tmp->next; + insert_message (&cur->child, ptr, usefunc); + } + else + tmp = tmp->next; + } +} + +void mutt_clear_threads (CONTEXT *ctx) +{ + int i; + + for (i = 0; i < ctx->msgcount; i++) + { + ctx->hdrs[i]->parent = NULL; + ctx->hdrs[i]->next = NULL; + ctx->hdrs[i]->prev = NULL; + ctx->hdrs[i]->child = NULL; + ctx->hdrs[i]->threaded = 0; + ctx->hdrs[i]->fake_thread = 0; + } + ctx->tree = NULL; +} + +HEADER *mutt_sort_subthreads (HEADER *hdr, sort_t *func) +{ + HEADER *top = NULL; + HEADER *t; + + while (hdr) + { + t = hdr; + hdr = hdr->next; + insert_message (&top, t, func); + if (t->child) + t->child = mutt_sort_subthreads (t->child, func); + } + return top; +} + +void mutt_sort_threads (CONTEXT *ctx, int init) +{ + sort_t *usefunc = NULL; + HEADER *tmp, *CUR; + int i, oldsort; + + /* set Sort to the secondary method to support the set sort_aux=reverse-* + * settings. The sorting functions just look at the value of + * SORT_REVERSE + */ + oldsort = Sort; + Sort = SortAux; + + /* get secondary sorting method. we can't have threads, so use the date + * if the user specified it + */ + if ((Sort & SORT_MASK) == SORT_THREADS) + Sort = (Sort & ~SORT_MASK) | SORT_DATE; + + /* if the SORT_LAST bit is set, we save sorting for later */ + if (!(Sort & SORT_LAST)) + usefunc = mutt_get_sort_func (Sort); + + for (i = 0; i < ctx->msgcount; i++) + { + CUR = ctx->hdrs[i]; + + if (CUR->fake_thread) + { + /* Move pseudo threads back to the top level thread so that they can + * can be moved later if they are descendants of messages that were + * just delivered. + */ + CUR->fake_thread = 0; + CUR->subject_changed = 1; + unlink_message (&CUR->parent->child, CUR); + CUR->parent = NULL; + insert_message (&ctx->tree, CUR, usefunc); + } + else if (!CUR->threaded) + { + if ((tmp = find_reference (CUR, ctx)) != NULL) + { + CUR->parent = tmp; + if (CUR->env->real_subj && tmp->env->real_subj) + CUR->subject_changed = strcmp (tmp->env->real_subj, CUR->env->real_subj) ? 1 : 0; + else + CUR->subject_changed = (CUR->env->real_subj || tmp->env->real_subj) ? 1 : 0; + } + else + CUR->subject_changed = 1; + + if (!init) + { + /* Search the children of `tmp' for decendants of `cur'. This is only + * done when the mailbox has already been threaded since we don't have + * to worry about the tree being threaded wrong (because of a missing + * parent) during the initial threading. + */ + if (CUR->env->message_id) + move_descendants (tmp ? &tmp->child : &ctx->tree, CUR, usefunc); + } + insert_message (tmp ? &tmp->child : &ctx->tree, CUR, usefunc); + CUR->threaded = 1; + } + } + + if (!option (OPTSTRICTTHREADS)) + pseudo_threads (ctx, usefunc); + + /* now that the whole tree is put together, we can sort by last-* */ + if (Sort & SORT_LAST) + { + for (i = 0; i < ctx->msgcount; i++) + ctx->hdrs[i]->last_sort = ctx->hdrs[i]; + ctx->tree = sort_last (ctx->tree); + } + + /* restore the oldsort order. */ + Sort = oldsort; + + /* Put the list into an array. If we are reverse sorting, give the + * offset of the last message, and work backwards (tested for and + * done inside the function), so that the threads go backwards. + * This, of course, means the auxillary sort has to go forwards + * because we map it backwards here. + */ + mutt_linearize_tree (ctx, 1); +} + +int _mutt_aside_thread (HEADER *hdr, short dir, short subthreads) +{ + if ((Sort & SORT_MASK) != SORT_THREADS) + { + mutt_error ("Threading is not enabled."); + return (hdr->virtual); + } + + if (!subthreads) + { + while (hdr->parent) + hdr = hdr->parent; + } + else + { + if (dir) + { + while (!hdr->next && hdr->parent) + hdr = hdr->parent; + } + else + { + while (!hdr->prev && hdr->parent) + hdr = hdr->parent; + } + } + + hdr = (dir != 0) ^ ((Sort & SORT_REVERSE) != 0) ? hdr->next : hdr->prev; + if (hdr) + { + if (Sort & SORT_REVERSE) + return (hdr->next ? hdr->next->virtual + 1 : 0); + else + return (hdr->virtual); + } + else + return (-1); +}