File: | libsynthesis/src/sysync/textprofile.cpp |
Warning: | line 1561, column 21 Value stored to 'h' is never read |
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); |
Value stored to 'h' is never read | |
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 |