Annotation of parser3/src/types/pa_vmail.C, revision 1.40.2.9
1.1 paf 1: /** @file
2: Parser: @b mail class.
3: relies on gmime library, by Jeffrey Stedfast <fejj@helixcode.com>
4:
1.40.2.3 paf 5: Copyright(c) 2001-2003 ArtLebedev Group(http://www.artlebedev.com)
1.1 paf 6: Author: Alexandr Petrosian <paf@design.ru>(http://paf.design.ru)
7: */
1.13 paf 8:
1.40.2.9! paf 9: static const char* IDENT_VMAIL_C="$Date: 2003/02/17 12:13:45 $";
1.1 paf 10:
11: #include "pa_sapi.h"
12: #include "pa_vmail.h"
13: #include "pa_vstring.h"
14: #include "pa_request.h"
15: #include "pa_common.h"
16: #include "pa_charset.h"
17: #include "pa_charsets.h"
18: #include "pa_vdate.h"
19: #include "pa_vfile.h"
20: #include "pa_uue.h"
21:
1.3 paf 22: #ifdef WITH_MAILRECEIVE
1.4 paf 23: extern "C" {
1.1 paf 24: #include "gmime.h"
1.4 paf 25: }
1.1 paf 26: #endif
27:
28: // defines
29:
30: #define RAW_NAME "raw"
31:
32: // internals
33:
34: enum PartType {
35: P_TEXT,
36: P_HTML,
37: P_FILE,
38: P_MESSAGE,
39:
40: P_TYPES_COUNT
41: };
42:
1.40.2.3 paf 43: static const char* const part_name_starts[P_TYPES_COUNT]={"text", "html", "file", "message"};
1.1 paf 44:
1.40.2.5 paf 45: // defines for statics
46:
47: #define FORMAT_NAME "format"
48: #define CHARSET_NAME "charset"
49:
50: // statics
51:
52: static StringPtr format_name(new String(FORMAT_NAME));
53: static StringPtr charset_name(new String(CHARSET_NAME));
54:
1.1 paf 55: // VMail
56:
1.40.2.7 paf 57: extern MethodedPtr mail_base_class;
1.1 paf 58:
1.40.2.5 paf 59: VMail::VMail(): VStateless_class(StringPtr(0), mail_base_class.get()) {}
1.1 paf 60:
1.3 paf 61: #ifdef WITH_MAILRECEIVE
1.1 paf 62:
63: static const String& maybeUpperCase(Pool& pool, const String& src, bool toUpperCase) {
64: return toUpperCase?src.change_case(pool, String::CC_UPPER):src;
65: }
66:
1.40.2.3 paf 67: static void UTF8toSource(Pool& pool, const char* source_body, size_t source_content_length,
1.1 paf 68: const void *& dest_body, size_t& dest_content_length) {
69: if(source_body) {
70: if(!source_content_length)
71: source_content_length=strlen(source_body);
72: Charset::transcode(pool,
73: *utf8_charset, source_body, source_content_length,
74: pool.get_source_charset(), dest_body, dest_content_length);
75: } else {
76: dest_body=0;
77: dest_content_length=0;
78: }
79: }
80:
1.40.2.3 paf 81: static void putReceived(Hash& received, const char* name, Value *value, bool nameToUpperCase=false) {
1.1 paf 82: Pool& pool=received.pool();
83: if(name && value) {
84: received.put(
85: maybeUpperCase(pool, String::OnPool(pool, name, 0, true/*tainted*/), nameToUpperCase),
86: value);
87: }
88: }
89:
1.40.2.3 paf 90: static void putReceived(Hash& received, const char* name, const char* value, size_t value_size=0, bool nameToUpperCase=false) {
1.1 paf 91: if(value) {
92: Pool& pool=received.pool();
93:
94: const void *value_dest_body;
95: size_t value_dest_content_length;
1.21 paf 96: // UTF8toSource(pool, value, value_size, value_dest_body, value_dest_content_length);
97: value_dest_body=value;
98: value_dest_content_length=value_size;
1.1 paf 99:
100: putReceived(received, name,
101: new(pool) VString(
1.40.2.3 paf 102: String::OnPool(pool,(const char* )value_dest_body, value_dest_content_length, true/*tainted*/)));
1.1 paf 103: }
104: }
105:
1.40.2.3 paf 106: static void putReceived(Hash& received, const char* name, time_t value) {
1.1 paf 107: Pool& pool=received.pool();
108: if(name)
109: received.put(String::OnPool(pool, name, 0, true/*tainted*/), new(pool) VDate(pool, value));
110: }
111:
1.40.2.3 paf 112: static void MimeHeaderField2received(const char* name, const char* value, gpointer data) {
1.1 paf 113: Hash& received=*static_cast<Hash *>(data);
114:
115: putReceived(received, name, value, 0, true/*nameInUpperCase*/);
116: }
117:
1.23 paf 118: static void parse(Request& r, GMimeStream *stream, Hash& received);
1.1 paf 119:
120: #ifndef DOXYGEN
121: struct MimePart2bodyInfo {
1.23 paf 122: Request *r;
1.1 paf 123: Hash *body;
124: int partCounts[P_TYPES_COUNT];
125: };
126: #endif
1.27 paf 127: /// @test why no copy to global in P_HTML putReceived?
1.1 paf 128: static void MimePart2body(GMimePart *part,
129: gpointer data) {
130: MimePart2bodyInfo& i=*static_cast<MimePart2bodyInfo *>(data);
131: Pool& pool=i.body->pool();
132:
133: if(const GMimeContentType *type=g_mime_part_get_content_type(part)) {
134: if(g_mime_content_type_is_type(type, "multipart", "*"))
135: return; // skipping frames
136:
137: PartType partType;
138: if(g_mime_content_type_is_type(type, "text", "plain"))
139: partType=P_TEXT;
140: else if(g_mime_content_type_is_type(type, "text", "html"))
141: partType=P_HTML;
142: else if(g_mime_content_type_is_type(type, "message", "*"))
143: partType=P_MESSAGE;
144: else
145: partType=P_FILE;
146:
147: // partName
1.40.2.3 paf 148: const char* partName;
1.1 paf 149: char partNameBuf[MAX_STRING];
1.40.2.3 paf 150: const char* partNameStart=part_name_starts[partType];
1.1 paf 151: if(int partNo=i.partCounts[partType]++) {
152: snprintf(partNameBuf, MAX_STRING, "%s%d", partNameStart, partNo);
153: partName=partNameBuf;
154: } else
155: partName=partNameStart;
156:
157: // $.partX[
158: VHash& vpartX=*new(pool) VHash(pool); putReceived(*i.body, partName, &vpartX);
159: Hash& partX=vpartX.hash(0);
160: {
161: // $.raw[
162: VHash& vraw=*new(pool) VHash(pool); putReceived(partX, RAW_NAME, &vraw);
163: g_mime_header_foreach(part->headers, MimeHeaderField2received, &vraw.hash(0));
164: }
1.40.2.3 paf 165: const char* content_filename=0;
1.1 paf 166: {
167: // $.content-type[
168: VHash& vcontent_type=*new(pool) VHash(pool); putReceived(partX, "content-type", &vcontent_type);
169: Hash& content_type=vcontent_type.hash(0);
170: {
171: // $.value[text/plain]
172: char value[MAX_STRING];
173: snprintf(value, MAX_STRING, "%s/%s",
174: type->type?type->type:"x-unknown",
175: type->subtype?type->subtype:"x-unknown");
176: putReceived(content_type, VALUE_NAME, value);
177: }
178: GMimeParam *param=type->params;
179: while(param) {
180: // $.charset[windows-1251] && co
181: putReceived(content_type, param->name, param->value, true);
182: if(strcasecmp(param->name, "name")==0)
183: content_filename=param->value;
184: param=param->next;
185: }
186: }
187: // $.description
188: putReceived(partX, "description", part->description);
189: // $.content-id
190: putReceived(partX, "content-id", part->content_id);
191: // $.content-md5
192: putReceived(partX, "content-md5", part->content_md5);
193: // $.content-location
194: putReceived(partX, "content-location", part->content_location);
195:
196: // todo GMimePart:
197: // GMimePartEncodingType encoding;
198: // GMimeDisposition *disposition;
199: if(part->disposition) {
200: GMimeParam *param=part->disposition->params;
201: while(param) {
202: // $.charset[windows-1251] && co
203: if(strcasecmp(param->name, "filename")==0)
204: content_filename=param->value;
205: param=param->next;
206: }
207: }
208:
209: // MESSAGE
210: if(partType==P_MESSAGE) {
211: if(part->content)
212: if(GMimeStream *stream=part->content->stream)
1.23 paf 213: parse(*i.r, stream, partX);
1.1 paf 214: } else {
215: // $.value[string|file]
216: size_t buf_len;
1.15 paf 217: const void *local_buf=g_mime_part_get_content(part, &buf_len);
1.1 paf 218: if(partType==P_FILE) {
219: VFile& vfile=*new(pool) VFile(pool);
1.40.2.8 paf 220: char *global_buf=pool.copy(local_buf, buf_len);
1.23 paf 221: VString *vcontent_type=content_filename?
222: new(pool) VString(i.r->mime_type_of(content_filename)):0;
223: vfile.set(true/*tainted*/, global_buf, buf_len, content_filename, vcontent_type);
1.1 paf 224: putReceived(partX, VALUE_NAME, &vfile);
225: } else {
226: // P_TEXT, P_HTML
1.15 paf 227: putReceived(partX, VALUE_NAME,(const char*)local_buf, buf_len);
1.1 paf 228: }
229: }
230: }
231: }
232:
1.30 paf 233: int gmt_offset() {
234: #if defined(HAVE_TIMEZONE) && defined(HAVE_DAYLIGHT)
1.31 paf 235: return timezone+(daylight?60*60*(timezone<0?-1:timezone>0?+1:0):0);
1.30 paf 236: #else
237: time_t t=time(0);
238: tm *tm=localtime(&t);
239: #if defined(HAVE_TM_GMTOFF)
240: return tm->tm_gmtoff;
241: #elif defined(HAVE_TM_TZADJ)
242: return tm->tm_tzadj;
243: #else
244: #error neither HAVE_TIMEZONE&HAVE_DAYIGHT nor HAVE_TM_GMTOFF nor HAVE_TM_TZADJ defined
245: #endif
246: #endif
247: }
248:
1.23 paf 249: static void parse(Request& r, GMimeStream *stream, Hash& received) {
1.1 paf 250: Pool& pool=received.pool();
251:
252: GMimeMessage *message=g_mime_parser_construct_message(stream);
253: try {
254: const GMimeMessageHeader *messageHeader=message->header;
255: if(!messageHeader)
256: return;
257:
258: // firstly user-defined strings go
259: // user headers
260: {
261: // $.raw[
1.5 paf 262: VHash& vraw=*new(pool) VHash(pool); putReceived(received, RAW_NAME, &vraw);
1.1 paf 263: g_mime_header_foreach(messageHeader->headers, MimeHeaderField2received, &vraw.hash(0));
264: }
265:
266: // maybe-todo-recipients
267: // x(messageHeader->recipients)
268:
269: // secondly standard headers&body go
270: // standard header
271: // .from
272: putReceived(received, "from", messageHeader->from);
273: // .reply-to
274: putReceived(received, "reply-to", messageHeader->reply_to);
275: // .to
276: // todo: messageHeader->recipients
277: // .subject
278: putReceived(received, "subject", messageHeader->subject);
279: // .date(date+gmt_offset)
280: int tt_offset =
281: ((messageHeader->gmt_offset / 100) *(60 * 60))
282: +(messageHeader->gmt_offset % 100) * 60;
1.30 paf 283:
1.1 paf 284: putReceived(received, "date",
285: messageHeader->date // local sender
286: -tt_offset // move local sender to GMT sender
1.30 paf 287: -gmt_offset() // move GMT sender to our local time
1.1 paf 288: );
289: // .message-id
290: putReceived(received, "message-id", messageHeader->message_id);
291:
292: // .body[part/parts
293: GMimePart *part=message->mime_part;
294: const GMimeContentType *type=g_mime_part_get_content_type(part);
1.23 paf 295: MimePart2bodyInfo info={&r, &received};
1.1 paf 296: g_mime_part_foreach(part, MimePart2body, &info);
297:
298: // normal unref
299: g_mime_object_unref(GMIME_OBJECT(message));
300: } catch(...) {
301: // abnormal unref
302: g_mime_object_unref(GMIME_OBJECT(message));
303: }
304: }
305: #endif
306:
307:
308:
1.40.2.5 paf 309: void VMail::fill_received(Pool& pool, Request_info& request_info) {
1.1 paf 310: // store letter to received
1.3 paf 311: #ifdef WITH_MAILRECEIVE
1.40.2.4 paf 312: if(request_info.mail_received) {
1.1 paf 313: // init
314: g_mime_init(GMIME_INIT_FLAG_UTF8);
315:
316: // create stream with CRLF filter
317: GMimeStream *stream = g_mime_stream_fs_new(fileno(stdin));
318: GMimeStream *istream = g_mime_stream_filter_new_with_stream(stream);
319: GMimeFilter *filter = g_mime_filter_crlf_new(GMIME_FILTER_CRLF_DECODE, GMIME_FILTER_CRLF_MODE_CRLF_ONLY);
320: g_mime_stream_filter_add(GMIME_STREAM_FILTER(istream), filter);
321: g_mime_stream_unref(stream);
322: stream = istream;
323: try {
324: // parse incoming stream
1.23 paf 325: parse(r, stream, vreceived.hash(0));
1.1 paf 326: // normal stream free
327: g_mime_stream_unref(stream);
328: } catch(...) {
329: // abnormal stream free
330: g_mime_stream_unref(stream);
331: }
332: }
333: #endif
334: }
335:
1.9 paf 336: typedef int (*string_contains_char_which_check)(int);
1.40.2.3 paf 337: static bool string_contains_char_which(const char* string, string_contains_char_which_check check) {
1.9 paf 338: while(char c=*string++) {
339: if(check(c))
340: return true;
341: }
342: return false;
343: }
1.32 paf 344: static char *trimBoth(char *s) {
345: // sanity check
346: if(!s)
347: return 0;
348:
349: // trim head whitespace
350: while(*s && isspace(*s))
351: s++;
352: // trim tail whitespace
353: char *tail=s+strlen(s);
354: if(tail>s) {
355: do {
356: --tail;
357: if(isspace(*tail))
358: *tail=0;
359: } while(tail>s);
360: }
361: // return it
362: return s;
363: }
1.40.2.5 paf 364: static void extractEmail(StringPtr result, char *email, StringPtr origin_string) {
1.32 paf 365: email=trimBoth(email);
1.40.2.5 paf 366: result->APPEND_TAINTED(email, 0, origin_string->origin().file, origin_string->origin().line);
1.9 paf 367:
368: /*
369: http://www.faqs.org/rfcs/rfc822.html
370:
371: addr-spec = local-part "@" domain ; global address
372:
373: local-part = word *("." word) ; uninterpreted case-preserved
374: word = atom / quoted-string
375:
376: domain = sub-domain *("." sub-domain)
377: sub-domain = domain-ref / domain-literal
378: domain-ref = atom ; symbolic reference
379:
380: domain-literal << ignoring for now
381: quoted-string in word << ignoring for now
382:
383: atom = 1*<any CHAR except specials, SPACE and CTLs> << the ONLY to check
384:
385: specials = "(" / ")" / "<" / ">" / "@" ; Must be in quoted-
386: / "," / ";" / ":" / "\" / <"> ; string, to use
387: / "." / "[" / "]" ; within a word.
388:
389: */
1.40.2.3 paf 390: const char* exception_type="email.format";
1.9 paf 391: if(strpbrk(email, "()<>,;:\\\"[]"/*specials minus @ and . */))
1.17 paf 392: throw Exception(exception_type,
1.40.2.5 paf 393: result,
1.32 paf 394: "email contains bad characters (specials)");
1.9 paf 395: if(string_contains_char_which(email, (string_contains_char_which_check)isspace))
1.17 paf 396: throw Exception(exception_type,
1.40.2.5 paf 397: result,
1.32 paf 398: "email contains bad characters (whitespace)");
1.9 paf 399: if(string_contains_char_which(email, (string_contains_char_which_check)iscntrl))
1.17 paf 400: throw Exception(exception_type,
1.40.2.5 paf 401: result,
1.32 paf 402: "email contains bad characters (control)");
1.40.2.5 paf 403: if(result->is_empty())
1.17 paf 404: throw Exception(exception_type,
1.40.2.5 paf 405: origin_string,
1.16 paf 406: "email is empty");
1.39 paf 407: }
408:
1.40.2.5 paf 409: /*nonstatic*/StringPtr extractEmails(Pool& pool, StringPtr string) { // used in classes/mail.C, which supported for backward compatibility
410: char *emails=string->cstr(pool);
411: StringPtr result(new String);
1.39 paf 412: while(char *email=lsplit(&emails, ',')) {
413: rsplit(email, '>');
414: if(char *in_brackets=lsplit(email, '<'))
415: email=in_brackets;
1.40.2.5 paf 416: if(!result->is_empty())
417: *result<<",";
1.39 paf 418: extractEmail(result, email, string);
419: }
1.9 paf 420:
421: return result;
422: }
1.40.2.5 paf 423:
424: #ifndef DOXYGEN
425: struct Store_message_element_info {
426: Pool* pool;
427: Request_charsets* charsets;
428: StringPtr header;
429: StringPtr from;
430: StringPtr to;
431: StringPtr errors_to;
432: bool mime_version_specified;
433: ArrayValuePtr parts[P_TYPES_COUNT];
434: int parts_count;
435: bool has_content_type;
436: };
437: #endif
438: static void store_message_element(
439: HashStringValue::key_type raw_element_name,
440: HashStringValue::value_type element_value,
441: Store_message_element_info *info) {
442: StringPtr low_element_name=raw_element_name->change_case(*info->pool,
443: info->charsets->source(), String::CC_LOWER);
1.1 paf 444:
445: // exclude internals
1.40.2.5 paf 446: if(*low_element_name==CHARSET_NAME
447: || *low_element_name==VALUE_NAME
448: || *low_element_name==RAW_NAME
449: || *low_element_name=="date")
1.1 paf 450: return;
451:
452: // grep parts
453: for(int pt=0; pt<P_TYPES_COUNT; pt++) {
1.40.2.5 paf 454: if(low_element_name->starts_with(part_name_starts[pt])) {
1.29 paf 455: // check that $.message# '#' is digit
456: size_t start_len=strlen(part_name_starts[pt]);
1.40.2.5 paf 457: if(low_element_name->size()>start_len) {
458: CharPtr at_num=low_element_name->mid(start_len, start_len+1)->cstr();
1.29 paf 459: if(!isdigit(*at_num))
460: continue;
461: }
1.40.2.5 paf 462: *info->parts[pt]+=element_value;
463: info->parts_count++;
1.1 paf 464: return;
465: }
466: }
467:
1.39 paf 468: bool skip=false;
469:
1.10 paf 470: // fetch some special headers
1.40.2.5 paf 471: if(*low_element_name=="from")
472: info->from=extractEmails(*info->pool, element_value->as_string(info->pool));
473: if(info->to) { // defined only on WIN32, see mail.C [collecting info for RCPT to-s]
474: bool is_to=*low_element_name=="to" ;
475: bool is_cc=*low_element_name=="cc" ;
476: bool is_bcc=*low_element_name=="bcc" ;
1.39 paf 477: if(is_to||is_cc||is_bcc) {
1.40.2.5 paf 478: if(!info->to)
479: info->to=StringPtr(new String);
1.39 paf 480: else
1.40.2.5 paf 481: *info->to << ",";
482: *info->to << extractEmails(*info->pool, element_value->as_string(info->pool));
1.39 paf 483: }
484:
485: if(is_bcc) // blinding it
486: skip=true;
487: }
1.40.2.5 paf 488: if(*low_element_name=="errors-to")
489: info->errors_to=extractEmails(*info->pool, element_value->as_string(info->pool));
490: if(*low_element_name=="mime-version")
491: info->mime_version_specified=true;
1.1 paf 492:
1.39 paf 493: if(!skip) {
1.40.2.5 paf 494: StringPtr source_line=attributed_meaning_to_string(*info->pool, element_value);
495: const char *source_line_cstr=source_line->cstr(*info->pool);
496: const void *mail_ptr; size_t mail_size;
497: Charset::transcode(*info->pool,
498: info->charsets->source(), source_line_cstr, source_line->size(),
499: info->charsets->mail(), mail_ptr, mail_size);
500: StringPtr mail_line(new String);
501: mail_line->APPEND((const char*)mail_ptr, mail_size, String::UL_MAIL_HEADER,
502: source_line->origin().file,
503: source_line->origin().line
504: );
1.40.2.1 paf 505:
1.40.2.5 paf 506: // append header line
507: *info->header
508: << raw_element_name
509: << ":" << mail_line->cstr(*info->pool, String::UL_UNSPECIFIED, 0, info->charsets)
510: << "\n";
1.39 paf 511: }
1.1 paf 512:
513: // has content type?
1.40.2.5 paf 514: if(*low_element_name==CONTENT_TYPE_NAME)
515: info->has_content_type=true;
1.1 paf 516: }
517:
1.40.2.5 paf 518: static StringPtr file_value_to_string(Request& r, StringPtr source,
519: ValuePtr send_value) {
1.1 paf 520: Pool& pool=r.pool();
1.40.2.5 paf 521: VFilePtr vfile;
522: StringPtr file_name(0);
523: ValuePtr vformat(0);
524: if(HashStringValue *send_hash=send_value->get_hash(source)) { // hash
1.1 paf 525: // $.value
1.40.2.5 paf 526: if(ValuePtr value=send_hash->get(value_name))
527: vfile=value->as_vfile(pool, String::UL_AS_IS);
1.1 paf 528: else
529: throw Exception("parser.runtime",
530: source,
531: "file part has no $value");
532:
533: // $.format
1.40.2.5 paf 534: vformat=send_hash->get(format_name);
1.1 paf 535:
1.6 paf 536: // $.name
1.40.2.5 paf 537: if(ValuePtr vfile_name=send_hash->get(name_name)) // $name specified
538: file_name=vfile_name->as_string(&pool);
1.28 paf 539: } else // must be VFile then
1.40.2.5 paf 540: vfile=send_value->as_vfile(pool, String::UL_AS_IS);
1.28 paf 541:
542: if(!file_name)
1.40.2.5 paf 543: file_name=vfile->fields().get(name_name)->as_string(&pool);
1.28 paf 544:
1.40.2.3 paf 545: const char* file_name_cstr=file_name->cstr();
1.1 paf 546:
1.40.2.5 paf 547: StringPtr result(new String);
1.1 paf 548:
549: // content-type: application/octet-stream
1.40.2.5 paf 550: *result << "content-type: " << r.mime_type_of(file_name_cstr)
1.1 paf 551: << "; name=\"" << file_name_cstr << "\"\n";
552: // content-disposition: attachment; filename="user_file_name"
1.40.2.5 paf 553: *result <<
1.35 paf 554: CONTENT_DISPOSITION_NAME ": "CONTENT_DISPOSITION_VALUE"; "
555: CONTENT_DISPOSITION_FILENAME_NAME"=\"" << file_name_cstr << "\"\n";
1.1 paf 556:
1.40.2.5 paf 557: StringPtr type=vformat?vformat->as_string(&pool):StringPtr(0);
1.1 paf 558: if(!type/*default = uue*/ || *type=="uue") {
1.40.2.5 paf 559: pa_uuencode(pool, *result, file_name, *vfile);
1.1 paf 560: } else // for now
561: throw Exception("parser.runtime",
562: type,
563: "unknown attachment encode format");
564:
565: return result;
566: }
567:
1.40.2.5 paf 568: static StringPtr text_value_to_string(Request& r, StringPtr source,
569: PartType pt, ValuePtr send_value,
1.1 paf 570: Store_message_element_info& info) {
571: Pool& pool=r.pool();
1.40.2.5 paf 572: StringPtr result(new String);
1.1 paf 573:
1.40.2.5 paf 574: ValuePtr text_value;
575: if(HashStringValue* send_hash=send_value->get_hash(source)) {
1.1 paf 576: // $.USER-HEADERS
577: info.has_content_type=false; // reset
578: send_hash->for_each(store_message_element, &info);
579: // $.value
1.40.2.5 paf 580: text_value=send_hash->get(value_name);
1.1 paf 581: if(!text_value)
582: throw Exception("parser.runtime",
583: source,
584: "%s part has no $" VALUE_NAME, part_name_starts[pt]);
585: } else
1.40.2.5 paf 586: text_value=send_value;
1.1 paf 587:
588: if(!info.has_content_type) {
1.40.2.5 paf 589: *result
590: << "content-type: text/" << (pt==P_TEXT?"plain":"html")
591: << "; charset=" << info.charsets->mail().name()
592: << "\n";
1.1 paf 593: }
594:
595: // header|body separator
1.40.2.5 paf 596: *result << "\n";
1.1 paf 597:
598: // body
599: switch(pt) {
600: case P_TEXT:
1.40.2.5 paf 601: *result<<text_value->as_string(&pool);
1.1 paf 602: break;
603: case P_HTML:
604: {
1.25 paf 605: Temp_lang temp_lang(r, String::UL_HTML | String::UL_OPTIMIZE_BIT);
1.40.2.5 paf 606: if(JunctionPtr junction=text_value->get_junction())
607: *result << r.process_to_string(text_value);
1.26 paf 608: else
1.1 paf 609: throw Exception("parser.runtime",
610: source,
611: "html part value must be code");
612:
613: break;
614: }
615: }
616:
617: return result;
618: };
619:
620: /// @todo files and messages in order (file, file2, ...)
1.40.2.5 paf 621: StringPtr VMail::message_hash_to_string(Request& r, StringPtr source,
622: HashStringValue* message_hash, int level,
623: StringPtr& from, StringPtr& to) {
1.34 paf 624: Pool& pool=r.pool();
625:
1.1 paf 626: if(!message_hash)
627: throw Exception("parser.runtime",
628: source,
629: "message must be hash");
630:
1.40.2.5 paf 631: StringPtr result(new String);
1.1 paf 632:
1.40.2.5 paf 633: if(ValuePtr vrecodecharset_name=message_hash->get(charset_name))
634: r.charsets.set_mail(charsets.get(vrecodecharset_name->as_string(&pool)));
1.1 paf 635: else
1.40.2.5 paf 636: r.charsets.set_mail(r.charsets.source());
637: // no big deal that we leave it set. they wont miss this point which would reset it
1.1 paf 638:
1.40.2.5 paf 639: Store_message_element_info info;
640: info.header=result;
641: info.from=from;
642: info.to=to;
1.1 paf 643: {
644: for(int pt=0; pt<P_TYPES_COUNT; pt++)
1.40.2.5 paf 645: info.parts[pt]=ArrayValuePtr(new ArrayValue(1));
1.1 paf 646: message_hash->for_each(store_message_element, &info);
1.10 paf 647: if(!info.errors_to)
1.40.2.5 paf 648: *result << "errors-to: postmaster\n"; // errors-to: default
1.37 paf 649: if(!info.mime_version_specified)
1.40.2.5 paf 650: *result << "MIME-Version: 1.0\n"; // MIME-Version: default
1.1 paf 651: }
652:
1.40.2.5 paf 653: int textCount=info.parts[P_TEXT]->count();
1.1 paf 654: if(textCount>1)
655: throw Exception("parser.runtime",
656: source,
657: "multiple text parts not supported, use file part");
1.40.2.5 paf 658: int htmlCount=info.parts[P_HTML]->count();
1.1 paf 659: if(htmlCount>1)
660: throw Exception("parser.runtime",
661: source,
662: "multiple html parts not supported, use file part");
663:
664:
665: bool multipart=info.parts_count>1;
666: bool alternative=textCount && htmlCount;
667: // header
668: char *boundary=0;
669: if(multipart) {
1.40.2.9! paf 670: boundary=new(pool) char[MAX_NUMBER];
1.1 paf 671: snprintf(boundary, MAX_NUMBER-5/*lEvEl*/, "lEvEl%d", level);
672: // multi-part
1.40.2.5 paf 673: *result
674: << "content-type: multipart/mixed; boundary=\""
675: << boundary << "\"\n"
676: "\n"
677: "This is a multi-part message in MIME format.";
1.1 paf 678: }
679:
680: // alternative or not
681: {
682: if(alternative) {
1.40.2.5 paf 683: *result << "\n\n--" << boundary << "\n" // intermediate boundary
684: "content-type: multipart/alternative; boundary=\"ALT" << boundary << "\"\n";
1.1 paf 685: }
686: for(int i=0; i<2; i++) {
687: PartType pt=i==0?P_TEXT:P_HTML;
1.40.2.5 paf 688: if(info.parts[pt]->count()) {
1.1 paf 689: if(alternative)
1.40.2.5 paf 690: *result << "\n\n--ALT" << boundary << "\n"; // intermediate boundary
1.1 paf 691: else if(boundary)
1.40.2.5 paf 692: *result << "\n\n--" << boundary << "\n"; // intermediate boundary
693: *result << *text_value_to_string(r, source, pt, info.parts[pt]->get(0), info);
1.1 paf 694: }
695: }
696: if(alternative)
1.40.2.5 paf 697: *result << "\n\n--ALT" << boundary << "--\n";
1.1 paf 698: }
699:
700: // files
701: {
1.40.2.5 paf 702: ArrayValue& files=*info.parts[P_FILE];
703: for(int i=0; i<files.count(); i++) {
1.1 paf 704: if(boundary)
1.40.2.5 paf 705: *result << "\n\n--" << boundary << "\n"; // intermediate boundary
706: *result << file_value_to_string(r, source, files.get(i));
1.1 paf 707: }
708: }
709:
710: // messages
711: {
1.40.2.5 paf 712: ArrayValue& messages=*info.parts[P_MESSAGE];
713: for(int i=0; i<messages.count(); i++) {
1.1 paf 714: if(boundary)
1.40.2.5 paf 715: *result << "\n\n--" << boundary << "\n"; // intermediate boundary
1.1 paf 716:
1.40.2.5 paf 717: StringPtr dummy_from;
718: StringPtr dummy_to;
719: *result << message_hash_to_string(r, source, messages.get(i)->get_hash(source), level+1,
720: dummy_from, dummy_to);
1.1 paf 721: }
722: }
723:
724: // tailer
725: if(boundary)
1.40.2.5 paf 726: *result << "\n\n--" << boundary << "--\n"; // finish boundary
1.1 paf 727:
728: // return
729: return result;
730: }
731:
732:
1.40.2.5 paf 733: ValuePtr VMail::get_element(StringPtr aname, Value& aself, bool looking_up) {
1.1 paf 734: // $fields
1.3 paf 735: #ifdef WITH_MAILRECEIVE
1.40.2.5 paf 736: if(*aname==MAIL_RECEIVED_ELEMENT_NAME)
737: return vreceived;
1.1 paf 738: #endif
739:
740: // $CLASS,$method
1.40.2.5 paf 741: if(ValuePtr result=VStateless_class::get_element(aname, aself, looking_up))
1.1 paf 742: return result;
743:
1.40.2.6 paf 744: return ValuePtr(0);
1.1 paf 745: }
746:
1.3 paf 747: #if defined(WITH_MAILRECEIVE) && defined(_MSC_VER)
1.1 paf 748: # define GNOME_LIBS "/parser3project/win32mailreceive/win32/gnome"
749: # pragma comment(lib, GNOME_LIBS "/glib/lib/libglib-1.3-11.lib")
750: # ifdef _DEBUG
751: # pragma comment(lib, GNOME_LIBS "/gmime-x.x.x/Debug/libgmime.lib")
752: # else
753: # pragma comment(lib, GNOME_LIBS "/gmime-x.x.x/Release/libgmime.lib")
754: # endif
755: #endif
E-mail: