Annotation of parser3/src/types/pa_vmail.C, revision 1.105

1.1       paf         1: /**    @file
                      2:        Parser: @b mail class.
                      3:        relies on gmime library, by Jeffrey Stedfast <fejj@helixcode.com>
                      4: 
1.98      moko        5:        Copyright (c) 2001-2012 Art. Lebedev Studio (http://www.artlebedev.com)
1.1       paf         6:        Author: Alexandr Petrosian <paf@design.ru>(http://paf.design.ru)
                      7: */
1.13      paf         8: 
1.1       paf         9: #include "pa_sapi.h"
                     10: #include "pa_vmail.h"
                     11: #include "pa_vstring.h"
                     12: #include "pa_request.h"
                     13: #include "pa_common.h"
                     14: #include "pa_charset.h"
                     15: #include "pa_charsets.h"
                     16: #include "pa_vdate.h"
                     17: #include "pa_vfile.h"
                     18: #include "pa_uue.h"
                     19: 
1.105   ! moko       20: volatile const char * IDENT_PA_VMAIL_C="$Id: pa_vmail.C,v 1.104 2012/07/20 20:56:06 moko Exp $" IDENT_PA_VMAIL_H;
1.98      moko       21: 
1.3       paf        22: #ifdef WITH_MAILRECEIVE
1.4       paf        23: extern "C" {
1.99      moko       24: #include "gmime/gmime.h"
1.4       paf        25: }
1.45      paf        26: 
                     27: #include "pa_charsets.h"
1.1       paf        28: #endif
                     29: 
                     30: // defines
                     31: 
                     32: #define RAW_NAME "raw"
                     33: 
                     34: // internals
                     35: 
                     36: enum PartType {
                     37:        P_TEXT,
                     38:        P_HTML,
                     39:        P_FILE,
                     40:        P_MESSAGE,
                     41:        P_TYPES_COUNT
                     42: };
                     43: 
1.99      moko       44: static const char* const part_name_begins[P_TYPES_COUNT] = {
                     45:        "text",
                     46:        "html",
                     47:        "file",
                     48:        "message"
                     49: };
1.45      paf        50: 
                     51: // defines for statics
                     52: 
                     53: #define FORMAT_NAME "format"
                     54: #define CHARSET_NAME "charset"
1.76      misha      55: #define CID_NAME "content-id"
1.45      paf        56: 
                     57: // statics
                     58: 
                     59: static const String format_name(FORMAT_NAME);
                     60: static const String charset_name(CHARSET_NAME);
1.76      misha      61: static const String cid_name(CID_NAME);
1.1       paf        62: 
1.61      paf        63: // consts
                     64: 
                     65: const int MAX_CHARS_IN_HEADER_LINE=500;
                     66: 
1.1       paf        67: // VMail
                     68: 
1.45      paf        69: extern Methoded* mail_base_class;
1.1       paf        70: 
1.45      paf        71: VMail::VMail(): VStateless_class(0, mail_base_class) {}
1.1       paf        72: 
1.3       paf        73: #ifdef WITH_MAILRECEIVE
1.1       paf        74: 
1.48      paf        75: #define EXCEPTION_VALUE "x-exception"
                     76: 
1.99      moko       77: static Charset* source_charset;
                     78: 
                     79: static void putReceived(HashStringValue& received, const char* name, Value* value, bool capitalizeName=false) {
                     80:        if(name && value)
                     81:                received.put(capitalizeName ? capitalize(pa_strdup(name)) : pa_strdup(name), value);
1.1       paf        82: }
                     83: 
1.99      moko       84: static void putReceived(HashStringValue& received, const char* name, const char* value, bool capitalizeName=false) {
                     85:        if(name && value)
                     86:                putReceived(received, name, new VString(*new String(pa_strdup(value))), capitalizeName);
1.1       paf        87: }
                     88: 
1.99      moko       89: static void putReceived(HashStringValue& received, const char* name, time_t value) {
                     90:        if(name)
                     91:                received.put(pa_strdup(name), new VDate(value) );
1.1       paf        92: }
                     93: 
1.99      moko       94: static void MimeHeaderField2received(const char* name, const char* value, gpointer data) {
                     95:        HashStringValue* received=static_cast<HashStringValue*>(data);
                     96:        putReceived(*received, name, value, true /*capitalizeName*/);
1.1       paf        97: }
                     98: 
1.99      moko       99: static void parse(Request& r, GMimeMessage *message, HashStringValue& received);
1.1       paf       100: 
                    101: #ifndef DOXYGEN
1.45      paf       102: struct MimePart2body_info {
                    103:        Request* r;
                    104:        HashStringValue* body;
1.1       paf       105:        int partCounts[P_TYPES_COUNT];
                    106: };
                    107: #endif
1.99      moko      108: 
                    109: 
                    110: static char *readStream(GMimeStream* gstream, size_t &length){
                    111:        length=MAX_STRING;
                    112:        char *result=(char*)pa_malloc_atomic(length+1);
                    113:        char *ptr=result;
                    114: 
                    115:        while(true) {
                    116:                size_t current_size=ptr-result;
                    117:                ssize_t todo_size=length-current_size;
                    118:                ssize_t received_size=g_mime_stream_read (gstream, ptr, todo_size);
                    119: 
                    120:                if(received_size<0)
                    121:                        throw Exception(PARSER_RUNTIME, 0,"mail content stream read error");
                    122:                if(received_size==0)
                    123:                        break;
                    124:                if(received_size==todo_size) {
                    125:                        length=length*2;
                    126:                        result=(char *)pa_realloc(result, length+1);
                    127:                        ptr=result+current_size+received_size;
                    128:                } else {
                    129:                        ptr+=received_size;
                    130:                }
                    131:        }
                    132: 
                    133:        length=ptr-result;
                    134:        result[length]='\0';
                    135:        return result;
                    136: }
                    137: 
                    138: static void MimePart2body(GMimeObject *parent, GMimeObject *part, gpointer data) {
1.45      paf       139:        MimePart2body_info& info=*static_cast<MimePart2body_info *>(data);
1.1       paf       140: 
1.99      moko      141:        // skipping message/partial & frames
1.101     moko      142:        if (GMIME_IS_MESSAGE_PARTIAL (part) || GMIME_IS_MULTIPART (part))
1.99      moko      143:                return;
                    144:        
                    145:        if (GMimeContentType *type=g_mime_object_get_content_type(part)) {
                    146:                PartType partType=P_FILE;
                    147:                
                    148:                if (GMIME_IS_MESSAGE_PART(part))
                    149:                        partType=P_MESSAGE;
1.70      paf       150:                else if(g_mime_content_type_is_type(type, "text", "plain"))
1.45      paf       151:                        partType=P_TEXT;
                    152:                else if(g_mime_content_type_is_type(type, "text", "html"))
                    153:                        partType=P_HTML;
1.99      moko      154: 
1.1       paf       155:                // partName
1.99      moko      156:                int partNumber=++info.partCounts[partType];
                    157:                const char *partName=part_name_begins[partType];
1.1       paf       158:                
1.99      moko      159:                char partNameNumbered[MAX_STRING];
                    160:                snprintf(partNameNumbered, MAX_STRING, "%s%d", partName, partNumber);
1.1       paf       161: 
1.99      moko      162:                // $.partN[
                    163:                VHash* vpartHash(new VHash);
                    164:                if(partNumber==1)
                    165:                        putReceived(*info.body, partName, vpartHash);
                    166:                putReceived(*info.body, partNameNumbered, vpartHash);
                    167: 
                    168:                HashStringValue& partHash=vpartHash->hash();
                    169: 
                    170:                // $.raw[
                    171:                VHash* vraw(new VHash);
                    172:                putReceived(partHash, RAW_NAME, vraw);
                    173:                g_mime_header_list_foreach(part->headers, MimeHeaderField2received, &vraw->hash());
                    174: 
                    175:                // $.content-type[
                    176:                VHash* vcontent_type(new VHash);
                    177:                putReceived(partHash, "content-type", vcontent_type);
                    178: 
                    179:                // $.value[text/plain]
                    180:                char value[MAX_STRING];
                    181:                snprintf(value, MAX_STRING, "%s/%s", type->type ? type->type : "x-unknown", type->subtype ? type->subtype : "x-unknown");
                    182:                putReceived(vcontent_type->hash(), VALUE_NAME, value);
                    183: 
                    184:                const GMimeParam *param=g_mime_content_type_get_params(type);
                    185:                while(param) {
                    186:                        // $.charset[windows-1251]  && co
                    187:                        putReceived(vcontent_type->hash(), g_mime_param_get_name(param), g_mime_param_get_value(param), true /*capitalizeName*/);
                    188:                        param=g_mime_param_next(param);
                    189:                }
                    190: 
                    191:                if (GMIME_IS_MESSAGE_PART (part)) {
1.102     moko      192:                        /* message/rfc822, $.raw[] will be overwitten */
1.99      moko      193:                        GMimeMessage *message = g_mime_message_part_get_message ((GMimeMessagePart *) part);
                    194:                        parse(*info.r, message, partHash);
1.1       paf       195:                } else {
1.101     moko      196:                        GMimePart *gpart = (GMimePart *)part;
                    197: 
                    198:                        putReceived(partHash, "description", g_mime_part_get_content_description(gpart));
                    199:                        putReceived(partHash, "content-id", g_mime_part_get_content_id(gpart));
                    200:                        putReceived(partHash, "content-md5", g_mime_part_get_content_md5(gpart));
                    201:                        putReceived(partHash, "content-location", g_mime_part_get_content_location(gpart));
                    202: 
1.1       paf       203:                        // $.value[string|file]
1.103     moko      204:                        if(GMimeDataWrapper* gcontent=g_mime_part_get_content_object(gpart)){
                    205:                                GMimeStream* gstream=g_mime_stream_filter_new(g_mime_data_wrapper_get_stream(gcontent));
                    206:                                
                    207:                                if(GMimeFilter* filter=g_mime_filter_basic_new(g_mime_part_get_content_encoding(gpart), false))
                    208:                                        g_mime_stream_filter_add(GMIME_STREAM_FILTER(gstream), filter);
                    209:                                
                    210:                                size_t length;
1.99      moko      211:                                
1.103     moko      212:                                if(partType==P_FILE) {
                    213:                                        char *content=readStream(gstream, length);
                    214:                                        const char* content_filename=g_mime_part_get_filename(gpart);
                    215:                                        VFile* vfile(new VFile);
                    216:                                        vfile->set_binary(true/*tainted*/, content, length, new String(content_filename), content_filename ? new VString(info.r->mime_type_of(content_filename)) : 0);
                    217:                                        putReceived(partHash, VALUE_NAME, vfile);
                    218:                                } else {
                    219:                                        // P_TEXT, P_HTML
                    220:                                        if(Value *charset=vcontent_type->hash().get("Charset"))
                    221:                                                if(GMimeFilter* filter=g_mime_filter_charset_new(charset->get_string()->cstr(), source_charset->NAME_CSTR()))
                    222:                                                        g_mime_stream_filter_add(GMIME_STREAM_FILTER(gstream), filter);
                    223:                                        
                    224:                                        char *content=readStream(gstream, length);
                    225:                                        putReceived(partHash, VALUE_NAME,new VString(*new String(content)));
                    226:                                }
1.1       paf       227:                        }
                    228:                }
                    229:        }
                    230: }
                    231: 
1.30      paf       232: int gmt_offset() {
                    233: #if defined(HAVE_TIMEZONE) && defined(HAVE_DAYLIGHT)
1.31      paf       234:        return timezone+(daylight?60*60*(timezone<0?-1:timezone>0?+1:0):0);
1.30      paf       235: #else
                    236:        time_t t=time(0);
                    237:        tm *tm=localtime(&t);
                    238: #if defined(HAVE_TM_GMTOFF)
                    239:        return tm->tm_gmtoff;
                    240: #elif defined(HAVE_TM_TZADJ)
                    241:        return tm->tm_tzadj;
                    242: #else
                    243: #error neither HAVE_TIMEZONE&HAVE_DAYIGHT nor HAVE_TM_GMTOFF nor HAVE_TM_TZADJ defined
                    244: #endif
                    245: #endif
                    246: }
                    247: 
1.99      moko      248: static void parse(Request& r, GMimeMessage *message, HashStringValue& received) {
1.1       paf       249:        try {
                    250:                // firstly user-defined strings go
                    251:                //  user headers
                    252:                {
                    253:                        // $.raw[
1.99      moko      254:                        VHash* vraw(new VHash);  putReceived( received, RAW_NAME, vraw);
                    255:                        g_mime_header_list_foreach(g_mime_object_get_header_list(GMIME_OBJECT(message)), MimeHeaderField2received, &vraw->hash());
                    256:                }
                    257: 
                    258:                //  secondly standard headers
                    259:                putReceived(received, "message-id", g_mime_message_get_message_id(message));
                    260:                putReceived(received, "from", g_mime_message_get_sender(message));
                    261:                putReceived(received, "reply-to", g_mime_message_get_reply_to(message));
                    262:                putReceived(received, "subject", g_mime_message_get_subject(message));
                    263:                // @todo: g_mime_message_get_recipients(message)
                    264:                
1.1       paf       265:                // .date(date+gmt_offset)
1.99      moko      266:                time_t date;
                    267:                int tz_offset;
                    268:                g_mime_message_get_date(message, &date, &tz_offset);
                    269:                int tt_offset = ((tz_offset / 100) *(60 * 60)) + (tz_offset % 100) * 60;
1.30      paf       270: 
1.99      moko      271:                putReceived(received, "date",
                    272:                        date // local sender
1.1       paf       273:                        -tt_offset // move local sender to GMT sender
1.30      paf       274:                        -gmt_offset() // move GMT sender to our local time
1.1       paf       275:                );
                    276: 
                    277:                // .body[part/parts
1.55      paf       278:                MimePart2body_info info={&r, &received, {0}};
1.99      moko      279:                g_mime_message_foreach(message, MimePart2body, &info);
1.1       paf       280: 
1.48      paf       281:        } catch(const Exception& e) {
1.99      moko      282:                putReceived(received, VALUE_NAME, "<exception occured while parsing message>");
                    283:                putReceived(received, EXCEPTION_VALUE, e.comment());
1.1       paf       284:        } catch(...) {
1.99      moko      285:                putReceived(received, VALUE_NAME, "<exception occured while parsing message>");
1.1       paf       286:        }
                    287: }
                    288: 
1.99      moko      289: void VMail::fill_received(Request& r) {
1.45      paf       290:        if(r.request_info.mail_received) {
1.99      moko      291:                source_charset=&r.charsets.source();
1.50      paf       292:                g_mime_init(0);
1.1       paf       293: 
                    294:                // create stream with CRLF filter
1.104     moko      295:                GMimeStream *stream = g_mime_stream_filter_new(g_mime_stream_file_new(stdin));
1.99      moko      296:                g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream), g_mime_filter_crlf_new(false, false));
1.1       paf       297:                try {
1.99      moko      298:                        // parse incoming message
                    299:                        GMimeMessage *message=g_mime_parser_construct_message(g_mime_parser_new_with_stream(stream));
                    300:                        parse(r, message, vreceived.hash());
1.101     moko      301:                        g_object_unref(GMIME_OBJECT(message));
1.48      paf       302:                } catch(const Exception& e) {
                    303:                        HashStringValue& received=vreceived.hash();
1.99      moko      304:                        putReceived(received, VALUE_NAME, "<exception occured while parsing message>");
                    305:                        putReceived(received, EXCEPTION_VALUE, e.comment());
1.1       paf       306:                } catch(...) {
1.99      moko      307:                        // abnormal stream free
                    308:                        g_object_unref(stream);
1.48      paf       309:                        rethrow;
1.1       paf       310:                }
1.99      moko      311:                g_object_unref(stream);
                    312:                
                    313:                g_mime_shutdown();
1.1       paf       314:        }
                    315: }
                    316: 
1.99      moko      317: #else // WITH_MAILRECEIVE
                    318: void VMail::fill_received(Request&){}
                    319: #endif // WITH_MAILRECEIVE
                    320: 
1.9       paf       321: typedef int (*string_contains_char_which_check)(int);
1.99      moko      322: 
1.45      paf       323: static bool string_contains_char_which(const char* string, string_contains_char_which_check check) {
1.9       paf       324:        while(char c=*string++) {
1.62      paf       325:                if(check((unsigned char)c))
1.9       paf       326:                        return true;
                    327:        }
                    328:        return false;
                    329: }
1.99      moko      330: 
1.32      paf       331: static char *trimBoth(char *s) {
                    332:        // sanity check
                    333:        if(!s)
                    334:                return 0;
                    335: 
                    336:        // trim head whitespace
1.62      paf       337:        while(*s && isspace((unsigned char)*s))
1.32      paf       338:                s++;
                    339:        // trim tail whitespace
                    340:        char *tail=s+strlen(s);
                    341:        if(tail>s) {
                    342:                do {
                    343:                        --tail;
1.62      paf       344:                        if(isspace((unsigned char)*tail))
1.32      paf       345:                                *tail=0;
                    346:                } while(tail>s);
                    347:        }
                    348:        // return it
                    349:        return s;
                    350: }
1.99      moko      351: 
1.45      paf       352: static void extractEmail(String& result, char *email) {
1.32      paf       353:        email=trimBoth(email);
1.45      paf       354:        result.append_help_length(email, 0, String::L_TAINTED);
1.9       paf       355: 
                    356:        /*
                    357:                http://www.faqs.org/rfcs/rfc822.html
                    358: 
                    359:                addr-spec   =  local-part "@" domain        ; global address
                    360:        
                    361:                local-part  =  word *("." word)             ; uninterpreted case-preserved
                    362:                word        =  atom / quoted-string
                    363: 
                    364:                domain      =  sub-domain *("." sub-domain)
                    365:                sub-domain  =  domain-ref / domain-literal
                    366:                domain-ref  =  atom                         ; symbolic reference
                    367: 
1.92      misha     368:                domain-literal << ignoring for now
1.9       paf       369:                quoted-string in word << ignoring for now
                    370: 
                    371:                atom        =  1*<any CHAR except specials, SPACE and CTLs>  << the ONLY to check
                    372: 
                    373:                specials    =  "(" / ")" / "<" / ">" / "@"  ; Must be in quoted-
1.92      misha     374:                                /  "," / ";" / ":" / "\" / <">  ;  string, to use
                    375:                                /  "." / "[" / "]"              ;  within a word.
1.9       paf       376: 
                    377:        */
1.45      paf       378:        const char* exception_type="email.format";
1.9       paf       379:        if(strpbrk(email, "()<>,;:\\\"[]"/*specials minus @ and . */))
1.17      paf       380:                throw Exception(exception_type,
1.9       paf       381:                        &result,
1.32      paf       382:                        "email contains bad characters (specials)");
1.9       paf       383:        if(string_contains_char_which(email, (string_contains_char_which_check)isspace))
1.17      paf       384:                throw Exception(exception_type,
1.9       paf       385:                        &result,
1.32      paf       386:                        "email contains bad characters (whitespace)");
1.9       paf       387:        if(string_contains_char_which(email, (string_contains_char_which_check)iscntrl))
1.17      paf       388:                throw Exception(exception_type,
1.9       paf       389:                        &result,
1.32      paf       390:                        "email contains bad characters (control)");
1.16      paf       391:        if(result.is_empty())
1.17      paf       392:                throw Exception(exception_type,
1.45      paf       393:                        0,
1.16      paf       394:                        "email is empty");
1.39      paf       395: }
                    396: 
1.45      paf       397: static const String& extractEmails(const String& string) {
                    398:        char *emails=string.cstrm();
                    399:        String& result=*new String;
1.39      paf       400:        while(char *email=lsplit(&emails, ',')) {
                    401:                rsplit(email, '>');
                    402:                if(char *in_brackets=lsplit(email, '<'))
                    403:                        email=in_brackets;
                    404:                if(!result.is_empty())
                    405:                        result<<",";
1.45      paf       406:                extractEmail(result, email);
1.39      paf       407:        }
1.9       paf       408: 
                    409:        return result;
                    410: }
1.45      paf       411: 
                    412: #ifndef DOXYGEN
                    413: struct Store_message_element_info {
                    414:        Request_charsets& charsets;
                    415:        String& header;
                    416:        const String* & from;
1.60      paf       417:        bool extract_to; String* & to;
1.45      paf       418:        const String* errors_to;
                    419:        bool mime_version_specified;
                    420:        ArrayValue* parts[P_TYPES_COUNT];
                    421:        int parts_count;
                    422:        bool backward_compatibility;
                    423:        Value* content_type;
1.66      paf       424:        bool had_content_disposition;
1.45      paf       425: 
                    426:        Store_message_element_info(
                    427:                Request_charsets& acharsets,
                    428:                String& aheader,
                    429:                const String* & afrom,
1.60      paf       430:                bool aextract_to, String* & ato
1.45      paf       431:                ):
                    432:                
                    433:                charsets(acharsets),
                    434:                header(aheader),
                    435:                from(afrom),
1.60      paf       436:                extract_to(aextract_to), to(ato),
1.45      paf       437:                errors_to(0),
                    438:                mime_version_specified(false),
                    439:                parts_count(0),
1.66      paf       440:                backward_compatibility(false), content_type(0),
                    441:                had_content_disposition(false){
1.45      paf       442:        }
                    443: };
                    444: #endif
1.99      moko      445: 
1.45      paf       446: static void store_message_element(HashStringValue::key_type raw_element_name, 
1.92      misha     447:                                HashStringValue::value_type element_value, 
                    448:                                Store_message_element_info *info) {
1.45      paf       449:        const String& low_element_name=String(raw_element_name, String::L_TAINTED).change_case(
                    450:                info->charsets.source(), String::CC_LOWER);
1.1       paf       451: 
                    452:        // exclude internals
1.52      paf       453:        if(low_element_name==MAIL_OPTIONS_NAME
                    454:                || low_element_name==CHARSET_NAME
1.5       paf       455:                || low_element_name==VALUE_NAME
                    456:                || low_element_name==RAW_NAME
1.64      paf       457:                || low_element_name==FORMAT_NAME
1.76      misha     458:                || low_element_name==NAME_NAME
1.94      misha     459:                || low_element_name==CID_NAME
                    460:                || low_element_name==MAIL_DEBUG_NAME)
1.1       paf       461:                return;
                    462: 
                    463:        // grep parts
                    464:        for(int pt=0; pt<P_TYPES_COUNT; pt++) {
1.45      paf       465:                if(low_element_name.starts_with(part_name_begins[pt])) {
1.29      paf       466:                        // check that $.message# '#' is digit
1.45      paf       467:                        size_t start_len=strlen(part_name_begins[pt]);
                    468:                        if(low_element_name.length()>start_len) {
                    469:                                const char* at_num=low_element_name.mid(start_len, start_len+1).cstr();
1.63      paf       470:                                if(!isdigit((unsigned char)*at_num))
1.29      paf       471:                                        continue;
                    472:                        }
1.45      paf       473:                        *info->parts[pt]+=element_value;
                    474:                        info->parts_count++;
1.1       paf       475:                        return;
                    476:                }
                    477:        }
                    478: 
1.10      paf       479:        // fetch some special headers
1.45      paf       480:        if(low_element_name=="from")
                    481:                info->from=&extractEmails(element_value->as_string());
1.90      misha     482:        if(low_element_name==CONTENT_DISPOSITION)
1.66      paf       483:                info->had_content_disposition=true;
1.60      paf       484:        if(info->extract_to) { // defined only when SMTP used, see mail.C [collecting info for RCPT to-s]
1.39      paf       485:                bool is_to=low_element_name=="to" ;
                    486:                bool is_cc=low_element_name=="cc" ;
                    487:                bool is_bcc=low_element_name=="bcc" ;
                    488:                if(is_to||is_cc||is_bcc) {
1.45      paf       489:                        if(!info->to)
                    490:                                info->to=new String;
1.39      paf       491:                        else
1.45      paf       492:                                *info->to << ",";
                    493:                        *info->to << extractEmails(element_value->as_string());
1.39      paf       494:                }
                    495: 
                    496:                if(is_bcc) // blinding it
1.45      paf       497:                        return;
1.39      paf       498:        }
1.12      paf       499:        if(low_element_name=="errors-to")
1.45      paf       500:                info->errors_to=&extractEmails(element_value->as_string());     
1.37      paf       501:        if(low_element_name=="mime-version")
1.45      paf       502:                info->mime_version_specified=true;
1.1       paf       503: 
1.45      paf       504:        // has content type?
                    505:        if(low_element_name==CONTENT_TYPE_NAME) {
                    506:                info->content_type=element_value;
                    507:                if(info->backward_compatibility)
                    508:                        return;
1.39      paf       509:        }
1.1       paf       510: 
1.45      paf       511:        // preparing header line
                    512:        const String& source_line=attributed_meaning_to_string(*element_value,
                    513:                String::L_PASS_APPENDED/*does not matter, would cstr(AS_IS) right away*/);
1.66      paf       514:        if(source_line.is_empty())
                    515:                return; // we don't need empty headers here [used in clearing content-disposition]
                    516: 
1.45      paf       517:        const char* source_line_cstr=source_line.cstr();
                    518:        String::C mail=Charset::transcode(
                    519:                String::C(source_line_cstr, source_line.length()),
                    520:                info->charsets.source(), 
                    521:                info->charsets.mail());
1.92      misha     522: 
1.45      paf       523:        String& mail_line=*new String;
1.73      paf       524:        if(low_element_name=="to"
                    525:                || low_element_name=="cc" 
                    526:                || low_element_name=="bcc") 
                    527:        {
                    528:                // never wrap address lines, mailer can not handle wrapped properly
                    529:                mail_line.append_strdup(mail.str, mail.length, String::L_MAIL_HEADER);
                    530:        } else {
                    531:                while(mail.length) {
                    532:                        bool too_long=mail.length>MAX_CHARS_IN_HEADER_LINE;
                    533:                        size_t length=too_long
                    534:                                ? MAX_CHARS_IN_HEADER_LINE
                    535:                                : mail.length;
                    536: 
                    537:                        mail_line.append_strdup(mail.str, length, String::L_MAIL_HEADER);
                    538:                        mail.length-=length;
                    539: 
                    540:                        if(too_long)
                    541:                                mail_line << "\n "; // break header and continue it on next line
                    542:                }       
                    543:        }
1.45      paf       544: 
                    545:        // append header line
                    546:        info->header 
1.91      misha     547:                << capitalize(raw_element_name.cstr())
1.96      misha     548:                << ": " << mail_line.untaint_cstr(String::L_AS_IS, 0, &info->charsets)
1.45      paf       549:                << "\n";
                    550: }
                    551: 
                    552: static const String& file_value_to_string(Request& r, Value* send_value) {
1.64      paf       553:        String& result=*new String;
                    554: 
1.45      paf       555:        VFile* vfile;
                    556:        const String* file_name=0;
                    557:        Value* vformat=0;
1.76      misha     558:        Value* vcid=0;
1.66      paf       559:        const String* dummy_from;
                    560:        String* dummy_to;
1.95      misha     561:        Store_message_element_info info(r.charsets, result, dummy_from, false, dummy_to);
                    562: 
1.45      paf       563:        if(HashStringValue *send_hash=send_value->get_hash()) { // hash
1.74      paf       564:                send_hash->for_each<Store_message_element_info*>(store_message_element, &info);
1.64      paf       565: 
1.1       paf       566:                // $.value
1.45      paf       567:                if(Value* value=send_hash->get(value_name))
1.85      misha     568:                        vfile=value->as_vfile(String::L_AS_IS);
1.1       paf       569:                else
1.78      misha     570:                        throw Exception(PARSER_RUNTIME,
1.45      paf       571:                                0,
1.1       paf       572:                                "file part has no $value");
                    573: 
                    574:                // $.format
1.45      paf       575:                vformat=send_hash->get(format_name);
1.1       paf       576: 
1.76      misha     577:                // $.content-id
                    578:                vcid=send_hash->get(cid_name);
                    579: 
1.6       paf       580:                // $.name
1.45      paf       581:                if(Value* vfile_name=send_hash->get(name_name)) // $name specified 
1.1       paf       582:                        file_name=&vfile_name->as_string();
1.28      paf       583:        } else  // must be VFile then
1.45      paf       584:                vfile=send_value->as_vfile(String::L_AS_IS);
1.28      paf       585: 
                    586:        if(!file_name)
1.45      paf       587:                file_name=&vfile->fields().get(name_name)->as_string();
1.28      paf       588: 
1.95      misha     589:        const char* file_name_cstr;
                    590:        const char* quoted_file_name_cstr;
                    591:        {
                    592:                Request_charsets charsets(r.charsets.source(), r.charsets.mail()/*uri!*/, r.charsets.mail());
1.97      misha     593:                file_name_cstr=file_name->untaint_and_transcode_cstr(String::L_FILE_SPEC, &charsets);
1.95      misha     594:                quoted_file_name_cstr=String(file_name_cstr).taint_cstr(String::L_MAIL_HEADER, 0, &charsets);
                    595:        }
1.1       paf       596: 
1.95      misha     597:        // Content-Type: application/octet-stream
                    598:        result
                    599:                << HTTP_CONTENT_TYPE_CAPITALIZED ": "
                    600:                << r.mime_type_of(file_name_cstr)
                    601:                << "; name=\""
                    602:                << quoted_file_name_cstr
                    603:                << "\"\n";
1.66      paf       604: 
1.95      misha     605:        if(!info.had_content_disposition) // $.Content-Disposition wasn't specified by user
1.79      misha     606:                result
1.91      misha     607:                        << CONTENT_DISPOSITION_CAPITALIZED ": "
1.79      misha     608:                        << ( vcid ? CONTENT_DISPOSITION_INLINE : CONTENT_DISPOSITION_ATTACHMENT )
                    609:                        << "; "
1.95      misha     610:                        << CONTENT_DISPOSITION_FILENAME_NAME"=\"" << quoted_file_name_cstr << "\"\n";
1.1       paf       611: 
1.79      misha     612:        if(vcid)
1.93      misha     613:                result
                    614:                        << "Content-Id: <"
                    615:                        << vcid->as_string()
                    616:                        << ">\n"; // @todo: value must be escaped as %hh
1.79      misha     617: 
1.45      paf       618:        const String* type=vformat?&vformat->as_string():0;
1.93      misha     619:        if(!type/*default*/ || *type=="base64") {
                    620:                result << CONTENT_TRANSFER_ENCODING_CAPITALIZED ": base64\n\n";
                    621:                result << pa_base64_encode(vfile->value_ptr(), vfile->value_size());
1.75      paf       622:        } else {
1.93      misha     623:                if(*type=="uue") {
                    624:                        result << CONTENT_TRANSFER_ENCODING_CAPITALIZED ": x-uuencode\n\n";
1.95      misha     625:                        result << pa_uuencode((const unsigned char*)vfile->value_ptr(), vfile->value_size(), file_name_cstr);
                    626:                } else
1.82      misha     627:                        throw Exception(PARSER_RUNTIME,
                    628:                                type,
                    629:                                "unknown attachment encode format");
1.75      paf       630:        }
1.95      misha     631: 
1.1       paf       632:        return result;
                    633: }
                    634: 
1.45      paf       635: static const String& text_value_to_string(Request& r,
1.92      misha     636:                                        PartType pt, Value* send_value,
                    637:                                        Store_message_element_info& info) {
1.45      paf       638:        String& result=*new String;
1.1       paf       639: 
1.45      paf       640:        Value* text_value;
1.81      misha     641:        Value* content_transfer_encoding=0;
1.45      paf       642:        if(HashStringValue* send_hash=send_value->get_hash()) {
1.1       paf       643:                // $.USER-HEADERS
1.92      misha     644:                info.content_type=0;
                    645:                info.backward_compatibility=false; // reset
1.74      paf       646:                send_hash->for_each<Store_message_element_info*>(store_message_element, &info);
1.1       paf       647:                // $.value
1.45      paf       648:                text_value=send_hash->get(value_name);
1.1       paf       649:                if(!text_value)
1.78      misha     650:                        throw Exception(PARSER_RUNTIME,
1.45      paf       651:                                0,
                    652:                                "%s part has no $" VALUE_NAME, part_name_begins[pt]);
1.81      misha     653:                content_transfer_encoding=send_hash->get(content_transfer_encoding_name);
1.1       paf       654:        } else
1.45      paf       655:                text_value=send_value;
1.1       paf       656: 
1.45      paf       657:        if(!info.content_type) {
                    658:                result 
1.91      misha     659:                        << HTTP_CONTENT_TYPE_CAPITALIZED ": text/" << (pt==P_TEXT?"plain":"html")
1.45      paf       660:                        << "; charset=" << info.charsets.mail().NAME()
                    661:                        << "\n";
1.1       paf       662:        }
1.81      misha     663:        if(!content_transfer_encoding)
1.91      misha     664:                result << CONTENT_TRANSFER_ENCODING_CAPITALIZED << ": 8bit\n";
1.1       paf       665: 
                    666:        // header|body separator
                    667:        result << "\n"; 
                    668: 
                    669:        // body
1.45      paf       670:        const String* body;
1.1       paf       671:        switch(pt) {
                    672:        case P_TEXT:
1.92      misha     673:                {
                    674:                        body=&text_value->as_string();
                    675:                        break;
                    676:                }
1.1       paf       677:        case P_HTML: 
                    678:                {
1.45      paf       679:                        Temp_lang temp_lang(r, String::Language(String::L_HTML | String::L_OPTIMIZE_BIT));
1.55      paf       680:                        if(text_value->get_junction())
1.41      paf       681:                                body=&r.process_to_string(*text_value);
1.92      misha     682:                        else
1.78      misha     683:                                throw Exception(PARSER_RUNTIME,
1.45      paf       684:                                        0,
1.1       paf       685:                                        "html part value must be code");
                    686: 
                    687:                        break;
                    688:                }
1.53      paf       689:        default:
                    690:                throw Exception(0,
                    691:                        0,
                    692:                        "unhandled part type #%d", pt);
1.41      paf       693:        }
                    694:        if(body) {
1.69      paf       695:                Request_charsets charsets(r.charsets.source(), r.charsets.mail()/*uri!*/, r.charsets.mail());
1.97      misha     696:                const char* body_cstr=body->untaint_and_transcode_cstr(String::L_AS_IS, &charsets);
1.95      misha     697:                result.append_know_length(body_cstr, strlen(body_cstr), String::L_CLEAN);
1.1       paf       698:        }
                    699: 
                    700:        return result;
                    701: };
                    702: 
                    703: /// @todo files and messages in order (file, file2, ...)
1.45      paf       704: const String& VMail::message_hash_to_string(Request& r,
1.92      misha     705:                                        HashStringValue* message_hash, int level, 
                    706:                                        const String* & from, bool extract_to, String* & to) {
1.34      paf       707:        
1.1       paf       708:        if(!message_hash)
1.78      misha     709:                throw Exception(PARSER_RUNTIME,
1.45      paf       710:                        0,
1.1       paf       711:                        "message must be hash");
                    712: 
1.45      paf       713:        String& result=*new String;
1.1       paf       714: 
1.45      paf       715:        if(Value* vrecodecharset_name=message_hash->get(charset_name))
                    716:                r.charsets.set_mail(charsets.get(vrecodecharset_name->as_string()
                    717:                        .change_case(r.charsets.source(), String::CC_UPPER)));
1.1       paf       718:        else
1.45      paf       719:                r.charsets.set_mail(r.charsets.source());
                    720:        // no big deal that we leave it set. they wont miss this point which would reset it
1.1       paf       721: 
1.45      paf       722:        Store_message_element_info info(r.charsets, 
1.60      paf       723:                result, from, extract_to, to);
1.1       paf       724:        {
1.45      paf       725:                // for backward compatibilyty $.body+$.content-type -> 
                    726:                // $.text[$.value[] $.content-type[]]
                    727: 
1.1       paf       728:                for(int pt=0; pt<P_TYPES_COUNT; pt++)
1.45      paf       729:                        info.parts[pt]=new ArrayValue(1);
                    730: 
                    731:                Value* body=message_hash->get("body");
                    732:                if(body) {
                    733:                        message_hash->remove("body");
                    734:                        info.backward_compatibility=true;
                    735:                }               
1.74      paf       736:                message_hash->for_each<Store_message_element_info*>(store_message_element, &info);
1.45      paf       737: 
                    738:                if(body) {
                    739:                        VHash& text_part=*new VHash(); 
                    740:                        HashStringValue& hash=text_part.hash();
                    741:                        hash.put(value_name, body);
                    742:                        if(info.content_type)
                    743:                                hash.put(content_type_name, info.content_type);
                    744:                        
                    745:                        *info.parts[P_TEXT]+=&text_part;
                    746:                        info.parts_count++;
                    747:                }
                    748: 
1.10      paf       749:                if(!info.errors_to)
1.91      misha     750:                        result << "Errors-To: postmaster\n"; // errors-to: default
1.37      paf       751:                if(!info.mime_version_specified)
                    752:                        result << "MIME-Version: 1.0\n"; // MIME-Version: default
1.1       paf       753:        }
                    754: 
1.45      paf       755:        int textCount=info.parts[P_TEXT]->count();
1.1       paf       756:        if(textCount>1)
1.78      misha     757:                throw Exception(PARSER_RUNTIME,
1.45      paf       758:                        0,
1.1       paf       759:                        "multiple text parts not supported, use file part");
1.45      paf       760:        int htmlCount=info.parts[P_HTML]->count();
1.1       paf       761:        if(htmlCount>1)
1.78      misha     762:                throw Exception(PARSER_RUNTIME,
1.45      paf       763:                        0,
1.1       paf       764:                        "multiple html parts not supported, use file part");
                    765: 
                    766: 
                    767:        bool multipart=info.parts_count>1;
                    768:        bool alternative=textCount && htmlCount;
                    769:        // header
                    770:        char *boundary=0;
                    771:        if(multipart) {
1.45      paf       772:                boundary=new(PointerFreeGC) char[MAX_NUMBER];
1.1       paf       773:                snprintf(boundary, MAX_NUMBER-5/*lEvEl*/, "lEvEl%d", level);
1.76      misha     774: 
                    775:                bool is_inline = false;
                    776:                {
                    777:                        ArrayValue& files=*info.parts[P_FILE];
                    778:                        for(size_t i=0; i<files.count(); i++) {
1.82      misha     779:                                HashStringValue* file;
                    780:                                if((file=files.get(i)->get_hash()) && file->get(cid_name)){
1.76      misha     781:                                        is_inline = true;
                    782:                                        break;
1.77      misha     783:                                }
1.76      misha     784:                        }
                    785:                }
                    786:                
1.91      misha     787:                result << HTTP_CONTENT_TYPE_CAPITALIZED ": " << ( is_inline ? HTTP_CONTENT_TYPE_MULTIPART_RELATED : HTTP_CONTENT_TYPE_MULTIPART_MIXED ) << ";";
1.76      misha     788: 
1.1       paf       789:                // multi-part
1.45      paf       790:                result 
1.76      misha     791:                         << " boundary=\"" << boundary << "\"\n"
1.45      paf       792:                                "\n"
                    793:                                "This is a multi-part message in MIME format.";
1.1       paf       794:        }
                    795: 
                    796:        // alternative or not
                    797:        {
                    798:                if(alternative) {
1.45      paf       799:                        result << "\n\n--" << boundary << "\n" // intermediate boundary
1.91      misha     800:                                HTTP_CONTENT_TYPE_CAPITALIZED ": multipart/alternative; boundary=\"ALT" << boundary << "\"\n";
1.1       paf       801:                } 
                    802:                for(int i=0; i<2; i++) {
                    803:                        PartType pt=i==0?P_TEXT:P_HTML;
1.45      paf       804:                        if(info.parts[pt]->count()) {
1.1       paf       805:                                if(alternative)
                    806:                                        result << "\n\n--ALT" << boundary << "\n"; // intermediate boundary
                    807:                                else if(boundary)
                    808:                                        result << "\n\n--" << boundary << "\n";  // intermediate boundary
1.45      paf       809:                                result << text_value_to_string(r, pt, info.parts[pt]->get(0), info);
1.1       paf       810:                        }
                    811:                }
                    812:                if(alternative)
                    813:                        result << "\n\n--ALT" << boundary << "--\n";
                    814:        }
                    815: 
                    816:        // files
                    817:        {
1.45      paf       818:                ArrayValue& files=*info.parts[P_FILE];
                    819:                for(size_t i=0; i<files.count(); i++) {
1.1       paf       820:                        if(boundary)
                    821:                                result << "\n\n--" << boundary << "\n";  // intermediate boundary
1.45      paf       822:                        result << file_value_to_string(r, files.get(i));
1.1       paf       823:                }
                    824:        }
                    825: 
                    826:        // messages
                    827:        {
1.45      paf       828:                ArrayValue& messages=*info.parts[P_MESSAGE];
                    829:                for(size_t i=0; i<messages.count(); i++) {
1.1       paf       830:                        if(boundary)
                    831:                                result << "\n\n--" << boundary << "\n";  // intermediate boundary
                    832:                        
1.45      paf       833:                        const String* dummy_from;
                    834:                        String* dummy_to;
                    835:                        result << message_hash_to_string(r, messages.get(i)->get_hash(), level+1,
1.60      paf       836:                                dummy_from, false, dummy_to);
1.1       paf       837:                }
                    838:        }
                    839:        
                    840:        // tailer
                    841:        if(boundary)
                    842:                result << "\n\n--" << boundary << "--\n"; // finish boundary
                    843: 
                    844:        // return
                    845:        return result;
                    846: }
                    847: 
                    848: 
1.88      misha     849: Value* VMail::get_element(const String& aname) {
1.1       paf       850:        // $fields
1.3       paf       851: #ifdef WITH_MAILRECEIVE
1.1       paf       852:        if(aname==MAIL_RECEIVED_ELEMENT_NAME)
1.48      paf       853:                return &vreceived;
1.1       paf       854: #endif
                    855: 
                    856:        // $CLASS,$method
1.88      misha     857:        if(Value* result=VStateless_class::get_element(aname))
1.1       paf       858:                return result;
                    859: 
                    860:        return 0;
                    861: }

E-mail: