Bug Summary

File:libsynthesis/src/sysync/textprofile.cpp
Warning:line 459, column 5
Value stored to 'p' is never read

Annotated Source Code

1/*
2 * File: textprofile.cpp
3 *
4 * Author: Lukas Zeller (luz@plan44.ch)
5 *
6 * TTextProfile
7 * utility class to parse line-by-line type text including RFC822 emails
8 *
9 * Copyright (c) 2002-2011 by Synthesis AG + plan44.ch
10 *
11 * 2005-07-26 : luz : extracted from textitemtype
12 *
13 */
14
15
16// includes
17#include "prefix_file.h"
18
19#include "sysync.h"
20#include "textprofile.h"
21
22
23using namespace sysync;
24
25
26// Config
27// ======
28
29#define X_LIMIT_HEADER_NAME"X-Sync-Message-Limit" "X-Sync-Message-Limit"
30
31// line map config
32
33TLineMapDefinition::TLineMapDefinition(TConfigElement *aParentElementP, sInt16 aFid) :
34 TConfigElement("lm",aParentElementP)
35{
36 // save field ID
37 fFid=aFid;
38 // init others
39 clear();
40} // TLineMapDefinition::TLineMapDefinition
41
42
43
44void TLineMapDefinition::clear(void)
45{
46 // clear
47 // - default: all text
48 fNumLines=0;
49 fInHeader=false; // not restricted to header
50 fAllowEmpty=false; // no empty ones
51 // - no tagged header
52 TCFG_CLEAR(fHeaderTag)fHeaderTag.erase();
53 // - no RFC822 specials
54 fValueType=vt822_plain;
55 fListSeparator=0;
56 fMaxRepeat=1;
57 fRepeatInc=1;
58 #ifdef OBJECT_FILTERING1
59 // - no filterkeyword
60 TCFG_CLEAR(fFilterKeyword)fFilterKeyword.erase();
61 #endif
62} // TLineMapDefinition::clear
63
64
65#ifdef CONFIGURABLE_TYPE_SUPPORT1
66
67// Conversion names for RFC(2)822 parsing
68const char * const RFC822ValueTypeNames[num822ValueTypes] = {
69 "text",
70 "date",
71 "body",
72 "rfc2047"
73};
74
75
76
77// server config element parsing
78bool TLineMapDefinition::localStartElement(const char *aElementName, const char **aAttributes, sInt32 aLine)
79{
80 // checking the elements
81 if (strucmp(aElementName,"numlines")==0)
82 expectInt16(fNumLines);
83 else if (strucmp(aElementName,"inheader")==0)
84 expectBool(fInHeader);
85 else if (strucmp(aElementName,"allowempty")==0)
86 expectBool(fAllowEmpty);
87 else if (strucmp(aElementName,"headertag")==0)
88 expectString(fHeaderTag);
89 // filtering
90 #ifdef OBJECT_FILTERING1
91 else if (strucmp(aElementName,"filterkeyword")==0)
92 expectString(fFilterKeyword);
93 #endif
94 // RFC(2)822 parsing options
95 else if (strucmp(aElementName,"valuetype")==0)
96 expectEnum(sizeof(fValueType),&fValueType,RFC822ValueTypeNames,num822ValueTypes);
97 else if (strucmp(aElementName,"list")==0) {
98 // list spec
99 // - separator
100 const char *attr = getAttr(aAttributes,"separator");
101 if (!attr)
102 fail("list needs 'separator'");
103 fListSeparator=*attr;
104 // - max repetitions
105 fMaxRepeat=1;
106 attr = getAttr(aAttributes,"repeat");
107 if (attr) {
108 #ifdef ARRAYFIELD_SUPPORT1
109 if (strucmp(attr,"array")==0) fMaxRepeat=REP_ARRAY32767;
110 else
111 #endif
112 if (!StrToShort(attr,fMaxRepeat))
113 return !fail("expected number or 'array' in 'repeat'");
114 }
115 fRepeatInc=1;
116 if (!getAttrShort(aAttributes,"increment",fRepeatInc,true))
117 return !fail("number expected in 'increment'");
118 }
119 // - none known here
120 else
121 return inherited::localStartElement(aElementName,aAttributes,aLine);
122 // ok
123 return true;
124} // TLineMapDefinition::localStartElement
125
126#endif
127
128
129TLineMapDefinition::~TLineMapDefinition()
130{
131 // nop so far
132} // TLineMapDefinition::~TLineMapDefinition
133
134
135
136// text-based datatype config
137
138TTextProfileConfig::TTextProfileConfig(const char *aElementName, TConfigElement *aParentElementP) :
139 TProfileConfig(aElementName,aParentElementP)
140{
141 clear();
142} // TTextProfileConfig::TTextProfileConfig
143
144
145TTextProfileConfig::~TTextProfileConfig()
146{
147 // make sure everything is deleted (was missing long time and caused mem leaks!)
148 clear();
149} // TTextProfileConfig::~TTextProfileConfig
150
151
152// init defaults
153void TTextProfileConfig::clear(void)
154{
155 // clear properties
156 // - init
157 fFieldListP=NULL__null;
158 #ifdef EMAIL_FORMAT_SUPPORT1
159 fMIMEMail=false;
160 fBodyMIMETypesFid=VARIDX_UNDEFINED-128;
161 fSizeLimitField=VARIDX_UNDEFINED-128;
162 fBodyCountFid=VARIDX_UNDEFINED-128;
163 #ifdef EMAIL_ATTACHMENT_SUPPORT1
164 // - no limit
165 fMaxAttachments=29999; // enough
166 // - fields not yet known
167 fAttachmentCountFid=VARIDX_UNDEFINED-128;
168 fAttachmentMIMETypesFid=VARIDX_UNDEFINED-128;
169 fAttachmentContentsFid=VARIDX_UNDEFINED-128;
170 fAttachmentSizesFid=VARIDX_UNDEFINED-128;
171 fAttachmentNamesFid=VARIDX_UNDEFINED-128;
172 #endif
173 #endif
174 // - remove line maps
175 TLineMapList::iterator pos;
176 for(pos=fLineMaps.begin();pos!=fLineMaps.end();pos++)
177 delete *pos;
178 fLineMaps.clear();
179 // clear inherited
180 inherited::clear();
181} // TTextProfileConfig::clear
182
183
184#ifdef CONFIGURABLE_TYPE_SUPPORT1
185
186
187// config element parsing
188bool TTextProfileConfig::localStartElement(const char *aElementName, const char **aAttributes, sInt32 aLine)
189{
190 /* %%% this must be enabled if we want to reference script vars
191 #ifdef SCRIPT_SUPPORT
192 // early resolve basic multifield scripts so we can refer to local vars
193 if (!fScriptsResolved) %%% resolve resolvecontext;
194 #endif
195 */
196 // checking the elements
197 if (strucmp(aElementName,"linemap")==0) {
198 // <linemap field="SUBJECT">
199 const char* nam = getAttr(aAttributes,"field");
200 if (!fFieldListP)
201 return fail("'use' must be specified before first <linemap>");
202 // search field
203 // %%% add context here if we have any
204 sInt16 fid = TConfigElement::getFieldIndex(nam,fFieldListP);
205 if (fid==VARIDX_UNDEFINED-128)
206 return fail("'field' references unknown field '%s'",nam);
207 // create new linemap
208 TLineMapDefinition *linemapP = new TLineMapDefinition(this,fid);
209 // - save in list
210 fLineMaps.push_back(linemapP);
211 // - let element handle parsing
212 expectChildParsing(*linemapP);
213 }
214 #ifdef EMAIL_FORMAT_SUPPORT1
215 else if (strucmp(aElementName,"mimemail")==0)
216 expectBool(fMIMEMail);
217 else if (strucmp(aElementName,"sizelimitfield")==0)
218 expectFieldID(fSizeLimitField,fFieldListP);
219 else if (strucmp(aElementName,"bodymimetypesfield")==0)
220 expectFieldID(fBodyMIMETypesFid,fFieldListP);
221 else if (strucmp(aElementName,"bodycountfield")==0)
222 expectFieldID(fBodyCountFid,fFieldListP);
223 #ifdef EMAIL_ATTACHMENT_SUPPORT1
224 else if (strucmp(aElementName,"maxattachments")==0)
225 expectInt16(fMaxAttachments);
226 else if (strucmp(aElementName,"attachmentcountfield")==0)
227 expectFieldID(fAttachmentCountFid,fFieldListP);
228 else if (strucmp(aElementName,"attachmentmimetypesfield")==0)
229 expectFieldID(fAttachmentMIMETypesFid,fFieldListP);
230 else if (strucmp(aElementName,"attachmentsfield")==0)
231 expectFieldID(fAttachmentContentsFid,fFieldListP);
232 else if (strucmp(aElementName,"attachmentsizesfield")==0)
233 expectFieldID(fAttachmentSizesFid,fFieldListP);
234 else if (strucmp(aElementName,"attachmentnamesfield")==0)
235 expectFieldID(fAttachmentNamesFid,fFieldListP);
236 #endif
237 #endif
238 // - none known here
239 else
240 return TProfileConfig::localStartElement(aElementName,aAttributes,aLine);
241 // ok
242 return true;
243} // TTextProfileConfig::localStartElement
244
245
246// resolve
247void TTextProfileConfig::localResolve(bool aLastPass)
248{
249 // nop
250 // resolve inherited
251 inherited::localResolve(aLastPass);
252} // TTextProfileConfig::localResolve
253
254#endif
255
256
257#ifdef HARDCODED_TYPE_SUPPORT
258
259TLineMapDefinition *TTextProfileConfig::addLineMap(
260 sInt16 aFid, sInt16 aNumLines, bool aAllowEmpty,
261 bool aInHeader, const char* aHeaderTag,
262 T822ValueType aValueType,
263 char aListSeparator, sInt16 aMaxRepeat, sInt16 aRepeatInc
264)
265{
266 // create new linemap
267 TLineMapDefinition *linemapP = new TLineMapDefinition(this,aFid);
268 // set properties
269 linemapP->fNumLines=aNumLines;
270 linemapP->fAllowEmpty=aAllowEmpty;
271 linemapP->fInHeader=aInHeader;
272 TCFG_ASSIGN(linemapP->fHeaderTag,aHeaderTag){ if (aHeaderTag) linemapP->fHeaderTag=aHeaderTag; else linemapP
->fHeaderTag.erase(); }
;
273 // save email options
274 linemapP->fValueType=aValueType;
275 linemapP->fListSeparator=aListSeparator;
276 linemapP->fListSeparator=aMaxRepeat;
277 linemapP->fListSeparator=aRepeatInc;
278 // save in list
279 fLineMaps.push_back(linemapP);
280 // return pointer
281 return linemapP;
282} // TTextProfileConfig::addLineMap
283
284#endif
285
286
287// handler factory
288TProfileHandler *TTextProfileConfig::newProfileHandler(TMultiFieldItemType *aItemTypeP)
289{
290 // check if fieldlists match as they should
291 if (aItemTypeP->getFieldDefinitions()!=fFieldListP) {
292 // profile is for another field list, cannot be used for this item type
293 return NULL__null;
294 }
295 // our handler is the text profile handler
296 return (TProfileHandler *)(new TTextProfileHandler(this,aItemTypeP));
297}
298
299
300
301/*
302 * Implementation of TTextProfileHandler
303 */
304
305
306TTextProfileHandler::TTextProfileHandler(
307 TTextProfileConfig *aTextProfileCfgP,
308 TMultiFieldItemType *aItemTypeP
309) : TProfileHandler(aTextProfileCfgP, aItemTypeP)
310{
311 // save profile config pointer
312 fProfileCfgP = aTextProfileCfgP;
313 // datastore settable options defaults
314 fNoAttachments = false;
315 fItemSizeLimit = -1;
316} // TTextProfileHandler::TTextProfileHandler
317
318
319TTextProfileHandler::~TTextProfileHandler()
320{
321 // nop for now
322} // TTextProfileHandler::~TTextProfileHandler
323
324
325#ifdef OBJECT_FILTERING1
326
327// Filtering: add keywords and property names to filterCap
328void TTextProfileHandler::addFilterCapPropsAndKeywords(SmlPcdataListPtr_t &aFilterKeywords, SmlPcdataListPtr_t &aFilterProps, TTypeVariantDescriptor aVariantDescriptor, TSyncItemType *aItemTypeP)
329{
330 // search linemaps for fields with keyword
331 TLineMapList::iterator pos;
332 for(pos=fProfileCfgP->fLineMaps.begin();pos!=fProfileCfgP->fLineMaps.end();pos++) {
333 // first priority: compare with explicit filterkeyword, if any
334 if (!TCFG_ISEMPTY((*pos)->fFilterKeyword)(*pos)->fFilterKeyword.empty()) {
335 // has a filterkeyword, show it
336 addPCDataStringToList(TCFG_CSTR((*pos)->fFilterKeyword)(*pos)->fFilterKeyword.c_str(), &aFilterKeywords);
337 }
338 }
339} // TTextProfileHandler::addFilterCapPropsAndKeywords
340
341
342
343// get field index of given filter expression identifier.
344sInt16 TTextProfileHandler::getFilterIdentifierFieldIndex(const char *aIdentifier, uInt16 aIndex)
345{
346 // search linemaps for tagged fields
347 TLineMapList::iterator pos;
348 for(pos=fProfileCfgP->fLineMaps.begin();pos!=fProfileCfgP->fLineMaps.end();pos++) {
349 // first priority: compare with explicit filterkeyword, if any
350 if (!TCFG_ISEMPTY((*pos)->fFilterKeyword)(*pos)->fFilterKeyword.empty()) {
351 // compare with filterkeyword
352 if (strucmp(TCFG_CSTR((*pos)->fFilterKeyword)(*pos)->fFilterKeyword.c_str(),aIdentifier)==0)
353 return (*pos)->fFid;
354 }
355 else if (TCFG_SIZE((*pos)->fHeaderTag)(*pos)->fHeaderTag.size()>1) { // one tag char and a separator at least
356 // if no filterkeyword defined, compare to header without separator
357 if (strucmp(TCFG_CSTR((*pos)->fHeaderTag)(*pos)->fHeaderTag.c_str(),aIdentifier,TCFG_SIZE((*pos)->fHeaderTag)(*pos)->fHeaderTag.size()-1)==0)
358 return (*pos)->fFid;
359 }
360 }
361 // no field ID found in profile
362 return FID_NOT_SUPPORTED-128;
363} // TTextProfileHandler::getFilterIdentifierFieldIndex
364
365#endif
366
367
368#ifdef EMAIL_FORMAT_SUPPORT1
369
370
371// find token in header data
372static cAppCharP findToken(cAppCharP aText, cAppCharP aTextEnd, char aStopChar, cAppCharP &aTokenStart, stringSize &aTokenLen)
373{
374 // skip trailing whitespace
375 cAppCharP e=aTextEnd;
376 if (!e)
377 e=aText+strlen(aText);
378 while (aText<e && isspace(*aText)) aText++;
379 bool quoted = (*aText=='"'); // " to fool buggy colorizer
380 if (quoted) {
381 aText++;
382 }
383 // token starts here
384 aTokenStart=aText;
385 // find end
386 while (aText<e) {
387 if (quoted) {
388 if (*aText=='"') break; // " to fool buggy colorizer
389 if (*aText=='\\') {
390 aText++;
391 if (*aText)
392 aText++; // do not interpret next
393 }
394 }
395 else {
396 if (*aText==aStopChar || isspace(*aText)) break;
397 }
398 aText++;
399 }
400 aTokenLen=aText-aTokenStart;
401 // skip ending quote
402 if (aText<e && quoted && *aText=='"') aText++; // " to fool buggy colorizer
403 // advance to next non-space
404 while (aText<e && isspace(*aText)) aText++;
405 // return position
406 return aText;
407} // findToken
408
409
410// check if line contains a MIME header
411static bool checkMimeHeaders(cAppCharP aLine, stringSize aSize, string &aContentType, TEncodingTypes &aEncoding, uInt32 &aContentLen, TCharSets &aCharSet, string &aBoundary, string *aFileNameP)
412{
413 stringSize toksz,valtoksz;
414 cAppCharP tok,valtok,p,e;
415 sInt16 en;
416 e=aLine+aSize;
417 // search header name
418 p=aLine;
419 p=findToken(p,e,':',tok,toksz);
420 if (*p!=':') return false; // no header
421 ++p;
422 // compare header name
423 if (strucmp(tok,"Content-Type",toksz)==0) {
424 // get content type
425 p=findToken(p,e,';',tok,toksz);
426 aContentType.assign(tok,toksz);
427 // get parameters
428 while (*p==';') {
429 p++;
430 p=findToken(p,e,'=',tok,toksz);
431 if (*p=='=') {
432 p++;
433 // get value
434 p=findToken(p,e,';',valtok,valtoksz);
435 if (strucmp(tok,"charset",toksz)==0) {
436 // charset
437 if (StrToEnum(MIMECharSetNames, numCharSets, en, valtok, valtoksz))
438 aCharSet=(TCharSets)en;
439 else
440 aCharSet=chs_unknown;
441 }
442 else if (strucmp(tok,"boundary",toksz)==0) {
443 // boundary
444 aBoundary.assign(valtok,valtoksz);
445 }
446 }
447 } // while params
448 }
449 else if (strucmp(tok,"Content-Transfer-Encoding",toksz)==0) {
450 // get encoding
451 p=findToken(p,e,';',tok,toksz);
452 if (StrToEnum(MIMEEncodingNames, numMIMEencodings, en, tok, toksz))
453 aEncoding = (TEncodingTypes)en;
454 else
455 aEncoding = enc_none;
456 }
457 else if (strucmp(tok,"Content-Length",toksz)==0) {
458 // get content length (not MIME, this is Synthesis' own invention, only valid for encoding=binary!!)
459 p=findToken(p,e,';',tok,toksz);
Value stored to 'p' is never read
460 StrToULong(tok, aContentLen, toksz);
461 }
462 else if (aFileNameP && strucmp(tok,"Content-Disposition",toksz)==0) {
463 // get disposition
464 p=findToken(p,e,';',tok,toksz);
465 aFileNameP->erase();
466 if (strucmp(tok,"attachment",toksz)==0 || strucmp(tok,"inline",toksz)==0) {
467 while (*p==';') {
468 p++;
469 p=findToken(p,e,'=',tok,toksz);
470 if (strucmp(tok,"filename",toksz)==0) {
471 // filename, get it
472 if (*p=='=') {
473 p++;
474 p=findToken(p,e,';',tok,toksz);
475 aFileNameP->assign(tok,toksz);
476 }
477 }
478 else {
479 // other param, skip it
480 if (*p=='=') {
481 p++;
482 p=findToken(p,e,';',tok,toksz);
483 }
484 }
485 }
486 }
487 }
488 else return false; // no MIME header
489 // found MIME header
490 return true;
491} // checkMimeHeaders
492
493
494// parse body into appropriate field(s)
495cAppCharP TTextProfileHandler::parseBody(
496 cAppCharP aText, // body text to parse
497 stringSize aTextSize, // max text to parse
498 cAppCharP aType, TEncodingTypes aEncoding, uInt32 aContentLen, TCharSets aCharSet,
499 TMultiFieldItem &aItem, TLineMapDefinition *aLineMapP,
500 cAppCharP aBoundary // boundary, NULL if none
501)
502{
503 // empty boundary is no boundary
504 if (aBoundary && *aBoundary==0) aBoundary=NULL__null;
505 POBJDEBUGPRINTFX(fItemTypeP->getSession(),DBG_PARSE,({ if ((fItemTypeP->getSession()) && (((0x00000200)
& (fItemTypeP->getSession())->getDbgMask()) == (0x00000200
))) (fItemTypeP->getSession())->getDbgLogger()->setNextMask
(0x00000200).DebugPrintfLastMask ( "Parsing body (part) with Content-Type='%s', Encoding=%s, contentLen=%ld, Charset=%s, Boundary='%s'"
, aType, MIMEEncodingNames[aEncoding], (long)aContentLen, MIMECharSetNames
[aCharSet], aBoundary ? aBoundary : "<none>" ); }
506 "Parsing body (part) with Content-Type='%s', Encoding=%s, contentLen=%ld, Charset=%s, Boundary='%s'",{ if ((fItemTypeP->getSession()) && (((0x00000200)
& (fItemTypeP->getSession())->getDbgMask()) == (0x00000200
))) (fItemTypeP->getSession())->getDbgLogger()->setNextMask
(0x00000200).DebugPrintfLastMask ( "Parsing body (part) with Content-Type='%s', Encoding=%s, contentLen=%ld, Charset=%s, Boundary='%s'"
, aType, MIMEEncodingNames[aEncoding], (long)aContentLen, MIMECharSetNames
[aCharSet], aBoundary ? aBoundary : "<none>" ); }
507 aType,{ if ((fItemTypeP->getSession()) && (((0x00000200)
& (fItemTypeP->getSession())->getDbgMask()) == (0x00000200
))) (fItemTypeP->getSession())->getDbgLogger()->setNextMask
(0x00000200).DebugPrintfLastMask ( "Parsing body (part) with Content-Type='%s', Encoding=%s, contentLen=%ld, Charset=%s, Boundary='%s'"
, aType, MIMEEncodingNames[aEncoding], (long)aContentLen, MIMECharSetNames
[aCharSet], aBoundary ? aBoundary : "<none>" ); }
508 MIMEEncodingNames[aEncoding],{ if ((fItemTypeP->getSession()) && (((0x00000200)
& (fItemTypeP->getSession())->getDbgMask()) == (0x00000200
))) (fItemTypeP->getSession())->getDbgLogger()->setNextMask
(0x00000200).DebugPrintfLastMask ( "Parsing body (part) with Content-Type='%s', Encoding=%s, contentLen=%ld, Charset=%s, Boundary='%s'"
, aType, MIMEEncodingNames[aEncoding], (long)aContentLen, MIMECharSetNames
[aCharSet], aBoundary ? aBoundary : "<none>" ); }
509 (long)aContentLen,{ if ((fItemTypeP->getSession()) && (((0x00000200)
& (fItemTypeP->getSession())->getDbgMask()) == (0x00000200
))) (fItemTypeP->getSession())->getDbgLogger()->setNextMask
(0x00000200).DebugPrintfLastMask ( "Parsing body (part) with Content-Type='%s', Encoding=%s, contentLen=%ld, Charset=%s, Boundary='%s'"
, aType, MIMEEncodingNames[aEncoding], (long)aContentLen, MIMECharSetNames
[aCharSet], aBoundary ? aBoundary : "<none>" ); }
510 MIMECharSetNames[aCharSet],{ if ((fItemTypeP->getSession()) && (((0x00000200)
& (fItemTypeP->getSession())->getDbgMask()) == (0x00000200
))) (fItemTypeP->getSession())->getDbgLogger()->setNextMask
(0x00000200).DebugPrintfLastMask ( "Parsing body (part) with Content-Type='%s', Encoding=%s, contentLen=%ld, Charset=%s, Boundary='%s'"
, aType, MIMEEncodingNames[aEncoding], (long)aContentLen, MIMECharSetNames
[aCharSet], aBoundary ? aBoundary : "<none>" ); }
511 aBoundary ? aBoundary : "<none>"{ if ((fItemTypeP->getSession()) && (((0x00000200)
& (fItemTypeP->getSession())->getDbgMask()) == (0x00000200
))) (fItemTypeP->getSession())->getDbgLogger()->setNextMask
(0x00000200).DebugPrintfLastMask ( "Parsing body (part) with Content-Type='%s', Encoding=%s, contentLen=%ld, Charset=%s, Boundary='%s'"
, aType, MIMEEncodingNames[aEncoding], (long)aContentLen, MIMECharSetNames
[aCharSet], aBoundary ? aBoundary : "<none>" ); }
512 )){ if ((fItemTypeP->getSession()) && (((0x00000200)
& (fItemTypeP->getSession())->getDbgMask()) == (0x00000200
))) (fItemTypeP->getSession())->getDbgLogger()->setNextMask
(0x00000200).DebugPrintfLastMask ( "Parsing body (part) with Content-Type='%s', Encoding=%s, contentLen=%ld, Charset=%s, Boundary='%s'"
, aType, MIMEEncodingNames[aEncoding], (long)aContentLen, MIMECharSetNames
[aCharSet], aBoundary ? aBoundary : "<none>" ); }
;
513 // params for parts
514 string contentType=aType; // content type
515 string boundary; // main boundary
516 string filename; // attachment
517 TEncodingTypes encoding=aEncoding; // main encoding
518 uInt32 contentlen=aContentLen; // content len in case encoding is binary
519 TCharSets charset=aCharSet; // main charset
520 // helpers
521 string decoded;
522 string converted;
523 TItemField *fldP;
524 // check if alternative
525 bool alternative = strucmp(aType,"multipart/alternative")==0;
526 bool foundBody=false;
527 // search for first boundary occurrence if this is multipart
528 cAppCharP toBeParsed=NULL__null; // pointer to previous part to be processed
529 size_t previousSize=0;
530 cAppCharP prevStart=NULL__null;
531 cAppCharP p=aText;
532 cAppCharP eot=p+aTextSize;
533 bool startpart=false;
534 // process parts
535 do {
536 startpart=false;
537 // search for a boundary occurrence if this is multipart
538 if (aBoundary) {
539 size_t bl=strlen(aBoundary);
540 prevStart=p;
541 // check for special case: if we have something to parse, and encoding is binary,
542 // use the content-length header to skip to next boundary
543 if (toBeParsed && encoding==enc_binary) {
544 // we simply KNOW how much to skip until next boundary starts
545 p=toBeParsed+contentlen;
546 // p now points to the next boundary indicator
547 }
548 // now detect boundary indicator
549 do {
550 if (p+2+bl<=eot && strucmp(p,"--",2)==0) {
551 p+=2;
552 if (strucmp(p,aBoundary,bl)==0) {
553 // start of line matches boundary
554 startpart=true;
555 // calc how much we skipped until finding this boundary
556 previousSize=p-2-prevStart;
557 // A boundary has to be preceeded by a CRLF according to rfc2046, and
558 // this CRLF belongs to the boundary and is NOT considered to be content of the preceeding part
559 // Therefore, the following code is only needed for error tolerance reasons - for
560 // correctly formatted message it's just a -=2.
561 if (previousSize>2) {
562 if (*(p-3)==0x0A || *(p-3)==0x0D) previousSize--;
563 if (*(p-4)==0x0A || *(p-4)==0x0D) previousSize--;
564 }
565 // Note: in case of binary encoding, previousSize should be contentlen here!
566 p+=bl;
567 }
568 }
569 // skip until end of line
570 while (p<eot && *p && *p!='\x0A') p++;
571 if (p<eot && *p) p++; // skip LF as well
572 } while (p<eot && *p && !startpart);
573 // if we exit here and have no startpart, body does not contain expected parts
574 // so exit here
575 if (!startpart)
576 return p;
577 else {
578 // altough previousSize should be equal to contentlen here, trust contentlen
579 if (toBeParsed && encoding==enc_binary)
580 previousSize=contentlen;
581 }
582 }
583 // p is now start of space after found boundary if startpart is set
584 // previousSize is now size of space between where we've started and
585 // start of next boundary. If toBeParsed, this is a part, otherwise it's a preamble
586 if (toBeParsed) {
587 if (previousSize>0) {
588 // parse leaf part (DO NOT CHANGE p HERE, it points to next part)
589 // decide if this is body or attachment
590 bool isText = contentType.empty() || (strucmp(contentType.c_str(),"text/",5)==0);
591 if (isText && filename.empty()) {
592 // this is a part of the body
593 // - check if we can store multiple bodies
594 bool multibody = aItem.getField(aLineMapP->fFid)->isArray();
595 if (!foundBody || multibody) {
596 sInt16 bodyIndex=VARIDX_UNDEFINED-128;
597 // not found plain text body yet (or we can store multiple bodies)
598 if (!alternative || strucmp(contentType.c_str(),"text/plain")==0) {
599 // strictly plain text or not alternative, store it in the first body array element
600 bodyIndex=0;
601 // found plain text body, discard other alternatives if we have no other body variants
602 if (alternative) foundBody=true;
603 }
604 else if (multibody) {
605 // text, but not plain - and we can store alternatives, do it!
606 bodyIndex=++fBodyAlternatives;
607 }
608 // if we shall store, do it now (otherwise, fldP is NULL)
609 if (bodyIndex!=VARIDX_UNDEFINED-128) {
610 // - get MIME-type field
611 fldP = aItem.getArrayField(fProfileCfgP->fBodyMIMETypesFid,bodyIndex);
612 if (fldP) {
613 fldP->setAsString(contentType); // store MIME type
614 }
615 // - get content field
616 fldP = aItem.getArrayField(aLineMapP->fFid,bodyIndex);
617 if (fldP) {
618 // - decode
619 decoded.erase();
620 decoded.reserve(previousSize); // we need approx this sizee -> reserve to speed up
621 appendDecoded(
622 toBeParsed,
623 previousSize,
624 decoded,
625 encoding
626 );
627 // - convert charset
628 converted.erase();
629 converted.reserve(decoded.size()); // we need approx this size -> reserve to speed up
630 appendStringAsUTF8(
631 decoded.c_str(),
632 converted,
633 charset,
634 lem_cstr
635 );
636 decoded.erase(); // we do not need this any more
637 // add to field
638 fldP->appendString(converted.c_str(), converted.size());
639 }
640 }
641 }
642 }
643 else {
644 // this is an attachment, ignore it if we have no attachment support
645 #ifdef EMAIL_ATTACHMENT_SUPPORT1
646 if (filename.empty()) {
647 // create filename
648 StringObjPrintf(filename,"attachment%hd.bin",fExtraParts);
649 }
650 // save filename
651 if (fProfileCfgP->fAttachmentNamesFid!=VARIDX_UNDEFINED-128) {
652 fldP=aItem.getArrayField(fProfileCfgP->fAttachmentNamesFid,fExtraParts);
653 if (fldP) {
654 fldP->setAsString(filename.c_str());
655 }
656 }
657 // save type
658 if (fProfileCfgP->fAttachmentMIMETypesFid!=VARIDX_UNDEFINED-128) {
659 fldP=aItem.getArrayField(fProfileCfgP->fAttachmentMIMETypesFid,fExtraParts);
660 if (fldP) {
661 fldP->setAsString(contentType.c_str());
662 }
663 }
664 // now decode attachment itself
665 decoded.erase();
666 decoded.reserve(previousSize); // we need approx this sizee -> reserve to speed up
667 appendDecoded(
668 toBeParsed,
669 previousSize,
670 decoded,
671 encoding
672 );
673 // save attachment data
674 if (fProfileCfgP->fAttachmentContentsFid!=VARIDX_UNDEFINED-128) {
675 fldP=aItem.getArrayField(fProfileCfgP->fAttachmentContentsFid,fExtraParts);
676 if (fldP) {
677 fldP->setAsString(decoded.c_str(),decoded.size());
678 }
679 }
680 // save size in extra field
681 if (fProfileCfgP->fAttachmentSizesFid!=VARIDX_UNDEFINED-128) {
682 fldP=aItem.getArrayField(fProfileCfgP->fAttachmentSizesFid,fExtraParts);
683 if (fldP) {
684 fldP->setAsInteger(decoded.size());
685 }
686 }
687 decoded.erase(); // we do not need this any more
688 // done, found one extra part
689 fExtraParts++;
690 #else
691 // Attachment cannot be processed
692 // - get body field
693 fldP = aItem.getArrayField(aLineMapP->fFid,0);
694 // - append attachment replacement message
695 string msg;
696 if (filename.empty()) filename="<unnamed>";
697 StringObjPrintf(msg,"\n\nAttachment not stored: %s\n\n",filename.c_str());
698 fldP->appendString(msg.c_str());
699 #endif
700 }
701 } // if not empty part
702 // done
703 toBeParsed=NULL__null;
704 // if parsing single part, we're done
705 if (!aBoundary) return p;
706 }
707 // check for more parts or exit if this was the closing boundary
708 if (startpart) {
709 // we have found a boundary above, p=space following it
710 // part starts here
711 bool isPart=false;
712 // - get headers
713 while (p<eot && *p && *p!='\x0D' && *p!='\x0A' && *p!='-') {
714 // not end or empty line or start of other boundary, find end of next header line
715 char c;
716 cAppCharP q=p;
717 while (q<eot && (c=*q++)) {
718 if (c=='\x0D' || c=='\x0A') {
719 if (c=='\x0D' && *q=='\x0A') {
720 // CRLF sequence, do not count CR
721 }
722 else {
723 // end of line, see if folded
724 if (*q=='\x0D' || *q=='\x0A' || !isspace(*q)) {
725 // not folded, end of line
726 break;
727 }
728 }
729 }
730 }
731 // check headers
732 if (checkMimeHeaders(p,q-p,contentType,encoding,contentlen,charset,boundary,&filename))
733 isPart=true; // at least one relevant header found, this is a part
734 // next line
735 p=q;
736 }
737 // now we have all the headers
738 // - skip end of headers (CR)LF if any
739 if (p<eot && *p=='\x0D') p++;
740 if (p<eot && *p=='\x0A') p++;
741 // - check for end of this multipart
742 if (!isPart) {
743 // this is not a part, end current multipart scanning
744 // - return start of next boundary or end of text
745 return p;
746 }
747 // we have now the valid headers for the next part
748 // - if this is a multipart, recurse
749 if (strucmp(contentType.c_str(),"multipart/",10)==0) {
750 // recurse
751 if (boundary.empty()) return NULL__null; // multipart w/o boundary is bad
752 p = parseBody(
753 p, // body text to parse
754 eot-p, // max size to parse
755 contentType.c_str(),encoding,contentlen,charset,
756 aItem, aLineMapP,
757 boundary.c_str() // boundary for nested parts
758 );
759 }
760 else {
761 // single leaf part, process it when we've found the next boundary
762 toBeParsed=p;
763 }
764 } // if startpart
765 else {
766 // no start of a part by boundary
767 if (!aBoundary) {
768 // not part of a multipart, parse as is
769 toBeParsed=p;
770 previousSize=eot-p;
771 }
772 }
773 } while(true);
774} // parseBody
775
776#endif
777
778// parse header fields
779
780
781// parse value into appropriate field(s)
782bool TTextProfileHandler::parseContent(const char *aValue, stringSize aValSize, TMultiFieldItem &aItem, TLineMapDefinition *aLineMapP)
783{
784 // get field
785 TItemField *fieldP = aItem.getField(aLineMapP->fFid);
786 // use appropriate translator
787 string s;
788 switch (aLineMapP->fValueType) {
789 case vt822_timestamp:
790 #ifdef EMAIL_FORMAT_SUPPORT1
791 // rfc822 timestamp
792 if (!fieldP->isBasedOn(fty_timestamp)) break;
793 s.assign(aValue,aValSize);
794 if (!(static_cast<TTimestampField *>(fieldP)->setAsRFC822date(s.c_str(),aItem.getSession()->fUserTimeContext,false)))
795 fieldP->assignEmpty();
796 break;
797 #endif
798 case vt822_body:
799 #ifdef EMAIL_FORMAT_SUPPORT1
800 // falls through to plain if email support is switched off
801 if (fProfileCfgP->fMIMEMail) {
802 // clear body text field
803 fieldP->assignEmpty();
804 fExtraParts=0;
805 fBodyAlternatives=0;
806 // now parse body (we should already have parsed the content headers
807 parseBody(
808 aValue, // body text to parse
809 aValSize,
810 fContentType.c_str(),fEncoding,fContentLen,fCharSet,
811 aItem, aLineMapP,
812 fBoundary.c_str() // boundary, NULL or empty if none
813 );
814 // save number of bodies
815 if (fProfileCfgP->fBodyCountFid!=VARIDX_UNDEFINED-128) {
816 TItemField *fldP=aItem.getField(fProfileCfgP->fBodyCountFid);
817 if (fldP) {
818 fldP->setAsInteger(fBodyAlternatives+1);
819 }
820 }
821 // save number of attachments
822 #ifdef EMAIL_ATTACHMENT_SUPPORT1
823 if (fProfileCfgP->fAttachmentCountFid!=VARIDX_UNDEFINED-128) {
824 TItemField *fldP=aItem.getField(fProfileCfgP->fAttachmentCountFid);
825 if (fldP) {
826 fldP->setAsInteger(fExtraParts);
827 }
828 }
829 #endif
830 break;
831 }
832 #endif
833 goto standardfield;
834 case vt822_rfc2047:
835 #ifdef EMAIL_FORMAT_SUPPORT1
836 // text field encoded according to RFC2047
837 s.erase();
838 appendRFC2047AsUTF8(aValue,aValSize,s);
839 fieldP->setAsString(s.c_str());
840 break;
841 #else
842 goto standardfield;
843 #endif
844
845 standardfield:
846 case vt822_plain:
847 // plain text
848 default:
849 // assign as string
850 fieldP->setAsString(aValue,aValSize);
851 break;
852 }
853 return true;
854} // TTextProfileHandler::parseContent
855
856
857
858#ifdef EMAIL_FORMAT_SUPPORT1
859
860
861// add a MIME-Boundary
862static void addBoundary(string &aString, sInt16 aLevel, bool aForHeader=false)
863{
864 if (!aForHeader) aString+="\x0D\x0A--";
865 StringObjAppendPrintf(aString,"--==========_=_nextpart_%03hd_42503735617.XE======",aLevel);
866 if (!aForHeader) aString+="\x0D\x0A";
867} // TTextProfileHandler::addBoundary
868
869
870// add a body content-type header
871static void addBodyTypeHeader(sInt16 aBodyTypeFid, sInt16 aBodyIndex, TMultiFieldItem &aItem, string &aString)
872{
873 string bodytype;
874 TItemField *fldP = aItem.getArrayField(aBodyTypeFid,aBodyIndex,true);
875 if (fldP && !fldP->isEmpty())
876 fldP->getAsString(bodytype);
877 else
878 bodytype="text/plain";
879 aString+="Content-Type: ";
880 aString+=bodytype;
881 aString+="; charset=\"UTF-8\"\x0D\x0A";
882} // addBodyTypeHeader
883
884
885// generate body (multipart/alternative if there is more than one)
886// if aLevel==0, content type was already set before
887bool TTextProfileHandler::generateBody(sInt16 aLevel, TMultiFieldItem &aItem, TLineMapDefinition *aLineMapP, string &aString)
888{
889 // check if we need to add headers
890 if (aLevel>0) {
891 #ifdef EMAIL_ATTACHMENT_SUPPORT1
892 if (fBodyAlternatives>0) {
893 // multiple bodies, nested multipart
894 aString+="Content-Type: multipart/alternative;\x0D\x0A boundary=\"";
895 addBoundary(aString,aLevel,true); // nested boundary
896 aString+="\"\x0D\x0A";
897 }
898 else
899 #endif
900 {
901 // single body, add body type
902 addBodyTypeHeader(fProfileCfgP->fBodyMIMETypesFid, 0, aItem, aString);
903 // - end part header
904 aString+="\x0D\x0A";
905 }
906 }
907 // now add body/bodies
908 // - get size limit for this item
909 sInt16 bodyindex=0;
910 do {
911 #ifdef EMAIL_ATTACHMENT_SUPPORT1
912 if (fBodyAlternatives>0 && fItemSizeLimit!=0) {
913 // - opening boundary
914 addBoundary(aString,aLevel); // nested
915 // - content type
916 addBodyTypeHeader(fProfileCfgP->fBodyMIMETypesFid, bodyindex, aItem, aString);
917 // - end part header
918 aString+="\x0D\x0A";
919 }
920 #endif
921 // get body field
922 TItemField *fieldP = aItem.getArrayField(aLineMapP->fFid,bodyindex);
923 if (!fieldP)
924 break; // should not happen
925 // now generate body contents
926 if (fItemSizeLimit==0) {
927 // limited to nothing, just don't send anything
928 fLimited=true;
929 break;
930 }
931 else if (fieldP->isBasedOn(fty_string)) {
932 TStringField *sfP = static_cast<TStringField *>(fieldP);
933 if (fItemSizeLimit>0) {
934 // limited string field
935 // - check if we can add more
936 if (fItemSizeLimit<=fGeneratedBytes) {
937 // already exhausted, suppress body completely
938 fLimited=true;
939 break;
940 }
941 // add limited number of body bytes
942 // - determine number of bytes to send
943 #ifdef STREAMFIELD_SUPPORT1
944 sInt32 bodysize=sfP->getStreamSize();
945 if (bodysize+fGeneratedBytes > fItemSizeLimit) {
946 bodysize=fItemSizeLimit-fGeneratedBytes;
947 fLimited=true;
948 }
949 // - get appropriate number of bytes
950 char *bodyP = new char[bodysize+1];
951 sfP->resetStream();
952 bodysize = sfP->readStream(bodyP,bodysize);
953 bodyP[bodysize]=0;
954 // - append to content string
955 aString.reserve(aString.size()+bodysize); // reserve what we need approximately
956 appendUTF8ToString(
957 bodyP,
958 aString,
959 chs_utf8, // always UTF8 for body
960 lem_dos // CRLFs for email
961 );
962 // approximately, UTF-8 conversion and CRLF might cause slightly more chars
963 fGeneratedBytes+=bodysize;
964 // - get rid of buffer
965 delete [] bodyP;
966 #else
967 // simply get it
968 fieldP->appendToString(aString,fItemSizeLimit);
969 fGeneratedBytes+=fieldP->getStringSize();
970 #endif
971 }
972 else {
973 // no limit, simply append to content string
974 appendUTF8ToString(
975 sfP->getCStr(),
976 aString,
977 chs_utf8, // always UTF8 for body
978 lem_dos // CRLFs for email
979 );
980 // approximately, UTF-8 conversion and CRLF might cause slightly more chars
981 fGeneratedBytes+=sfP->getStringSize();
982 }
983 }
984 else {
985 // no string field, just append string representation
986 fieldP->appendToString(aString);
987 fGeneratedBytes+=fieldP->getStringSize();
988 }
989 // done one body
990 bodyindex++;
991 // repeat until all done
992 } while (bodyindex<=fBodyAlternatives && (fItemSizeLimit<0 || fGeneratedBytes<fItemSizeLimit));
993 // now add final boundary if we had alternatives
994 #ifdef EMAIL_ATTACHMENT_SUPPORT1
995 if (fBodyAlternatives>0 && fItemSizeLimit!=0) {
996 // - closing boundary for last part
997 addBoundary(aString,aLevel); // nested
998 }
999 #endif
1000 return true;
1001} // TTextProfileHandler::generateBody
1002
1003#endif
1004
1005
1006// generate contents of a header or body
1007// returns true if tagging and folding is needed on output,
1008// false if output can simply be appended to text (such as: no output at all)
1009bool TTextProfileHandler::generateContent(TMultiFieldItem &aItem, TLineMapDefinition *aLineMapP, string &aString)
1010{
1011 aString.erase(); // nothing by default
1012 bool needsfolding=true;
1013 string s;
1014
1015 // %%% missing repeats
1016 TItemField *fieldP=aItem.getField(aLineMapP->fFid);
1017 if (!fieldP) return false; // no field contents, do not even show the tag
1018 switch (aLineMapP->fValueType) {
1019 case vt822_body:
1020 // body with size restriction
1021 #ifdef EMAIL_FORMAT_SUPPORT1
1022 #ifdef EMAIL_ATTACHMENT_SUPPORT1
1023 // - multipart is supported
1024 if (fExtraParts>0) {
1025 // add body as first part
1026 // - opening boundary
1027 addBoundary(aString,0);
1028 // - add body on level 1 (means that it must add its own headers)
1029 generateBody(1,aItem,aLineMapP,aString);
1030 // - add attachments
1031 sInt16 attIdx;
1032 TItemField *fldP;
1033 for (attIdx=0; attIdx<fExtraParts; attIdx++) {
1034 // - get size of next attachment
1035 sInt32 sizebefore=aString.size(); // remember size before this attachment
1036 sInt32 attachsize=0;
1037 if (fProfileCfgP->fAttachmentSizesFid!=VARIDX_UNDEFINED-128) {
1038 fldP=aItem.getArrayField(fProfileCfgP->fAttachmentSizesFid,attIdx,true);
1039 if (!fldP) continue;
1040 // get it from separate field
1041 attachsize=fldP->getAsInteger();
1042 }
1043 else {
1044 // get it from attachment itself (will probably pull proxy)
1045 fldP=aItem.getArrayField(fProfileCfgP->fAttachmentContentsFid,attIdx,true);
1046 if (!fldP) continue;
1047 attachsize=fldP->getStringSize();
1048 }
1049 // - check if we have data for the attachment
1050 if (attachsize==0) continue;
1051 // Prepare attachment
1052 TItemField *attfldP = NULL__null; // none yet
1053 string attachMsg;
1054 attachMsg.erase(); // no message
1055 // - get content type
1056 bool isText=false;
1057 fldP = aItem.getArrayField(fProfileCfgP->fAttachmentMIMETypesFid,attIdx,true);
1058 string contenttype;
1059 if (fldP && !fldP->isEmpty()) {
1060 fldP->getAsString(contenttype);
1061 }
1062 else {
1063 contenttype="application/octet-stream";
1064 }
1065 // - check for text/xxxx contents
1066 if (strucmp(contenttype.c_str(),"text/",5)==0) isText=true;
1067 // - check if attachment has enough room
1068 if (
1069 (fItemSizeLimit>=0 && fGeneratedBytes+attachsize>fItemSizeLimit) || // limit specified by client
1070 !fItemTypeP->getSession()->dataSizeTransferable(fGeneratedBytes+attachsize*(isText ? 3 : 4)/3) // physical limit as set by maxMsgSize in SyncML 1.0 and maxObjSize in SyncML 1.1
1071 ) {
1072 // no room for attachment, include a text message instead
1073 fldP = aItem.getArrayField(fProfileCfgP->fAttachmentNamesFid,attIdx,true);
1074 string attnam;
1075 if (fldP)
1076 fldP->getAsString(attnam);
1077 else
1078 attnam="unnamed";
1079 StringObjPrintf(attachMsg,
1080 "\x0D\x0A" "Attachment suppressed: '%s' (%ld KBytes)\x0D\x0A",
1081 attnam.empty() ? "<unnamed>" : attnam.c_str(),
1082 long(attachsize/1024)
1083 );
1084 // set type
1085 contenttype="text/plain";
1086 isText=true; // force in-line
1087 // signal incomplete message
1088 // NOTE: other attachments that are smaller may still be included
1089 fLimited=true;
1090 }
1091 else {
1092 // we can send the attachment
1093 attfldP = aItem.getArrayField(fProfileCfgP->fAttachmentContentsFid,attIdx,true);
1094 if (!attfldP) continue; // cannot generate this attachment
1095 }
1096 // - opening boundary for attachment
1097 addBoundary(aString,0);
1098 // - add disposition
1099 aString+="Content-Disposition: ";
1100 if (isText) {
1101 // text is always in-line
1102 aString+="inline";
1103 }
1104 else {
1105 // non-text is attachment if it has a filename
1106 fldP = aItem.getArrayField(fProfileCfgP->fAttachmentNamesFid,attIdx,true);
1107 if (fldP && !fldP->isEmpty()) {
1108 // has a filename, make attachment
1109 aString+="attachment; filename=\"";
1110 fldP->appendToString(aString);
1111 aString+="\"";
1112 }
1113 else {
1114 // has no filename, show inline
1115 aString+="inline";
1116 }
1117 }
1118 aString+="\x0D\x0A";
1119 // - start content type (but no charset yet)
1120 aString+="Content-Type: ";
1121 aString+=contenttype;
1122 // - check attachment mode
1123 if (attfldP && attfldP->isBasedOn(fty_blob)) {
1124 // Attachment is a BLOB, so it may contain binary data
1125 TBlobField *blobP = static_cast<TBlobField *>(attfldP);
1126 // make sure charset/encoding are valid
1127 blobP->makeContentsValid();
1128 // - get charset from the BLOB
1129 if (blobP->fCharset!=chs_unknown) {
1130 aString+="; charset=\"";
1131 aString+=MIMECharSetNames[blobP->fCharset];
1132 aString+='"';
1133 }
1134 aString+="\x0D\x0A";
1135 // - if known, use originally requested encoding
1136 TEncodingTypes enc = blobP->fWantsEncoding;
1137 TEncodingTypes hasenc = blobP->fHasEncoding;
1138 // - make sure we use a valid encoding that is ok for sending as text
1139 if (enc==enc_b || enc==enc_none || enc==enc_binary) {
1140 enc = isText ? enc_8bit : enc_base64;
1141 }
1142 // - see if we should transmit the existing encoding
1143 if (isText) {
1144 if (hasenc==enc_7bit && hasenc==enc_8bit && hasenc==enc_quoted_printable)
1145 enc=hasenc; // already encoded
1146 }
1147 else {
1148 if (hasenc==enc_base64 || hasenc==enc_b)
1149 enc=hasenc; // already encoded
1150 }
1151 // when we are in Synthesis-special mode, and encoding is WBXML, we can use plain binary encoding
1152 // (specifying the length with a Content-Length: header)
1153 if (fItemTypeP->getTypeConfig()->fBinaryParts && fItemTypeP->getSession()->getEncoding()==SML_WBXML && (enc==enc_b || enc==enc_base64)) {
1154 // switch to 1:1 binary
1155 enc=enc_binary;
1156 StringObjAppendPrintf(aString,"Content-Length: %ld\x0D\x0A", long(blobP->getStringSize()));
1157 }
1158 // - set transfer encoding from the BLOB
1159 aString+="Content-Transfer-Encoding: ";
1160 aString+=MIMEEncodingNames[enc];
1161 aString+="\x0D\x0A";
1162 // - end of part headers
1163 aString+="\x0D\x0A";
1164 // - now add contents as-is (this pulls the proxy now)
1165 appendEncoded(
1166 (const uInt8 *)blobP->getCStr(), // input
1167 blobP->getStringSize(),
1168 aString, // append output here
1169 enc==hasenc ? enc_none : enc, // desired encoding if not already encoded
1170 MIME_MAXLINESIZE75, // limit to standard MIME-linesize
1171 0, // current line size
1172 false // insert CRLFs for line breaks
1173 );
1174 }
1175 else {
1176 // Attachment isn't a BLOB, but a string. Transmit as 8bit, UTF-8
1177 aString+="; charset=\"";
1178 aString+=MIMECharSetNames[chs_utf8];
1179 aString+="\"\x0D\x0A";
1180 // - content encoding is 8bit
1181 aString+="Content-Transfer-Encoding: ";
1182 aString+=MIMEEncodingNames[enc_8bit];
1183 aString+="\x0D\x0A";
1184 // - end of part headers
1185 aString+="\x0D\x0A";
1186 // - simply append string
1187 if (attfldP) {
1188 attfldP->getAsString(s);
1189 appendUTF8ToString(
1190 s.c_str(),
1191 aString,
1192 chs_utf8, // always UTF8 for body
1193 lem_dos // CRLFs for email
1194 );
1195 }
1196 else {
1197 // append attachment suppression message
1198 aString+=attachMsg; // no attachment field, append replacement text instead
1199 }
1200 }
1201 // count added bytes
1202 fGeneratedBytes+=(aString.size()-sizebefore);
1203 } // for all attachments
1204 // - closing boundary
1205 addBoundary(aString,0);
1206 }
1207 else
1208 #endif
1209 {
1210 // message consists only of a body (which might have alternatives)
1211 // - add body on level 0 (means that it must not have own headers)
1212 generateBody(0,aItem,aLineMapP,aString);
1213 }
1214 // Body does not need any folding
1215 needsfolding=false;
1216 break;
1217 #else
1218 // no EMAIL FORMAT support
1219 goto standardfield;
1220 #endif
1221 case vt822_rfc2047:
1222 // text field encoded according to RFC2047
1223 #ifdef EMAIL_FORMAT_SUPPORT1
1224 if (fieldP->isUnassigned()) return false; // field not assigned, do not even show the tag
1225 fieldP->getAsString(s);
1226 appendUTF8AsRFC2047(s.c_str(),aString);
1227 break;
1228 #else
1229 goto standardfield;
1230 #endif
1231 case vt822_timestamp:
1232 if (fieldP->isUnassigned()) return false; // field not assigned, do not even show the tag
1233 #ifdef EMAIL_FORMAT_SUPPORT1
1234 if (!fieldP->isBasedOn(fty_timestamp)) break;
1235 static_cast<TTimestampField *>(fieldP)->getAsRFC822date(aString,aItem.getSession()->fUserTimeContext,true);
1236 break;
1237 #endif
1238
1239#ifndef EMAIL_FORMAT_SUPPORT1
1240 standardfield:
1241#endif
1242 case vt822_plain:
1243 // plain text
1244 if (fieldP->isUnassigned()) return false; // field not assigned, do not even show the tag
1245 fieldP->getAsString(aString);
1246 break;
1247 case num822ValueTypes:
1248 // not handled?
1249 break;
1250 }
1251 return needsfolding;
1252} // TTextProfileHandler::generateContent
1253
1254
1255// generate Data item (includes header and footer)
1256void TTextProfileHandler::generateText(TMultiFieldItem &aItem, string &aString)
1257{
1258 TLineMapList::iterator pos;
1259
1260 // reset byte counter
1261 fGeneratedBytes=0;
1262 fLimited=false;
1263 #ifdef EMAIL_ATTACHMENT_SUPPORT1
1264 fExtraParts=0;
1265 #endif
1266 #ifdef EMAIL_FORMAT_SUPPORT1
1267 fBodyAlternatives=0;
1268 bool multipart=false;
1269 #endif
1270
1271 #ifdef SYDEBUG2
1272 POBJDEBUGPRINTFX(fItemTypeP->getSession(),DBG_GEN+DBG_HOT,("Generating....")){ if ((fItemTypeP->getSession()) && (((0x00000400 +
0x00000001) & (fItemTypeP->getSession())->getDbgMask
()) == (0x00000400 +0x00000001))) (fItemTypeP->getSession(
))->getDbgLogger()->setNextMask(0x00000400 +0x00000001)
.DebugPrintfLastMask ("Generating...."); }
;
1273 aItem.debugShowItem(DBG_DATA0x00000080+DBG_GEN0x00000400);
1274 #endif
1275
1276 // init attachment limit
1277 // - get from datastore if one is related
1278 if (fRelatedDatastoreP) {
1279 fItemSizeLimit = fRelatedDatastoreP->getItemSizeLimit();
1280 fNoAttachments = fRelatedDatastoreP->getNoAttachments();
1281 }
1282 // - if size limit is zero or attachments explicitly disabled,
1283 // attachments are not allowed for this item
1284 #ifdef EMAIL_ATTACHMENT_SUPPORT1
1285 if (fItemSizeLimit==0 || fNoAttachments)
1286 fAttachmentLimit=0; // no attachments
1287 else
1288 fAttachmentLimit=fProfileCfgP->fMaxAttachments; // use limit from datatype config
1289 #endif
1290 // generate according to linemaps
1291 bool header = (*fProfileCfgP->fLineMaps.begin())->fInHeader; // we are in header if first is in header
1292 for (pos=fProfileCfgP->fLineMaps.begin();pos!=fProfileCfgP->fLineMaps.end();pos++) {
1293 // get linemap config
1294 TLineMapDefinition *linemapP = *pos;
1295 // separate body
1296 if (header && !linemapP->fInHeader) {
1297 // add special email headers
1298 #ifdef EMAIL_FORMAT_SUPPORT1
1299 if (fProfileCfgP->fMIMEMail) {
1300 // basic support
1301 TItemField *cntFldP;
1302 #ifdef EMAIL_ATTACHMENT_SUPPORT1
1303 // attachments allowed, get number
1304 cntFldP = aItem.getField(fProfileCfgP->fAttachmentCountFid);
1305 if (cntFldP && !cntFldP->isEmpty()) {
1306 // we have a count field, get it's value
1307 fExtraParts=cntFldP->getAsInteger();
1308 }
1309 else {
1310 // determine exta part number by counting attachments
1311 if (fProfileCfgP->fAttachmentContentsFid) {
1312 fExtraParts=aItem.getField(fProfileCfgP->fAttachmentContentsFid)->arraySize();
1313 }
1314 }
1315 // limit to what is allowed
1316 if (fExtraParts > fProfileCfgP->fMaxAttachments) fExtraParts=fProfileCfgP->fMaxAttachments;
1317 if (fExtraParts > fAttachmentLimit) { fExtraParts=fAttachmentLimit; fLimited=true; }
1318 // check if we have body alternatives
1319 cntFldP = aItem.getField(fProfileCfgP->fBodyCountFid);
1320 if (cntFldP && !cntFldP->isEmpty()) {
1321 // we have a count field, get it's value
1322 fBodyAlternatives=cntFldP->getAsInteger()-1;
1323 }
1324 else {
1325 fBodyAlternatives = aItem.getField(linemapP->fFid)->arraySize()-1;
1326 }
1327 if (fBodyAlternatives<0) fBodyAlternatives=0;
1328 // now add multipart content header if we have extra parts to send
1329 if (fExtraParts>0) {
1330 // we have attachments, this will be a multipart/mixed
1331 aString+="Content-Type: multipart/mixed;\x0D\x0A boundary=\"";
1332 addBoundary(aString,0,true);
1333 aString+="\"\x0D\x0A";
1334 multipart=true;
1335 }
1336 else if (fBodyAlternatives) {
1337 // no attachments, but multiple bodies
1338 aString+="Content-Type: multipart/alternative;\x0D\x0A boundary=\"";
1339 addBoundary(aString,0,true);
1340 aString+="\"\x0D\x0A";
1341 multipart=true;
1342 }
1343 else {
1344 // no attachments and no body alternatives, single body
1345 // - set type header for first and only part
1346 addBodyTypeHeader(fProfileCfgP->fBodyMIMETypesFid, 0,aItem,aString);
1347 }
1348 #else
1349 // only single body supported, always UTF-8
1350 addBodyTypeHeader(fProfileCfgP->fBodyMIMETypesFid, 0,aItem,aString);
1351 #endif
1352 // now add encoding header, always 8-bit
1353 aString+="Content-Transfer-Encoding: 8BIT\x0D\x0A";
1354 }
1355 else {
1356 // no mail format, end headers here
1357 aString.append("\x0D\x0A"); // extra empty line
1358 }
1359 #else
1360 // end headers
1361 aString.append("\x0D\x0A"); // extra empty line
1362 #endif
1363 }
1364 // generate value
1365 string fval;
1366 bool tagandfold=generateContent(aItem,linemapP,fval);
1367 // prevent empty ones if selected
1368 if (fval.empty() && !linemapP->fAllowEmpty) continue;
1369 // prefix with tag if any
1370 bool tagged=!TCFG_ISEMPTY(linemapP->fHeaderTag)linemapP->fHeaderTag.empty();
1371 // add field contents now
1372 // generate contents from field
1373 if (!tagandfold) {
1374 // no folding necessary
1375 #ifdef EMAIL_FORMAT_SUPPORT1
1376 // - add updated limit header here if message is really limited
1377 if (fProfileCfgP->fMIMEMail) {
1378 if (fProfileCfgP->fSizeLimitField!=VARIDX_UNDEFINED-128) {
1379 TItemField *fldP = aItem.getField(fProfileCfgP->fSizeLimitField);
1380 fieldinteger_t limit = fItemSizeLimit;
1381 // now update its value
1382 if (!fLimited) limit=-1;
1383 fldP->setAsInteger(limit); // limited to specified size
1384 // now add the updated limit header (we have no linemap for it)
1385 aString+=X_LIMIT_HEADER_NAME"X-Sync-Message-Limit" ": ";
1386 fldP->appendToString(aString);
1387 aString+="\x0D\x0A"; // end of header
1388 }
1389 // terminate header not before here
1390 if (header && !linemapP->fInHeader) {
1391 // end headers
1392 aString.append("\x0D\x0A"); // extra empty line
1393 }
1394 }
1395 #endif
1396 // - fVal includes everything needed INCLUDING tag AND CRLF at end
1397 // or fVal is empty meaning that the value does not need to be added at all
1398 aString.append(fval);
1399 }
1400 else {
1401 // add tag if tagged line
1402 if (tagged) {
1403 aString.append(linemapP->fHeaderTag);
1404 aString+=' '; // this extra space is common usage in RFC822 mails
1405 }
1406 // add with folding
1407 const char *p = fval.c_str();
1408 sInt16 n=(*pos)->fNumLines;
1409 sInt16 i=0;
1410 sInt16 cnt=0; // char counter
1411 sInt16 lastLWSP=-1; // no linear whitespace found yet
1412 char c;
1413 // add multi-line field contents
1414 while ((c=*p++)) {
1415 if (c=='\r') continue; // ignore CRs
1416 if (tagged) {
1417 // apply RFC822 folding (65 recommened for old terminals, 72 max)
1418 if (cnt>=65) {
1419 // check where we can fold
1420 if (lastLWSP>=0) {
1421 // this is the last LWSP
1422 // - new size of line is string beginning with lastLWSP up to end of string
1423 cnt=aString.size()-lastLWSP;
1424 // - insert a CRLF before the last LWSP
1425 aString.insert(lastLWSP,"\x0D\x0A");
1426 // - this one is now invalid
1427 lastLWSP=-1; // invalidate again
1428 }
1429 }
1430 if (isspace(c)) {
1431 // remember possible position for folding
1432 lastLWSP=aString.size(); // index of this LWSP
1433 }
1434 }
1435 if (c=='\n') {
1436 // line break in data
1437 if (tagged) {
1438 // for tagged fields, line break in data is used as recommended folding
1439 // position, so just fold NOW
1440 aString.append("\x0D\x0A ");
1441 lastLWSP=-1;
1442 cnt=1; // the space is already here
1443 }
1444 else {
1445 // for non-tagged fields, we might cut writing data here if no more lines allowed
1446 // - check if more lines allowed
1447 if (i<n || n==0) {
1448 // one more line allowed
1449 aString.append("\x0D\x0A");
1450 i++; // count the line
1451 }
1452 }
1453 }
1454 else {
1455 aString+=c; // append char as is
1456 cnt++;
1457 }
1458 }
1459 // one line at least
1460 aString.append("\x0D\x0A");
1461 }
1462 }
1463 #ifdef SYDEBUG2
1464 POBJDEBUGPRINTFX(fItemTypeP->getSession(),DBG_GEN,("Generated:")){ if ((fItemTypeP->getSession()) && (((0x00000400)
& (fItemTypeP->getSession())->getDbgMask()) == (0x00000400
))) (fItemTypeP->getSession())->getDbgLogger()->setNextMask
(0x00000400).DebugPrintfLastMask ("Generated:"); }
;
1465 if (fItemTypeP->getDbgMask() & DBG_GEN0x00000400) {
1466 // note, do not use debugprintf because string is too long
1467 POBJDEBUGPUTSXX(fItemTypeP->getSession(),DBG_GEN+DBG_USERDATA,aString.c_str(),0,true){ if ((fItemTypeP->getSession()) && (((0x00000400 +
0x01000000) & (fItemTypeP->getSession())->getDbgMask
()) == (0x00000400 +0x01000000))) (fItemTypeP->getSession(
))->getDbgLogger()->DebugPuts( 0x00000400 +0x01000000,aString
.c_str(),0,true); }
;
1468 }
1469 #endif
1470} // TTextProfileHandler::generateText
1471
1472
1473// parse Data item (includes header and footer)
1474bool TTextProfileHandler::parseText(const char *aText, stringSize aTextSize, TMultiFieldItem &aItem)
1475{
1476 TLineMapList::iterator pos;
1477
1478 // get options from datastore if one is related
1479 if (fRelatedDatastoreP) {
1480 fItemSizeLimit = fRelatedDatastoreP->getItemSizeLimit();
1481 fNoAttachments = fRelatedDatastoreP->getNoAttachments();
1482 }
1483 // parse according to linemaps
1484 pos=fProfileCfgP->fLineMaps.begin();
1485 if (pos==fProfileCfgP->fLineMaps.end()) return true; // simply return, no mappings defined
1486 // - we are in header if first is in header
1487 bool header=(*pos)->fInHeader;
1488 #ifdef EMAIL_FORMAT_SUPPORT1
1489 fContentType.erase(); // no known type
1490 fBoundary.erase(); // no boundary yet
1491 fEncoding=enc_8bit; // 8 bit
1492 fContentLen=0; // not defined until we have Synthesis-style binary encoding in parts
1493 fCharSet=chs_utf8; // UFT-8 is SyncML default
1494 fContentType.erase(); // no main content type
1495 #endif
1496 // - header has tagged fields if first has a tag
1497 bool tagged=!TCFG_ISEMPTY((*pos)->fHeaderTag)(*pos)->fHeaderTag.empty();
1498 const char *p = aText;
1499 const char *eot = aText+aTextSize;
1500 // if we are starting in body, simulate a preceeding EOLN
1501 bool lastwaseoln=!header;
1502 while (pos!=fProfileCfgP->fLineMaps.end()) {
1503 // check special case of this linemap eating all of the remaining body text
1504 if (!header && (*pos)->fNumLines==0) {
1505 // Optimization: this linemap will receive the entire remainder of the message
1506 parseContent(p, eot-p, aItem, *pos);
1507 // and we are done
1508 goto parsed;
1509 }
1510 // scan input data
1511 string fval;
1512 fval.erase();
1513 char c=0;
1514 sInt16 i=0,n=0;
1515 bool assignnow=false;
1516 bool fielddone=false;
1517 while (p<=eot) {
1518 // get char, simulate a NUL if we are at end of text
1519 c = (p==eot) ? 0 : *p;
1520 p++; // make sure we're over eot now
1521 // convert all types of line ends: 0A, 0D0A and 0D are allowed
1522 // special 0D0D0A that sometimes happens (CRLF saved through a DOS
1523 // linefeed expander) is also detected correctly
1524 if (c==0x0D) {
1525 // CR, discard LF if one follows
1526 if (p<eot && *p==0x0A) {
1527 p++; // discard
1528 // check if previous char was 0D as well
1529 if (lastwaseoln && *(p-3)==0x0D) {
1530 // this is the famous 0D0D0A sequence
1531 continue; // simply completely ignore it, as previous CR was already detected as line end
1532 }
1533 }
1534 c='\n'; // internal line end char
1535 }
1536 else if (c==0x0A)
1537 c='\n'; // single LF is treated as line end char as well
1538 // process now
1539 if (c==0 || c=='\n') {
1540 // end of input line, if tagged headers, process line but ONLY if it's not an empty line
1541 if (header && tagged && !lastwaseoln) {
1542 // check if we have the entire line already
1543 if (p>=eot || c==0 || (*p!=' ' && *p!='\t')) {
1544 // end of text or next line does not begin with space or TAB -> end of header
1545 #ifdef EMAIL_FORMAT_SUPPORT1
1546 if (fProfileCfgP->fMIMEMail) {
1547 // check for MIME-content relevant mail headers first
1548 if (!checkMimeHeaders(fval.c_str(),fval.size(),fContentType,fEncoding,fContentLen,fCharSet,fBoundary,NULL__null)) {
1549 // check for X-Sync-Limit special header
1550 const char *h = fval.c_str();
1551 const char *e = h+fval.size();
1552 const char *tok;
1553 stringSize toksz;
1554 h=findToken(h,e,':',tok,toksz);
1555 if (*h==':') {
1556 // header field
1557 ++h;
1558 // check name
1559 if (strucmp(tok,X_LIMIT_HEADER_NAME"X-Sync-Message-Limit",toksz)==0) {
1560 // X-Sync-Limit special header
1561 h=findToken(h,e,':',tok,toksz);
1562 TItemField *limfldP = aItem.getField(fProfileCfgP->fSizeLimitField);
1563 if (limfldP)
1564 limfldP->setAsString(tok,toksz);
1565 }
1566 }
1567 }
1568 }
1569 // note that mime headers can still be mapped to fields, so fall through
1570 #endif
1571 // - search by tag for matching linemap now
1572 TLineMapList::iterator tagpos;
1573 for (tagpos=fProfileCfgP->fLineMaps.begin();tagpos!=fProfileCfgP->fLineMaps.end();tagpos++) {
1574 TCFG_STRINGstring &s = (*tagpos)->fHeaderTag;
1575 if ((*tagpos)->fInHeader && !TCFG_ISEMPTY(s)s.empty()) {
1576 if (strucmp(fval.c_str(),TCFG_CSTR(s)s.c_str(),TCFG_SIZE(s)s.size())==0) {
1577 // tag matches, set position to matching linemap
1578 pos=tagpos;
1579 // remove tag from input data
1580 fval.erase(0,TCFG_SIZE(s)s.size());
1581 // remove leading spaces
1582 size_t j=0;
1583 while (fval.size()>j && isspace(fval[j])) j++;
1584 if (j>0) fval.erase(0,j);
1585 // assign value now
1586 assignnow=true;
1587 break; // break for loop
1588 }
1589 }
1590 } // search for correct map
1591 // assignnow is set if we have found a map now, otherwise, header will be ignored
1592 fielddone=true; // cause loop exit, but first check for transition from header to body and set lastwaseoln
1593 }
1594 else {
1595 // process possible folding
1596 if (p<eot && c!=0 && isspace(*p)) {
1597 // this is a lineend because of folding -> ignore line end and just keep LWSP
1598 fval+=*p++; // keep the LWSP
1599 lastwaseoln=false; // last was LWSP, not EOLN :-)
1600 continue; // just check next one
1601 }
1602 }
1603 }
1604 // - check for switch from header to body
1605 if (header && lastwaseoln) {
1606 // two line ends in succession = end of header
1607 header=false;
1608 if (tagged) {
1609 // find first non-header linemap (pos can be anywhere within header linemaps here
1610 while (pos!=fProfileCfgP->fLineMaps.end() && (*pos)->fInHeader) pos++;
1611 // but no need to store, as tagged headers have stored already
1612 }
1613 else {
1614 // end of untagged headers
1615 // assign what is already accumulated
1616 assignnow=true;
1617 }
1618 tagged=false;
1619 // just stop here if no more linemaps
1620 if (pos==fProfileCfgP->fLineMaps.end()) {
1621 goto parsed; // do not spend time and memory with parsing unneeded data
1622 }
1623 // important optimization: if the last linemap does not have a line
1624 // count restriction, process rest of text without filling it into a string var
1625 if ((*pos)->fNumLines==0) {
1626 // this linemap will receive the entire remainder of the message
1627 parseContent(p, eot-p, aItem, *pos);
1628 // and we are done
1629 goto parsed;
1630 }
1631 break;
1632 }
1633 lastwaseoln=true;
1634 // end of input line
1635 if (!tagged) {
1636 i++; // count line
1637 n=(*pos)->fNumLines;
1638 if ((i>=n && n!=0) || c==0) {
1639 // line count exhausted or end of input text, assign to field now
1640 assignnow=true; // assign fval to field now
1641 break;
1642 }
1643 else
1644 if (c) fval+='\n'; // multi-line field, eoln if not eostring
1645 }
1646 } // if end of input line
1647 else {
1648 // not end of input line
1649 lastwaseoln=false;
1650 // add to value
1651 fval+=c;
1652 }
1653 if (!c || fielddone) break;
1654 }
1655 // assign accumulated value
1656 if (assignnow) {
1657 // assign according to linemap
1658 parseContent(fval.c_str(), fval.size(), aItem, *pos);
1659 // advance to next map if not in tagged header mode
1660 if (!tagged) pos++;
1661 }
1662 // all parsed, rest of definitions is not relevant, p is invalid
1663 if (c==0) break;
1664 } // while
1665parsed:
1666 #ifdef SYDEBUG2
1667 POBJDEBUGPRINTFX(fItemTypeP->getSession(),DBG_PARSE,("Successfully parsed: ")){ if ((fItemTypeP->getSession()) && (((0x00000200)
& (fItemTypeP->getSession())->getDbgMask()) == (0x00000200
))) (fItemTypeP->getSession())->getDbgLogger()->setNextMask
(0x00000200).DebugPrintfLastMask ("Successfully parsed: "); }
;
1668 if (fItemTypeP->getDbgMask() & DBG_PARSE0x00000200) {
1669 // very detailed
1670 POBJDEBUGPUTSXX(fItemTypeP->getSession(),DBG_PARSE+DBG_USERDATA+DBG_EXOTIC,aText,0,true){ if ((fItemTypeP->getSession()) && (((0x00000200 +
0x01000000 +0x80000000) & (fItemTypeP->getSession())->
getDbgMask()) == (0x00000200 +0x01000000 +0x80000000))) (fItemTypeP
->getSession())->getDbgLogger()->DebugPuts( 0x00000200
+0x01000000 +0x80000000,aText,0,true); }
;
1671 }
1672 aItem.debugShowItem(DBG_DATA0x00000080+DBG_PARSE0x00000200);
1673 #endif
1674 return true;
1675} // TTextProfileHandler::parseData
1676
1677
1678/* end of TTextProfileHandler implementation */
1679
1680// eof