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