File: | libsynthesis/src/sysync/textprofile.cpp |
Warning: | line 103, column 20 Dereference of null pointer (loaded from variable 'attr') |
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 | ||||
23 | using 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 | ||||
33 | TLineMapDefinition::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 | ||||
44 | void 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 | |||
68 | const char * const RFC822ValueTypeNames[num822ValueTypes] = { | |||
69 | "text", | |||
70 | "date", | |||
71 | "body", | |||
72 | "rfc2047" | |||
73 | }; | |||
74 | ||||
75 | ||||
76 | ||||
77 | // server config element parsing | |||
78 | bool 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 | ||||
129 | TLineMapDefinition::~TLineMapDefinition() | |||
130 | { | |||
131 | // nop so far | |||
132 | } // TLineMapDefinition::~TLineMapDefinition | |||
133 | ||||
134 | ||||
135 | ||||
136 | // text-based datatype config | |||
137 | ||||
138 | TTextProfileConfig::TTextProfileConfig(const char *aElementName, TConfigElement *aParentElementP) : | |||
139 | TProfileConfig(aElementName,aParentElementP) | |||
140 | { | |||
141 | clear(); | |||
142 | } // TTextProfileConfig::TTextProfileConfig | |||
143 | ||||
144 | ||||
145 | TTextProfileConfig::~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 | |||
153 | void 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 | |||
188 | bool 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 | |||
247 | void 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 | ||||
259 | TLineMapDefinition *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 | |||
288 | TProfileHandler *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 | ||||
306 | TTextProfileHandler::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 | ||||
319 | TTextProfileHandler::~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 | |||
328 | void 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. | |||
344 | sInt16 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 | |||
372 | static 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 | |||
411 | static 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); | |||
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) | |||
495 | cAppCharP 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) | |||
782 | bool 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 | |||
862 | static 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 | |||
871 | static 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 | |||
887 | bool 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) | |||
1009 | bool 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) | |||
1256 | void 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) | |||
1474 | bool 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 | |||
1665 | parsed: | |||
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 |