# HG changeset patch # User Ben Schmidt # Date 1424896141 -39600 # Node ID b68213ccb9d28b29185cf4d3903d2845b07e1012 # Parent 04d916168efba6736758a9e53fbefef58cc4a7b5 Add README.footers and footer-related resources. diff -r 04d916168efb -r b68213ccb9d2 ChangeLog --- a/ChangeLog Wed Feb 25 18:22:37 2015 +1100 +++ b/ChangeLog Thu Feb 26 07:29:01 2015 +1100 @@ -1,3 +1,4 @@ + o Add README.footers and footer-related resources o Support ESMTP so OpenSMTPD uses 8 bits (Paul Fariello) o Use iconv to convert unknown character sets o Handle unfolded header lines better diff -r 04d916168efb -r b68213ccb9d2 Makefile.am --- a/Makefile.am Wed Feb 25 18:22:37 2015 +1100 +++ b/Makefile.am Thu Feb 26 07:29:01 2015 +1100 @@ -4,7 +4,7 @@ EXTRA_DIST = include VERSION LICENSE UPGRADE FAQ \ TUNABLES README.access README.archives README.listtexts man \ README.exim4 README.sendmail README.security README.qmail \ - README.postfix + README.postfix README.footers CLEANFILES = *~ mlmmj-*.tar.* man1_MANS = man/mlmmj-bounce.1 man/mlmmj-make-ml.1 man/mlmmj-receive.1 \ diff -r 04d916168efb -r b68213ccb9d2 README.footers --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.footers Thu Feb 26 07:29:01 2015 +1100 @@ -0,0 +1,47 @@ +README.footers + +Footers in Mlmmj +================ + +Mlmmj's built-in footer support is very rudimentary. It will work for plain +text emails, and that's about it. It doesn't understand HTML or MIME or +anything like that. + +There are a few solutions to this. They all involve piping incoming mail +through a filter before it reaches Mlmmj. A script to do this, called +mlmmj-amime-receive, is included in amime-receive in the contrib directory. + +It can be used with a number of different pre-processors. One day we also hope +to improve the integration of these external filters with Mlmmj, e.g. so only +list posts are processed. However, the piping solution has worked for a number +of people over the years quite satisfactorily, so this is not a high priority. + +Here are some pre-processors you can use. + +alterMIME +--------- + +The mlmmj-amime-receive script is designed to work with a program called +alterMIME. The script itself (in amime-receive in the contrib directory) +contains links to that software, and instructions. + +Foot Filter +----------- + +alterMIME didn't allow me to reach my particular goals, so I wrote an +alternative called Foot Filter. It is a single source-file C program; the code +and a very simple Makefile can be found in foot_filter in the contrib +directory, along with an altered version of mlmmj-amime-receive, called +mlmmj-recieve-ff. + +Foot Filter will output documentation if you run it without arguments, and +again, instructions for the script that handles the piping are found within it. + +Py-MIME +------- + +A third option is Py-MIME. It was developed for use at The Document Foundation +(LibreOffice) and is included in pymime in the contrib directory. + + + diff -r 04d916168efb -r b68213ccb9d2 README.listtexts --- a/README.listtexts Wed Feb 25 18:22:37 2015 +1100 +++ b/README.listtexts Thu Feb 26 07:29:01 2015 +1100 @@ -1,6 +1,6 @@ README.listtexts -List texts in mlmmj +List texts in Mlmmj =================== List texts are stored in listdir/text. They specify the content of various diff -r 04d916168efb -r b68213ccb9d2 contrib/Makefile.am --- a/contrib/Makefile.am Wed Feb 25 18:22:37 2015 +1100 +++ b/contrib/Makefile.am Thu Feb 26 07:29:01 2015 +1100 @@ -1,4 +1,4 @@ ## Process this file with automake to produce Makefile.in -EXTRA_DIST = web amime-receive +EXTRA_DIST = web amime-receive foot_filter pymime SUBDIRS = receivestrip diff -r 04d916168efb -r b68213ccb9d2 contrib/foot_filter/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/foot_filter/Makefile Thu Feb 26 07:29:01 2015 +1100 @@ -0,0 +1,14 @@ +all: foot_filter +dev: tags splint foot_filter +.PHONY: splint clean clobber +tags: foot_filter.c + ctags --excmd=number '--regex-c=-/\*[[:blank:]]*tag:[[:blank:]]*([[:alnum:]_]+)-\1-' foot_filter.c +splint: + splint +unixlib -exitarg -initallelements foot_filter.c +foot_filter: foot_filter.c + gcc -Wall -g -o foot_filter foot_filter.c -O3 +clean: + -rm tags +clobber: clean + -rm foot_filter + -rm test diff -r 04d916168efb -r b68213ccb9d2 contrib/foot_filter/foot_filter.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/foot_filter/foot_filter.c Thu Feb 26 07:29:01 2015 +1100 @@ -0,0 +1,2685 @@ +/* + +foot_filter.c + +(C) 2010 Ben Schmidt + +This Source Code Form is subject to the terms of the Mozilla Public License +Version 2.0. If a copy of the MPL was not distributed with this file, You can +obtain one at http://mozilla.org/MPL/2.0/. + +*/ + +// Check out the -V option; it outputs this and more +#define FOOT_FILTER_VERSION "foot_filter version 1.2, (C) 2010 Ben Schmidt" + +static const char * USAGE="\n\ +usage: foot_filter [-p plain_footer_file] [-h html_footer_file]\n\ + [{-P|-H} mime_footer_file] [-s]\n\ + foot_filter -V\n\ +\n\ +plain_footer_file, if present, will be appended to mails with plain text\n\ +sections only. Similarly, html_footer_file. If mime_footer_file (either\n\ +plain, -P, or HTML, -H) is given, it will be used when a mail with\n\ +alternative formats is encountered, or if the footer for the relevant\n\ +type of mail is not present; a new MIME section will be added.\n\ +\n\ +-s turns on smart mode which endeavours to remove included/quoted copies of\n\ +the (or a similar) footer by surrounding the footer with patterns it later\n\ +recognises. It also endeavours to strip 'padding' surrounding the old\n\ +footers to make things as clean as possible. This includes whitespace\n\ +(including ' ' and '
'), '>' quoting characters, various pairs of\n\ +HTML tags (p, blockquote, div, span, font; it's naive, it doesn't check\n\ +tags in between are balanced at all, so in '

prefix

suffix

' the\n\ +first and last tags are paired), and even horizontal rules when inside\n\ +paired tags (e.g. use '

footer
'). If the smart strings are\n\ +found in the footer, they won't be added by the program, so you have the\n\ +necessary control to do this.\n\ +\n\ +New footers are added prior to trailing whitespace and a few closing html\n\ +tags (body, html) as well. You almost certainly want to begin your footer\n\ +with an empty line because of this.\n\ +\n\ +Since these alterations, by their very nature, break signed mail,\n\ +signatures are removed while processing. To keep some value from signatures,\n\ +have the MTA verify them and add a header (or even supply an alternative\n\ +footer to this program), and resign them to authenticate they came from the\n\ +mailing list directly after the signature verification was done and recorded.\n\ +Or don't use these kinds of transformations at all.\n\ +\n\ +-V shows the version and exits.\n\ +\n\ +Program is running now. Send EOF or interrupt to stop it. To avoid this usage\n\ +message if wanting to run without arguments, use '--' as an argument.\n\ +\n"; + +/* + +This is a fairly simple program not expecting much extension. As such, some +liberties have been taken and some fun has been had by the author. Correctness +has been prioritised in design, but speed and efficiency have been taken into +consideration and prioritised above readability and modularity and other such +generally recommended programming practices. If making changes, great care +should be taken to understand how and where (everywhere) globals are used +before making them. Don't try to modify the program without understanding how +the whole thing works together or you will get burnt. You have been warned. + +Relevant RFCs: +http://www.ietf.org/rfc/rfc2015.txt +http://www.ietf.org/rfc/rfc3851.txt +http://www.ietf.org/rfc/rfc2045.txt +http://www.ietf.org/rfc/rfc2046.txt +http://www.ietf.org/rfc/rfc822.txt +http://www.ietf.org/rfc/rfc2183.txt + +For program configuration, see the 'constants' section below. + +Also see code comments throughout. + +Future possibilities: + +- Saving copies of original mail in 'semi-temp' files for debugging. + +- Stripping attachments and save them (e.g. in a location that can become a + 'files uploaded' section on a website). Replace them with links to the + website, even. + +- Making the prefixes, suffixes, replacements, padding, guts, pairs, + configurable at runtime. + +- Attaching signed mail, or wrapping in a multipart rather than removing + signatures; wouldn't be hard if always using MIME footers. + +- Following a script to allow various other header transformations (addition, + removal, etc.), or other transformations. + +- Prologues as well as or instead of footers. + +*/ + +/* tag: includes */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* tag: typedefs */ + +// splint has bools, but C doesn't! +#ifndef S_SPLINT_S +typedef int bool; +#define false (0) +#define true (1) +#endif + +// This is mostly to be able to include splint annotations +typedef /*@null@*//*@observer@*/ const char * const_null_string; +typedef /*@null@*/ char * null_string; +typedef /*@null@*//*@owned@*/ char * owned_null_string; +typedef /*@null@*//*@dependent@*/ char * dependent_null_string; + +// 'Callbacks'; they communicate primarily using globals, see below +typedef bool (*callback_t)(); +typedef void (*function_t)(); + +// For fill() +typedef enum { + echo, + encode, + shunt, + discard, + stop, + fail +} when_full_t; + +// Various places +typedef enum { + unencoded, + quoted_printable, + base64 +} encoding_t; + +// For returning multiple characters, and a request to delete backlog +// when decoding +typedef struct { + int r; + int c1; + int c2; + int c3; +} decode_t; + +/* tag: constants */ + +/* tag: header_constants */ + +// How many MIME Content- headers we expect, maximum, in a mail. If we have +// more than that, we won't be able to process MIME so well, but we won't fail +// catastrophically. +#define mime_headers_max 16 + +/* tag: footer_constants */ + +// Stuff for processing the footer's smart removal and (smart or not) +// insertion + +static const char * plain_prefix = "------~----------"; +static const char * plain_suffix = "------~~---------"; +static const char * plain_replacement = "\r\n\r\n"; +static const_null_string plain_tails[] = { + " ","\t","\r","\n", + NULL +}; +static const_null_string plain_padding[] = { + ">"," ","\t","\r","\n", + NULL +}; +static const_null_string plain_guts[] = { + NULL +}; +static const_null_string plain_pairs[] = { + NULL +}; + +static const char * html_prefix = "------~----------"; +static const char * html_suffix = "------~~---------"; +static const char * html_replacement = "\r\n

\r\n"; +static const_null_string html_tails[] = { + "","","","", + " "," "," ","\t","\r","\n", + "
","
","
","
","
","
", + NULL +}; +static const_null_string html_padding[] = { + ">",">", + " "," "," ","\t","\r","\n", + "
","
","
","
","
","
", + NULL +}; +static const_null_string html_guts[] = { + // These are removed in an attempt to make a pair + "
","
","
","
","
","
", + " "," "," ","\t","\r","\n", + "
","
","
","
","
","
", + NULL +}; +static const_null_string html_pairs[] = { + // Closing part (or NULL to mark no more), end of opening part, + // start of opening part, NULL + // The search strategy is fairly naive; if it finds the closing part, + // it checks for the end of the opening part; if it finds that, it + // searches back for the first character of each of the opening part + // variants, and if that character is found and is the beginning of the + // whole variant, it removes the pair. + "

",">","

","

",">","

","

",">","

","
",">","
","
",">","
","
",">","
","
",">","","",">","","",">","","",">","","0,"unexpected commandline argument"); + // Load footers + if (plain_footer_file!=NULL) + load_footer(&plain_footer,&plain_footer_buffer, + plain_footer_file, + smart_footer?plain_prefix:NULL,smart_footer?plain_suffix:NULL); + if (html_footer_file!=NULL) + load_footer(&html_footer,&html_footer_buffer, + html_footer_file, + smart_footer?html_prefix:NULL,smart_footer?html_suffix:NULL); + if (mime_footer_file!=NULL) + load_footer(&mime_footer,&mime_footer_buffer, + mime_footer_file,NULL,NULL); + // Do the job + process_section(true,true,NULL); + // Finish + if (plain_footer_buffer!=NULL) free(plain_footer_buffer); + if (html_footer_buffer!=NULL) free(html_footer_buffer); + if (mime_footer_buffer!=NULL) free(mime_footer_buffer); + exit(EX_OK); +} + +static void load_footer(/*@out@*//*@shared@*/ char ** footer, + /*@reldef@*/ char ** footer_buffer, + char * file, + /*@unique@*/ const_null_string prefix, + /*@unique@*/ const_null_string suffix) { + FILE * f; + int prefixl=0, footerl=0, suffixl=0; + char * ff; + if (prefix!=NULL&&suffix!=NULL) { + prefixl=(int)strlen(prefix); + suffixl=(int)strlen(suffix); + } + f=fopen(file,"r"); + resort_to_errno(f==NULL,"error opening footer file",EX_NOINPUT); + resort_to_errno(fseek(f,0,SEEK_END)!=0, + "error seeking end of footer file",EX_IOERR); + resort_to_errno((footerl=(int)ftell(f))==-1, + "error finding footer length",EX_IOERR); + resort_to_errno(fseek(f,0,SEEK_SET)!=0, + "error seeking in footer file",EX_IOERR); + // prefix, \n, footer, \n, suffix, \0 + *footer_buffer=alloc_or_exit(sizeof(char)*(prefixl+footerl+suffixl+3)); + *footer=*footer_buffer; + *footer+=prefixl+1; + resort_to_errno(fread(*footer,1,(size_t)footerl,f)<(size_t)footerl, + "error reading footer",EX_IOERR); + // We strip off a single trailing newline to keep them from accumulating + // but to allow the user the option of adding them if desired + if ((*footer)[footerl-1]=='\n') --footerl; + (*footer)[footerl]='\0'; + if (prefix==NULL||suffix==NULL) return; + // Put in the prefix and suffix as necessary + ff=strstr(*footer,prefix); + if (ff!=NULL) { + ff=strstr(ff,suffix); + if (ff!=NULL) return; + (*footer)[footerl]='\n'; + ++footerl; + strcpy(*footer+footerl,suffix); + (*footer)[footerl+suffixl]='\0'; + } else { + ff=strstr(*footer,suffix); + if (ff==NULL) { + (*footer)[footerl]='\n'; + ++footerl; + strcpy(*footer+footerl,suffix); + (*footer)[footerl+suffixl]='\0'; + } + *footer-=prefixl+1; + strcpy(*footer,prefix); + (*footer)[prefixl]='\n'; + } +} + +// Should be called with the boundary for the section as lookahead +// in the buffer, but nothing more, and no lookbehind. +static void process_section(bool add_footer, + bool can_reenvelope, /*@null@*/ bool * parent_needs_footer) { + char * external=NULL; + char * internal=NULL; + char * generated=NULL; + bool reenveloping=false; + bool child_needed_footer=false; + bool needs_footer=false; + bool unsigning=false; + if (parent_needs_footer!=NULL) *parent_needs_footer=false; + // The headers must be read, saved and echoed before making any + // recursive calls, as I'm naughty and using globals. + read_boundary(&external); + read_and_save_mime_headers(); + if (mime_bad) { + // If an error, just resort to echoing + echo_buffer(); // Boundary and headers + // End headers with the extra line break + resort_to_errno(putstr("\r\n")==EOF, + "error echoing string",EX_IOERR); + free_saved_mime_headers(); + // Body + echo_to_boundary(external); + free(external); + return; + } + // Headers determining we skip this section + if (is_signature()) { + skip_buffer(); // Boundary and headers + skip_to_boundary(external); + return; + } + // Header processing + if (is_signed()) unsigning=true; + if (unsigning) change_to_mixed(); + if (add_footer&&mime_footer!=NULL&&( + is_alternative()||(is_multipart(NULL)&&!is_mixed())|| + (is_plain()&&plain_footer==NULL)|| + (is_html()&&html_footer==NULL) + )) { + add_footer=false; + if (can_reenvelope) { + reenveloping=true; + remove_mime_headers(); + } else if (parent_needs_footer!=NULL) *parent_needs_footer=true; + } + // Headers + echo_buffer(); // Boundary and possibly modified headers + if (reenveloping) { + generate_boundary(&generated); + output_mime_mixed_headers(generated); + output_prolog(); + output_boundary(generated); + output_saved_mime_headers(); + } + // End the headers with the extra line break + resort_to_errno(putstr("\r\n")==EOF, + "error echoing string",EX_IOERR); + // Body processing + if (is_multipart(&internal)) { + // This branch frees the MIME headers before recursing. + // Don't include the prolog if it used to be signed; + // it usually says something like 'this message is signed' + if (unsigning) { + skip_to_boundary(internal); + resort_to_errno(putstr("\r\n")==EOF, + "error echoing string",EX_IOERR); + } else { + echo_to_boundary(internal); + } + // The recursive call needs these globals + free_saved_mime_headers(); + while (!at_final_boundary(internal)) { + process_section(add_footer,false,&child_needed_footer); + if (child_needed_footer) needs_footer=true; + } + if (needs_footer) output_mime_footer(internal); + free(internal); + echo_to_boundary(external); + } else { + // This branch frees the MIME headers at the end + if (!is_attachment()&&( + (is_plain()&&plain_footer!=NULL)|| + (is_html()&&html_footer!=NULL))) { + // alternatively + // if (!is_attachment()&&( + // (is_plain()&&((add_footer&&plain_footer!=NULL)||smart_footer))|| + // (is_html()&&((add_footer&&html_footer!=NULL)||smart_footer)))) { + if (is_plain()) { + process_text_section(add_footer,plain_footer, + plain_prefix,plain_suffix,plain_replacement, + plain_tails,plain_padding,plain_guts,plain_pairs,external); + } else { + process_text_section(add_footer,html_footer, + html_prefix,html_suffix,html_replacement, + html_tails,html_padding,html_guts,html_pairs,external); + } + } else { + echo_to_boundary(external); + } + free_saved_mime_headers(); + } + // MIME stuff is freed now; take care not to use it. + /*@-branchstate@*/ + if (reenveloping) { + // We ensure generated is not null in another if(reenveloping) + // conditional above + /*@-nullpass@*/ + output_mime_footer(generated); + output_final_boundary(generated); + free(generated); + /*@=nullpass@*/ + } + /*@=branchstate@*/ + free(external); +} + +/* tag: header_functions */ + +static inline void read_and_save_mime_headers() { + /*@-mustfreeonly@*/ + mime_bad=false; + // Mark current end of buffer + buffer_mark=buffer_read; + buffer_marked=true; + for (;;) { + do { + // Extend current header until beginning of next + callback_bool=false; + (void)fill(until_eol,shunt); + if (buffer_filled==buffer_read) { + // We probably hit EOF; just get out, and the whole + // mail will end up echoed out + warning("unexpected end of input"); + break; + } + (void)look(one_char,buffer_read,false); + if (callback_int==(int)' '||callback_int==(int)'\t') { + // Continuation of previous header; read it + read_buffer(); + continue; + } + // Start of new header; don't read it; process the old one + // (from the mark to the end of the lookbehind) + break; + } while (true); + // Process the old header, if there is one + if (buffer_mark0) { + if (*h=='\0') break; + if (*h=='\\') { + ++h; + if (*h=='\0') break; + } + else if (*h=='(') ++levels; + else if (*h==')') --levels; + ++h; + } + if (!delimiting(*h,ext)&&!delimiting(*(hh-1),ext)) { + // Put in some whitespace if something delimiting isn't + // coming and hasn't just been + *hh=' '; + ++hh; + } + continue; + } else if (*h=='"'||*h=='[') { + if (*h=='[') close=']'; + else close='"'; + *hh=*h; + ++h; ++hh; + hhh=hh; + while (*h!='\0'&&*h!=close) { + if (*h=='\\') { + *hh=*h; + ++hh; ++h; + if (*h=='\0') break; + if (*h=='\r'&&*(h+1)=='\n') { + *hh=*h; ++hh; ++h; + *hh=*h; ++hh; ++h; + if (*h=='\0') break; + ++hh; ++h; + continue; + } + } else if (*h==(char)8) { + --hh; ++h; + if (hh'||c=='@'|| + c==','||c==';'||c==':'||c=='\\'||c=='"'|| + c=='.'||c=='['||c==']'|| + (ext&&(c=='/'||c=='='||c=='?'))); +} +static inline void remove_mime_headers() { + int h; + for (h=0;h0, + "internal error: unexpected data in buffer",EX_SOFTWARE); + set_decoding_type(); + encoding=decoding; + decode_and_read_to_boundary_encoding_when_full(boundary); + if (smart_footer&&footer!=NULL) { + // alternatively + // if (smart_footer) { + for (;;) { + prefix_pos=pos_of(prefix,0,buffer_read); + if (prefix_pos==EOF) break; + suffix_pos=pos_of(suffix,prefix_pos,buffer_read); + if (suffix_pos==EOF) break; + for (;;) { + later_prefix_pos= + pos_of(prefix,prefix_pos+prefixl,suffix_pos-prefixl); + if (later_prefix_pos!=EOF) prefix_pos=later_prefix_pos; + else break; + } + suffix_pos+=suffixl; + pad(padding,guts,pairs,&prefix_pos,&suffix_pos); + replacement_starts[replacements_count]=prefix_pos; + replacement_ends[replacements_count]=suffix_pos; + // We may not want the last replacement so replace + // with nothing first + replacement_strings[replacements_count]=NULL; + ++replacements_count; + // We want the last replacement; encode it now before + // doing any more encoding + if (removed_footers) encode_string(replacement); + encode_replacements(); + removed_footers=true; + } + } + if (*boundary!='\0'&&(decoding==quoted_printable||decoding==unencoded)) { + // If we're not using base64 encoding, and we're in multipart, there + // will be a final CRLF that is part of the input but logically part of + // the boundary, not the text. Removing the footer may have already + // removed it, so we need to check if it's here or not. + if (buffer_read>1) { + callback_compare="\r\n"; + (void)look(comparing_head,buffer_read-2,false); + callback_compare=NULL; + if (callback_bool) boundary_newline=true; + } + } + if (add_footer&&footer!=NULL) { + // This will skip past the boundary newline + mark_tail(tails); + if (removed_footers&&buffer_mark==0) { + // The last replacement coincides with where the footer + // is going to go; don't use the replacement text. + removed_footers=false; + } + } + if (removed_footers) encode_string(replacement); + if (add_footer&&footer!=NULL) { + if (buffer_mark0) (void)empty(until_no_buffer); +} +static inline void echo_lookbehind() { + make_replacements(echoing_one_char,echoing_until_start_marked); + if (buffer_read>0) (void)empty(echoing_until_no_lookbehind); +} +static inline void encode_lookbehind() { + make_replacements(encoding_one_char,encoding_until_start_marked); + if (buffer_read>0) (void)empty(encoding_until_no_lookbehind); +} +static inline void encode_replacements() { + make_replacements(encoding_one_char,encoding_until_start_marked); +} +static inline void make_replacements(callback_t one_char, + callback_t start_marked) { + int r, minr=0; + const char * c; + if (buffer_read==0) return; + buffer_marked=false; + while (replacements_count>0) { + for (r=0;r0) (void)empty(start_marked); + c = replacement_strings[minr]; + if (c!=NULL) { + while (*c!='\0') { + buffer_char=(int)(unsigned int)*c; + (void)(*one_char)(); + ++c; + } + } + buffer_marked=true; + buffer_mark=replacement_ends[minr]; + for (r=0;r0) (void)empty(until_start_marked); + for (r=minr;r0) (void)empty(encoding_until_start_marked); +} +static inline void echo_disk_buffer() { + if (disk_buffer_filled>0) (void)empty(echoing_until_no_disk_buffer); +} +static inline void encode_disk_buffer() { + if (disk_buffer_filled>0) (void)empty(encoding_until_no_disk_buffer); +} +static inline void skip_disk_buffer() { + if (disk_buffer_filled>0) (void)empty(until_no_disk_buffer); +} +static inline void read_boundary(/*@out@*/ char ** boundary) { + int l=0; + if (buffer_filled>buffer_read) { + callback_bool=false; + callback_int=0; + resort_to_exit(!look(counting_until_eol,buffer_read,false), + "internal error: missing eol at section boundary",EX_SOFTWARE); + l=callback_int-2; // remove the CRLF, but keep the leading '--' + } + // Leave room to append a trailing '--' for testing final boundary; + // the CRLF will be written in this space by saving_until_eol too. + *boundary = alloc_or_exit(sizeof(char)*(l+3)); + if (buffer_filled>buffer_read) { + callback_bool=false; + callback_save=*boundary; + (void)look(saving_until_eol,buffer_read,false); + callback_save=NULL; + } + (*boundary)[l]='\0'; + if (buffer_filled>buffer_read) { + callback_bool=false; + (void)look(until_eol,buffer_read,true); + } +} +static inline void echo_to_boundary(const char * boundary) { + do { + echo_buffer(); + } while (!process_one_line_checking_boundary( + echoing_n_chars,NULL,until_eol,echo,boundary)); +} +static inline void skip_to_boundary(const char * boundary) { + do { + skip_buffer(); + } while (!process_one_line_checking_boundary( + n_chars,NULL,until_eol,discard,boundary)); +} +static inline void decode_and_read_to_boundary_encoding_when_full( + const char * boundary) { + do { + read_buffer(); + } while (!process_one_line_checking_boundary( + encoding_n_chars,decode_lookahead, + decoding_until_eol,encode,boundary)); + finish_decoding(); // This just sets state, doesn't change data +} +static inline bool process_one_line_checking_boundary(callback_t n_chars, + /*@null@*/ function_t process, callback_t processing, + when_full_t when_full, const char * boundary) { + bool stopped_by_design; + if (feof(stdin)!=0) { + // We're done! Call it a boundary (even if it isn't--we need to + // get out of loops cleanly and tidy up as best we can). + return true; + } + // Empty until enough space for boundary + if (mem_buffer_size-mem_buffer_filled<80) { + callback_int=80-(mem_buffer_size-mem_buffer_filled); + (void)empty(n_chars); + } + callback_bool=false; + stopped_by_design=fill(until_eol,stop); + if (stopped_by_design||feof(stdin)!=0) { + if (buffer_filled-buffer_read==0) { + return *boundary=='\0'; + } + callback_bool=false; + if (*boundary!='\0') { + // Can only be at a boundary without being at EOF if there + // really is a boundary + /*@-temptrans@*/ + callback_compare=boundary; + /*@=temptrans@*/ + (void)look(comparing_head,buffer_read,false); + callback_compare=NULL; + } + if (!callback_bool&&process!=NULL) (*process)(); + return callback_bool; + } else { + // Line is too long to be a boundary, so must be decoded + if (process!=NULL) (*process)(); + callback_bool=false; + (void)fill(processing,when_full); + return false; + } +} + +// Return the position of text whose start may occur in the buffer +// anywhere between from and (just before) to. Use EOF for from to +// go from current location; use EOF for to to read indefinitely; +// EOF is returned if text is not found. +static int pos_of(const char * text,int from,int to) { + int saved_buffer_read; + int pos=EOF; + if (*text=='\0') return from; + saved_buffer_read=buffer_read; + if (from!=EOF) buffer_read=from; + callback_match=(int)(unsigned int)*text; + for (;;) { + if (to!=EOF) { + callback_int=to-buffer_read; + if (!look(n_chars_until_match,buffer_read,true)) break; + } else { + if (!look(until_match,buffer_read,true)) break; + } + if (!callback_bool) break; + /*@-temptrans@*/ + callback_compare=text+1; + /*@=temptrans@*/ + (void)look(comparing_head,buffer_read,false); + callback_compare=NULL; + if (callback_bool) { + // Include the first character + pos=buffer_read-1; + break; + } + } + buffer_read=saved_buffer_read; + return pos; +} + +// Look at characters in the buffer, starting at offset from, +// 'reading' if so indicated (and looking at that location). +// The callback is called after updating the reading pointer +// and placing the character in the buffer. The character is +// also passed by means of the buffer_char global. +// EOF is sent to the callback when we run out of data. +// There is no automatic attempt to fill the buffer. +// The callback should return a boolean indicating whether +// to continue. This function will return true if the callback +// indicated to stop (including if it so indicated on EOF), or +// false if it stopped for EOF. +// We always call the callback at least once, so don't call +// this function at all unless you definitely want to look +// at something. +static bool look(callback_t callback,int from,bool read) { + int pos=from; + int disk_buffer_pos; + char * mem_buffer_pos; + if (pos=mem_buffer_end) mem_buffer_pos-=mem_buffer_size; + while (pos=disk_buffer_filled) { + mem_buffer_pos=mem_buffer_next_empty+(pos-disk_buffer_filled); + if (mem_buffer_pos>=mem_buffer_end) mem_buffer_pos-=mem_buffer_size; + while (pos>=disk_buffer_filled) { + buffer_char=(int)(unsigned int)*mem_buffer_pos; + if (!(*callback)()) return true; + --mem_buffer_pos; + if (mem_buffer_pos==mem_buffer_start-1) + mem_buffer_pos=mem_buffer_end-1; + if (mark&&pos==buffer_mark) --buffer_mark; + --pos; + } + } + if (pos>=0&&disk_buffer_filled>0) { + disk_buffer_pos=disk_buffer_start+pos; + // Reading backwards in the disk buffer is potentially very nasty; + // hopefully it never actually happens + while (pos>=0) { + /*@-nullpass@*/ + resort_to_errno(fseek(disk_buffer,disk_buffer_pos,SEEK_SET)!=0, + "error seeking in temporary file",EX_IOERR); + disk_buffer_sought=disk_buffer_pos; + buffer_char=getc(disk_buffer); + /*@=nullpass@*/ + resort_to_errno(buffer_char==EOF, + "error reading temporary file",EX_IOERR); + ++disk_buffer_sought; + if (!(*callback)()) return true; + --disk_buffer_pos; + if (mark&&pos==buffer_mark) --buffer_mark; + --pos; + } + } + if (mark&&buffer_mark==-1) { + buffer_mark=0; + buffer_marked=false; + } + // We don't call the callback on EOF when going backwards + // buffer_char=EOF; + // (void)(*callback)(); + return false; +} +// Remove characters from the (beginning of the) buffer. The same +// general principles as for look() apply. The callback is called +// after the character is removed and all accounting has been done, so +// perhaps the only place you can reliably find the character is in +// the buffer_char global. Again the callback gets an EOF call if +// there's nothing more to empty, and no automatic filling is done. +// The callback and function return values are as for look() and +// again, the callback is always called at least once; this means at +// least one character is always removed from the buffer, so only call +// the function if something definitely should be removed. +static bool empty(callback_t callback) { + if (disk_buffer_filled>0) { + if (disk_buffer_sought!=disk_buffer_start) { + /*@-nullpass@*/ + resort_to_errno(fseek(disk_buffer,disk_buffer_start,SEEK_SET)!=0, + "error seeking in temporary file",EX_IOERR); + /*@=nullpass@*/ + disk_buffer_sought=disk_buffer_start; + } + while (disk_buffer_filled>0) { + /*@-nullpass@*/ + buffer_char=getc(disk_buffer); + /*@=nullpass@*/ + resort_to_errno(buffer_char==EOF, + "error reading temporary file",EX_IOERR); + ++disk_buffer_sought; + ++disk_buffer_start; + --disk_buffer_filled; + --buffer_filled; + if (buffer_read>0) --buffer_read; + if (buffer_marked) { + if (buffer_mark>0) --buffer_mark; + else buffer_marked=false; + } + if (!(*callback)()) return true; + } + } + while (mem_buffer_filled>0) { + buffer_char=(int)(unsigned int)*mem_buffer_next_empty; + ++mem_buffer_next_empty; + if (mem_buffer_next_empty==mem_buffer_end) mem_buffer_next_empty=mem_buffer_start; + --mem_buffer_filled; + --buffer_filled; + if (buffer_read>0) --buffer_read; + if (buffer_marked) { + if (buffer_mark>0) --buffer_mark; + else buffer_marked=false; + } + if (!(*callback)()) return true; + } + buffer_char=EOF; + if (!(*callback)()) return true; + return false; +} +// Get more characters into the (end of the) buffer. The same +// general principles as for look() apply. The callback is called +// after the character is added and all accounting has been done, +// gets the character via buffer_char, including an EOF when no more +// input is available (EOF on stdin). It should return whether to get +// more characters, and this function will return whether its exit was +// requested by the callback or not (the callback may signal EOF is +// an appropriate place to stop and we still return true). +// When the buffer is full there are a number of automatic options +// echo old the data to stdout or call encodechar for it one character +// at a time; shunt a block off to disk, keeping mem_buffer_keep in +// memory, discard it a character at a time, stop (and return false; +// no EOF call is made), or fail (exit). Here 'full' is defined as +// less than mem_buffer_margin of space after adding the most recent +// character, so there is always a bit of space for callbacks to do +// input transformations. Again, at least one character is always +// added (if possible), and thus consumed from stdin, so only call this +// if you really want to do that. +static bool fill(callback_t callback, when_full_t when_full) { + if (feof(stdin)!=0) { + buffer_char=EOF; + if (!(*callback)()) return true; + return false; + } + for (;;) { + /*@-infloops@*/ + while (mem_buffer_filled>=mem_buffer_size-mem_buffer_margin) { + switch (when_full) { + case echo: + if (disk_buffer_filled>0) echo_disk_buffer(); + (void)empty(echoing_one_char); + break; + case encode: + if (disk_buffer_filled>0) encode_disk_buffer(); + (void)empty(encoding_one_char); + break; + case discard: + if (disk_buffer_filled>0) skip_disk_buffer(); + (void)empty(one_char); + break; + case shunt: + shunt_to_disk(mem_buffer_filled-mem_buffer_keep); + break; + case stop: + return false; + case fail: default: + resort_to_exit(true,"buffer full",EX_SOFTWARE); + } + } + /*@=infloops@*/ + buffer_char=get(); + if (buffer_char==EOF) { + resort_to_errno(ferror(stdin)!=0,"error reading input",EX_IOERR); + if (!(*callback)()) return true; + return false; + } + *mem_buffer_next_fill=(char)buffer_char; + ++mem_buffer_next_fill; + if (mem_buffer_next_fill==mem_buffer_end) mem_buffer_next_fill=mem_buffer_start; + ++mem_buffer_filled; + ++buffer_filled; + if (!(*callback)()) return true; + } +} + +static inline void create_disk_buffer() { + int fildes; + fildes=mkstemp(disk_buffer_template); + resort_to_errno(fildes==-1, + "cannot create temporary file",EX_CANTCREAT); + disk_buffer=fdopen(fildes,"rw"); + resort_to_errno(disk_buffer==NULL, + "cannot create temporary stream",EX_CANTCREAT); +} +static void remove_disk_buffer() { + if (disk_buffer!=NULL) { + resort_to_warning(fclose(disk_buffer)!=0, + "error closing temporary file"); + disk_buffer=NULL; + resort_to_warning(unlink(disk_buffer_template)!=0, + "error removing temporary file"); + } +} +static inline void shunt_to_disk(int n) { + if (disk_buffer==NULL) create_disk_buffer(); + if (disk_buffer_sought!=disk_buffer_start+disk_buffer_filled) { + disk_buffer_sought=disk_buffer_start+disk_buffer_filled; + /*@-nullpass@*/ + resort_to_errno(fseek(disk_buffer, + disk_buffer_start+disk_buffer_filled,SEEK_SET)!=0, + "cannot seek to end of temporary file",EX_IOERR); + /*@=nullpass@*/ + } + while (n>0) { + resort_to_exit(mem_buffer_filled==0, + "internal error: shunting too much to disk",EX_SOFTWARE); + /*@-nullpass@*/ + resort_to_errno(putc(*mem_buffer_next_empty,disk_buffer)==EOF, + "error writing to temporary file",EX_IOERR); + /*@=nullpass@*/ + ++disk_buffer_sought; + ++disk_buffer_filled; + ++mem_buffer_next_empty; + if (mem_buffer_next_empty==mem_buffer_end) mem_buffer_next_empty=mem_buffer_start; + --mem_buffer_filled; + --n; + } +} + +/* tag: callback_functions */ + +static bool one_char() { + callback_int=buffer_char; + return false; +} +static bool echoing_one_char() { + if (buffer_char!=EOF) { + resort_to_errno(put(buffer_char)==EOF,"error echoing",EX_IOERR); + } + callback_int=buffer_char; + return false; +} +static bool encoding_one_char() { + if (buffer_char!=EOF) encodechar(buffer_char); + callback_int=buffer_char; + return false; +} +// Set up callback_int before using this. +static bool n_chars() { + return --callback_int>0; +} +// Set up callback_int before using this. +static bool echoing_n_chars() { + if (buffer_char!=EOF) { + resort_to_errno(put(buffer_char)==EOF,"error echoing",EX_IOERR); + } + return --callback_int>0; +} +// Set up callback_int before using this. +static bool encoding_n_chars() { + if (buffer_char!=EOF) encodechar(buffer_char); + return --callback_int>0; +} +// Set up callback_int and callback_save before using this. +static bool saving_n_chars() { + if (buffer_char!=EOF) *callback_save++=(char)buffer_char; + // We don't actually need this, though it's a good idea, really! + // *callback_save='\0'; + return --callback_int>0; +} +// Set up callback_int and callback_match before using this. +static bool n_chars_until_match() { + callback_bool=buffer_char==callback_match; + return --callback_int>0&&buffer_char!=callback_match; +} +// Do callback_bool=false before using this. +static bool until_eol() { + if (buffer_char==(int)'\n') return !callback_bool; + callback_bool=buffer_char==(int)'\r'; + return true; +} +// Do callback_bool=false before using this. +/*static bool echoing_until_eol() { + if (buffer_char!=EOF) { + resort_to_errno(put(buffer_char)==EOF,"error echoing",EX_IOERR); + } + if (buffer_char==(int)'\n') return !callback_bool; + callback_bool=buffer_char==(int)'\r'; + return true; +}*/ +// Do callback_bool=false, callback_int=0 before using this. +static bool counting_until_eol() { + if (buffer_char!=EOF) ++callback_int; + if (buffer_char==(int)'\n') return !callback_bool; + callback_bool=buffer_char==(int)'\r'; + return true; +} +// Do callback_bool=false and set up callback_save before using this. +static bool saving_until_eol() { + if (buffer_char!=EOF) *callback_save++=(char)buffer_char; + // We don't actually need this, though it's a good idea, really! + // *callback_save='\0'; + if (buffer_char==(int)'\n') return !callback_bool; + callback_bool=buffer_char==(int)'\r'; + return true; +} +// Do callback_bool=false before using this. +static bool decoding_until_eol() { + // We decode as we fill and work directly in the buffer to make + // the transformation. We are guaranteed enough space to do this by + // mem_buffer_margin. + decode_t decoded; + decoded=decodechar(buffer_char); + // We always remove the latest undecoded character from the + // buffer. + ++decoded.r; + if (decoded.r>mem_buffer_filled) { + // This will only happen for quoted-printable decoding + // whitespace stripping, and we can just live with it + // if we can't get rid of it all; with sensible constants + // something really is disobeying MIME and probably SMTP + // about line length anyway if this happens. + warning("unable to strip all whitespace; not enough in memory"); + decoded.r=mem_buffer_filled; + } + if (buffer_filled-decoded.r(int)'Z'||c2!=c1-(int)'A'+(int)'a')&& + (c2<(int)'A'||c2>(int)'Z'||c1!=c2-(int)'A'+(int)'a')) { + callback_bool=false; + return false; + } + /*@-modobserver@*/ + ++callback_compare; + /*@=modobserver@*/ + if (*callback_compare=='\0') { + callback_bool=true; + return false; + } + return true; + /*@=nullderef@*/ +} + +/* tag: encoding_functions */ + +static inline void encode_string(const char * s) { + while (*s!='\0') { + encodechar((int)(unsigned int)*s); + s++; + } +} +static void encodechar(int c) { + if (encoding==unencoded) { + if (c!=EOF) resort_to_errno(put(c)==EOF,"error encoding",EX_IOERR); + return; + } else if (encoding==quoted_printable) { + if (encoding_echoed>=68) { + // We need a soft line break, or are close enough to needing + // one (76 chars max; unclear whether that counts the CRLF; and + // we may output two 3 character sequences which we don't want + // to follow with an unescaped CRLF). This scheme will probably + // make mail look a bit awful, but that's fairly standard anyway, + // and it shouldn't degrade. + resort_to_errno(putstr("=\r\n")==EOF, + "error encoding string",EX_IOERR); + encoding_echoed=0; + } + if (encoding_filled==1) { + // Whatever happens, we'll deal with this now + encoding_filled=0; + if (encoding_buffer[0]=='\r') { + if (c==(int)'\n') { + // Output them as is and we're done for now + resort_to_errno(putstr("\r\n")==EOF, + "error encoding string",EX_IOERR); + encoding_echoed=0; + return; + } else { + // Must encode the bare CR and continue as normal + resort_to_errno(put((int)'=')==EOF,"error encoding",EX_IOERR); + encode_hex_byte((unsigned int)'\r'); + encoding_echoed+=3; + } + } else { + // encoding_buffer[0] must be whitespace + if (c==EOF||c==(int)'\r') { + // Must encode it + resort_to_errno(put((int)'=')==EOF,"error encoding",EX_IOERR); + encode_hex_byte((unsigned int)encoding_buffer[0]); + encoding_echoed+=3; + } else { + // It is fine to output it now as something else is coming + resort_to_errno(put( + (int)(unsigned int)encoding_buffer[0])==EOF, + "error encoding",EX_IOERR); + encoding_echoed+=1; + } + } + } + if ((c>=33&&c<=60)||(c>=62&&c<=126)) { + resort_to_errno(put(c)==EOF,"error encoding",EX_IOERR); + ++encoding_echoed; + } else if (c==(int)' '||c==(int)'\t') { + if (encoding_echoed>=55) { + // My concession to readability; since it's likely to be + // a big mess with a 68 character width, we might as well + // break a bit earlier on a nice word boundary. And it'll + // in fact look better if we break with roughly equal size + // lines, assuming they come in at close to 76 characters + // wide, so we might as well make a nice skinny column. + // rather than a ragged one that uses the same amount of + // space. Compromising between the two, then, as some + // formats, like HTML, don't have many hard line breaks + // anyway, is what we get. + resort_to_errno(put(c)==EOF,"error encoding",EX_IOERR); + resort_to_errno(putstr("=\r\n")==EOF, + "error encoding string",EX_IOERR); + encoding_echoed=0; + } else { + // Store it; we may need to encode it if it's at end of line + encoding_filled=1; + encoding_buffer[0]=(char)c; + } + } else if (c==(int)'\r') { + // Store it; '\n' may be coming up + encoding_filled=1; + encoding_buffer[0]='\r'; + } else if (c==EOF) { + // No buffer, and we're done! Reset for another run. + encoding_echoed=0; + } else { + // Anything else must be encoded as a sequence. + resort_to_errno(put((int)'=')==EOF,"error encoding",EX_IOERR); + encode_hex_byte((unsigned int)c); + encoding_echoed+=3; + } + } else if (encoding==base64) { + if (c==EOF) { + // Reset for next run; we won't need it here + encoding_echoed=0; + if (encoding_filled==0) return; + encoding_buffer[encoding_filled]='\0'; + } else { + encoding_buffer[encoding_filled++]=(char)c; + } + if (encoding_filled==3||c==EOF) { + encode_64((((unsigned int)encoding_buffer[0]>>2)&0x3f)); + encode_64((((unsigned int)encoding_buffer[0]&0x03)<<4)| + (((unsigned int)encoding_buffer[1]>>4)&0x0f)); + if (encoding_filled==1) { + resort_to_errno(put((int)'=')==EOF,"error encoding",EX_IOERR); + resort_to_errno(put((int)'=')==EOF,"error encoding",EX_IOERR); + // Reset for next run + encoding_filled=0; + return; + } + encode_64((((unsigned int)encoding_buffer[1]&0x0f)<<2)| + (((unsigned int)encoding_buffer[2]>>6)&0x03)); + if (encoding_filled==2) { + resort_to_errno(put((int)'=')==EOF,"error encoding",EX_IOERR); + // Reset for next run + encoding_filled=0; + return; + } + encode_64((((unsigned int)encoding_buffer[2]&0x3f))); + encoding_echoed+=4; + if (encoding_echoed>=72) { + resort_to_errno(putstr("\r\n")==EOF, + "error encoding string",EX_IOERR); + encoding_echoed=0; + } + encoding_filled=0; + } + } else { + resort_to_exit(true,"internal error: unknown encoding",EX_SOFTWARE); + } +} +static inline void finish_encoding() { + encodechar(EOF); +} +// The function takes an input character c and returns up to four output +// characters (a character will be EOF to indicate no further characters +// to store; note that this doesn't mean there will be no more ever; only +// if EOF is returned when EOF was input does it meant this), and a number +// of characters to remove before adding the aforementioned characters. +static decode_t decodechar(int c) { + int h; + unsigned int b1, b2, b3, b4; + decode_t o; + o.r=0; o.c1=EOF; o.c2=EOF; o.c3=EOF; + if (decoding==unencoded) { + o.c1=c; + return o; + } else if (decoding==quoted_printable) { + // decoding_buffer may hold '=' and maybe a hex digit or a CR. + if (decoding_filled==2) { + // Whatever happens, it's all settled now. + decoding_filled=0; + if (decoding_buffer[1]=='\r') { + if (c==(int)'\n') { return o; } + // Invalid; leave as is--will be encoded later. + o.c1=(int)'='; o.c2=(int)'\r'; o.c3=c; + return o; + } + h=decode_hex(c); + if (h==EOF) { + // Invalid; leave as is--will be encoded later. + o.c1=(int)'='; o.c2=(int)(unsigned int)decoding_buffer[1]; o.c3=c; + return o; + } + // We have a full sequence representing a single character. + o.c1=decode_hex((int)(unsigned int)decoding_buffer[1])*16+h; + return o; + } else if (decoding_filled==1) { + if (c==(int)'\r'||decode_hex(c)!=EOF) { + // Valid character after = + decoding_filled=2; + decoding_buffer[1]=(char)c; + return o; + } + // Invalid; leave as is--will be encoded later. + decoding_filled=0; + o.c1=(int)'='; o.c2=c; + return o; + } else if (decoding_filled==0) { + if (c==(int)'=') { + // The first character can only ever be '=' so we + // don't actually bother to store it; just say it's there. + decoding_white=0; + decoding_filled=1; + return o; + } + // Keep track of whitespace. + if (c==(int)' '||c==(int)'\t') ++decoding_white; + else decoding_white=0; + // Remove trailing whitespace. + if (c==EOF||c==(int)'\r') { o.r=decoding_white; decoding_white=0; } + // Otherwise we just keep it. If it's EOF, we're done. + o.c1=c; + return o; + } else { + warning("internal error: decoding buffer too full"); + return o; + } + } else if (decoding==base64) { + if (c==EOF) { + // Just in case it was corrupted, make sure we're reset + decoding_filled=0; + return o; + } + if (c==(int)'='||decode_64(c)!=EOF) + decoding_buffer[decoding_filled++]=(char)c; + if (decoding_filled==4) { + // We empty it whatever happens here + decoding_filled=0; + b1=(unsigned int)decode_64((int)decoding_buffer[0]); + b2=(unsigned int)decode_64((int)decoding_buffer[1]); + o.c1=(int)(((b1&0x3f)<<2)|((b2>>4)&0x03)); + if (decoding_buffer[2]=='=') return o; + b3=(unsigned int)decode_64((int)decoding_buffer[2]); + o.c2=(int)(((b2&0x0f)<<4)|((b3>>2)&0x0f)); + if (decoding_buffer[3]=='=') return o; + b4=(unsigned int)decode_64((int)decoding_buffer[3]); + o.c3=(int)(((b3&0x03)<<6)|(b4&0x3f)); + } + return o; + } else { + resort_to_exit(true,"internal error: unknown encoding",EX_SOFTWARE); + // Never reached + return o; + } +} +static void decode_lookahead() { + // Decoding will always shrink, so this is quite easy + char * c; + char * cc; + decode_t decoded; + int pos=buffer_read; + int decpos=buffer_read; + resort_to_exit(buffer_read=mem_buffer_end) c-=mem_buffer_size; + cc=c; + while (pos0) { + resort_to_exit(decpos-decoded.r=(int)'0'&&c<=(int)'9') return c-(int)'0'; + if (c>=(int)'A'&&c<=(int)'F') return c-(int)'A'+10; + return EOF; +} +static inline int decode_64(int c) { + if (c>=(int)'A'&&c<=(int)'Z') return c-(int)'A'; + if (c>=(int)'a'&&c<=(int)'z') return c-(int)'a'+26; + if (c>=(int)'0'&&c<=(int)'9') return c-(int)'0'+52; + if (c==(int)'+') return 62; + if (c==(int)'/') return 63; + // if (c==(int)'=') return EOF; + return EOF; +} +static inline void encode_hex_byte(unsigned int h) { + int h1=(int)((h>>4)&0x0f); + int h2=(int)(h&0x0f); + if (h1<10) resort_to_errno(put((int)'0'+h1)==EOF,"error encoding",EX_IOERR); + else if (h1<16) + resort_to_errno(put((int)'A'+h1-10)==EOF,"error encoding",EX_IOERR); + else resort_to_exit(true,"internal error: byte too large",EX_SOFTWARE); + if (h2<10) resort_to_errno(put((int)'0'+h2)==EOF,"error encoding",EX_IOERR); + else if (h2<16) + resort_to_errno(put((int)'A'+h2-10)==EOF,"error encoding",EX_IOERR); + else resort_to_exit(true,"internal error: byte too large",EX_SOFTWARE); +} +static inline void encode_64(unsigned int b) { + if (b<26) + resort_to_errno(put((int)'A'+b)==EOF,"error encoding",EX_IOERR); + else if (b<52) + resort_to_errno(put((int)'a'+b-26)==EOF,"error encoding",EX_IOERR); + else if (b<62) + resort_to_errno(put((int)'0'+b-52)==EOF,"error encoding",EX_IOERR); + else if (b==62) + resort_to_errno(put((int)'+')==EOF,"error encoding",EX_IOERR); + else if (b==63) + resort_to_errno(put((int)'/')==EOF,"error encoding",EX_IOERR); + else resort_to_exit(true, + "internal error: base64 value too large",EX_SOFTWARE); +} + +/* tag: error_functions */ + +// Syslog constants: +// level: LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG +// facility: LOG_MAIL, LOG_DAEMON, LOG_USER, LOG_LOCALn(0-7) + +static inline void * alloc_or_exit(size_t s) /*@allocates result@*/ { + void * m; + m=malloc(s); + if (m==NULL) { +#ifdef USE_STDERR + fprintf(stderr,"foot_filter: %s\n","out of memory"); +#endif +#ifdef USE_SYSLOG + syslog(LOG_ERR|LOG_MAIL,"%s\n","out of memory"); +#endif + exit(EX_OSERR); + } + return m; +} +static inline void /*noreturnwhentrue*/ + resort_to_exit(bool when,const char * message,int status) { + if (when) { +#ifdef USE_STDERR + fprintf(stderr,"foot_filter: %s\n",message); +#endif +#ifdef USE_SYSLOG + syslog(LOG_ERR|LOG_MAIL,"%s\n",message); +#endif + exit(status); + } +} +static inline void /*noreturnwhentrue*/ + resort_to_errno(bool when,const char * message,int status) { + if (when) { +#ifdef USE_STDERR + fprintf(stderr,"foot_filter: %s (%s)\n",message,strerror(errno)); +#endif +#ifdef USE_SYSLOG + syslog(LOG_ERR|LOG_MAIL,"%s (%m)\n",message); +#endif + exit(status); + } +} +static inline void resort_to_warning(bool when,const char * message) { + if (when) warning(message); +} +static inline void warning(const char * message) { +#ifdef USE_STDERR + fprintf(stderr,"foot_filter: %s\n",message); +#endif +#ifdef USE_SYSLOG + syslog(LOG_WARNING|LOG_MAIL,"%s\n",message); +#endif +} + +/* tag: helper_functions */ + +// The program was written following all the specs using CRLF for newlines, +// but we get them from Postfix with LF only, so these wrapper functions +// do the translation in such a way that it can easily be disabled if desired. +static inline int get() { + int c; +#ifdef UNIX_EOL + static bool got_nl=false; + if (got_nl) { + got_nl=false; + return 10; + } +#endif + c=getchar(); +#ifdef UNIX_EOL + if (c==10) { + got_nl=true; + return 13; + } +#endif + return c; +} +static inline int put(int c) { +#ifdef UNIX_EOL + if (c==13) return c; +#endif + return putchar(c); +} +static inline int putstr(const char * s) { + while (*s!='\0') if (put((int)(unsigned int)*s++)==EOF) return EOF; + return 0; +} + +static inline bool case_insensitively_heads(const char * head,const char * buffer) { + const char * s1=head; + const char * s2=buffer; + for (;;) { + if (*s1=='\0') return true; /* for equality return *s2=='\0'; */ + else if (*s2=='\0') return false; + if (*s1!=*s2&& + (*s1<'A'||*s1>'Z'||*s2!=*s1-'A'+'a')&& + (*s2<'A'||*s2>'Z'||*s1!=*s2-'A'+'a')) return false; + ++s1; ++s2; + } +} + diff -r 04d916168efb -r b68213ccb9d2 contrib/pymime/LICENSE --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/pymime/LICENSE Thu Feb 26 07:29:01 2015 +1100 @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. 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 +them 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 prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. 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. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +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 +state 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) + + 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 3 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, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program 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, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU 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 Lesser General +Public License instead of this License. But first, please read +. diff -r 04d916168efb -r b68213ccb9d2 contrib/pymime/README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/pymime/README.md Thu Feb 26 07:29:01 2015 +1100 @@ -0,0 +1,55 @@ +pymime - A MIME formatter in python +=================================== + +This program is designed to take a MIME-formatted email and do the following actions: +- convert HTML-parts of singlepart-mails to plaintext +- strip HTML-parts of multipart-mails +- strip attachments +- optionally append a footer to the mail +- optionally add an X-Archived-At header for mail-archive.com + +Requirements +------------ + +Tested with python 2.6 and 2.7. +Currently not compatible with python 3.X, as this would require dropping 2.6 support. + +Usage +----- + + Usage: pymime.py [options] + Options: + -h, --help + show this help message and exit + -i INPUT, --input=INPUT + Where to read the mail from. Defaults to STDIN + -o OUTPUT, --output=OUTPUT + Where to write the transformed mail. Defaults to STDOUT + -f FOOTER, --footer=FOOTER + UTF-8 encoded footer to append to every mail. + -k, --keep-going + Ignore failures (ATM only missing footer file) as much + as possible before failing. + -a, --archive-header + Add Archived-At header for mail-archive.com + + +License +------- + +GPLv3 + +Source +------ + +https://github.com/tdf/pymime + +Bugs +---- + +https://github.com/tdf/pymime/issues + +Contact +------- + +alex@documentfoundation.org \ No newline at end of file diff -r 04d916168efb -r b68213ccb9d2 contrib/pymime/integration/mlmmj/mlmmj-pymime --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/pymime/integration/mlmmj/mlmmj-pymime Thu Feb 26 07:29:01 2015 +1100 @@ -0,0 +1,49 @@ +#!/bin/bash +MLMMJRECIEVE=/usr/bin/mlmmj-recieve +PYMIME=/var/spool/mlmmj/pymime.py + +# check executables +if ! [ -x $MLMMJRECIEVE ]; then + echo "can't find $MLMMJRECIEVE executable, aborting" + exit 1 +fi + +if ! [ -x $PYMIME ]; then + echo "can't find $PYMIME executable, aborting" + exit 1 +fi + +# read parameters +I=1 +PARAM_L=0 +while [ $I -le $# ] && [ $PARAM_L == 0 ]; do + if [ "${!I}" == "-L" ]; then + PARAM_L=1 + fi + I=$[$I+1] +done + +if [ $PARAM_L == 1 ] && [ $I -le $# ]; then + MLPATH="${!I}" +else + echo "parameter -L /path/to/listdir missing, aborting" + exit 1 +fi + +if ! [ -d "${MLPATH}" ]; then + echo "${MLPATH} is not existing or no directory, aborting" + exit 1 +fi + +CONTROLD="${MLPATH}/control" + +if ! [ -d "${CONTROLD}" ]; then + echo "${CONTROLD} is not existing or no directory, aborting" + exit 1 +fi + +# go to the mailinglist directory +cd $MLPATH + +# pipe the calls +$PYMIME | $MLMMJRECIEVE "$@" diff -r 04d916168efb -r b68213ccb9d2 contrib/pymime/src/pymime.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/pymime/src/pymime.py Thu Feb 26 07:29:01 2015 +1100 @@ -0,0 +1,288 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright 2011 Alexander Werner +# 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 3 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, see . + +""" +This script takes a MIME-formatted email and does various transformations with it, +e.g. converts HTML-mails to plain text mails and strips attachements. +""" +__version__="1.0.0" +import HTMLParser, email, email.utils, sys +from optparse import OptionParser +# for archive-header +import hashlib +import base64 +#------------------------------------------------------------------------------- +# Configuration Options start here + +IGNORETAGS = ( "script", "head", "title", "link" ) +MAP_STARTTAGS = {"li": "\n* ", + "p": "\n"} +MAP_ENDTAGS = { "p": "\n", + "div": "\n", + "h1": "\n==============================================================\n\n", + "h2": "\n--------------------------------------------------------------\n\n", + "h3": "\n", + "h4": "\n", + "h5": "\n", + "h6": "\n"} +MAXNUMNEWLINES = 2 + +# No Configuration beneath this line +# ------------------------------------------------------------------------------- + +# Parse command line options +parser = OptionParser() +parser.add_option( "-i", "--input", dest = "input", default = "-", + help = "Where to read the mail from. Defaults to STDIN" ) +parser.add_option( "-o", "--output", dest = "output", default = "-", + help = "Where to write the transformed mail. Defaults to STDOUT" ) +parser.add_option( "-f", "--footer", dest = "footer", default = None, + help = "UTF-8 encoded footer to append to every mail." ) +parser.add_option( "-k", "--keep-going", dest = "keep_going", action = "store_true", default = False, + help = "Ignore failures (ATM only missing footer file) as much as possible before failing." ) +parser.add_option( "-a", "--archive-header", dest = "archive_header", action = "store_true", default = False, + help = "Add Archived-At header for mail-archive.com" ) +parser.add_option( "-V", "--version", dest = "print_version", action = "store_true", default = False, help = "Show version and exit" ) +options, args = parser.parse_args() + + +class StripHTML( HTMLParser.HTMLParser ): + """ + This class provides the necessary logic to convert HTML to plain text. + """ + def __init__( self ): + self.reset() + self.plain = [] + self.last_starttag = None + def handle_starttag( self, tag, attributes ): + self.last_starttag = tag + if tag in MAP_STARTTAGS: + self.plain.append( MAP_STARTTAGS[tag] ) + def handle_endtag( self, tag ): + if tag in MAP_ENDTAGS.keys(): + self.plain.append( MAP_ENDTAGS[tag] ) + def handle_data( self, data ): + if self.last_starttag not in IGNORETAGS: + self.plain.append( data.strip() ) + def remove_whitespace( self ): + # Split at newlines instead of tags + self.plain = "".join( self.plain ).split( "\n" ) + numspace = 0 + # Copy the whole text + oldplain = self.plain[:] + self.plain = [] + for line in oldplain: + if line.isspace() or line is "": + numspace = numspace + 1 + if numspace <= MAXNUMNEWLINES: + # number of blank newlines is lower than limit, append line + self.plain.append( "\n" ) + else: + numspace = 0 + # line is no blank newline, append line + self.plain.append( line ) + def get_plain_data( self ): + self.remove_whitespace() + return "\n".join( self.plain ) + +class EMail( object ): + """ + This class represents an email or a single payload of a multipart email. + """ + def __init__( self, keep_going = False ): + self.to_include = False + self.keep_going = keep_going + def feed( self, fp = None, string = None, message = None ): + """ + Feeds the EMail object with data. If fp is supplied, a new message will be created using + email.message_from_file, if string is supplied, a new message will be created using + email.message_from_string, if message is supplied, the supplied message will be used. + """ + if fp is not None: + self.message = email.message_from_file( fp ) + elif string is not None: + self.message = email.message_from_string( string ) + elif message is not None: + self.message = message + else: + raise AttributeError + def parse( self ): + """ + Parses the supplied message object. EMail.feed must have been called before. + """ + if self.message.is_multipart(): + self.parse_multipart() + else: + self.parse_singlepart() + def parse_multipart( self ): + """ + Parses a multipart message object. + """ + mails = [] + #--------------------------------------------------------------------------- + # Test if the message has a text/plain and a text/html part + has_plaintext = False + has_html = False + for part in self.message.walk(): + content_type = part.get_content_type() + if content_type == "text/plain": + has_plaintext = True + if content_type == "text/html": + has_html = True + #--------------------------------------------------------------------------- + # If the message has both text/plain and text/html parts, use the existing text/plain part + # and dismiss all other parts + if has_plaintext and has_html: + for part in self.message.walk(): + content_type = part.get_content_type() + if content_type == "text/plain": + mails.append( part ) + #--------------------------------------------------------------------------- + # Parse every part of the message for inclusion + else: + for part in self.message.walk(): + # Avoid infinite recursion, message.walk also yields the parent message. + if part.is_multipart(): + continue + # Parse the submessage + mail = EMail() + mail.feed( message = part ) + mail.parse() + # If the submessage contains useful data, append it to the final list of submessages + if mail.to_include: + mails.append( mail.message ) + #--------------------------------------------------------------------------- + # Test if at least one submessage survived the parsing + if mails: + # Delete the body from the message + self.message.set_payload( None ) + # Only one submessage to be included in the body, a restructuring of the parent + # message is necessary. + if len( mails ) == 1: + mail = mails[0] + # Copy the Headers from the submessage to the parent message + for key in mail.keys(): + del self.message[key] + self.message[ key] = mail[key] + # If the parent message is still multipart, the submessage didn't have a Content-Type + # Header. Replace the old parent Content-Type with a default. + if "multipart" in self.message.get_content_maintype(): + del self.message["Content-Type"] + self.message["Content-Type"] = "text/plain" + # Copy the body from the submessage to teh parent message + self.message.set_payload( mails[0].get_payload() ) + # Multiple submessages are to be included, simply attach them. + else: + for mail in mails: + self.message.attach( mail ) + # No Message is to be included + else: + raise Exception + def addArchiveHeader( self ): + """ + Add mail-archive.com direct-link to archive http://www.mail-archive.com/faq.html#listserver + """ + message_id = self.message['message-id'] + list_post = email.utils.parseaddr(self.message['to']) + if ( message_id is not None ) and ( list_post[1] is not '' ): + # remove < and > from msg-id + sha = hashlib.sha1( message_id[1:-1] ) + sha.update( list_post[1] ) + hash = base64.urlsafe_b64encode( sha.digest() ) + url = "" % hash + self.message['Archived-At'] = url + # in case debugging is needed + #self.message['X-Archived-At-msgid'] = message_id[1:-1] + #self.message['X-Archived-At-list-post'] = list_post[1] + + def parse_singlepart( self ): + """ + Parses a singlepart message object. + """ + content_type = self.message.get_content_type() + if content_type == "text/plain": + # text/plain is fine, if I'm a submessage I want to be included + self.to_include = True + elif content_type == "text/html": + # text/html must be stripped down to text/plain + s = StripHTML() + # Feed the HTML-Stripper with the body of the message + s.feed( self.message.get_payload() ) + # And set the body of the message to the output of the HTML-Stripper + self.message.set_payload( s.get_plain_data() ) + # if I'm a submessage I also want to be included + self.to_include = True + # rewrite the Content-Type header, im no longer an evil HTML mail :) + self.message.set_type( "text/plain" ) + def get_string( self ): + """ + Returns the string representation of the supplied message. + """ + return self.message.as_string() + + def append_footer_from_file( self, filename ): + rawfooter = None + footer = None + orig_cs = self.message.get_content_charset() + if orig_cs == None: + cs = "iso-8859-15" + orig_cs = "ascii" + else: + cs = orig_cs + try: + with open( filename ) as f: + rawfooter = f.read() + except: + if not self.keep_going: + raise + if rawfooter: + try: + footer = rawfooter.decode( "utf-8" ).encode( cs ) + except: + cs = "utf-8" + footer = rawfooter.decode( "utf-8" ).encode( cs ) + if footer: + sep = "\n" + if footer.startswith( "\n" ): + sep = "" + payload = self.message.get_payload( decode = True ).decode( orig_cs ).encode( cs ) + sep + footer + del self.message["MIME-Version"] + del self.message["Content-Transfer-Encoding"] + self.message.set_payload( payload, cs ) + + +if __name__ == "__main__": + if options.print_version: + print("pymime "+__version__) + exit() + if options.input == "-": + input = sys.stdin + else: + input = file( options.input ) + if options.output == "-": + output = sys.stdout + else: + output = file( options.output, "w" ) + e = EMail( keep_going = options.keep_going ) + e.feed( fp = input ) + e.parse() + if options.archive_header: + e.addArchiveHeader() + if options.footer: + e.append_footer_from_file( options.footer ) + output.write( e.get_string() ) + output.close() diff -r 04d916168efb -r b68213ccb9d2 contrib/pymime/src/test.eml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/pymime/src/test.eml Thu Feb 26 07:29:01 2015 +1100 @@ -0,0 +1,107 @@ +Message-ID: +Date: Wed, 04 Apr 2000 04:17:00 +0200 +From: MHTML +MIME-Version: 1.0 +To: mhtml@dsv.su.se +Subject: Test message no. 17 (modification of no. 13) +Content-Type: multipart/alternative; boundary="==boundary-2" + +--==boundary-2 + +This is test message no. 17 +=========================== + +Here comes the red test image: +----------------------------- + + + +Here comes the yellow test image: +-------------------------------- + + + +This is the last line of this test message. +--==boundary-2 +Content-Type: multipart/related; boundary="==boundary-1";type="text/html" + +Text displayed only to non-MIME-compliant mailers +--==boundary-1 +Content-Type: text/html; charset=us-ascii +Content-Transfer-Encoding: 7bit + + + +Test message no. 17 + + +

This is test message no. 17

+ +

Here comes the red test image:

+ + +

Here comes the yellow test image:

+ + +

This is the last line of this test message. + + +--==boundary-1 +Content-Type: image/gif +Content-ID: +Content-Transfer-Encoding: base64 +Content-Disposition: inline; filename="red-test-image.gif" + +R0lGODlhdQAgAPcAAP//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/MM//MAP+Z//+Z +zP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8A +zP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZ +zMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wA +zMwAmcwAZswAM8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZ +zJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkzM5kzAJkA/5kA +zJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZ +zGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YA +zGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZ +zDOZmTOZZjOZMzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMA +zDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDMMwDMAACZ/wCZ +zACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAA +zAAAmQAAZgAAM+4AAN0AALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI +AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAARAAAIgAAEe7u7t3d3bu7 +u6qqqoiIiHd3d1VVVURERCIiIhEREQAAACH5BAAAAAAALAAAAAB1ACAAQAj/AP8JHEiwoMGD +CBMqXMiwocOGIyJKnEixosWLGDNq3MixY0aBFKuJrBZypMiIJ0ekrLhSpUmSKEdObMkSpsuY +OG/qTMnzJUWQHoMKHUq0KEagRpMqXaoUaU6dG2lKlOqRKtOkTq9q3VrV5sd/XMOKZZp1rNmz +GsuiXct2hNq2cMVmXdkzZ12LLe/ehYrXpsy/MPUGHvw04lzCdhFbzasYMd+aUxsnnrzTq1uw +cTN3tVrxrebPWDGDHr3UM+nTHE2jXn1RNevXEl3Dfi179urDJrte5BzVcknNhyNHZiyzJnGv +uWMuppu7uHLkyV1Kxe1ccOGZ0Cn/xshcu8/K2g2LQ8bJGPJj4eh3+/WNHb118PAtBn8aXTrn +6s7tl2QP9b399fhNN55tbe31FYEITlRbgqAtyCBwAz5I20MUVmjhhRgyFBAAOw== +--==boundary-1 +Content-Type: image/gif +Content-ID: +Content-Transfer-Encoding: base64 +Content-Disposition: inline; filename="yellow-test-image.gif" + +R0lGODlhmAAgAPcAAP//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/MM//MAP+Z//+Z +zP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8A +zP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZ +zMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wA +zMwAmcwAZswAM8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZ +zJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkzM5kzAJkA/5kA +zJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZ +zGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YA +zGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZ +zDOZmTOZZjOZMzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMA +zDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDMMwDMAACZ/wCZ +zACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAA +zAAAmQAAZgAAM+4AAN0AALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI +AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAARAAAIgAAEe7u7t3d3bu7 +u6qqqoiIiHd3d1VVVURERCIiIhEREQAAACH5BAAAAAAALAAAAACYACAAQAj/AP8JHEiwoMGD +CBMqXMiwocOHECMWmEixosWLGDNq3Mixo8ePIEN+FPixWrWKJlOenGgS5coCLWG+VKlSY0yW +NCnW1PkS482YQFcGxUlU5s6MJEUqXcq0qdOnGpNCnUq1qlWlUm0K7emz502qX0WGvTo1K9mz +aNOO7Wg2rdu3cDG2jUu3Ltm5dvPqZYp3r9+/G/u6/LmVp0vDMosO7Xo4sWPCGwmfTPm4cGXF +XC0KbowZcdHLiNd6Br3YcUbIpC0vLi33H+DXbo+G3Ay7tl7atnPHxa27N1revoNXBS68uFPi +xpOLRK68OVvXHFlfpIwz81PRHrGb9ku7tOyv4Kmnbu4avvxM8RbF50T//ajs1tnVW96OWnLO +015VW9def/791fPBFx9nBPbXGH/5RXaegqHp1xloz5XEVXsFogchfueZl55oBpp2n1H2ccSc +c4BJ59GIJKZYEYoqqshiiyS+CGNzEdVo44045qhjQwEBADs= +--==boundary-1-- +--==boundary-2--