Bug Summary

File:libsynthesis/src/sysync/mimedirprofile.cpp
Warning:line 2185, column 11
Value stored to 'firstunwritten' is never read

Annotated Source Code

1/*
2 * File: mimedirprofile.cpp
3 *
4 * Author: Lukas Zeller (luz@plan44.ch)
5 *
6 * TMimeDirItemType
7 * base class for MIME DIR based content types (vCard, vCalendar...)
8 *
9 * Copyright (c) 2001-2011 by Synthesis AG + plan44.ch
10 *
11 * 2009-01-09 : luz : created from mimediritemtype.cpp
12 *
13 */
14
15// includes
16#include "prefix_file.h"
17#include "sysync.h"
18#include "vtimezone.h"
19#include "rrules.h"
20
21#include "mimedirprofile.h"
22#include "mimediritemtype.h"
23
24#include "syncagent.h"
25
26#include <ctype.h>
27
28using namespace sysync;
29
30
31namespace sysync {
32
33
34// mime-DIR mode names
35const char * const mimeModeNames[numMimeModes] = {
36 "old",
37 "standard"
38};
39
40
41// VTIMEZONE generation modes
42const char * const VTimeZoneGenModes[numVTimeZoneGenModes] = {
43 "current",
44 "start",
45 "end",
46 "range",
47 "openend"
48};
49
50
51// VTIMEZONE generation modes
52const char * const VTzIdGenModes[numTzIdGenModes] = {
53 "default",
54 "olson"
55};
56
57
58// enumeration modes
59const char * const EnumModeNames[numEnumModes] = {
60 "translate", // translation from value to name and vice versa
61 "prefix", // translation of prefix while copying rest of string
62 "defaultname", // default name when translating from value to name
63 "defaultvalue", // default value when translating from name to value
64 "ignore" // ignore value or name
65};
66
67
68// profile modes
69const char * const ProfileModeNames[numProfileModes] = {
70 "custom", // custom profile
71 "vtimezones", // VTIMEZONE profile(s), expands to a VTIMEZONE for every time zone referenced by convmode TZID fields
72};
73
74
75// Config
76// ======
77
78#pragma exceptions off
79
80// Type registry
81
82TMIMEProfileConfig::TMIMEProfileConfig(const char* aName, TConfigElement *aParentElement) :
83 TProfileConfig(aName,aParentElement)
84{
85 fRootProfileP=NULL__null; // no profile yet
86 clear();
87} // TMIMEProfileConfig::TMIMEProfileConfig
88
89
90TMIMEProfileConfig::~TMIMEProfileConfig()
91{
92 clear();
93} // TMIMEProfileConfig::~TMIMEProfileConfig
94
95
96// init defaults
97void TMIMEProfileConfig::clear(void)
98{
99 // init defaults
100 if (fRootProfileP) {
101 delete fRootProfileP;
102 fRootProfileP=NULL__null; // no profile any more
103 }
104 // init options
105 fUnfloatFloating = false;
106 fVTimeZoneGenMode = vtzgen_current; // show VTIMEZONE valid for current year (i.e. not dependent on record's time stamps)
107 fTzIdGenMode = tzidgen_default; // default TZIDs
108 // reset group building mechanism
109 #ifdef CONFIGURABLE_TYPE_SUPPORT1
110 fLastProperty = NULL__null;
111 fPropertyGroupID = 1; // start at 1 (0=no group)
112 #endif
113 // clear inherited
114 inherited::clear();
115} // TMIMEProfileConfig::clear
116
117#pragma exceptions reset
118
119
120// handler factory
121TProfileHandler *TMIMEProfileConfig::newProfileHandler(TMultiFieldItemType *aItemTypeP)
122{
123 // check if fieldlists match as they should
124 if (aItemTypeP->getFieldDefinitions()!=fFieldListP) {
125 // profile is for another field list, cannot be used for this item type
126 return NULL__null;
127 }
128 // our handler is the text profile handler
129 return (TProfileHandler *)(new TMimeDirProfileHandler(this,aItemTypeP));
130}
131
132
133#ifdef CONFIGURABLE_TYPE_SUPPORT1
134
135
136// get conversion mode, virtual, can be overridden by derivates
137bool TMIMEProfileConfig::getConvMode(cAppCharP aText, sInt16 &aConvMode)
138{
139 // separate options
140 size_t n=0; // no size
141 cAppCharP op = strchr(aText, '+');
142 if (op) n = op-aText;
143 // check basic modes
144 if (op && n==0) {
145 // only options, no basic mode
146 aConvMode = CONVMODE_NONE0;
147 }
148 else {
149 // first item is basic mode
150 if (strucmp(aText,"none",n)==0)
151 aConvMode = CONVMODE_NONE0;
152 else if (strucmp(aText,"version",n)==0)
153 aConvMode = CONVMODE_VERSION1;
154 else if (strucmp(aText,"prodid",n)==0)
155 aConvMode = CONVMODE_PRODID2;
156 else if (strucmp(aText,"timestamp",n)==0)
157 aConvMode = CONVMODE_TIMESTAMP3;
158 else if (strucmp(aText,"date",n)==0)
159 aConvMode = CONVMODE_DATE4;
160 else if (strucmp(aText,"autodate",n)==0)
161 aConvMode = CONVMODE_AUTODATE5;
162 else if (strucmp(aText,"autoenddate",n)==0)
163 aConvMode = CONVMODE_AUTOENDDATE6;
164 else if (strucmp(aText,"tz",n)==0)
165 aConvMode = CONVMODE_TZ7;
166 else if (strucmp(aText,"daylight",n)==0)
167 aConvMode = CONVMODE_DAYLIGHT8;
168 else if (strucmp(aText,"tzid",n)==0)
169 aConvMode = CONVMODE_TZID9;
170 else if (strucmp(aText,"emptyonly",n)==0)
171 aConvMode = CONVMODE_EMPTYONLY10;
172 else if (strucmp(aText,"bitmap",n)==0)
173 aConvMode = CONVMODE_BITMAP11;
174 else if (strucmp(aText,"multimix",n)==0)
175 aConvMode = CONVMODE_MULTIMIX15;
176 else if (strucmp(aText,"blob_b64",n)==0)
177 aConvMode = CONVMODE_BLOB_B6412;
178 else if (strucmp(aText,"blob_auto",n)==0)
179 aConvMode = CONVMODE_BLOB_AUTO17;
180 else if (strucmp(aText,"mailto",n)==0)
181 aConvMode = CONVMODE_MAILTO13;
182 else if (strucmp(aText,"valuetype",n)==0)
183 aConvMode = CONVMODE_VALUETYPE14;
184 else if (strucmp(aText,"fullvaluetype",n)==0)
185 aConvMode = CONVMODE_FULLVALUETYPE16;
186 else if (strucmp(aText,"rrule",n)==0)
187 aConvMode = CONVMODE_RRULE20 +0;
188 else {
189 fail("'conversion' value '%s' is invalid",aText);
190 return false;
191 }
192 }
193 // now check for options flags and or them into conversion value
194 while (op) {
195 aText = op+1; // skip +
196 n = 0;
197 op = strchr(aText, '+');
198 if (op) n = op-aText;
199 if (strucmp(aText,"extfmt",n)==0)
200 aConvMode |= CONVMODE_FLAG_EXTFMT0x0100;
201 else if (strucmp(aText,"millisec",n)==0)
202 aConvMode |= CONVMODE_FLAG_MILLISEC0x0200;
203 else {
204 fail("'conversion' option '%s' is invalid",aText);
205 return false;
206 }
207 }
208 return true;
209} // TMIMEProfileConfig::getConvMode
210
211
212
213
214// private helper
215bool TMIMEProfileConfig::getConvAttrs(const char **aAttributes, sInt16 &aFid, sInt16 &aConvMode, char &aCombSep)
216{
217 // - get options
218 const char *fnam = getAttr(aAttributes,"field");
219 if (fnam && *fnam!=0) {
220 // has field spec
221 // - find field ID
222 aFid = fFieldListP->fieldIndex(fnam);
223 if (aFid==VARIDX_UNDEFINED-128) {
224 fail("'field' '%s' does not exist in field list '%s'",fnam,fFieldListP->getName());
225 return false;
226 }
227 }
228 // - get conversion mode
229 const char *conv = getAttr(aAttributes,"conversion");
230 if (conv) {
231 if (!getConvMode(conv,aConvMode)) return false;
232 }
233 // - get combination char
234 const char *comb = getAttr(aAttributes,"combine");
235 if (comb) {
236 if (strucmp(comb,"no")==0)
237 aCombSep=0;
238 else if (strucmp(comb,"lines")==0)
239 aCombSep='\n';
240 else if (strlen(comb)==1)
241 aCombSep=*comb;
242 else {
243 fail("'combine' value '%s' is invalid",comb);
244 return false;
245 }
246 }
247 return true; // ok
248} // TMIMEProfileConfig::getConvAttrs
249
250
251bool TMIMEProfileConfig::getMask(const char **aAttributes, const char *aName, TParameterDefinition *aParamP, TNameExtIDMap &aMask)
252{
253 const char *m=getAttr(aAttributes,aName);
254 if (m) {
255 while (*m) {
256 // skip comma separators and spaces
257 if (*m==',' || *m<=0x20) { m++; continue; }
258 // decode substring
259 TParameterDefinition *paramP = aParamP; // default param
260 size_t n=0;
261 while(m[n]>0x20 && m[n]!=',') {
262 if (m[n]=='.') {
263 // qualified enum, search param
264 paramP=fOpenProperty->findParameter(m,n);
265 if (!paramP) {
266 fail("Unknown param '%s' referenced in '%s'",m,aName);
267 return false;
268 }
269 m+=n+1; // set start to enum name
270 n=0; // start anew
271 continue; // prevent increment
272 }
273 n++;
274 }
275 if (!paramP) {
276 fail("Enum value must be qualified with parameter name: '%s'",m);
277 return false;
278 }
279 TNameExtIDMap msk = paramP->getExtIDbit(m,n);
280 if (msk==0) {
281 fail("'%s' is not an enum of parameter '%s'",m,paramP->paramname.c_str());
282 return false;
283 }
284 aMask = aMask | msk;
285 m+=n; // advance pointer
286 }
287 }
288 return true; // ok;
289} // TMIMEProfileConfig::getMask
290
291
292bool TMIMEProfileConfig::processPosition(TParameterDefinition *aParamP, const char **aAttributes)
293{
294 // <position has="TYPE.HOME" hasnot="FAX,CELL" shows="VOICE"
295 // field="TEL_HOME" repeat="4" increment="1" minshow="0"/>
296 // - get maps
297 TNameExtIDMap hasmap=0;
298 TNameExtIDMap hasnotmap=0;
299 TNameExtIDMap showsmap=0;
300 if (!getMask(aAttributes,"has",aParamP,hasmap)) return false; // failed
301 if (!getMask(aAttributes,"hasnot",aParamP,hasnotmap)) return false; // failed
302 if (!getMask(aAttributes,"shows",aParamP,showsmap)) return false; // failed
303 // - get field
304 sInt16 fid=FID_NOT_SUPPORTED-128;
305 const char *fnam = getAttr(aAttributes,"field");
306 if (fnam) {
307 fid = fFieldListP->fieldIndex(fnam);
308 if (fid==VARIDX_UNDEFINED-128)
309 return !fail("'field' '%s' does not exist in field list '%s'",fnam,fFieldListP->getName());
310 }
311 // - calculate offset from first specified value field in property
312 sInt16 fidoffs=FID_NOT_SUPPORTED-128;
313 if (fid>=0) {
314 for (sInt16 k=0; k<fOpenProperty->numValues; k++) {
315 fidoffs=fOpenProperty->convdefs[k].fieldid;
316 if (fidoffs>=0) break; // found field offset
317 }
318 if (fidoffs<0)
319 return !fail("property '%s' does not have any field assigned, cannot use 'position'",fOpenProperty->propname.c_str());
320 // calc now
321 fidoffs=fid-fidoffs;
322 }
323 // - get repeat and increment
324 sInt16 repeat=1; // no repeat, no rewrite, but check other <position>s when property occurs again.
325 sInt16 incr=1; // inc by 1
326 sInt16 minshow=-1; // auto mode, same as repeat
327 bool overwriteempty=true; // do not store empty repetitions
328 bool readonly=false; // not just a parsing alternative
329 sInt16 sharecountoffs=0; // no repeat counter sharing
330 // - check special maxrepeat values
331 const char *repval = getAttr(aAttributes,"repeat");
332 if (repval) {
333 if (strucmp(repval,"rewrite")==0) repeat=REP_REWRITE0;
334 #ifdef ARRAYFIELD_SUPPORT1
335 else if (strucmp(repval,"array")==0) repeat=REP_ARRAY32767;
336 #endif
337 else if (!StrToShort(repval,repeat))
338 return !fail("expected number, 'rewrite' or 'array' in 'repeat'");
339 }
340 // - increment and minshow
341 if (
342 !getAttrShort(aAttributes,"increment",incr,true) ||
343 !getAttrShort(aAttributes,"minshow",minshow,true) ||
344 !getAttrShort(aAttributes,"sharepreviouscount",sharecountoffs,true)
345 )
346 return !fail("number expected in 'increment', 'minshow' and 'sharepreviouscount'");
347 // - overwrite empty
348 if (!getAttrBool(aAttributes,"overwriteempty",overwriteempty,true))
349 return !fail("expected boolean value in 'overwriteempty'");
350 // - read only position
351 if (!getAttrBool(aAttributes,"readonly",readonly,true))
352 return !fail("expected boolean value in 'readonly'");
353 // - create name extension (position)
354 fOpenProperty->addNameExt(
355 fRootProfileP,hasmap,hasnotmap,showsmap,fidoffs,
356 repeat,incr,minshow,overwriteempty,readonly,sharecountoffs
357 );
358 expectEmpty(); // no contents (and not a separte nest level)
359 return true; // ok
360} // TMIMEProfileConfig::processPosition
361
362
363// called at end of nested parsing level
364void TMIMEProfileConfig::nestedElementEnd(void)
365{
366 // - change mode
367 if (fOpenConvDef) fOpenConvDef=NULL__null; // done with value spec
368 else if (fOpenParameter) fOpenParameter=NULL__null; // done with paramater
369 else if (fOpenProperty) fOpenProperty=NULL__null; // done with property
370 else if (fOpenProfile) {
371 fOpenProfile=fOpenProfile->parentProfile; // back to parent profile (or NULL if root)
372 // groups do not span profiles
373 fLastProperty=NULL__null;
374 }
375} // TMIMEProfileConfig::nestedElementEnd
376
377
378// config element parsing
379bool TMIMEProfileConfig::localStartElement(const char *aElementName, const char **aAttributes, sInt32 aLine)
380{
381 sInt16 nummand;
382 const char *nam;
383 const char *val;
384 const char *fnam;
385 sInt16 fid;
386 sInt16 convmode;
387 char combsep;
388
389 // MIME profile. This is multi-level and therefore needs
390 // complicated parser.
391 if (fNest==0) {
392 // reset to root level
393 fOpenProfile=NULL__null;
394 fOpenProperty=NULL__null;
395 fOpenParameter=NULL__null;
396 fOpenConvDef=NULL__null;
397 }
398 // - parse generics
399 // - get MIME-DIR type dependency
400 TMimeDirMode modeDep = numMimeModes; // no mode dependency by default
401 sInt16 m;
402 cAppCharP modeDepName = getAttr(aAttributes,"onlyformode");
403 if (modeDepName) {
404 if (!StrToEnum(mimeModeNames,numMimeModes,m,modeDepName))
405 return fail("unknown 'onlyformode' attribute value '%s'",modeDepName);
406 else
407 modeDep=(TMimeDirMode)m;
408 }
409 // - now parse specifics
410 if (fOpenConvDef) {
411 if (strucmp(aElementName,"enum")==0) {
412 // <enum name="nam" value="val" mode="translate" positional="yes">
413 // - get name and value
414 nam = getAttr(aAttributes,"name");
415 val = getAttr(aAttributes,"value");
416 // - get mode
417 TEnumMode mode=enm_translate; // default to translate
418 const char *mod=getAttr(aAttributes,"mode");
419 if (mod) {
420 if (!StrToEnum(EnumModeNames,numEnumModes,m,mod))
421 return fail("unknown 'mode' '%s'",mod);
422 else
423 mode=(TEnumMode)m;
424 }
425 // - get options
426 bool positional=fOpenParameter && fOpenParameter->extendsname; // default to parameter
427 if (!getAttrBool(aAttributes,"positional",positional,true))
428 return fail("bad boolean value for 'positional'");
429 if (fOpenParameter && positional && !fOpenParameter->extendsname)
430 return fail("'parameter' must have set 'positional' to use positional 'enum's");
431 // check logic
432 if (mode!=enm_default_value && mode!=enm_ignore && (!nam || *nam==0))
433 return fail("non-default/non-positional 'enum' must have 'name' attribute");
434 // 1:1 translation shortcut: if no value specified, use name as value
435 if (mode==enm_translate && !val)
436 val=nam; // use name as value
437 // default name and ignore can have no value (prefix can have empty value)
438 if (!positional && mode!=enm_default_name && mode!=enm_ignore && (!val || (*val==0 && mode!=enm_prefix)))
439 return fail("non-default 'enum' must have (possibly non-empty) 'value' attribute");
440 // - create enum
441 if (positional)
442 fOpenConvDef->addEnumNameExt(fOpenProperty, nam,val,mode);
443 else
444 fOpenConvDef->addEnum(nam,val,mode);
445 expectEmpty(); // no contents (and not a separate nest level)
446 }
447 // none known here
448 else
449 return false; // parent is TConfigElement, no need to call inherited
450 }
451 else if (fOpenParameter) {
452 if (strucmp(aElementName,"value")==0) {
453 // <value field="N_FIRST" conversion="none" combine="no"/>
454 // - set default options
455 fid=FID_NOT_SUPPORTED-128;
456 convmode = CONVMODE_NONE0;
457 combsep = 0;
458 // - get other options of convdef
459 if (!getConvAttrs(aAttributes,fid,convmode,combsep)) return true; // failed
460 // - set convdef
461 fOpenConvDef = fOpenParameter->setConvDef(fid,convmode,combsep);
462 startNestedParsing();
463 }
464 else if (strucmp(aElementName,"position")==0) {
465 // Position within parameter, enums reference this parameter without
466 // explicitly qualified enum names. To reference enums of other params,
467 // qualified names are allowed.
468 // <position has="TYPE.HOME" hasnot="FAX,CELL" shows="VOICE" field="TEL_HOME" repeat="4" increment="1"/>
469 if (!processPosition(fOpenParameter,aAttributes))
470 return true; // failed
471 }
472 // none known here
473 else
474 return inherited::localStartElement(aElementName, aAttributes, aLine); // call inherited
475 }
476 else if (fOpenProperty) {
477 if (strucmp(aElementName,"value")==0) {
478 // <value index="1" field="N_FIRST" conversion="none" combine="no"/>
479 // - get index
480 sInt16 idx=0;
481 if (!getAttrShort(aAttributes,"index",idx,fOpenProperty->numValues==1)) // optional only for 1-value properties (or lists)
482 return fail("'index' missing or with invalid value");
483 if (idx>=fOpenProperty->numValues)
484 return fail("'index' out of range (0..%hd)",fOpenProperty->numValues);
485 // - set default options
486 fid=FID_NOT_SUPPORTED-128;
487 convmode = CONVMODE_NONE0;
488 combsep = 0;
489 // - get other options of convdef
490 if (!getConvAttrs(aAttributes,fid,convmode,combsep))
491 return true; // failed
492 // - set convdef
493 fOpenConvDef = fOpenProperty->setConvDef(idx,fid,convmode,combsep);
494 startNestedParsing();
495 }
496 else if (strucmp(aElementName,"parameter")==0) {
497 // <parameter name="TYPE" default="yes" positional="yes">
498 // - get name
499 nam = getAttr(aAttributes,"name");
500 if (!nam || *nam==0)
501 return fail("'parameter' must have 'name' attribute");
502 // - get options
503 bool positional=false;
504 bool defparam=false;
505 bool shownonempty=false; // don't show properties that have only param values, but no main value
506 bool showinctcap=false; // don't show parameter in CTCap by default
507 bool sharedfield=false; // assume traditional, unshared field for parameter
508 if (
509 !getAttrBool(aAttributes,"sharedfield",sharedfield,true) ||
510 !getAttrBool(aAttributes,"positional",positional,true) ||
511 !getAttrBool(aAttributes,"default",defparam,true) ||
512 !getAttrBool(aAttributes,"shownonempty",shownonempty,true) ||
513 !getAttrBool(aAttributes,"show",showinctcap,true) ||
514 !getAttrBool(aAttributes,"showindevinf",showinctcap,true) // synonymous with "show" for parameters (note that "show" on properties is no longer effective on purpose!)
515 )
516 return fail("bad boolean value");
517 // - add parameter
518 fOpenParameter = fOpenProperty->addParam(nam,defparam,positional,shownonempty,showinctcap,modeDep,sharedfield);
519 #ifndef NO_REMOTE_RULES
520 const char *depRuleName = getAttr(aAttributes,"rule");
521 TCFG_ASSIGN(fOpenParameter->dependencyRuleName,depRuleName){ if (depRuleName) fOpenParameter->dependencyRuleName=depRuleName
; else fOpenParameter->dependencyRuleName.erase(); }
; // save name for later resolving
522 #endif
523 startNestedParsing();
524 }
525 else if (strucmp(aElementName,"position")==0) {
526 // Position outside parameter, enums must reference parameters using
527 // explicitly qualified enum names like "TYPE.HOME"
528 // <position has="TYPE.HOME" hasnot="TYPE.FAX,TYPE.CELL" shows="TYPE.VOICE" field="TEL_HOME" repeat="4" increment="1"/>
529 if (!processPosition(NULL__null,aAttributes))
530 return true; // failed
531 }
532 // none known here
533 else
534 return inherited::localStartElement(aElementName, aAttributes, aLine); // call inherited
535 }
536 else if (fOpenProfile) {
537 if (strucmp(aElementName,"subprofile")==0) {
538 // <subprofile name="VTODO" nummandatory="1" field="KIND" value="TODO" showlevel="yes" showprops="yes">
539 // <subprofile name="VTODO" nummandatory="1" useproperties="VEVENT" field="KIND" value="TODO" showlevel="yes" showprops="yes">
540 // - starting a new subprofile starts a new property group anyway
541 fLastProperty=NULL__null;
542 // - get name
543 nam = getAttr(aAttributes,"name");
544 if (!nam || *nam==0)
545 return fail("'subprofile' must have 'name' attribute");
546 // - get profile mode
547 TProfileModes mode = profm_custom;
548 cAppCharP pfmode = getAttr(aAttributes,"mode");
549 if (pfmode) {
550 if (!StrToEnum(ProfileModeNames,numProfileModes,m,pfmode))
551 return fail("unknown profile 'mode' '%s'",pfmode);
552 else
553 mode=(TProfileModes)m;
554 }
555 // - check mode dependent params
556 TProfileDefinition *profileP = NULL__null; // no foreign properties by default
557 if (mode==profm_custom) {
558 // Custom profile
559 // - get number of mandatory properties
560 if (!getAttrShort(aAttributes,"nummandatory",nummand,false))
561 return fail ("missing or bad 'nummandatory' specification");
562 // - check if using properties of other profile
563 const char *use = getAttr(aAttributes,"useproperties");
564 if (use) {
565 profileP = fRootProfileP->findProfile(use);
566 if (!profileP)
567 return fail("unknown profile '%s' specified in 'useproperties'",use);
568 expectEmpty(true); // subprofile is a nest level, so we need to flag that (otherwise, nestedElementEnd() would not get called)
569 }
570 else {
571 // parsing nested elements in this TConfigElement
572 startNestedParsing();
573 }
574 }
575 else {
576 // non-custom profiles are expected to be empty
577 expectEmpty(true); // subprofile is a nest level, so we need to flag that (otherwise, nestedElementEnd() would not get called)
578 }
579 // - get DevInf visibility options
580 bool showifselectedonly = false; // default: show anyway
581 if (!getAttrBool(aAttributes,"showifselectedonly",showifselectedonly,true))
582 return fail("bad boolean value for showifselectedonly");
583 // - create subprofile now
584 fOpenProfile = fOpenProfile->addSubProfile(nam,nummand,showifselectedonly,mode,modeDep);
585 // - add properties of other level if any
586 if (profileP) fOpenProfile->usePropertiesOf(profileP);
587 // - add level control field stuff, if any
588 fnam = getAttr(aAttributes,"field");
589 if (fnam) {
590 // - "value" is optional, without a value subprofile is activated if field is non-empty
591 val = getAttr(aAttributes,"value");
592 // - find field
593 fid = fFieldListP->fieldIndex(fnam);
594 if (fid==VARIDX_UNDEFINED-128)
595 return fail("'field' '%s' does not exist in field list '%s'",fnam,fFieldListP->getName());
596 // - set level control convdef
597 TConversionDef *cdP = fOpenProfile->setConvDef(fid); // set field ID of level control field
598 if (val)
599 cdP->addEnum("",val,enm_translate); // set value to be set into level control field when level is entered
600 }
601 }
602 else if (strucmp(aElementName,"property")==0) {
603 // <property name="VERSION" rule="other" values="1" mandatory="no" show="yes" suppressempty="no" delayedparsing="0">
604 // - get name
605 nam = getAttr(aAttributes,"name");
606 if (!nam || *nam==0)
607 return fail("'property' must have 'name' attribute");
608 // - check grouping
609 if (!fLastProperty || strucmp(TCFG_CSTR(fLastProperty->propname)fLastProperty->propname.c_str(),nam)!=0) {
610 // first property in group
611 fPropertyGroupID++; // new group ID
612 }
613 #ifndef NO_REMOTE_RULES
614 // - get rule dependency
615 bool isRuleDep=false;
616 const char *depRuleName = getAttr(aAttributes,"rule");
617 if (depRuleName) {
618 isRuleDep=true;
619 if (strucmp(depRuleName,"other")==0) {
620 // "other" rule (property is active if no other property from the group gets active)
621 depRuleName=NULL__null;
622 }
623 }
624 #endif
625 // - get number of values
626 sInt16 numval=1; // default to 1
627 const char *nvs = getAttr(aAttributes,"values");
628 if (nvs) {
629 if (strucmp(nvs,"list")==0) numval=NUMVAL_LIST-1;
630 else if (strucmp(nvs,"expandedlist")==0) numval=NUMVAL_REP_LIST-2;
631 else if (!StrToShort(nvs,numval))
632 return fail("invalid value in 'values' attribute");
633 }
634 // - get options
635 bool mandatory = false;
636 bool showprop = true; // show property in devInf by default
637 bool suppressempty = false;
638 bool allowFoldAtSep = false;
639 bool canfilter = false; // 3.2.0.9 onwards: do not show filter caps by default (devInf gets too large)
640 if (
641 !getAttrBool(aAttributes,"mandatory",mandatory,true) ||
642 !getAttrBool(aAttributes,"showindevinf",showprop,true) || // formerly just called "show" (but renamed to make it ineffective in old configs as new engine prevents duplicates automatically)
643 !getAttrBool(aAttributes,"suppressempty",suppressempty,true) ||
644 !getAttrBool(aAttributes,"filter",canfilter,true) ||
645 !getAttrBool(aAttributes,"foldbetween",allowFoldAtSep,true)
646 ) return fail("bad boolean value");
647 const char *valsep= getAttr(aAttributes,"valueseparator");
648 if (!valsep) valsep=";"; // default to semicolon if not defined
649 const char *altvalsep= getAttr(aAttributes,"altvalueseparator");
650 if (!altvalsep) altvalsep=""; // default to none if not defined
651 // - group field ID
652 sInt16 groupFieldID = FID_NOT_SUPPORTED-128; // no group field ID by default
653 cAppCharP gfin = getAttr(aAttributes, "groupfield");
654 if (gfin) {
655 groupFieldID = fFieldListP->fieldIndex(gfin);
656 if (groupFieldID==VARIDX_UNDEFINED-128) {
657 fail("'groupfield' '%s' does not exist in field list '%s'",gfin,fFieldListP->getName());
658 return false;
659 }
660 }
661 // - delayed processing
662 sInt16 delayedprocessing=0; // default to 0
663 if (!getAttrShort(aAttributes,"delayedparsing",delayedprocessing,true))
664 return fail ("bad 'delayedparsing' specification");
665 // - create property now and open new level of parsing
666 fOpenProperty=fOpenProfile->addProperty(nam, numval, mandatory, showprop, suppressempty, delayedprocessing, *valsep, fPropertyGroupID, canfilter, modeDep, *altvalsep, groupFieldID, allowFoldAtSep);
667 fLastProperty=fOpenProperty; // for group checking
668 #ifndef NO_REMOTE_RULES
669 // - add rule dependency (pointer will be resolved later)
670 fOpenProperty->dependsOnRemoterule=isRuleDep;
671 fOpenProperty->ruleDependency=NULL__null; // not known yet
672 TCFG_ASSIGN(fOpenProperty->dependencyRuleName,depRuleName){ if (depRuleName) fOpenProperty->dependencyRuleName=depRuleName
; else fOpenProperty->dependencyRuleName.erase(); }
; // save name for later resolving
673 #endif
674 startNestedParsing();
675 }
676 // none known here
677 else
678 return inherited::localStartElement(aElementName, aAttributes, aLine); // call inherited
679 }
680 else {
681 if (strucmp(aElementName,"profile")==0) {
682 // <profile name="VCARD" nummandatory="2">
683 if (fRootProfileP)
684 return fail("'profile' cannot be defined more than once");
685 // new profile starts new property group
686 fLastProperty=NULL__null;
687 // get name
688 nam = getAttr(aAttributes,"name");
689 if (!nam || *nam==0)
690 return fail("'profile' must have 'name' attribute");
691 // - get number of mandatory properties
692 if (!getAttrShort(aAttributes,"nummandatory",nummand,false))
693 return fail ("missing or bad 'nummandatory' specification");
694 // create root profile
695 fRootProfileP = new TProfileDefinition(NULL__null,nam,nummand,false,profm_custom,numMimeModes); // root needs no selection to be shown, is always a custom profile, and not mode dependent
696 // parsing nested elements in this TConfigElement
697 fOpenProfile=fRootProfileP; // current open profile
698 startNestedParsing();
699 }
700 else if (strucmp(aElementName,"unfloattimestamps")==0)
701 expectBool(fUnfloatFloating);
702 else if (strucmp(aElementName,"vtimezonegenmode")==0)
703 expectEnum(sizeof(fVTimeZoneGenMode),&fVTimeZoneGenMode,VTimeZoneGenModes,numVTimeZoneGenModes);
704 else if (strucmp(aElementName,"tzidgenmode")==0)
705 expectEnum(sizeof(fTzIdGenMode),&fTzIdGenMode,VTzIdGenModes,numTzIdGenModes);
706 // none known here
707 else
708 return inherited::localStartElement(aElementName, aAttributes, aLine);
709 }
710 // ok
711 return true;
712} // TMIMEProfileConfig::localStartElement
713
714
715
716#ifndef NO_REMOTE_RULES
717// resolve remote rule dependencies in profile (recursive)
718static void resolveRemoteRuleDeps(TProfileDefinition *aProfileP, TAgentConfig *aSessionConfigP)
719{
720 TProfileDefinition *profileP = aProfileP;
721 while (profileP) {
722 // resolve properties
723 TPropertyDefinition *propP = profileP->propertyDefs;
724 while (propP) {
725 // check for rule-dependent props
726 if (propP->dependsOnRemoterule) {
727 propP->ruleDependency=NULL__null; // assume the "other" rule entry
728 if (!TCFG_ISEMPTY(propP->dependencyRuleName)propP->dependencyRuleName.empty()) {
729 // find remote rule
730 TRemoteRulesList::iterator pos;
731 for(pos=aSessionConfigP->fRemoteRulesList.begin();pos!=aSessionConfigP->fRemoteRulesList.end();pos++) {
732 if (strucmp(TCFG_CSTR(propP->dependencyRuleName)propP->dependencyRuleName.c_str(),(*pos)->getName())==0) {
733 // found rule by name
734 propP->ruleDependency=(*pos);
735 break;
736 }
737 }
738 if (propP->ruleDependency==NULL__null) {
739 string s;
740 StringObjPrintf(s,"property '%s' depends on unknown rule '%s'",TCFG_CSTR(propP->propname)propP->propname.c_str(),TCFG_CSTR(propP->dependencyRuleName)propP->dependencyRuleName.c_str());
741 SYSYNC_THROW(TConfigParseException(s.c_str()))throw TConfigParseException(s.c_str());
742 }
743 } // rule specified
744 }
745
746 // also fix rule-dependent parameters
747 TParameterDefinition *paramP = propP->parameterDefs;
748 while (paramP) {
749 if (!TCFG_ISEMPTY(paramP->dependencyRuleName)paramP->dependencyRuleName.empty()) {
750 TRemoteRulesList::iterator pos;
751 for(pos=aSessionConfigP->fRemoteRulesList.begin();pos!=aSessionConfigP->fRemoteRulesList.end();pos++) {
752 if (strucmp(TCFG_CSTR(paramP->dependencyRuleName)paramP->dependencyRuleName.c_str(),(*pos)->getName())==0) {
753 paramP->ruleDependency=(*pos);
754 break;
755 }
756 }
757 if (paramP->ruleDependency==NULL__null) {
758 string s;
759 StringObjPrintf(s,"parameter '%s' in property '%s' depends on unknown rule '%s'",
760 TCFG_CSTR(paramP->paramname)paramP->paramname.c_str(),
761 TCFG_CSTR(propP->propname)propP->propname.c_str(),
762 TCFG_CSTR(propP->dependencyRuleName)propP->dependencyRuleName.c_str());
763 SYSYNC_THROW(TConfigParseException(s.c_str()))throw TConfigParseException(s.c_str());
764 }
765 }
766 paramP = paramP->next;
767 }
768
769 // next
770 propP=propP->next;
771 }
772 // resolve subprofiles
773 resolveRemoteRuleDeps(profileP->subLevels,aSessionConfigP);
774 // next
775 profileP=profileP->next;
776 }
777} // resolveRemoteRuleDeps
778#endif
779
780// resolve
781void TMIMEProfileConfig::localResolve(bool aLastPass)
782{
783 if (aLastPass) {
784 // check for required settings
785 if (!fRootProfileP)
786 SYSYNC_THROW(TConfigParseException("empty 'mimeprofile' not allowed"))throw TConfigParseException("empty 'mimeprofile' not allowed"
)
;
787 #ifndef NO_REMOTE_RULES
788 // recursively resolve remote rule dependencies in all properties
789 resolveRemoteRuleDeps(
790 fRootProfileP,
791 static_cast<TAgentConfig *>(static_cast<TRootConfig *>(getRootElement())->fAgentConfigP)
792 );
793 #endif
794 }
795 // resolve inherited
796 inherited::localResolve(aLastPass);
797} // TMIMEProfileConfig::localResolve
798
799#endif // CONFIGURABLE_TYPE_SUPPORT
800
801
802
803
804
805
806// implementation of MIME-DIR info classes
807
808#pragma exceptions off
809#define EXCEPTIONS_HERE1 0
810
811
812TEnumerationDef::TEnumerationDef(const char *aEnumName, const char *aEnumVal, TEnumMode aMode, sInt16 aNameExtID)
813{
814 next=NULL__null;
815 TCFG_ASSIGN(enumtext,aEnumName){ if (aEnumName) enumtext=aEnumName; else enumtext.erase(); };
816 TCFG_ASSIGN(enumval,aEnumVal){ if (aEnumVal) enumval=aEnumVal; else enumval.erase(); };
817 enummode=aMode;
818 nameextid=aNameExtID;
819} // TEnumerationDef::TEnumerationDef
820
821
822TEnumerationDef::~TEnumerationDef()
823{
824 // make sure entire chain gets deleted
825 if (next) delete next;
826} // TEnumerationDef::~TEnumerationDef
827
828
829
830TConversionDef::TConversionDef()
831{
832 fieldid=FID_NOT_SUPPORTED-128;
833 enumdefs=NULL__null;
834 convmode=0;
835 combineSep=0;
836} // TConversionDef::TConversionDef
837
838
839TConversionDef::~TConversionDef()
840{
841 // make sure enum list gets deleted
842 if (enumdefs) delete enumdefs;
843} // TEnumerationDef::~TEnumerationDef
844
845
846TConversionDef *TConversionDef::setConvDef(
847 sInt16 aFieldId,
848 sInt16 aConvMode,
849 char aCombSep
850)
851{
852 fieldid=aFieldId;
853 convmode=aConvMode;
854 combineSep=aCombSep;
855 return this;
856} // TConversionDef::setConvDef
857
858
859const TEnumerationDef *TConversionDef::findEnumByName(const char *aName, sInt16 n)
860const
861{
862 TEnumerationDef *enumP = enumdefs;
863 TEnumerationDef *defaultenumP = NULL__null;
864 while(enumP) {
865 // check plain match
866 if (
867 (enumP->enummode==enm_translate || enumP->enummode==enm_ignore) &&
868 strucmp(aName,TCFG_CSTR(enumP->enumtext)enumP->enumtext.c_str(),n)==0
869 ) break; // found full match
870 // check prefix match
871 else if (
872 enumP->enummode==enm_prefix &&
873 (TCFG_SIZE(enumP->enumtext)enumP->enumtext.size()==0 || strucmp(aName,TCFG_CSTR(enumP->enumtext)enumP->enumtext.c_str(),TCFG_SIZE(enumP->enumtext)enumP->enumtext.size())==0)
874 ) break; // found prefix match (or prefix entry with no text, which means match as well)
875 // otherwise: remember if this is a default
876 else if (enumP->enummode==enm_default_value) {
877 // default value entry
878 defaultenumP=enumP; // anyway: remember default value entry
879 // allow searching default value by name (for "has","hasnot" parsing via getExtIDbit())
880 if (!(TCFG_ISEMPTY(enumP->enumtext)enumP->enumtext.empty()) && strucmp(aName,TCFG_CSTR(enumP->enumtext)enumP->enumtext.c_str(),n)==0)
881 break; // found named default value
882 }
883 // check next
884 enumP=enumP->next;
885 }
886 return enumP ? enumP : defaultenumP;
887} // TConversionDef::findEnumByName
888
889
890const TEnumerationDef *TConversionDef::findEnumByVal(const char *aVal, sInt16 n)
891const
892{
893 TEnumerationDef *enumP = enumdefs;
894 TEnumerationDef *defaultenumP = NULL__null;
895 while(enumP) {
896 // check full match
897 if (
898 (enumP->enummode==enm_translate || enumP->enummode==enm_ignore) &&
899 strucmp(aVal,TCFG_CSTR(enumP->enumval)enumP->enumval.c_str(),n)==0
900 ) break; // found
901 // check prefix match
902 else if (
903 enumP->enummode==enm_prefix &&
904 (TCFG_SIZE(enumP->enumval)enumP->enumval.size()==0 || strucmp(aVal,TCFG_CSTR(enumP->enumval)enumP->enumval.c_str(),TCFG_SIZE(enumP->enumval)enumP->enumval.size())==0)
905 ) break; // found prefix match (or prefix entry with no value, which means match as well)
906 // remember if this is a default
907 else if (enumP->enummode == enm_default_name) defaultenumP=enumP; // remember default
908 // check next
909 enumP=enumP->next;
910 }
911 return enumP ? enumP : defaultenumP;
912} // TConversionDef::findEnumByVal
913
914
915void TConversionDef::addEnum(const char *aEnumName, const char *aEnumVal, TEnumMode aMode)
916{
917 TEnumerationDef **enumPP = &enumdefs;
918 while(*enumPP!=NULL__null) enumPP=&((*enumPP)->next); // find last in chain
919 *enumPP = new TEnumerationDef(aEnumName,aEnumVal,aMode); // w/o name extension
920} // TConversionDef::addEnum
921
922
923
924// add enum for name extension, auto-creates property-unique name extension ID
925void TConversionDef::addEnumNameExt(TPropertyDefinition *aProp, const char *aEnumName, const char *aEnumVal, TEnumMode aMode)
926{
927 TEnumerationDef **enumPP = &enumdefs;
928 while(*enumPP!=NULL__null) enumPP=&((*enumPP)->next); // find last in chain
929 if (aProp->nextNameExt>31)
930 #if EXCEPTIONS_HERE1
931 SYSYNC_THROW(TSyncException(DEBUGTEXT("more than 32 name extensions","mdit3")))throw TSyncException("more than 32 name extensions");
932 #else
933 return; // silently ignore
934 #endif
935 *enumPP = new TEnumerationDef(aEnumName,aEnumVal, aMode, aProp->nextNameExt++);
936} // TConversionDef::addEnumNameExt
937
938
939TParameterDefinition::TParameterDefinition(
940 const char *aName, bool aDefault, bool aExtendsName, bool aShowNonEmpty, bool aShowInCTCap, TMimeDirMode aModeDep
941, bool aSharedField) {
942 next=NULL__null;
943 TCFG_ASSIGN(paramname,aName){ if (aName) paramname=aName; else paramname.erase(); };
944 defaultparam=aDefault;
945 extendsname=aExtendsName;
946 shownonempty=aShowNonEmpty;
947 showInCTCap=aShowInCTCap;
948 sharedField=aSharedField;
949 modeDependency=aModeDep;
950 #ifndef NO_REMOTE_RULES
951 ruleDependency=NULL__null;
952 TCFG_CLEAR(dependencyRuleName)dependencyRuleName.erase();
953 #endif
954} // TParameterDefinition::TParameterDefinition
955
956
957TParameterDefinition::~TParameterDefinition()
958{
959 if (next) delete next;
960} // TParameterDefinition::~TParameterDefinition
961
962
963TNameExtIDMap TParameterDefinition::getExtIDbit(const char *aEnumName, sInt16 n)
964{
965 const TEnumerationDef *enumP=convdef.findEnumByName(aEnumName,n);
966 if (enumP) {
967 return ((TNameExtIDMap)1<<enumP->nameextid);
968 }
969 return 0;
970} // TParameterDefinition::getExtIDbit
971
972
973TPropNameExtension::TPropNameExtension(
974 TNameExtIDMap aMusthave_ids, TNameExtIDMap aForbidden_ids, TNameExtIDMap aAddtlSend_ids,
975 sInt16 aFieldidoffs, sInt16 aMaxRepeat, sInt16 aRepeatInc, sInt16 aMinShow,
976 bool aOverwriteEmpty, bool aReadOnly, sInt16 aRepeatID
977) {
978 next=NULL__null;
979 musthave_ids=aMusthave_ids;
980 forbidden_ids=aForbidden_ids;
981 addtlSend_ids=aAddtlSend_ids;
982 fieldidoffs=aFieldidoffs;
983 maxRepeat=aMaxRepeat;
984 repeatInc=aRepeatInc;
985 minShow=aMinShow;
986 overwriteEmpty=aOverwriteEmpty;
987 readOnly=aReadOnly;
988 repeatID=aRepeatID;
989} // TPropNameExtension::TPropNameExtension
990
991
992TPropNameExtension::~TPropNameExtension()
993{
994 if (next) delete next;
995} // TPropNameExtension::~TPropNameExtension
996
997
998TPropertyDefinition::TPropertyDefinition(const char* aName, sInt16 aNumVals, bool aMandatory, bool aShowInCTCap, bool aSuppressEmpty, uInt16 aDelayedProcessing, char aValuesep, char aAltValuesep, uInt16 aPropertyGroupID, bool aCanFilter, TMimeDirMode aModeDep, sInt16 aGroupFieldID, bool aAllowFoldAtSep)
999{
1000 next = NULL__null;
1001 TCFG_ASSIGN(propname,aName){ if (aName) propname=aName; else propname.erase(); };
1002 nameExts = NULL__null; // none yet
1003 nextNameExt = 0; // no enums with name extensions defined yet for this property
1004 valuelist = false; // no value list by default
1005 expandlist = false; // not expanding value list into repeating property by default
1006 valuesep = aValuesep; // separator for structured-value and value-list properties
1007 altvaluesep = aAltValuesep; // alternate separator for structured-value and value-list properties (for parsing only)
1008 allowFoldAtSep = aAllowFoldAtSep; // allow folding at value separators even if it inserts a space at the end of the previous value
1009 groupFieldID = aGroupFieldID; // fid for field that contains the group tag (prefix to the property name, like "a" in "a.TEL:079122327")
1010 propGroup = aPropertyGroupID; // property group ID
1011 // check if this is an unprocessed wildcard property
1012 unprocessed = strchr(aName, '*')!=NULL__null;
1013 // check value list
1014 if (aNumVals==NUMVAL_LIST-1 || aNumVals==NUMVAL_REP_LIST-2) {
1015 // value list
1016 valuelist = true;
1017 expandlist = aNumVals==NUMVAL_REP_LIST-2;
1018 numValues = 1; // we accept a single convdef only
1019 }
1020 else {
1021 // individual values
1022 numValues = aNumVals;
1023 }
1024 // create convdefs array
1025 convdefs = new TConversionDef[numValues];
1026 parameterDefs = NULL__null; // none yet
1027 mandatory = aMandatory;
1028 showInCTCap = aShowInCTCap;
1029 canFilter = aCanFilter;
1030 suppressEmpty = aSuppressEmpty;
1031 delayedProcessing = aDelayedProcessing;
1032 modeDependency = aModeDep;
1033 #ifndef NO_REMOTE_RULES
1034 // not dependent on rule yet (as rules do not exists at TPropertyDefinition creation,
1035 // dependency will be added later, if any)
1036 dependsOnRemoterule = false;
1037 ruleDependency = NULL__null;
1038 #endif
1039} // TPropertyDefinition::TPropertyDefinition
1040
1041
1042TPropertyDefinition::~TPropertyDefinition()
1043{
1044 // delete name extensions
1045 if (nameExts) delete nameExts;
1046 // delete convdefs array
1047 if (convdefs) delete [] convdefs;
1048 // delete parameter definitions
1049 if (parameterDefs) delete parameterDefs;
1050 // delete rest of chain
1051 if (next) delete next;
1052} // TPropertyDefinition::~TPropertyDefinition
1053
1054
1055TConversionDef *TPropertyDefinition::setConvDef(sInt16 aValNum, sInt16 aFieldId,sInt16 aConvMode,char aCombSep)
1056{
1057 if (aValNum<0 || aValNum>=numValues)
1058 #if EXCEPTIONS_HERE1
1059 SYSYNC_THROW(TSyncException(DEBUGTEXT("setConvDef for Property with bad value number","mdit4")))throw TSyncException("setConvDef for Property with bad value number"
)
;
1060 #else
1061 return NULL__null; // silently ignore
1062 #endif
1063 return convdefs[aValNum].setConvDef(aFieldId,aConvMode,aCombSep);
1064}; // TPropertyDefinition::TConversionDef
1065
1066
1067void TPropertyDefinition::addNameExt(TProfileDefinition *aRootProfile, // for profile-global RepID generation
1068 TNameExtIDMap aMusthave_ids, TNameExtIDMap aForbidden_ids, TNameExtIDMap aAddtlSend_ids,
1069 sInt16 aFieldidoffs, sInt16 aMaxRepeat, sInt16 aRepeatInc, sInt16 aMinShow,
1070 bool aOverwriteEmpty, bool aReadOnly, sInt16 aShareCountOffs
1071)
1072{
1073 TPropNameExtension **namextPP = &nameExts;
1074 while(*namextPP!=NULL__null) namextPP=&((*namextPP)->next); // find last in chain
1075 if (aMinShow<0) {
1076 if (aMaxRepeat==REP_ARRAY32767)
1077 aMinShow=0; // by default, show nothing if array is empty
1078 else
1079 aMinShow=aMaxRepeat; // auto mode, show all repetitions
1080 }
1081 *namextPP = new TPropNameExtension(
1082 aMusthave_ids,aForbidden_ids,aAddtlSend_ids,aFieldidoffs,
1083 aMaxRepeat,aRepeatInc,aMinShow,aOverwriteEmpty,aReadOnly,
1084 // readOnly alternative parsing <position> might want to share the
1085 // repeat count with previous <position> occurrences
1086 aShareCountOffs ? aRootProfile->nextRepID-aShareCountOffs : aRootProfile->nextRepID++
1087 );
1088} // TPropertyDefinition::addNameExt
1089
1090
1091TParameterDefinition *TPropertyDefinition::addParam(
1092 const char *aName, bool aDefault, bool aExtendsName, bool aShowNonEmpty, bool aShowInCTCap, TMimeDirMode aModeDep
1093, bool aSharedField)
1094{
1095 TParameterDefinition **paramPP = &parameterDefs;
1096 while(*paramPP!=NULL__null) paramPP=&((*paramPP)->next); // find last in chain
1097 *paramPP = new TParameterDefinition(aName,aDefault,aExtendsName,aShowNonEmpty,aShowInCTCap, aModeDep, aSharedField);
1098 return *paramPP;
1099} // TPropertyDefinition::addParam
1100
1101
1102// find parameter by name
1103TParameterDefinition *TPropertyDefinition::findParameter(const char *aNam, sInt16 aLen)
1104{
1105 TParameterDefinition *paramP = parameterDefs;
1106 while (paramP) {
1107 if (strucmp(aNam,TCFG_CSTR(paramP->paramname)paramP->paramname.c_str(),aLen)==0)
1108 return paramP; // found
1109 paramP=paramP->next; // next
1110 }
1111 // not found
1112 return NULL__null;
1113} // TPropertyDefinition::findParameter
1114
1115
1116TProfileDefinition::TProfileDefinition(
1117 TProfileDefinition *aParentProfileP, // parent profile
1118 const char *aProfileName, // name
1119 sInt16 aNumMandatory,
1120 bool aShowInCTCapIfSelectedOnly,
1121 TProfileModes aProfileMode,
1122 TMimeDirMode aModeDep
1123)
1124{
1125 parentProfile=aParentProfileP; // NULL if root
1126 next=NULL__null;
1127 // set fields
1128 TCFG_ASSIGN(levelName,aProfileName){ if (aProfileName) levelName=aProfileName; else levelName.erase
(); }
;
1129 shownIfSelectedOnly = aShowInCTCapIfSelectedOnly;
1130 profileMode = aProfileMode;
1131 modeDependency = aModeDep;
1132 // init
1133 numMandatoryProperties=aNumMandatory;
1134 propertyDefs=NULL__null;
1135 subLevels=NULL__null;
1136 ownsProps=true;
1137 nextRepID=0;
1138} // TProfileDefinition::TProfileDefinition
1139
1140
1141TProfileDefinition::~TProfileDefinition()
1142{
1143 if (propertyDefs && ownsProps) delete propertyDefs;
1144 if (subLevels) delete subLevels;
1145 if (next) delete next;
1146} // TProfileDefinition::~TProfileDefinition
1147
1148
1149TProfileDefinition *TProfileDefinition::addSubProfile(
1150 const char *aProfileName, // name
1151 sInt16 aNumMandatory,
1152 bool aShowInCTCapIfSelectedOnly,
1153 TProfileModes aProfileMode,
1154 TMimeDirMode aModeDep
1155)
1156{
1157 TProfileDefinition **profilePP=&subLevels;
1158 while (*profilePP!=NULL__null) profilePP=&((*profilePP)->next);
1159 *profilePP=new TProfileDefinition(this,aProfileName,aNumMandatory,aShowInCTCapIfSelectedOnly,aProfileMode,aModeDep);
1160 return *profilePP;
1161} // TProfileDefinition::addSubProfile
1162
1163
1164TPropertyDefinition *TProfileDefinition::addProperty(
1165 const char *aName, // name
1166 sInt16 aNumValues, // number of values
1167 bool aMandatory, // mandatory
1168 bool aShowInCTCap, // show in CTCap
1169 bool aSuppressEmpty, // suppress empty ones on send
1170 uInt16 aDelayedProcessing, // delayed processing when parsed, 0=immediate processing, 1..n=delayed
1171 char aValuesep, // value separator
1172 uInt16 aPropertyGroupID, // property group ID (alternatives for same-named properties should have same ID>0)
1173 bool aCanFilter, // can be filtered -> show in filter cap
1174 TMimeDirMode aModeDep, // property valid only for specific MIME mode
1175 char aAltValuesep, // alternate separator (for parsing)
1176 sInt16 aGroupFieldID, // group field ID
1177 bool aAllowFoldAtSep // allow folding at separators
1178)
1179{
1180 TPropertyDefinition **propPP=&propertyDefs;
1181 while (*propPP!=NULL__null) propPP=&((*propPP)->next);
1182 *propPP=new TPropertyDefinition(aName,aNumValues,aMandatory,aShowInCTCap,aSuppressEmpty,aDelayedProcessing,aValuesep,aAltValuesep,aPropertyGroupID,aCanFilter,aModeDep,aGroupFieldID,aAllowFoldAtSep);
1183 // return new property
1184 return *propPP;
1185} // TProfileDefinition::addProperty
1186
1187
1188void TProfileDefinition::usePropertiesOf(TProfileDefinition *aProfile)
1189{
1190 ownsProps=false;
1191 propertyDefs=aProfile->propertyDefs;
1192} // TProfileDefinition::usePropertiesOf
1193
1194
1195// find (sub)profile by name, recursively
1196TProfileDefinition *TProfileDefinition::findProfile(const char *aNam)
1197{
1198 // check myself
1199 if (levelName==aNam) return this;
1200 // check sublevels
1201 TProfileDefinition *lvlP = subLevels;
1202 TProfileDefinition *foundlvlP;
1203 while(lvlP) {
1204 foundlvlP=lvlP->findProfile(aNam);
1205 if (foundlvlP) return foundlvlP;
1206 lvlP=lvlP->next;
1207 }
1208 // does not match myself nor one of my sublevels
1209 return NULL__null;
1210} // TProfileDefinition::findProfile
1211
1212#pragma exceptions reset
1213#undef EXCEPTIONS_HERE1
1214#define EXCEPTIONS_HERE1 TARGET_HAS_EXCEPTIONS1
1215
1216
1217#ifdef OBJECT_FILTERING1
1218
1219// get property definition of given filter expression identifier.
1220TPropertyDefinition *TProfileDefinition::getPropertyDef(const char *aPropName)
1221{
1222 TPropertyDefinition *propP = NULL__null;
1223
1224 if (!aPropName) return propP; // no name, no fid
1225 // Depth first: search in subprofiles, if any
1226 TProfileDefinition *profileP = subLevels;
1227 while (profileP) {
1228 // search depth first
1229 if ((propP=profileP->getPropertyDef(aPropName))!=NULL__null)
1230 return propP; // found
1231 // test next profile
1232 profileP=profileP->next;
1233 }
1234 // now search my own properties
1235 propP = propertyDefs;
1236 while (propP) {
1237 // compare names
1238 if (strucmp(aPropName,TCFG_CSTR(propP->propname)propP->propname.c_str())==0) {
1239 return propP;
1240 }
1241 // test next property
1242 propP=propP->next;
1243 }
1244 // not found
1245 return NULL__null;
1246} // TProfileDefinition::getPropertyDef
1247
1248
1249// get field index of given filter expression identifier.
1250sInt16 TProfileDefinition::getPropertyMainFid(const char *aPropName, uInt16 aIndex)
1251{
1252 sInt16 fid = VARIDX_UNDEFINED-128;
1253
1254 // search property definition with matching name
1255 TPropertyDefinition *propP = getPropertyDef(aPropName);
1256 // search for first value with a field assigned
1257 if (propP) {
1258 // found property with matching name
1259 if (propP->convdefs) {
1260 if (aIndex==0) {
1261 // no index specified -> search first with a valid FID
1262 for (uInt16 i=0; i<propP->numValues; i++) {
1263 if ((fid=propP->convdefs[i].fieldid)!=VARIDX_UNDEFINED-128)
1264 return fid; // found a field index
1265 }
1266 }
1267 else {
1268 // index specified for multivalued properties -> return specified value's ID
1269 if (aIndex<=propP->numValues) {
1270 return propP->convdefs[aIndex-1].fieldid;
1271 }
1272 }
1273 }
1274 }
1275 // not found
1276 return VARIDX_UNDEFINED-128;
1277} // TProfileDefinition::getPropertyMainFid
1278
1279
1280#endif // OBJECT_FILTERING
1281
1282
1283
1284/*
1285 * Implementation of TMimeDirProfileHandler
1286 */
1287
1288
1289TMimeDirProfileHandler::TMimeDirProfileHandler(
1290 TMIMEProfileConfig *aMIMEProfileCfgP,
1291 TMultiFieldItemType *aItemTypeP
1292) : TProfileHandler(aMIMEProfileCfgP, aItemTypeP)
1293{
1294 // save profile config pointer
1295 fProfileCfgP = aMIMEProfileCfgP;
1296 fProfileDefinitionP = fProfileCfgP->fRootProfileP;
1297 // settable options defaults
1298 fMimeDirMode=mimo_standard;
1299 fReceiverCanHandleUTC = true;
1300 fVCal10EnddatesSameDay = false; // avoid 23:59:59 style end date by default
1301 fReceiverTimeContext = TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)); // none in particular
1302 fDontSendEmptyProperties = false; // send all defined properties
1303 fDefaultOutCharset = chs_utf8; // standard
1304 fDefaultInCharset = chs_utf8; // standard
1305 fDoQuote8BitContent = false; // no quoting needed per se
1306 fDoNotFoldContent = false; // standard requires folding
1307 fTreatRemoteTimeAsLocal = false; // only for broken implementations
1308 fTreatRemoteTimeAsUTC = false; // only for broken implementations
1309 fActiveRemoteRules.clear(); // no dependency on certain remote rules
1310} // TMimeDirProfileHandler::TMimeDirProfileHandler
1311
1312
1313TMimeDirProfileHandler::~TMimeDirProfileHandler()
1314{
1315 // nop for now
1316} // TMimeDirProfileHandler::~TTextProfileHandler
1317
1318
1319
1320#ifdef OBJECT_FILTERING1
1321
1322// get field index of given filter expression identifier.
1323sInt16 TMimeDirProfileHandler::getFilterIdentifierFieldIndex(const char *aIdentifier, uInt16 aIndex)
1324{
1325 // search properties for field index
1326 return fProfileDefinitionP->getPropertyMainFid(aIdentifier, aIndex);
1327} // TMimeDirProfileHandler::getFilterIdentifierFieldIndex
1328
1329#endif // OBJECT_FILTERING
1330
1331
1332
1333// parses enum value for CONVMODE_MULTIMIX
1334// [offs.](Bx|Lzzzzzzz)
1335// aN returns the bit number or the offset of the zzzzz literal within aMixVal, depending on aIsBitMap
1336static bool mixvalparse(cAppCharP aMixVal, uInt16 &aOffs, bool &aIsBitMap, uInt16 &aN)
1337{
1338 aOffs = 0;
1339 cAppCharP p = aMixVal;
1340 // check offset (2 digit max)
1341 if (isdigit(*p)) {
1342 p+=StrToUShort(p,aOffs,2);
1343 if (*p++ != '.') return false; // wrong syntax
1344 }
1345 // check command
1346 if (*p == 'B') {
1347 // bit number
1348 aIsBitMap = true;
1349 if (StrToUShort(p+1,aN,2)<1) return false; // wrong syntax
1350 }
1351 else if (*p=='L') {
1352 // literal, return position within string
1353 aIsBitMap = false;
1354 aN = p+1-aMixVal; // literal starts at this position
1355 }
1356 else
1357 return false; // unknown command
1358 return true;
1359} // mixvalparse
1360
1361
1362
1363// returns the size of the field block (how many fids in sequence) related
1364// to a given convdef (for multi-field conversion modes such as CONVMODE_RRULE
1365sInt16 TMimeDirProfileHandler::fieldBlockSize(const TConversionDef &aConvDef)
1366{
1367 if ((aConvDef.convmode & CONVMODE_MASK0xFF)==CONVMODE_RRULE20 +0)
1368 return 6; // RRULE fieldblock: DTSTART,FREQ,INTERVAL,FIRSTMASK,LASTMASK,UNTIL = 6 fields
1369 else
1370 return 1; // single field
1371} // TMimeDirProfileHandler::fieldBlockSize
1372
1373
1374
1375// special field translation (to be extended in derived classes)
1376// Note: the string returned by this function will be scanned as a
1377// value list if combinesep is set, and every single value will be
1378// enum-translated if enums defined.
1379bool TMimeDirProfileHandler::fieldToMIMEString(
1380 TMultiFieldItem &aItem, // the item where data comes from
1381 sInt16 aFid, // the field ID (can be NULL for special conversion modes)
1382 sInt16 aArrIndex, // the repeat offset to handle array fields
1383 const TConversionDef *aConvDefP, // the conversion definition record
1384 string &aString // output string
1385)
1386{
1387 const int maxmix = 10;
1388 uInt16 mixOffs[maxmix];
1389 bool mixIsFlags[maxmix];
1390 TEnumerationDef *enumP;
1391 uInt16 offs; bool isFlags;
1392 int nummix, i;
1393 fieldinteger_t flags;
1394 uInt16 bitNo;
1395 TTimestampField *tsFldP;
1396 TIntegerField *ifP;
1397 TStringField *sfP;
1398 timecontext_t tctx;
1399 lineartime_t ts;
1400 string s;
1401 // RRULE field block values
1402 char freq; // frequency
1403 char freqmod; // frequency modifier
1404 sInt16 interval; // interval
1405 fieldinteger_t firstmask; // day mask counted from the first day of the period
1406 fieldinteger_t lastmask; // day mask counted from the last day of the period
1407 lineartime_t until; // last day
1408 timecontext_t untilcontext;
1409
1410
1411 // get pointer to leaf field
1412 TItemField *fldP = aItem.getArrayField(aFid,aArrIndex,true); // existing array elements only
1413
1414 bool dateonly = false; // assume timestamp mode
1415 bool autodate = true; // show date-only values automatically as date-only, even if stored in a timestamp field
1416 bool extFmt = (aConvDefP->convmode & CONVMODE_FLAG_EXTFMT0x0100)!=0;
1417 bool milliSec = (aConvDefP->convmode & CONVMODE_FLAG_MILLISEC0x0200)!=0;
1418 sInt16 convmode = aConvDefP->convmode & CONVMODE_MASK0xFF;
1419 switch (convmode) {
1420 // no special mode
1421 case CONVMODE_NONE0:
1422 case CONVMODE_EMPTYONLY10:
1423 // just get field as string
1424 if (!fldP) return false; // no field, no value
1425 if (!fldP->isBasedOn(fty_timestamp)) goto normal;
1426 // Based on timestamp
1427 // - handle date-only specially
1428 if (fldP->getType()==fty_date)
1429 goto dateonly; // date-only
1430 else
1431 goto timestamp; // others are treated as timestamps
1432 // date & time modes
1433 case CONVMODE_DATE4: // always show as date
1434 dateonly:
1435 dateonly = true; // render as date in all cases
1436 goto timestamp;
1437 case CONVMODE_AUTOENDDATE6:
1438 case CONVMODE_AUTODATE5: // show date-only as date in iCal 2.0 (mimo_standard), but always as timestamp for vCal 1.0 (mimo_old)
1439 if (fMimeDirMode==mimo_standard) goto timestamp; // use autodate if MIME-DIR format is not vCal 1.0 style
1440 // for vCal 1.0 style, always renders as timestamp (as date-only values are not allowed there)
1441 case CONVMODE_TIMESTAMP3: // always show as timestamp
1442 // get explictly as timestamp (even if field or field contents is date)
1443 autodate = false; // do not show as date, even if it is a date-only
1444 timestamp:
1445 if (!fldP) return false; // no field, no value
1446 if (!fldP->isBasedOn(fty_timestamp)) goto normal;
1447 // show as timestamp
1448 tsFldP = static_cast<TTimestampField *>(fldP);
1449 tctx = tsFldP->getTimeContext();
1450 // check for auto-date
1451 if (autodate) {
1452 if (TCTX_IS_DATEONLY(tctx))
1453 dateonly=true;
1454 }
1455 // check for special cases
1456 if (TCTX_IS_DURATION(tctx)) {
1457 // duration is shown as such
1458 tsFldP->getAsISO8601(aString, TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)) | TCTX_DURATION, false, false, extFmt, milliSec);
1459 }
1460 else if (dateonly) {
1461 // date-only are either floating or shown as date-only part of original timestamp
1462 tsFldP->getAsISO8601(aString, TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)) | TCTX_DATEONLY, false, false, extFmt, milliSec);
1463 }
1464 else if (fReceiverCanHandleUTC && !tsFldP->isFloating()) {
1465 // remote can handle UTC and the timestamp is not floating
1466 if (!TCTX_IS_UNKNOWN(fPropTZIDtctx)) {
1467 // if we have rendered a TZID for this property, this means that apparently the remote
1468 // supports TZID (otherwise the field would not be marked available in the devInf).
1469 // - show it as floating, explicitly with both date AND time (both flags set)
1470 tsFldP->getAsISO8601(aString, TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)) | TCTX_TIMEONLY | TCTX_DATEONLY, false, false, extFmt, milliSec);
1471 }
1472 else {
1473 // - show it as UTC
1474 tsFldP->getAsISO8601(aString, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), true, false, extFmt, milliSec);
1475 }
1476 }
1477 else {
1478 // remote cannot handle UTC or time is floating (possibly dateonly or duration)
1479 if (tsFldP->isFloating()) {
1480 // floating, show as-is
1481 ts = tsFldP->getTimestampAs(TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)));
1482 if (ts==noLinearTime)
1483 aString.erase();
1484 else {
1485 if (TCTX_IS_DATEONLY(tctx)) {
1486 // value is a date-only, but we must render it a datetime
1487 ts=lineartime2dateonlyTime(ts); // make time part 0:00:00
1488 }
1489 // first check for auto-end-date (which must be floating)
1490 // Note: we don't get here with a date only mimo_standard because it will be catched above, so test is not really needed
1491 if (convmode==CONVMODE_AUTOENDDATE6 && fVCal10EnddatesSameDay && TCTX_IS_DATEONLY(tctx) && fMimeDirMode==mimo_old)
1492 ts-=1; // subtract one unit to make end show last time unit of previous day
1493 // now show as floating ISO8601
1494 TimestampToISO8601Str(aString, ts, TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)), extFmt, milliSec);
1495 }
1496 }
1497 else {
1498 // not floating (=not a enddateonly), but we can't send UTC - render as localtime
1499 // in item time zone (which defaults to session time zone)
1500 tsFldP->getAsISO8601(aString, fItemTimeContext, false, false, extFmt, milliSec);
1501 }
1502 }
1503 return true; // found
1504 normal:
1505 // simply as string
1506 fldP->getAsString(aString);
1507 return true; // found
1508 case CONVMODE_TZ7:
1509 case CONVMODE_TZID9:
1510 case CONVMODE_DAYLIGHT8:
1511 // use now as default point in time for possible offset calculations
1512 ts = getSession()->getSystemNowAs(TCTX_SYSTEM((timecontext_t) ((tctx_tz_system) | TCTX_SYMBOLIC_TZ)));
1513 // if no field is specified, the item context is used (which defaults to
1514 // the session's user context)
1515 // Note that testing fldP is not enough, because an empty array will also cause fldP==NULL
1516 if (!fldP) {
1517 if (aFid!=FID_NOT_SUPPORTED-128)
1518 return false; // field not available (but conversion definition DOES refer to a field --> no time zone)
1519 // conversion definition does not refer to a field: use item context
1520 tctx = fItemTimeContext;
1521 }
1522 else if (fldP->isBasedOn(fty_timestamp)) {
1523 // time zone of a timestamp
1524 tsFldP = static_cast<TTimestampField *>(fldP);
1525 // - if floating time, we have no time zone
1526 if (tsFldP->isFloating() || tsFldP->isDuration()) return false; // floating or duration -> no time zone
1527 // - get context
1528 tctx = tsFldP->getTimeContext(); // get the context
1529 // - get the value
1530 ts = tsFldP->getTimestampAs(TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)));
1531 // prevent generating TZID (and associated VTIMEZONES later) for empty timestamp
1532 if (ts==noLinearTime) return false; // no timestamp -> no time zone
1533 }
1534 else if (fldP->getCalcType()==fty_integer) {
1535 // integer field is simply a time zone offset in minutes
1536 tctx = TCTX_MINOFFSET(fldP->getAsInteger());
1537 }
1538 else if (!fldP->isEmpty()) {
1539 // string field can be timezone name (internal or olson) or numeric minute offset
1540 fldP->getAsString(s);
1541 if (!TimeZoneNameToContext(s.c_str(),tctx,getSessionZones(), true)) {
1542 // if not recognized as time zone name, use integer value
1543 tctx = TCTX_MINOFFSET(fldP->getAsInteger());
1544 }
1545 }
1546 else
1547 return false; // no TZ to show
1548 // if remote cannot handle UTC (i.e. only understands localtime), then make sure
1549 // the time zone shown is the general item zone (user zone).
1550 if (!fReceiverCanHandleUTC) {
1551 TzConvertTimestamp(ts,tctx,fItemTimeContext,getSessionZones());
1552 tctx = fItemTimeContext; // use item zone
1553 }
1554 // now render context as selected
1555 if (convmode==CONVMODE_TZID9) {
1556 // time zone ID for iCal 2.0 TZID parameter
1557 // - make sure meta context is resolved (we don't want "SYSTEM" as TZID!)
1558 if (!TzResolveMetaContext(tctx, getSessionZones())) return false; // cannot resolve, no time zone ID
1559 // - if time zone is not UTC (which is represented as "Z" and needs no TZID), show name
1560 if (!TCTX_IS_UTC(tctx) && !TCTX_IS_UNKNOWN(tctx) && !TCTX_IS_DATEONLY(tctx)) {
1561 // - show name of zone as TZID
1562 if (!TimeZoneContextToName(tctx, aString, getSessionZones(), fProfileCfgP->fTzIdGenMode==tzidgen_olson ? "o" : NULL__null)) return false; // cannot get name/ID
1563 // - flag property-level TZID generated now
1564 fPropTZIDtctx=tctx;
1565 // - add to set of TZID-referenced time zones (for vTimezone generation)
1566 fUsedTCtxSet.insert(fUsedTCtxSet.end(),tctx);
1567 // - update range of time covered for generating VTIMEZONE later
1568 if (ts) {
1569 if (fEarliestTZDate==noLinearTime || fEarliestTZDate>ts) fEarliestTZDate = ts; // new minimum
1570 if (fLatestTZDate==noLinearTime || fLatestTZDate<ts) fLatestTZDate = ts; // new maximum
1571 }
1572 }
1573 }
1574 else {
1575 // CONVMODE_TZ or CONVMODE_DAYLIGHT
1576 // - there's only one TZ/DAYLIGHT per item, so set it as item context
1577 if (!fReceiverCanHandleUTC) {
1578 // devices that can't handle UTC should not be bothered with TZ info
1579 // (e.g. for N-Gage/3650 presence of a TZ shifts the data by the TZ value!?)
1580 return false; // prevent generation of TZ or DAYLIGHT props
1581 }
1582 else {
1583 // only if remote can handle UTC we may change the item time context
1584 // (otherwise, the timestamp must be rendered in itemzone/userzone)
1585 fItemTimeContext = tctx;
1586 fHasExplicitTZ = true; // flag setting explicit time zone for item
1587 }
1588 // - get resolved TZ offset and DAYLIGHT string for vCal 1.0
1589 ContextToTzDaylight(tctx,ts,s,tctx,getSessionZones());
1590 if (convmode==CONVMODE_TZ7) {
1591 // time zone in +/-hh[:mm] format for vCal 1.0 TZ property
1592 // - render offset in extended format
1593 aString.erase();
1594 // - return true only if we actually have a TZ
1595 return ContextToISO8601StrAppend(aString, tctx, true);
1596 }
1597 else if (convmode==CONVMODE_DAYLIGHT8) {
1598 // TZ and DAYLIGHT property for vCal 1.0
1599 aString = s;
1600 // - return true only if we actually have a DAYLIGHT
1601 return !s.empty();
1602 }
1603 }
1604 // done
1605 return true;
1606 case CONVMODE_MAILTO13:
1607 // make sure we have a mailto: prefix (but not if string is empty)
1608 if (!fldP) return false; // no field, no value
1609 fldP->getAsString(s);
1610 aString.erase();
1611 if (strucmp(s.c_str(),"mailto:",7)!=0 && s.size()>0)
1612 aString="mailto:";
1613 aString+=s;
1614 return true;
1615 case CONVMODE_VALUETYPE14:
1616 case CONVMODE_FULLVALUETYPE16:
1617 // specify value type of field if needed
1618 if (!fldP) return false; // no field -> no VALUE param
1619 if (fldP->isBasedOn(fty_timestamp)) {
1620 // show VALUE=DATE if we have date-only or time-only
1621 tctx = static_cast<TTimestampField *>(fldP)->getTimeContext();
1622 if (TCTX_IS_DURATION(tctx)) aString="DURATION";
1623 else if (TCTX_IS_DATEONLY(tctx)) aString="DATE";
1624 else if (TCTX_IS_TIMEONLY(tctx)) aString="TIME";
1625 else {
1626 // only show type if full value type requested
1627 if (convmode==CONVMODE_FULLVALUETYPE16)
1628 aString="DATE-TIME";
1629 else
1630 return false; // we don't need a VALUE param for normal datetimes
1631 }
1632 }
1633 else
1634 return false; // no field type that needs VALUE param
1635 // valuetype generated
1636 return true;
1637 case CONVMODE_VERSION1:
1638 // version string
1639 aString=aItem.getItemType()->getTypeVers(fProfileMode);
1640 return true;
1641 case CONVMODE_PRODID2:
1642 // PRODID ISO9070 non-registered FPI
1643 // -//ABC Corporation//NONSGML My Product//EN
1644 aString = SYSYNC_FPI"-//Synthesis AG//NONSGML SyncML Engine V" "3" "." "4" "." "0"
"." "47" "//EN"
;
1645 return true;
1646 case CONVMODE_BITMAP11:
1647 // bitmap is a special case of multimix, set up params
1648 nummix = 1;
1649 mixOffs[0]=0;
1650 mixIsFlags[0]=true;
1651 goto genmix;
1652 case CONVMODE_MULTIMIX15:
1653 // list of special values that can be either literals or bit masks, and can optionally affect more than one field
1654 // Syntax:
1655 // Bx : Bit number x (like in CONVMODE_BITMAP, x = 0..63)
1656 // Lxxxx : Literal xxxxx (xxxxx will just be copied from the source field)
1657 // y.Bx or y.Lxxxx : use y as field offset to use (no y means 0 offset)
1658 // - collect parameters to generate mix from enums
1659 nummix = 0;
1660 enumP = aConvDefP->enumdefs;
1661 while(enumP) {
1662 if (mixvalparse(TCFG_CSTR(enumP->enumval)enumP->enumval.c_str(),offs,isFlags,bitNo)) {
1663 // check if this field is in list already
1664 for (i=0; i<nummix; i++) {
1665 if (mixOffs[i] == offs) goto next; // referring to same field again, skip
1666 }
1667 // is a new field, add it to list
1668 mixOffs[nummix] = offs;
1669 mixIsFlags[nummix] = isFlags;
1670 nummix++;
1671 if (nummix>=maxmix) break; // no more mixes allowed, stop scanning
1672 }
1673 next:
1674 // check next enum
1675 enumP=enumP->next;
1676 }
1677 genmix:
1678 // now generate strings from collected data
1679 aString.erase();
1680 for (i=0; i<nummix; i++) {
1681 // get target field
1682 fldP = aItem.getArrayField(aFid+mixOffs[i],aArrIndex,true); // existing array elements only
1683 if (fldP) {
1684 if (mixIsFlags[i]) {
1685 // use target as bitmask to create bit numbers
1686 flags=fldP->getAsInteger();
1687 bitNo=0;
1688 while (flags) {
1689 if (flags & 1) {
1690 // create bit representation
1691 if (!aString.empty() && aConvDefP->combineSep)
1692 aString+=aConvDefP->combineSep; // separator first if not first item
1693 if (convmode==CONVMODE_MULTIMIX15) {
1694 // multimix mode, use full syntax
1695 if (mixOffs[i]>0)
1696 StringObjAppendPrintf(aString,"%d.",mixOffs[i]);
1697 aString += 'B';
1698 }
1699 // add bit number
1700 StringObjAppendPrintf(aString,"%hd",bitNo);
1701 }
1702 flags >>= 1; // consume this one
1703 bitNo++;
1704 }
1705 }
1706 else {
1707 // literal
1708 if (!fldP->isEmpty()) {
1709 if (!aString.empty() && aConvDefP->combineSep)
1710 aString+=aConvDefP->combineSep; // append separator if there are more flags
1711 if (mixOffs[i]>0)
1712 StringObjAppendPrintf(aString,"%d.",mixOffs[i]);
1713 aString += 'L'; // literal
1714 fldP->appendToString(aString);
1715 }
1716 }
1717 } // field available
1718 } // for each mix
1719 return true;
1720 case CONVMODE_RRULE20 +0: {
1721 // get values from field block
1722 if (aFid<0) return false; // no field, no string
1723 // - freq/freqmod
1724 if (!(sfP = ITEMFIELD_DYNAMIC_CAST_PTR(TStringField,fty_string,aItem.getArrayField(aFid,aArrIndex,true))(aItem.getArrayField(aFid,aArrIndex,true)->isBasedOn(fty_string
) ? static_cast<TStringField *>(aItem.getArrayField(aFid
,aArrIndex,true)) : __null)
)) return false;
1725 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
1726 sfP->getAsString(s);
1727 freq='0'; // none
1728 freqmod=' '; // no modifier
1729 if (s.size()>0) freq=s[0];
1730 if (s.size()>1) freqmod=s[1];
1731 // - interval
1732 if (!(ifP = ITEMFIELD_DYNAMIC_CAST_PTR(TIntegerField,fty_integer,aItem.getArrayField(aFid,aArrIndex,true))(aItem.getArrayField(aFid,aArrIndex,true)->isBasedOn(fty_integer
) ? static_cast<TIntegerField *>(aItem.getArrayField(aFid
,aArrIndex,true)) : __null)
)) return false;
1733 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
1734 interval=(sInt16)ifP->getAsInteger();
1735 // - firstmask
1736 if (!(ifP = ITEMFIELD_DYNAMIC_CAST_PTR(TIntegerField,fty_integer,aItem.getArrayField(aFid,aArrIndex,true))(aItem.getArrayField(aFid,aArrIndex,true)->isBasedOn(fty_integer
) ? static_cast<TIntegerField *>(aItem.getArrayField(aFid
,aArrIndex,true)) : __null)
)) return false;
1737 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
1738 firstmask=ifP->getAsInteger();
1739 // - lastmask
1740 if (!(ifP = ITEMFIELD_DYNAMIC_CAST_PTR(TIntegerField,fty_integer,aItem.getArrayField(aFid,aArrIndex,true))(aItem.getArrayField(aFid,aArrIndex,true)->isBasedOn(fty_integer
) ? static_cast<TIntegerField *>(aItem.getArrayField(aFid
,aArrIndex,true)) : __null)
)) return false;
1741 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
1742 lastmask=ifP->getAsInteger();
1743 // - until
1744 if (!(tsFldP = ITEMFIELD_DYNAMIC_CAST_PTR(TTimestampField,fty_timestamp,aItem.getArrayField(aFid,aArrIndex,true))(aItem.getArrayField(aFid,aArrIndex,true)->isBasedOn(fty_timestamp
) ? static_cast<TTimestampField *>(aItem.getArrayField(
aFid,aArrIndex,true)) : __null)
)) return false;
1745 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
1746 // Until
1747 // - UTC preferred as output format if basically possible and not actively disabled
1748 untilcontext=
1749 fReceiverCanHandleUTC && getSession()->canHandleUTC() ?
1750 TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)) :
1751 fItemTimeContext;
1752 // - get in preferred zone (or floating)
1753 until=tsFldP->getTimestampAs(untilcontext,&untilcontext);
1754 lineartime_t tzend = until;
1755 // A RRULE with no end extends at least into current time (for tz range update, see below)
1756 if (until==noLinearTime) {
1757 // no end, but we still need a range to generate time zones for
1758 tzend = getSession()->getSystemNowAs(TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)));
1759 }
1760 else {
1761 // Treat RR_END similar to CONVMODE_AUTODATE, i.e. prevent rendering a date-only value in mimo_old (which is not correct according to the standard)
1762 if (TCTX_IS_DATEONLY(untilcontext) && fMimeDirMode==mimo_old) {
1763 // there are no date-only recurrence ends in vCalendar 1.0
1764 until = lineartime2dateonlyTime(until)+secondToLinearTimeFactor*SecsPerHour*24-1 ; // make time part 23:59:59.999 of this day
1765 untilcontext &= ~TCTX_DATEONLY; // clear dateonly rendering flag
1766 }
1767 }
1768 // Now do the conversion
1769 bool ok;
1770 if (fMimeDirMode==mimo_old) {
1771 // vCalendar 1.0 type RRULE
1772 ok = internalToRRULE1(
1773 aString,
1774 freq,
1775 freqmod,
1776 interval,
1777 firstmask,
1778 lastmask,
1779 until,
1780 untilcontext,
1781 GETDBGLOGGER(getDbgLogger())
1782 );
1783 }
1784 else {
1785 // iCalendar 2.0 type RRULE
1786 ok = internalToRRULE2(
1787 aString,
1788 freq,
1789 freqmod,
1790 interval,
1791 firstmask,
1792 lastmask,
1793 until,
1794 untilcontext,
1795 GETDBGLOGGER(getDbgLogger())
1796 );
1797 }
1798 // if we actually generated a RRULE, the range of used time zones must be updated according
1799 // to the recurrence end (date or open end, see tzend calculation above)
1800 if (!aString.empty()) {
1801 if (fEarliestTZDate==noLinearTime || tzend<fEarliestTZDate) fEarliestTZDate = tzend;
1802 if (fLatestTZDate==noLinearTime || tzend>fLatestTZDate) fLatestTZDate = tzend;
1803 }
1804 return ok;
1805 break; // just in case
1806 }
1807 default:
1808 // unknown mode, no value
1809 return false;
1810 }
1811 return false;
1812} // TMimeDirProfileHandler::fieldToMIMEString
1813
1814
1815
1816/// @brief test if char is part of a line end
1817/// @return true if aChar is a line end char
1818/// @param [in] aChar charcter to check
1819static bool isLineEndChar(appChar aChar)
1820{
1821 return (aChar=='\x0D') || (aChar=='\x0A');
1822} // isLineEndChar
1823
1824
1825/// @brief test if char is end of a line or end of the text (NUL)
1826/// @return true if aChar is a line end char or NUL
1827/// @param [in] aChar charcter to check
1828static bool isEndOfLineOrText(appChar aChar)
1829{
1830 return (aChar==0) || isLineEndChar(aChar);
1831} // isEndOfLineOrText
1832
1833
1834/// @brief test if a line end of any kind is at aText
1835/// @note CR,LF,CRLF and CR...CRLF sequences are all considered one line end
1836/// @return true if line end found
1837/// @param [in/out] aText advance past line end sequence
1838static bool testAndSkipLineEnd(cAppCharP &aText)
1839{
1840 cAppCharP p = aText;
1841 bool crFound = false;
1842 // skip sequence of CRs
1843 while (*p=='\x0D') {
1844 p++;
1845 crFound = true;
1846 }
1847 // past all CRs in a row
1848 if (*p=='\x0A') {
1849 // independent of the number of CRs preceeding, this is a line end including the LF
1850 aText = p+1; // past LF
1851 return true;
1852 }
1853 else if (crFound) {
1854 // we previously found at least one CR at the beginning, but no LF is following
1855 // -> assume CR only line ends, consider first CR as a line end by itself
1856 aText++; // skip first CR
1857 return true;
1858 }
1859 // not a line end
1860 return false;
1861} // testAndSkipLineEnd
1862
1863
1864
1865// return incremented pointer pointing to original char or next non-folded char
1866static cAppCharP skipfolded(cAppCharP aText, TMimeDirMode aMimeMode, bool qpSoftBreakCancel=false)
1867{
1868 cAppCharP p = aText;
1869 if (testAndSkipLineEnd(p)) {
1870 // check for folding sequence
1871 if (*p==' ' || *p=='\x09') {
1872 // line end followed by space: folding sequence
1873 if (aMimeMode==mimo_standard) {
1874 // ignore entire sequence (CR,LF,SPACE/TAB)
1875 return p+1;
1876 }
1877 else {
1878 // old folding type, LWSP must be preserved
1879 return p;
1880 }
1881 }
1882 }
1883 else if (qpSoftBreakCancel && *p=='=') {
1884 // could be soft break sequence, check for line end
1885 p++;
1886 if (testAndSkipLineEnd(p)) {
1887 return p;
1888 }
1889 }
1890 // not folding sequence, return ptr to char as is
1891 return aText;
1892} // skipfolded
1893
1894
1895// get next character, while skipping MIME-DIR folding sequences
1896// if qpSoftBreakCancel, QUOTED-PRINTABLE encoding style soft-line-break sequences
1897// will be eliminated
1898static const char *nextunfolded(const char *p, TMimeDirMode aMimeMode, bool qpSoftBreakCancel=false)
1899{
1900 if (*p==0) return p; // at end of string, do not advance
1901 p++; // point to next
1902 return skipfolded(p,aMimeMode,qpSoftBreakCancel);
1903} // nextunfolded
1904
1905
1906// helper for MIME DIR parsing:
1907// - apply encoding and charset conversion to values part of property if needed
1908static void decodeValue(
1909 TEncodingTypes aEncoding, // the encoding to be used
1910 TCharSets aCharset, // charset to be applied to 8-bit chars
1911 TMimeDirMode aMimeMode, // the MIME mode
1912 char aStructSep, // input is structured value, stop when aStructSep is encountered
1913 char aAltSep, // alternate separator, also stop when encountering this one (but only if aStructSep is !=0)
1914 const char *&aText, // where to start decoding, updated past last char added to aVal
1915 string &aVal // decoded data is stored here (possibly some binary data)
1916)
1917{
1918 const int maxseqlen=6;
1919 int seqlen;
1920 char c,chrs[maxseqlen];
1921 const char *p,*q;
1922
1923 aVal.erase();
1924 bool escaped = false;
1925 bool lastWasQPCR = false;
1926 if (aEncoding==enc_quoted_printable) {
1927 // decode quoted-printable content
1928 p = skipfolded(aText,aMimeMode,true); // get unfolded start point (in case value starts with folding sequence)
1929 do {
1930 // decode standard content
1931 c=*p;
1932 if (isEndOfLineOrText(c) || (!escaped && aStructSep!=0 && (c==aStructSep || c==aAltSep))) break; // EOLN and struct separators terminate value
1933 // test if escape char (but do not filter it out, as actual de-escaping is done in parseValue() later
1934 escaped=(!escaped) && (c=='\\'); // escape next only if we are not escaped already
1935 // char found
1936 if (c=='=') {
1937 uInt16 code;
1938 const char *s;
1939 char hex[2];
1940
1941 // The Nokia N9 vCalendar implementation sends ==0A=0D= at the end of
1942 // the line when it should send just the =. Apparently the CRLF octets
1943 // get inserted before quoted-printable encoding and then get encoded.
1944 // Such a sequence is invalid because = cannot be used literally
1945 // and must be followed by characters representing the hex value,
1946 // i.e. == is already invalid.
1947 //
1948 // We must skip over the entire sequence and then move forward to the
1949 // next valid character, i.e. skip over the soft line break that
1950 // follows the ==0A=0D.
1951 //
1952 // Without this special case, the code below would insert additional
1953 // characters into the decoded text.
1954 if (!strcmp(p, "==0A=0D=")) {
1955 p += strlen("==0A=0D=") - 1;
1956 p=nextunfolded(p,aMimeMode,true);
1957 continue;
1958 }
1959
1960 s=nextunfolded(p,aMimeMode,true);
1961 if (*s==0) break; // end of string
1962 hex[0]=*s; // first digit
1963 s=nextunfolded(s,aMimeMode,true);
1964 if (*s==0) break; // end of string
1965 hex[1]=*s; // second digit
1966 if (HexStrToUShort(hex,code,2)==2) {
1967 p=s; // continue with next char after second digit
1968 c=code; // decoded char
1969 if (c=='\x0D') {
1970 c='\n'; // convert to newline
1971 lastWasQPCR = true; // remember
1972 }
1973 else if (c=='\x0A') {
1974 if (lastWasQPCR) {
1975 // if last was CR, ignore LF (CR already has generated a newline)
1976 p = nextunfolded(p,aMimeMode,true); // but skip it for now.
1977 lastWasQPCR = false;
1978 continue; // ignore LF
1979 }
1980 // LF not preceeded by CR is a newline
1981 c='\n'; // convert to newline
1982 }
1983 else {
1984 // neither CR nor LF
1985 lastWasQPCR = false;
1986 }
1987 }
1988 }
1989 else {
1990 lastWasQPCR = false;
1991 }
1992 seqlen=1; // assume logical char consists of single byte
1993 chrs[0]=c;
1994 do {
1995 seqlen=appendCharsAsUTF8(chrs,aVal,aCharset,seqlen); // add char (possibly with UTF8 expansion) to aVal
1996 if (seqlen<=1) break; // done
1997 // need more bytes to encode entire char
1998 for (int i=1;i<seqlen;i++) {
1999 p=nextunfolded(p,aMimeMode,true);
2000 chrs[i]=*p;
2001 }
2002 } while(true);
2003 p=nextunfolded(p,aMimeMode,true);
2004 } while(true);
2005 } // quoted printable
2006 else if (aEncoding==enc_base64 || aEncoding==enc_b) {
2007 // Decode b64
2008 // - find start of property value
2009 p = skipfolded(aText,aMimeMode,false); // get unfolded start point (in case value starts with folding sequence
2010 // - find end of property value
2011 q=p;
2012 while (*q) {
2013 if (aStructSep!=0 && (*q==aStructSep || *q==aAltSep))
2014 break; // structure separator terminates B64 as well (colon, semicolon and comma never appear in B64)
2015 if (isLineEndChar(*q)) {
2016 // end of line. Check if this is folding or end of property
2017 cAppCharP r=skipfolded(q,aMimeMode,false);
2018 if (r==q) {
2019 // no folding skipped -> this appears to be the end of the property
2020 // Now for ill-encoded vCard 2.1 which chop B64 into lines, but do not prefix continuation
2021 // lines with some whitespace, make sure the next line contains a colon
2022 // - skip that line end
2023 while (isLineEndChar(*r)) r++;
2024 // - examine next line
2025 bool eob64 = false;
2026 for (cAppCharP r2=r; *r2 && !isLineEndChar(*r2); r2++) {
2027 if (*r2==':' || *r2==';') {
2028 eob64 = true;
2029 break;
2030 }
2031 }
2032 if (eob64) break; // q is end of B64 string -> go decode it
2033 // there's more to the b64 string at r, continue looking for end
2034 }
2035 // skip to continuation of B64 string
2036 q=r;
2037 }
2038 else
2039 q++;
2040 }
2041 // - decode base 64
2042 uInt32 binsz=0;
2043 uInt8 *binP = b64::decode(p, q-p, &binsz);
2044 aVal.append((const char *)binP,binsz);
2045 b64::free(binP);
2046 // - continue at next char after b64 value
2047 p=q;
2048 }
2049 else {
2050 // no (known) encoding
2051 p = skipfolded(aText,aMimeMode,false); // get unfolded start point (in case value starts with folding sequence)
2052 do {
2053 c=*p;
2054 if (isEndOfLineOrText(c) || (!escaped && aStructSep!=0 && (c==aStructSep || c==aAltSep))) break; // EOLN and structure-sep (usually ;) terminate value
2055 // test if escape char (but do not filter it out, as actual de-escaping is done in parseValue() later
2056 escaped=(!escaped) && (c=='\\'); // escape next only if we are not escaped already
2057 // process char
2058 seqlen=1; // assume logical char consists of single byte
2059 chrs[0]=c;
2060 do {
2061 seqlen=appendCharsAsUTF8(chrs,aVal,aCharset,seqlen); // add char (possibly with UTF8 expansion) to aVal
2062 if (seqlen<=1) break; // done
2063 // need more bytes to encode entire char
2064 for (int i=1;i<seqlen;i++) {
2065 p=nextunfolded(p,aMimeMode,false);
2066 chrs[i]=*p;
2067 }
2068 } while(true);
2069 p=nextunfolded(p,aMimeMode,false);
2070 } while(true);
2071 } // no encoding
2072 // return pointer to terminating char
2073 aText=p;
2074} // decodeValue
2075
2076
2077// helper for MIME DIR generation:
2078// - apply encoding to values part of property if needed
2079static void encodeValues(
2080 TEncodingTypes aEncoding, // the encoding to be used
2081 TCharSets aCharSet, // charset to be applied to 8-bit chars
2082 const string &aValuedata, // the data to be encoded (possibly some binary data)
2083 string &aPropertytext, // the property string where encoded data is appended
2084 bool aDoNotFoldContent // special override for folding
2085)
2086{
2087 const uInt8 *valPtr = (const uInt8 *)aValuedata.c_str();
2088 size_t valSz = aValuedata.size();
2089 string s;
2090 if (aCharSet!=chs_utf8) {
2091 // we need to convert to target charset first
2092 appendUTF8ToString((const char *)valPtr,s,aCharSet,lem_none,qm_none);
2093 valPtr = (const uInt8 *)s.c_str();
2094 valSz = s.size();
2095 }
2096 // - apply encoding if needed
2097 appendEncoded(
2098 valPtr, // input
2099 valSz,
2100 aPropertytext, // append output here
2101 aEncoding, // desired encoding
2102 aDoNotFoldContent ?
2103 0 // disable insertion of soft line breaks
2104 : MIME_MAXLINESIZE75-1, // limit to standard MIME-linesize, leave one free for possible extra folding space
2105 aPropertytext.size() % MIME_MAXLINESIZE75, // current line size
2106 true // insert CRs only for softbreaks (for post-processing by folding)
2107 );
2108} // encodeValues
2109
2110
2111// helper for MIME DIR generation:
2112// - fold, copy and terminate (CRLF) property into aString output
2113// - \n in input is explicit "fold here" indicator
2114// - \b in input is an optional "fold here" indicator, which will appear as space in the
2115// output when needed, but will otherwise be discarded
2116// - \r in input indicates that a line end must be inserted
2117// if aDoSoftBreak==true, only a line break is inserted (QUOTED-PRINTABLE soft line break)
2118// otherwise, a full folding sequence (CRLF + space) is inserted. In case of MIME-DIR,
2119// QP softbreaks are nothing special, and still need an extra space (as this is reversed on parsing).
2120static void finalizeProperty(
2121 const char *proptext,
2122 string &aString,
2123 TMimeDirMode aMimeMode, // MIME mode (older or newer vXXX format compatibility)
2124 bool aDoNotFold, // set to prevent folding
2125 bool aDoSoftBreak // set to insert QP-softbreaks when \r is encountered, otherwise do a full hard break (which essentially inserts a space for mimo_old)
2126)
2127{
2128 // make sure that allocation does not increase char by char
2129 aString.reserve(aString.size()+strlen(proptext)+100);
2130 char c;
2131 ssize_t n = 0, llen = 0;
2132 ssize_t foldLoc = -1; // possible break location - linear white space or explicit break indicator
2133 bool explf;
2134 cAppCharP firstunwritten=proptext; // none written yet
2135 while (proptext && (c=*proptext)!=0) {
2136 // remember position of last lwsp (space or TAB)
2137 if (aMimeMode==mimo_old && (c==' ' || c==0x09))
2138 foldLoc=n; // white spaces are relevant in mimo_old only (but '\b' are checked also for MIME-DIR, see below)
2139 // next (UTF8) char
2140 // Note: we prevent folding within UTF8 sequences as result string would become inconvertible e.g. into UTF16
2141 uInt32 uc;
2142 cAppCharP nP = UTF8toUCS4(proptext, uc);
2143 if (uc!=0) {
2144 // UTF-8 compliant byte (or byte sequence), skip as an entiety
2145 n += nP-proptext;
2146 proptext = nP;
2147 }
2148 else {
2149 // Not UTF-8 compliant, simply one byte
2150 n++;
2151 proptext++;
2152 }
2153 // check for optional break indicator (MIME-DIR or allowFoldAtSep)
2154 if (c=='\b') {
2155 aString.append(firstunwritten,n-1); // copy what we have up to that '\b'
2156 firstunwritten += n; // now pointing to next char after '\b'
2157 foldLoc = 0; // usually now pointing to a NON-LWSP, except if by accident a LWSP follows, which is ok as well
2158 n = 0; // continue checking from here
2159 continue; // check next
2160 }
2161 // update line length
2162 llen++;
2163 // explicit linefeed flag
2164 explf=(c=='\n' || c=='\r');
2165 if (aDoNotFold) {
2166 // prohibit folding for ugly devices like V3i
2167 if (explf) {
2168 // append what we have until here
2169 n--; // explicit \n or \r is ignored
2170 aString.append(firstunwritten,n);
2171 // forget the explicit linefeed - and continue
2172 n = 0;
2173 llen = 0;
2174 firstunwritten = proptext;
2175 }
2176 }
2177 else if ((llen>=MIME_MAXLINESIZE75 && *proptext) || explf) { // avoid unnecessary folding (there must be something more coming)
2178 // folding needed (line gets longer than MIME_MAXLINESIZE or '\n' found in input string)
2179 if (aMimeMode==mimo_old && !explf) {
2180 // vCard 2.1 type folding, must occur before an LWSP
2181 if (foldLoc<0) {
2182 // emergency force fold and accept data being shredded
2183 // - copy all we have by now
2184 aString.append(firstunwritten,n);
2185 firstunwritten += n; // now pointing to next
Value stored to 'firstunwritten' is never read
2186 n = 0; // none left - new line is empty now
2187 // - insert line break
2188 aString.append("\x0D\x0A "); // line break AND an extra shredding space
2189 }
2190 else {
2191 // - copy all up to (but not including) last LWSP (or '\b' break indicator)
2192 aString.append(firstunwritten,foldLoc);
2193 firstunwritten += foldLoc; // now pointing to LWSP (or non-LWSP in case of '\b')
2194 n -= foldLoc; // number of chars left (including LWSP)
2195 // - insert line break
2196 aString.append("\x0D\x0A"); // line break
2197 if (*firstunwritten!=' ' && *firstunwritten!=0x09)
2198 aString += ' '; // breaking at location indicated by '\b', LWSP must be added
2199 // - copy rest scanned so far (except in '\b' case, this begins with an LWSP)
2200 aString.append(firstunwritten,n);
2201 }
2202 }
2203 else {
2204 // MIME-DIR type folding, can occur anywhere and *adds* a LWSP (which is removed at unfolding later)
2205 // or mimo-old type folding containing explicit CR(LF)s -> break here
2206 if (explf) {
2207 // explicit \n or \r is not copied, but only causes line break to occur
2208 n--;
2209 }
2210 if (foldLoc<0 || explf) {
2211 // no or explf indicator, just fold here
2212 aString.append(firstunwritten,n);
2213 n = 0; // nothing left to carry over - new line is empty now
2214 }
2215 else {
2216 // we have a preferred folding location detected before: copy all up to (but not including) break indicator
2217 aString.append(firstunwritten,foldLoc);
2218 firstunwritten += foldLoc; // now pointing to next char after break)
2219 n -= foldLoc; // number of chars left (including LWSP)
2220 }
2221 // now fold
2222 aString.append("\x0D\x0A"); // line break
2223 if (
2224 (c!='\r' && aMimeMode==mimo_standard) || // folding indicator and MIME-DIR -> folding always must insert extra space
2225 (c=='\r' && !aDoSoftBreak) // soft-break indicator, but not in softbreak mode (i.e. B64 input) -> always insert extra space
2226 )
2227 aString += ' '; // not only soft line break, but MIMD-DIR type folding
2228 // - copy carry over from previous line
2229 if (n>0) aString.append(firstunwritten,n);
2230 }
2231 // we are on a new line now
2232 llen = n; // new line has this size of what was carried over from the previous line
2233 foldLoc = -1;
2234 n = 0;
2235 firstunwritten = proptext;
2236 }
2237 }
2238 // append rest
2239 aString.append(firstunwritten,n);
2240 // terminate property
2241 aString.append("\x0D\x0A"); // CRLF
2242} // finalizeProperty
2243
2244
2245// results for generateValue:
2246#define GENVALUE_NOTSUPPORTED0 0 // field not supported
2247#define GENVALUE_EXHAUSTED1 1 // array field exhausted
2248#define GENVALUE_EMPTYELEMENT2 2 // array field empty
2249#define GENVALUE_EMPTY3 3 // non-array field empty
2250#define GENVALUE_ELEMENT4 4 // non-empty array element
2251#define GENVALUE_NONEMPTY5 5 // non-empty non-array value
2252
2253// helper for generateMimeDir()
2254// - generate parameter or property value(list),
2255// returns: GENVALUE_xxx
2256sInt16 TMimeDirProfileHandler::generateValue(
2257 TMultiFieldItem &aItem, // the item where data comes from
2258 const TConversionDef *aConvDefP,
2259 sInt16 aBaseOffset, // basic fid offset to use
2260 sInt16 aRepOffset, // repeat offset, adds to aBaseOffset for non-array fields, is array index for array fields
2261 string &aString, // where value is ADDED
2262 char aSeparator, // separator to be used between values if field contains multiple values in a list separated by confdef->combineSep
2263 TMimeDirMode aMimeMode, // MIME mode (older or newer vXXX format compatibility)
2264 bool aParamValue, // set if generating parameter value (different escaping rules, i.e. colon must be escaped, or entire value double-quoted)
2265 bool aStructured, // set if value consists of multiple values (needs semicolon content escaping)
2266 bool aCommaEscape, // set if "," content escaping is needed (for values in valuelists like TYPE=TEL,WORK etc.)
2267 TEncodingTypes &aEncoding, // modified if special value encoding is required
2268 bool &aNonASCII, // set if any non standard 7bit ASCII-char is contained
2269 char aFirstChar, // will be appended before value if there is any value (and in MIME-DIR '\b' optional break indicator is appended as well)
2270 sInt32 &aNumNonSpcs, // how many non-spaces are already in the value
2271 bool aFoldAtSeparators, // if true, even in mimo_old folding may appear at value separators (adding an extra space - which is ok for EXDATE and similar)
2272 bool aEscapeOnlyLF // if true, only linefeeds are escaped as \n, but nothing else (not even \ itself)
2273)
2274{
2275 string vallist; // as received from fieldToMIMEString()
2276 string val; // single value
2277 string outval; // entire value (list) escaped
2278 char c;
2279
2280 // determine field ID
2281 bool isarray = false; // no array by default
2282 sInt16 fid=aConvDefP->fieldid;
2283 if (fid>=0) {
2284 // field has storage
2285 // - fid is always offset by baseoffset
2286 fid += aBaseOffset;
2287 // - adjust now
2288 isarray = aItem.adjustFidAndIndex(fid,aRepOffset);
2289 // generate only if available in both source and target (or non-SyncML context)
2290 if (isFieldAvailable(aItem,fid)) {
2291 // find out if value exists
2292 if (aItem.isAssigned(fid)) {
2293 // - field has a value assigned (altough this might be empty string)
2294 // determine max size to truncate value if needed
2295 outval.erase();
2296 sInt32 valsiz=0; // net size of value
2297 //%%%% getTargetItemType???
2298 sInt32 maxSiz=aItem.getTargetItemType()->getFieldOptions(fid)->maxsize;
2299 if (maxSiz==FIELD_OPT_MAXSIZE_UNKNOWN-1 || maxSiz==FIELD_OPT_MAXSIZE_NONE0)
2300 maxSiz = 0; // no size restriction
2301 bool noTruncate=aItem.getTargetItemType()->getFieldOptions(fid)->notruncate;
2302 // check for BLOB values
2303 sInt16 convmode = aConvDefP->convmode & CONVMODE_MASK0xFF;
2304 if (convmode==CONVMODE_BLOB_B6412 || convmode==CONVMODE_BLOB_AUTO17) {
2305 // no value lists, escaping, enums. Simply set value and encoding
2306 TItemField *fldP = aItem.getArrayField(fid,aRepOffset,true); // existing array elements only
2307 if (!fldP) return GENVALUE_EXHAUSTED1; // no leaf field - must be exhausted array (fldP==NULL is not possible here for non-arrays)
2308 if (fldP->isUnassigned()) return GENVALUE_EMPTYELEMENT2; // must be empty element empty element, but field supported (fldP==NULL is not possible here for non-arrays)
2309 // check max size and truncate if needed
2310 if (maxSiz && sInt32(fldP->getStringSize())>maxSiz) {
2311 if (noTruncate || getSession()->getSyncMLVersion()<syncml_vers_1_2) {
2312 // truncate not allowed (default for pre-SyncML 1.2 for BLOB fields)
2313 PDEBUGPRINTFX(DBG_ERROR+DBG_GEN,("BLOB value exceeds max size (%ld) and cannot be truncated -> omit", (long)maxSiz)){ if (((0x00000002 +0x00000400) & getDbgMask()) == (0x00000002
+0x00000400)) getDbgLogger()->setNextMask(0x00000002 +0x00000400
).DebugPrintfLastMask ("BLOB value exceeds max size (%ld) and cannot be truncated -> omit"
, (long)maxSiz); }
;
2314 return GENVALUE_NOTSUPPORTED0; // treat it as if field was not supported locally
2315 }
2316 }
2317 // append to existing string
2318 fldP->appendToString(outval,maxSiz);
2319 if (convmode==CONVMODE_BLOB_AUTO17) {
2320 // auto mode: use B64 encoding only if non-printable or
2321 // non-ASCII characters are in the value
2322 size_t len = outval.size();
2323 for (size_t i = 0; i < len; i++) {
2324 char c = outval[i];
2325 if (!isascii(c) || !isprint(c)) {
2326 aEncoding=enc_base64;
2327 break;
2328 }
2329 }
2330 }
2331 else {
2332 // blob mode: always use B64
2333 aEncoding=enc_base64;
2334 }
2335 // only ASCII in value: either because it contains only
2336 // those to start with or because they will be encoded
2337 aNonASCII=false;
2338 }
2339 else {
2340 // apply custom field(s)-to-string translation if needed
2341 if (!fieldToMIMEString(aItem,fid,aRepOffset,aConvDefP,vallist)) {
2342 // check if no value because array was exhausted
2343 if (aItem.getArrayField(fid,aRepOffset,true))
2344 return isarray ? GENVALUE_EMPTYELEMENT2 : GENVALUE_EMPTY3; // no value (but field supported)
2345 else
2346 return GENVALUE_EXHAUSTED1; // no leaf field - must be exhausted array
2347 }
2348 // separate value list into multiple values if needed
2349 const char *lp = vallist.c_str(); // list item pointer
2350 const char *sp; // start of item pointer (helper)
2351 sInt32 n;
2352 while (*lp!=0) {
2353 // find (single) input value string's end
2354 for (sp=lp,n=0; (c=*lp)!=0; lp++, n++) {
2355 if (c==aConvDefP->combineSep) break;
2356 }
2357 // - n=size of input value, p=ptr to end of value (0 or sep)
2358 val.assign(sp,n);
2359 // perform enum translation if needed
2360 if (aConvDefP->enumdefs) {
2361 const TEnumerationDef *enumP = aConvDefP->findEnumByVal(val.c_str());
2362 if (enumP) {
2363 PDEBUGPRINTFX(DBG_GEN+DBG_EXOTIC,("Val='%s' translated to enumName='%s' mode=%s", val.c_str(), TCFG_CSTR(enumP->enumtext), EnumModeNames[enumP->enummode])){ if (((0x00000400 +0x80000000) & getDbgMask()) == (0x00000400
+0x80000000)) getDbgLogger()->setNextMask(0x00000400 +0x80000000
).DebugPrintfLastMask ("Val='%s' translated to enumName='%s' mode=%s"
, val.c_str(), enumP->enumtext.c_str(), EnumModeNames[enumP
->enummode]); }
;
2364 if (enumP->enummode==enm_ignore)
2365 val.erase(); // ignore -> make value empty as empty values are never stored
2366 else if (enumP->enummode==enm_prefix) {
2367 // replace value prefix by text prefix
2368 n=TCFG_SIZE(enumP->enumval)enumP->enumval.size();
2369 val.replace(0,n,TCFG_CSTR(enumP->enumtext)enumP->enumtext.c_str()); // replace val prefix by text prefix
2370 }
2371 else {
2372 // simply use translated value
2373 val=enumP->enumtext;
2374 }
2375 }
2376 else {
2377 PDEBUGPRINTFX(DBG_GEN+DBG_EXOTIC,("No translation found for Val='%s'", val.c_str())){ if (((0x00000400 +0x80000000) & getDbgMask()) == (0x00000400
+0x80000000)) getDbgLogger()->setNextMask(0x00000400 +0x80000000
).DebugPrintfLastMask ("No translation found for Val='%s'", val
.c_str()); }
;
2378 }
2379 }
2380 // - val is now translated enum (or original value if value does not match any enum text)
2381 valsiz+=val.size();
2382 // We have two choices for parameter values:
2383 // - quoted string in double quotes
2384 // - a simple string without
2385 //
2386 // Line breaks and control characters are not supported
2387 // either way; the backslash escape mechanism is not used
2388 // unless explicitly specified otherwise for specific
2389 // parameters (like TYPE).
2390 //
2391 // We pick the simple string option only if the value
2392 // contains only alphanumeric characters plus hyphen, slash and
2393 // underscore. Spaces are allowed by the RFC, but are
2394 // known to cause issues in other parsers (EDS before
2395 // 3.10) unless used in a quoted string, therefore we
2396 // are more conservative than the RFC.
2397 bool quotedstring = false;
2398 if (aParamValue) {
2399 for (const char *p=val.c_str();(c=*p)!=0;p++) {
2400 if (!(isalnum(c) ||
2401 c == '/' || // for TZID=Europe/Paris - Funambol OneMedia does not handle quoting that.
2402 c == '-' ||
2403 c == '_')) {
2404 quotedstring = true;
2405 outval+='"';
2406 break;
2407 }
2408 }
2409 }
2410
2411 // perform escaping and determine need for encoding
2412 bool spaceonly = true;
2413 for (const char *p=val.c_str();(c=*p)!=0 && (c!=aConvDefP->combineSep);p++) {
2414 // process char
2415 // - check for whitespace
2416 if (!isspace(c)) {
2417 spaceonly = false; // does not consist of whitespace only
2418 aNumNonSpcs++; // count consecutive non-spaces
2419 if (aMimeMode==mimo_old && aEncoding==enc_none && aNumNonSpcs>MIME_MAXLINESIZE75) {
2420 // If text contains words with critical (probably unfoldable) size in mimo-old, select quoted printable encoding
2421 aEncoding=enc_quoted_printable;
2422 }
2423 }
2424 else {
2425 aNumNonSpcs = 0; // new word starts
2426 }
2427 // only text must be fully escaped, turn escaping off for RRULE (RECUR type)
2428 // escape reserved chars
2429 switch (c) {
2430 case '"':
2431 if (aParamValue) { c = '\''; goto add_char; } // replace double quotes with single quotes
2432 goto add_char; // otherwise, just add
2433 case ',':
2434 // in MIME-DIR, always escape commas, in pre-MIME-DIR only if usage in value list requires it
2435 if ((!aCommaEscape && aMimeMode==mimo_old) || quotedstring) goto add_char;
2436 goto do_escape;
2437 case ':':
2438 // always escape colon in parameters
2439 if (!aParamValue || quotedstring) goto add_char;
2440 goto do_escape;
2441 case '\\':
2442 if (quotedstring) goto add_char;
2443 // Backslash must always be escaped
2444 // - for MIMO-old: at least Nokia 9210 does it this way
2445 // - for MIME-DIR: specified in the standard
2446 goto do_escape;
2447 case ';':
2448 // in MIME-DIR, always escape semicolons, in pre-MIME-DIR only in parameters and structured values
2449 if ((!aParamValue && !aStructured && aMimeMode==mimo_old) || quotedstring) goto add_char;
2450 do_escape:
2451 if (!aEscapeOnlyLF) {
2452 // escape chars with backslash
2453 outval+='\\';
2454 }
2455 goto out_char;
2456 case '\r':
2457 // ignore returns
2458 break;
2459 case '\n':
2460 if (quotedstring) { c = ' '; goto add_char; }
2461 // quote linefeeds
2462 if (aMimeMode==mimo_old) {
2463 if (aEncoding==enc_none) {
2464 // For line ends in mimo_old: select quoted printable encoding
2465 aEncoding=enc_quoted_printable;
2466 }
2467 // just pass it, will be encoded later
2468 goto add_char;
2469 }
2470 else {
2471 // MIME-DIR: use quoted C-style notation
2472 outval.append("\\n");
2473 }
2474 break;
2475 default:
2476 add_char:
2477 // prevent adding space-only for params
2478 if (spaceonly && aParamValue) break; // just check next
2479 out_char:
2480 // check for non ASCII and set flag if found
2481 if ((uInt8)c > 0x7F) aNonASCII=true;
2482 // just copy to output
2483 outval+=c;
2484 break;
2485 }
2486 } // for all chars in val item
2487
2488 // terminate quoted string parameter value
2489 if (quotedstring) {
2490 outval+='"';
2491 }
2492
2493 // go to next item in the val list (if any)
2494 if (*lp!=0) {
2495 // more items in the list
2496 // - add separator if previous one is not empty param value
2497 if (!(spaceonly && aParamValue)) {
2498 if (aMimeMode==mimo_standard || aFoldAtSeparators) {
2499 outval+='\b'; // preferred break location (or location where extra space is allowed for mimo_old)
2500 aNumNonSpcs=0; // we can fold here, so word is broken
2501 }
2502 outval+=aSeparator;
2503 aNumNonSpcs++; // count it (assuming separator is never a space!)
2504 valsiz++; // count it as part of the value
2505 }
2506 lp++; // skip input list separator
2507 }
2508 // check for truncation needs (do not truncate parameters, ever)
2509 if (maxSiz && valsiz>maxSiz && !aParamValue) {
2510 // size exceeded
2511 if (noTruncate) {
2512 // truncate not allowed
2513 PDEBUGPRINTFX(DBG_ERROR+DBG_GEN,({ if (((0x00000002 +0x00000400) & getDbgMask()) == (0x00000002
+0x00000400)) getDbgLogger()->setNextMask(0x00000002 +0x00000400
).DebugPrintfLastMask ( "Value '%" ".40" "s' exceeds %ld chars net length but is noTruncate -> omit"
, outval.c_str(), (long)maxSiz ); }
2514 "Value '%" FMT_LENGTH(".40") "s' exceeds %ld chars net length but is noTruncate -> omit",{ if (((0x00000002 +0x00000400) & getDbgMask()) == (0x00000002
+0x00000400)) getDbgLogger()->setNextMask(0x00000002 +0x00000400
).DebugPrintfLastMask ( "Value '%" ".40" "s' exceeds %ld chars net length but is noTruncate -> omit"
, outval.c_str(), (long)maxSiz ); }
2515 FMT_LENGTH_LIMITED(40,outval.c_str()),{ if (((0x00000002 +0x00000400) & getDbgMask()) == (0x00000002
+0x00000400)) getDbgLogger()->setNextMask(0x00000002 +0x00000400
).DebugPrintfLastMask ( "Value '%" ".40" "s' exceeds %ld chars net length but is noTruncate -> omit"
, outval.c_str(), (long)maxSiz ); }
2516 (long)maxSiz{ if (((0x00000002 +0x00000400) & getDbgMask()) == (0x00000002
+0x00000400)) getDbgLogger()->setNextMask(0x00000002 +0x00000400
).DebugPrintfLastMask ( "Value '%" ".40" "s' exceeds %ld chars net length but is noTruncate -> omit"
, outval.c_str(), (long)maxSiz ); }
2517 )){ if (((0x00000002 +0x00000400) & getDbgMask()) == (0x00000002
+0x00000400)) getDbgLogger()->setNextMask(0x00000002 +0x00000400
).DebugPrintfLastMask ( "Value '%" ".40" "s' exceeds %ld chars net length but is noTruncate -> omit"
, outval.c_str(), (long)maxSiz ); }
;
2518 // treat it as if field was not supported locally
2519 return GENVALUE_NOTSUPPORTED0;
2520 }
2521 else {
2522 // truncate allowed, shorten output accordingly
2523 outval.erase(outval.size()-(valsiz-maxSiz));
2524 PDEBUGPRINTFX(DBG_GEN,({ if (((0x00000400) & getDbgMask()) == (0x00000400)) getDbgLogger
()->setNextMask(0x00000400).DebugPrintfLastMask ( "Truncated value '%"
".40" "s' to %ld chars net length (maxSize)", outval.c_str()
, (long)maxSiz ); }
2525 "Truncated value '%" FMT_LENGTH(".40") "s' to %ld chars net length (maxSize)",{ if (((0x00000400) & getDbgMask()) == (0x00000400)) getDbgLogger
()->setNextMask(0x00000400).DebugPrintfLastMask ( "Truncated value '%"
".40" "s' to %ld chars net length (maxSize)", outval.c_str()
, (long)maxSiz ); }
2526 FMT_LENGTH_LIMITED(40,outval.c_str()),{ if (((0x00000400) & getDbgMask()) == (0x00000400)) getDbgLogger
()->setNextMask(0x00000400).DebugPrintfLastMask ( "Truncated value '%"
".40" "s' to %ld chars net length (maxSize)", outval.c_str()
, (long)maxSiz ); }
2527 (long)maxSiz{ if (((0x00000400) & getDbgMask()) == (0x00000400)) getDbgLogger
()->setNextMask(0x00000400).DebugPrintfLastMask ( "Truncated value '%"
".40" "s' to %ld chars net length (maxSize)", outval.c_str()
, (long)maxSiz ); }
2528 )){ if (((0x00000400) & getDbgMask()) == (0x00000400)) getDbgLogger
()->setNextMask(0x00000400).DebugPrintfLastMask ( "Truncated value '%"
".40" "s' to %ld chars net length (maxSize)", outval.c_str()
, (long)maxSiz ); }
;
2529 // do not add more chars
2530 break;
2531 }
2532 }
2533 } // while value chars available
2534 } // not BLOB conversion
2535 // value generated in outval (altough it might be an empty string)
2536 } // if field assigned
2537 else {
2538 // not assigned. However a not assigned array means an array with no elements, which
2539 // is the same as an exhausted array
2540 return isarray ? GENVALUE_EXHAUSTED1 : GENVALUE_NOTSUPPORTED0; // array is exhaused, non-array unassigned means not available
2541 }
2542 } // source and target both support the field (or field belongs to mandatory property)
2543 else
2544 return GENVALUE_NOTSUPPORTED0; // field not supported by either source or target (and not mandatory) -> do not generate value
2545 } // if fieldid exists
2546 else {
2547 // could be special conversion using no data or data from
2548 // internal object variables (such as VERSION value)
2549 if (fieldToMIMEString(aItem,FID_NOT_SUPPORTED-128,0,aConvDefP,vallist)) {
2550 // got some output, use it as value
2551 outval=vallist;
2552 }
2553 else
2554 // no value, no output
2555 return GENVALUE_NOTSUPPORTED0; // field not supported
2556 }
2557 // now we have a value in outval, check if encoding needs to be applied
2558 // - check if we should select QUOTED-PRINTABLE because of nonASCII
2559 if (aNonASCII && fDoQuote8BitContent && aEncoding==enc_none)
2560 aEncoding=enc_quoted_printable;
2561 // just append
2562 if (!outval.empty() && aFirstChar!=0) {
2563 if (aMimeMode==mimo_standard || aFoldAtSeparators) {
2564 aString+='\b'; // preferred break location (or location where extra space is allowed for mimo_old)
2565 aNumNonSpcs = 0; // we can break here, new word starts
2566 }
2567 aString+=aFirstChar; // we have a value, add sep char first
2568 aNumNonSpcs++; // count it (assuming separator is never a space!)
2569 }
2570 aString.append(outval);
2571 // done
2572 return outval.empty()
2573 ? (isarray ? GENVALUE_EMPTYELEMENT2 : GENVALUE_EMPTY3) // empty
2574 : (isarray ? GENVALUE_ELEMENT4 : GENVALUE_NONEMPTY5); // non empty
2575} // TMimeDirProfileHandler::generateValue
2576
2577
2578
2579// generate parameters for one property instance
2580// - returns true if parameters with shownonempty=true were generated
2581bool TMimeDirProfileHandler::generateParams(
2582 TMultiFieldItem &aItem, // the item where data comes from
2583 string &aString, // the string to add parameters to
2584 const TPropertyDefinition *aPropP, // the property to generate (all instances)
2585 TMimeDirMode aMimeMode, // MIME mode (older or newer vXXX format compatibility)
2586 sInt16 aBaseOffset,
2587 sInt16 aRepOffset,
2588 TPropNameExtension *aPropNameExt, // propname extension for generating musthave param values
2589 sInt32 &aNumNonSpcs // number of consecutive non-spaces, accumulated so far
2590)
2591{
2592 const TParameterDefinition *paramP;
2593 bool paramstarted;
2594 char sep=0; // separator for value lists
2595 bool nonasc=false;
2596 TEncodingTypes encoding;
2597 string paramstr;
2598 bool showalways=false;
2599
2600 // Generate parameters
2601 // Note: altough positional values are always the same, non-positional values
2602 // can vary from repetition to repetition and can be mixed with the
2603 // positional values. So we must generate the mixture again for
2604 // every repetition.
2605 // - check all parameters for musthave values
2606 paramP = aPropP->parameterDefs;
2607 while (paramP) {
2608 // parameter not started yet
2609 paramstarted=false;
2610 // process param only if matching mode and active rules
2611 if (mimeModeMatch(paramP->modeDependency)
2612 #ifndef NO_REMOTE_RULES
2613 && (!paramP->ruleDependency || isActiveRule(paramP->ruleDependency))
2614 #endif
2615 ) {
2616 // first append extendsname param values
2617 if (paramP->extendsname && aPropNameExt) {
2618 const TEnumerationDef *enumP = paramP->convdef.enumdefs;
2619 while (enumP) {
2620 if (enumP->nameextid>=0) {
2621 // value is relevant for name extension, check if required for this param
2622 if ((((TNameExtIDMap)1<<enumP->nameextid) & (aPropNameExt->musthave_ids | aPropNameExt->addtlSend_ids))!=0) {
2623 // found param value which is required or flagged to be sent additionally as name extension
2624 if (!paramstarted) {
2625 paramstarted=true;
2626 aString+=';'; // param always starts with ;
2627 if (paramP->defaultparam && (aMimeMode==mimo_old)) {
2628 // default param, values are written like a list of params
2629 sep=';'; // separator, in case other values follow
2630 }
2631 else {
2632 // normal parameter, first add param separator and name
2633 // - lead-in
2634 aString.append(paramP->paramname);
2635 aString+='=';
2636 // - separator, in case other values follow
2637 sep=','; // value list separator is comma by default
2638 }
2639 }
2640 else {
2641 // add separator for one more value
2642 aString+=sep;
2643 }
2644 // add value
2645 aString.append(enumP->enumtext);
2646 } // if enum value is a "must have" value for name extension
2647 } // if enum value is relevant to name extension
2648 // next enum value
2649 enumP=enumP->next;
2650 } // while enum values
2651 } // if extendsname
2652 // append value(s) if there is an associated field
2653 paramstr.erase(); // none to start with
2654 if (paramP->convdef.fieldid!=FID_NOT_SUPPORTED-128) {
2655 if (!paramstarted) {
2656 // Note: paramstarted must not be set here, as empty value might prevent param from being written
2657 // parameter starts with ";"
2658 paramstr+=';';
2659 if (paramP->defaultparam && (aMimeMode==mimo_old)) {
2660 // default param, values are written like a list of params
2661 sep=';';
2662 }
2663 else {
2664 // normal parameter, first add name
2665 paramstr.append(paramP->paramname);
2666 paramstr+='=';
2667 sep=','; // value list separator is comma by default
2668 }
2669 }
2670 else {
2671 // already started values, just add more
2672 // - next value starts with a separator
2673 paramstr+=sep;
2674 }
2675 // add parameter value(list)
2676 encoding=enc_none; // parameters are not encoded
2677 // NOTE: only non-empty parameters are generated
2678 // NOTE: parameters themselves cannot have a value list that is stored in an array,
2679 // but parameters of repeating properties can be stored in array elements (using the
2680 // same index as for the property itself)
2681 // Note: Escape commas if separator is a comma
2682 sInt32 numNoSpcs = aNumNonSpcs;
2683 if (generateValue(aItem,&(paramP->convdef),aBaseOffset,aRepOffset,paramstr,sep,aMimeMode,true,false,sep==',',encoding,nonasc,0,numNoSpcs,false,false)>=GENVALUE_ELEMENT4) {
2684 // value generated, add parameter name/value (or separator/value for already started params)
2685 aString.append(paramstr);
2686 aNumNonSpcs = numNoSpcs; // actually added, count now
2687 paramstarted=true; // started only if we really have appended something at all
2688 }
2689 } // if field defined for this param
2690 // update show status
2691 if (paramP->shownonempty && paramstarted)
2692 showalways=true; // param has a value and must make property show
2693 }
2694 // next param
2695 paramP=paramP->next;
2696 } // while params
2697 return showalways;
2698} // TMimeDirProfileHandler::generateParams
2699
2700
2701// generateProperty return codes:
2702#define GENPROP_EXHAUSTED0 0 // nothing generated because data source exhausted (or field not supported)
2703#define GENPROP_EMPTY1 1 // nothing generated because empty value (but field supported)
2704#define GENPROP_NONEMPTY2 2 // something generated
2705
2706
2707
2708// helper for generateMimeDir(), expansion of property according to nameExts
2709void TMimeDirProfileHandler::expandProperty(
2710 TMultiFieldItem &aItem, // the item where data comes from
2711 string &aString, // the string to add properties to
2712 const char *aPrefix, // the prefix (property name)
2713 const TPropertyDefinition *aPropP, // the property to generate (all instances)
2714 TMimeDirMode aMimeMode // MIME mode (older or newer vXXX format compatibility)
2715)
2716{
2717 // scan nameExts to generate name-extended variants and repetitions
2718 TPropNameExtension *propnameextP = aPropP->nameExts;
2719 if (!propnameextP) {
2720 // no name extensions -> this is a non-repeating property
2721 // just generate once, even if empty (except if it has suppressempty set)
2722 generateProperty(
2723 aItem, // the item where data comes from
2724 aString, // the string to add properties to
2725 aPrefix, // the prefix (property name)
2726 aPropP, // the property to generate
2727 0, // field ID offset to be used
2728 0, // additional repeat offset / array index
2729 aMimeMode, // MIME mode (older or newer vXXX format compatibility)
2730 false // if set, a property with only empty values will never be generated
2731 );
2732 }
2733 else {
2734 // scan name extensions
2735 sInt16 generated=0;
2736 sInt16 maxOccur=0; // default to no limit
2737 while (propnameextP) {
2738 sInt16 baseoffs=propnameextP->fieldidoffs;
2739 sInt16 repoffs=0; // no repeat offset yet
2740 if (baseoffs!=OFFS_NOSTORE-9999 && !propnameextP->readOnly) {
2741 // we can address fields for this property and it's not readonly (parsing variant)
2742 // generate value part
2743 sInt16 n=propnameextP->maxRepeat;
2744 // check for value list
2745 if (aPropP->valuelist && !aPropP->expandlist) {
2746 // property contains a value list -> all repetitions are shown within ONE property instance
2747 // NOTE: generateProperty will exhaust possible repeats
2748 generateProperty(
2749 aItem, // the item where data comes from
2750 aString, // the string to add properties to
2751 aPrefix, // the prefix (property name)
2752 aPropP, // the property to generate
2753 baseoffs, // field ID offset to be used
2754 repoffs, // additional repeat offset / array index
2755 aMimeMode, // MIME mode (older or newer vXXX format compatibility)
2756 propnameextP->minShow<1, // suppress if fewer to show than 1 (that is, like suppressempty in this case)
2757 propnameextP // propname extension for generating musthave param values and maxrep/repinc for valuelists
2758 );
2759 }
2760 else {
2761 // now generate separate properties for all repetitions
2762 // Note: strategy is to keep order as much as possible (completely if
2763 // minShow is >= maxRepeat
2764 sInt16 emptyRepOffs=-1;
2765 // get occurrence limit as provided by remote
2766 for (sInt16 i=0; i<aPropP->numValues; i++) {
2767 sInt16 fid=aPropP->convdefs[0].fieldid;
2768 if (fid>=0) {
2769 if (fRelatedDatastoreP) {
2770 // only if datastore is related we are in SyncML context, otherwise we should not check maxOccur
2771 maxOccur = aItem.getItemType()->getFieldOptions(fid)->maxoccur;
2772 }
2773 else
2774 maxOccur = 0; // no limit
2775 // Note: all value fields of the property will have the same maxOccur, so we can stop here
2776 break;
2777 }
2778 }
2779 do {
2780 // generate property for this repetition
2781 // - no repeating within generateProperty takes place!
2782 sInt16 genres = generateProperty(
2783 aItem, // the item where data comes from
2784 aString, // the string to add properties to
2785 aPrefix, // the prefix (property name)
2786 aPropP, // the property to generate
2787 baseoffs, // field ID offset to be used
2788 repoffs, // additional repeat offset / array index
2789 aMimeMode, // MIME mode (older or newer vXXX format compatibility)
2790 propnameextP->minShow-generated<n, // suppress if fewer to show than remaining repeats
2791 propnameextP // propname extension for generating musthave param values
2792 );
2793 if (genres==GENPROP_NONEMPTY2) {
2794 // generated a property
2795 generated++;
2796 }
2797 else {
2798 // nothing generated
2799 if (emptyRepOffs<0) emptyRepOffs=repoffs; // remember empty
2800 // check for array repeat, in which case exhausted array or non-supported field will stop generating
2801 if (propnameextP->maxRepeat==REP_ARRAY32767 && genres==GENPROP_EXHAUSTED0) break; // exit loop if any only if array exhausted
2802 }
2803 // one more generated of the maximum possible (note: REP_ARRAY=32k, so this will not limit an array)
2804 n--;
2805 repoffs+=propnameextP->repeatInc;
2806 // end generation if remote's maxOccur limit is reached
2807 if (maxOccur && generated>=maxOccur) {
2808 PDEBUGPRINTFX(DBG_GEN,({ if (((0x00000400) & getDbgMask()) == (0x00000400)) getDbgLogger
()->setNextMask(0x00000400).DebugPrintfLastMask ( "maxOccur (%hd) for Property '%s' reached - no more instances will be generated"
, maxOccur, aPropP->propname.c_str() ); }
2809 "maxOccur (%hd) for Property '%s' reached - no more instances will be generated",{ if (((0x00000400) & getDbgMask()) == (0x00000400)) getDbgLogger
()->setNextMask(0x00000400).DebugPrintfLastMask ( "maxOccur (%hd) for Property '%s' reached - no more instances will be generated"
, maxOccur, aPropP->propname.c_str() ); }
2810 maxOccur,{ if (((0x00000400) & getDbgMask()) == (0x00000400)) getDbgLogger
()->setNextMask(0x00000400).DebugPrintfLastMask ( "maxOccur (%hd) for Property '%s' reached - no more instances will be generated"
, maxOccur, aPropP->propname.c_str() ); }
2811 TCFG_CSTR(aPropP->propname){ if (((0x00000400) & getDbgMask()) == (0x00000400)) getDbgLogger
()->setNextMask(0x00000400).DebugPrintfLastMask ( "maxOccur (%hd) for Property '%s' reached - no more instances will be generated"
, maxOccur, aPropP->propname.c_str() ); }
2812 )){ if (((0x00000400) & getDbgMask()) == (0x00000400)) getDbgLogger
()->setNextMask(0x00000400).DebugPrintfLastMask ( "maxOccur (%hd) for Property '%s' reached - no more instances will be generated"
, maxOccur, aPropP->propname.c_str() ); }
;
2813 break;
2814 }
2815 } while(n>0);
2816 // add empty ones if needed
2817 while (generated<propnameextP->minShow && emptyRepOffs>=0 && !(maxOccur && generated>=maxOccur)) {
2818 // generate empty ones (no suppression)
2819 generateProperty(aItem,aString,aPrefix,aPropP,baseoffs,emptyRepOffs,aMimeMode,false);
2820 generated++; // count as generated anyway (even in case generation of empty is globally turned off)
2821 }
2822 } // repeat properties when we have repeating enabled
2823 } // if name extension is stored
2824 propnameextP=propnameextP->next;
2825 // stop if maxOccur reached
2826 if (maxOccur && generated>=maxOccur) break;
2827 } // while nameexts
2828 } // if nameexts at all
2829} // TMimeDirProfileHandler::expandProperty
2830
2831
2832// helper for expandProperty: generates property
2833// returns: GENPROP_xxx
2834sInt16 TMimeDirProfileHandler::generateProperty(
2835 TMultiFieldItem &aItem, // the item where data comes from
2836 string &aString, // the string to add properties to
2837 const char *aPrefix, // the prefix (property name)
2838 const TPropertyDefinition *aPropP, // the property to generate (all instances)
2839 sInt16 aBaseOffset, // field ID offset to be used
2840 sInt16 aRepeatOffset, // additional repeat offset / array index
2841 TMimeDirMode aMimeMode, // MIME mode (older or newer vXXX format compatibility)
2842 bool aSuppressEmpty, // if set, a property with only empty values will not be generated
2843 TPropNameExtension *aPropNameExt // propname extension for generating musthave param values and maxrep/repinc for valuelists
2844)
2845{
2846 string proptext; // unfolded property text
2847 proptext.reserve(300); // not too small
2848 string elemtext; // single element (value or param) text
2849 TEncodingTypes encoding;
2850 bool nonasc=false;
2851
2852 // - reset TZID presence flag
2853 fPropTZIDtctx = TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ));
2854 // - start with empty text
2855 proptext.erase();
2856 // - init flags
2857 bool anyvaluessupported = false; // at least one of the main values must be supported by the remote in order to generate property at all
2858 bool arrayexhausted = false; // flag will be set if a main value was not generated because array exhausted
2859 bool anyvalues = false; // flag will be set when at least one value has been generated
2860 sInt32 numNonSpcs=0;
2861 encoding=enc_none; // default is no encoding
2862 sInt16 v=0; // value counter
2863 nonasc=false; // assume plain ASCII
2864 sInt16 genres;
2865 TEncodingTypes enc;
2866 bool na;
2867 const TConversionDef *convP;
2868 // - now generate
2869 if (aPropP->unprocessed) {
2870 convP = &(aPropP->convdefs[0]);
2871 // unfolded and not-linefeed-escaped raw property stored in string - generate it as one value
2872 enc=encoding;
2873 na=false;
2874 genres=generateValue(
2875 aItem,
2876 convP,
2877 aBaseOffset, // base offset, relative to
2878 aRepeatOffset, // repeat offset or array index
2879 elemtext, // value will be stored here (might be binary in case of BLOBs, but then encoding will be set)
2880 0, // should for some exotic reason values consist of a list, separate it by "," (";" is reserved for structured values)
2881 aMimeMode,
2882 false, // no parameter
2883 false, // not structured value, don't escape ";"
2884 false, // don't escape commas, either
2885 enc, // will receive needed encoding (usually B64 for binary values)
2886 na,
2887 0, // no first char
2888 numNonSpcs, // number of consecutive non-spaces, accumulated
2889 aPropP->allowFoldAtSep,
2890 true // linefeed only escaping
2891 );
2892 // check if something was generated
2893 if (genres>=GENVALUE_ELEMENT4) {
2894 // generated something, might have caused encoding/noasc change
2895 encoding=enc;
2896 nonasc=nonasc || na;
2897 anyvaluessupported=true;
2898 anyvalues=true;
2899 // separate name+params and values
2900 bool dq = false;
2901 bool fc = true;
2902 size_t pti = string::npos;
2903 appChar c;
2904 for (cAppCharP p=elemtext.c_str(); (c=*p)!=0; p++) {
2905 if (dq) {
2906 if (c=='"') dq = false; // end of double quoted part
2907 }
2908 else {
2909 // not in doublequote
2910 if (fc && c=='"') dq = true; // if first char is doublequote, start quoted string
2911 else if (c==':') { pti = p-elemtext.c_str(); break; } // found
2912 else if (c=='\\' && *(p+1)) p++; // make sure next char is not evaluated
2913 }
2914 fc = false; // no longer first char
2915 }
2916 if (pti!=string::npos) {
2917 proptext.assign(elemtext, 0, pti); // name+params without separator
2918 elemtext.erase(0, pti+1); // remove name and separator
2919 }
2920 }
2921 else {
2922 arrayexhausted = true; // no more values, assume array exhausted
2923 }
2924 }
2925 else {
2926 // Normally generated property
2927 // - first set group if there is one
2928 if (aPropP->groupFieldID!=FID_NOT_SUPPORTED-128) {
2929 // get group name
2930 TItemField *g_fldP = aItem.getArrayFieldAdjusted(aPropP->groupFieldID+aBaseOffset, aRepeatOffset, true);
2931 if (g_fldP && !g_fldP->isEmpty()) {
2932 g_fldP->appendToString(proptext);
2933 proptext += '.'; // group separator
2934 }
2935 }
2936 // - append name and (possibly) parameters that are constant over all repetitions
2937 proptext += aPrefix;
2938 // - up to here assume no spaces
2939 numNonSpcs = proptext.size()+14; // some extra room for possible ";CHARSET=UTF8"
2940 // - append parameter values
2941 // anyvalues gets set if a parameter with shownonempty attribute was generated
2942 anyvalues=generateParams(
2943 aItem, // the item where data comes from
2944 proptext, // where params will be appended
2945 aPropP, // the property definition
2946 aMimeMode,
2947 aBaseOffset,
2948 aRepeatOffset,
2949 aPropNameExt,
2950 numNonSpcs
2951 );
2952 // - append value(s)
2953 sInt16 maxrep=1,repinc=1;
2954 if (aPropNameExt) {
2955 maxrep=aPropNameExt->maxRepeat;
2956 repinc=aPropNameExt->repeatInc;
2957 }
2958 // generate property contents
2959 if (aPropP->valuelist && !aPropP->expandlist) {
2960 // property with value list
2961 // NOTE: convdef[0] is used for all values, aRepeatOffset changes
2962 convP = &(aPropP->convdefs[0]);
2963 // - now iterate over available repeats or array contents
2964 while(aRepeatOffset<maxrep*repinc || maxrep==REP_ARRAY32767) {
2965 // generate one value
2966 enc=encoding;
2967 na=false;
2968 genres=generateValue(
2969 aItem,
2970 convP,
2971 aBaseOffset, // offset relative to base field
2972 aRepeatOffset, // additional offset or array index
2973 elemtext,
2974 aPropP->valuesep, // use valuelist separator between multiple values possibly generated from a list in a single field (e.g. CATEGORIES)
2975 aMimeMode,
2976 false, // not a param
2977 true, // always escape ; in valuelist properties
2978 aPropP->valuesep==',' || aPropP->altvaluesep==',', // escape commas if one of the separators is a comma
2979 enc,
2980 na,
2981 v>0 ? aPropP->valuesep : 0, // separate with specified multi-value-delimiter if not first value
2982 numNonSpcs, // number of consecutive non-spaces, accumulated
2983 aPropP->allowFoldAtSep,
2984 (convP->convmode & CONVMODE_MASK0xFF)==CONVMODE_RRULE20 +0 // RRULES are not to be escaped
2985 );
2986 // check if something was generated
2987 if (genres>=GENVALUE_ELEMENT4) {
2988 // generated something, might have caused encoding/noasc change
2989 encoding=enc;
2990 nonasc=nonasc || na;
2991 }
2992 // update if we have at least one value of this property supported (even if empty) by the remote party
2993 if (genres>GENVALUE_NOTSUPPORTED0) anyvaluessupported=true;
2994 if (genres==GENVALUE_EXHAUSTED1) arrayexhausted=true; // for at least one component of the property, the array is exhausted
2995 // update if we have any value now (even if only empty)
2996 // - generate empty property according to
2997 // - aSuppressEmpty
2998 // - session-global fDontSendEmptyProperties
2999 // - supressEmpty property flag in property definition
3000 // - if no repeat (i.e. no aPropNameExt), exhausted array is treated like empty value (i.e. rendered unless suppressempty set)
3001 anyvalues = anyvalues || (genres>=
3002 (aSuppressEmpty || fDontSendEmptyProperties || aPropP->suppressEmpty
3003 ? GENVALUE_ELEMENT4 // if empty values should be suppressed, we need a non-empty element (array or simple field)
3004 : GENVALUE_EXHAUSTED1 // for valuelists, if empty values are not explicitly suppressed (i.e. minshow==0, see caller) exhausted array must always produce an empty value
3005 )
3006 );
3007 // count effective value appended
3008 v++;
3009 // update repeat offset
3010 aRepeatOffset+=repinc;
3011 // check for array mode - stop if array is exhausted or field not supported
3012 if (maxrep==REP_ARRAY32767 && genres<=GENVALUE_EXHAUSTED1) break;
3013 }
3014 }
3015 else {
3016 // property with individual values (like N)
3017 // NOTE: field changes with different convdefs, offsets remain stable
3018 arrayexhausted = true; // assume all arrays exhausted unless we find at least one non-exhausted array
3019 bool somearrays = false; // no arrays yet
3020 do {
3021 convP = &(aPropP->convdefs[v]);
3022 // generate one value
3023 enc=encoding;
3024 na=false;
3025 genres=generateValue(
3026 aItem,
3027 convP,
3028 aBaseOffset, // base offset, relative to
3029 aRepeatOffset, // repeat offset or array index
3030 elemtext, // value will be stored here (might be binary in case of BLOBs, but then encoding will be set)
3031 ',', // should for some exotic reason values consist of a list, separate it by "," (";" is reserved for structured values)
3032 aMimeMode,
3033 false,
3034 aPropP->numValues>1, // structured value, escape ";"
3035 aPropP->altvaluesep==',', // escape commas if alternate separator is a comma
3036 enc, // will receive needed encoding (usually B64 for binary values)
3037 na,
3038 0, // no first char
3039 numNonSpcs, // number of consecutive non-spaces, accumulated
3040 aPropP->allowFoldAtSep,
3041 (convP->convmode & CONVMODE_MASK0xFF)==CONVMODE_RRULE20 +0 // RRULES are not to be escaped
3042 );
3043 //* %%% */ PDEBUGPRINTFX(DBG_EXOTIC,("generateValue #%hd for property '%s' returns genres==%hd",v,TCFG_CSTR(aPropP->propname),genres));
3044 // check if something was generated
3045 if (genres>=GENVALUE_ELEMENT4) {
3046 // generated something, might have caused encoding/noasc change
3047 encoding=enc;
3048 nonasc=nonasc || na;
3049 }
3050 // update if we have at least one value of this property supported (even if empty) by the remote party
3051 if (genres>GENVALUE_NOTSUPPORTED0) anyvaluessupported=true;
3052 if (genres==GENVALUE_ELEMENT4 || genres==GENVALUE_EMPTYELEMENT2) {
3053 arrayexhausted = false; // there is at least one non-exhausted array we're reading from (even if only empty value)
3054 somearrays = true; // generating from array
3055 }
3056 else if (genres==GENVALUE_EXHAUSTED1)
3057 somearrays = true; // generating from array
3058 // update if we have any value now (even if only empty)
3059 // - generate empty property according to
3060 // - aSuppressEmpty
3061 // - session-global fDontSendEmptyProperties
3062 // - supressEmpty property flag in property definition
3063 // - if no repeat (i.e. no aPropNameExt), exhausted array is treated like empty value (i.e. rendered unless suppressempty set)
3064 anyvalues = anyvalues ||
3065 (genres>=(aSuppressEmpty || fDontSendEmptyProperties || aPropP->suppressEmpty ? GENVALUE_ELEMENT4 : (aPropNameExt ? GENVALUE_EMPTYELEMENT2 : GENVALUE_EXHAUSTED1)));
3066 // insert delimiter if not last value
3067 v++;
3068 if (v>=aPropP->numValues) break; // done with all values
3069 // add delimiter for next value
3070 if (aMimeMode==mimo_standard || aPropP->allowFoldAtSep) {
3071 elemtext+='\b'; // preferred break location (or location where extra space is allowed for mimo_old)
3072 numNonSpcs = 0; // can break here, new word starts
3073 }
3074 elemtext+=aPropP->valuesep;
3075 numNonSpcs++; // count it (assuming separator is never a space!)
3076 // add break indicator
3077 } while(true);
3078 // if none of the data sources is an array, we can't be exhausted.
3079 if (!somearrays) arrayexhausted = false;
3080 }
3081 } // normal generated property from components
3082 // - finalize property if it contains supported fields at all (or is mandatory)
3083 if ((anyvaluessupported && anyvalues) || aPropP->mandatory) {
3084 // - generate charset parameter if needed
3085 // NOTE: MIME-DIR based formats do NOT have the CHARSET attribute any more!
3086 if (nonasc && aMimeMode==mimo_old && fDefaultOutCharset!=chs_ansi) {
3087 // non-ASCII chars contained, generate property telling what charset is used
3088 proptext.append(";CHARSET=");
3089 proptext.append(MIMECharSetNames[fDefaultOutCharset]);
3090 }
3091 // - generate encoding parameter if needed
3092 if (encoding!=enc_none) {
3093 // in MIME-DIR, only "B" is allowed for binary, for vCard 2.1 it is "BASE64"
3094 if (encoding==enc_base64 || encoding==enc_b) {
3095 encoding = aMimeMode==mimo_standard ? enc_b : enc_base64;
3096 }
3097 // add the parameter
3098 proptext.append(";ENCODING=");
3099 proptext.append(MIMEEncodingNames[encoding]);
3100 }
3101 // - separate value from property text
3102 proptext+=':';
3103 // - append (probably encoded) values now, always in UTF-8
3104 encodeValues(encoding,fDefaultOutCharset,elemtext,proptext,fDoNotFoldContent);
3105 // - fold, copy and terminate (CRLF) property into aString output
3106 finalizeProperty(proptext.c_str(),aString,aMimeMode,fDoNotFoldContent,encoding==enc_quoted_printable);
3107 // - special case: base64 (but not B) encoded value must have an extra CRLF even if folding is
3108 // disabled, so we need to insert it here (because non-folding mode eliminates it from being
3109 // generated automatically in encodeValues/finalizeProperty)
3110 if (fDoNotFoldContent && encoding==enc_base64)
3111 aString.append("\x0D\x0A"); // extra CRLF terminating a base64 encoded property (note, base64 only occurs in mimo_old)
3112 // - property generated
3113 return GENPROP_NONEMPTY2;
3114 }
3115 else {
3116 // Note: it is essential to return GENPROP_EXHAUSTED if no values are supported for this property at
3117 // all (otherwise caller might loop endless trying to generate a non-empty property
3118 return
3119 anyvaluessupported
3120 ? (arrayexhausted ? GENPROP_EXHAUSTED0 : GENPROP_EMPTY1) // no property generated
3121 : GENPROP_EXHAUSTED0; // no values supported means "exhausted" as well
3122 }
3123} // TMimeDirProfileHandler::generateProperty
3124
3125
3126
3127// generate MIME-DIR from item into string object
3128void TMimeDirProfileHandler::generateMimeDir(TMultiFieldItem &aItem, string &aString)
3129{
3130 // clear string
3131 aString.reserve(3000); // not too small
3132 aString.erase();
3133 // reset item time zone before generating
3134 fHasExplicitTZ = false; // none set explicitly
3135 fItemTimeContext = fReceiverTimeContext; // default to receiver context
3136 fUsedTCtxSet.clear(); // no TZIDs used yet
3137 fEarliestTZDate = noLinearTime; // reset range of generated timestamps related to a TZID or TZ/DAYLIGHT
3138 fLatestTZDate = noLinearTime;
3139 fVTimeZonePendingProfileP = NULL__null; // no VTIMEZONE pending for generation
3140 fVTimeZoneInsertPos = 0; // no insert position yet
3141 // recursively generate levels
3142 generateLevels(aItem,aString,fProfileDefinitionP);
3143 // now generate VTIMEZONE, if needed
3144 if (fVTimeZonePendingProfileP) {
3145 string s, val, vtz;
3146 vtz.erase();
3147 // generate needed vTimeZones (according to fUsedTCtxSet)
3148 for (TTCtxSet::iterator pos=fUsedTCtxSet.begin(); pos!=fUsedTCtxSet.end(); pos++) {
3149 // - calculate first and last year covered by timestamps in this record
3150 sInt16 startYear=0,endYear=0;
3151 if (fEarliestTZDate && fProfileCfgP->fVTimeZoneGenMode!=vtzgen_current) {
3152 // dependent on actually created dates
3153 lineartime2date(fEarliestTZDate, &startYear, NULL__null, NULL__null);
3154 lineartime2date(fLatestTZDate, &endYear, NULL__null, NULL__null);
3155 // there is at least one date in the record
3156 switch (fProfileCfgP->fVTimeZoneGenMode) {
3157 case vtzgen_start:
3158 endYear = startYear; // only show for start of range
3159 break;
3160 case vtzgen_end:
3161 startYear = endYear; // only show for end of range
3162 break;
3163 case vtzgen_range:
3164 // pass both start and end year
3165 break;
3166 case vtzgen_openend:
3167 // pass start year but request that all rules from start up to the current date are inlcuded
3168 endYear = 0;
3169 break;
3170 case vtzgen_current:
3171 case numVTimeZoneGenModes:
3172 // case statement to keep gcc happy, will not be reached because of if() above
3173 break;
3174 }
3175 }
3176 // - lead-in
3177 s="BEGIN:";
3178 s.append(fVTimeZonePendingProfileP->levelName);
3179 finalizeProperty(s.c_str(),vtz,fMimeDirMode,false,false);
3180 // - generate raw string
3181 //%%% endYear is not yet implemented in internalToVTIMEZONE(), fTzIdGenMode has only the olson option for now
3182 internalToVTIMEZONE(*pos, val, getSessionZones(), NULL__null, startYear, endYear, fProfileCfgP->fTzIdGenMode==tzidgen_olson ? "o" : NULL__null);
3183 size_t i,n = 0;
3184 while (val.size()>n) {
3185 i = val.find('\n',n); // next line end
3186 if (i==string::npos) i=val.size();
3187 if (i-n>1) {
3188 // more than one char = not only a trailing line end
3189 s.assign(val,n,i-n);
3190 // finalize and add property
3191 finalizeProperty(s.c_str(),vtz,fMimeDirMode,false,false);
3192 // advance cursor beyond terminating LF
3193 n=i+1;
3194 }
3195 }
3196 // - lead out
3197 s="END:";
3198 s.append(fVTimeZonePendingProfileP->levelName);
3199 finalizeProperty(s.c_str(),vtz,fMimeDirMode,false,false);
3200 } // for
3201 // now insert the VTIMEZONE into the output string (so possibly making it appear BEFORE the
3202 // properties that use TZIDs)
3203 aString.insert(fVTimeZoneInsertPos, vtz);
3204 // done
3205 fVTimeZonePendingProfileP = NULL__null;
3206 } // if pending VTIMEZONE
3207} // TMimeDirProfileHandler::generateMimeDir
3208
3209
3210// generate nested levels of MIME-DIR content
3211void TMimeDirProfileHandler::generateLevels(
3212 TMultiFieldItem &aItem,
3213 string &aString,
3214 const TProfileDefinition *aProfileP
3215)
3216{
3217 // check if level must be generated
3218 bool dolevel=false;
3219 string s,val;
3220 sInt16 fid=aProfileP->levelConvdef.fieldid;
3221 if (fid<0) dolevel=true; // if no controlling field there, generate anyway
3222 else {
3223 // check field contents to determine if generation is needed
3224 if (aItem.isAssigned(fid)) {
3225 const TEnumerationDef *enumP = aProfileP->levelConvdef.enumdefs;
3226 aItem.getField(fid)->getAsString(val);
3227 if (enumP) {
3228 // if enumdefs, content must match first enumdef's enumval (NOT enumtext!!)
3229 dolevel = strucmp(val.c_str(),TCFG_CSTR(enumP->enumval)enumP->enumval.c_str())==0;
3230 }
3231 else {
3232 // just being not empty enables level
3233 dolevel = !aItem.getField(fid)->isEmpty();
3234 }
3235 }
3236 }
3237 // check for MIME mode dependency
3238 dolevel = dolevel && mimeModeMatch(aProfileP->modeDependency);
3239 // generate level if enabled
3240 if (dolevel) {
3241 // generate level start
3242 if (aProfileP->profileMode==profm_vtimezones) {
3243 // don't generate now, just remember the string position where we should add the
3244 // VTIMEZONEs when we're done generating the record.
3245 fVTimeZonePendingProfileP = aProfileP;
3246 fVTimeZoneInsertPos = aString.size();
3247 }
3248 else {
3249 // standard custom level
3250 s="BEGIN:";
3251 s.append(aProfileP->levelName);
3252 finalizeProperty(s.c_str(),aString,fMimeDirMode,false,false);
3253 // loop through all properties of that level
3254 const TPropertyDefinition *propP = aProfileP->propertyDefs;
3255 #ifndef NO_REMOTE_RULES
3256 uInt16 propGroup=0; // group identifier (all props with same name have same group ID)
3257 const TPropertyDefinition *otherRulePropP = NULL__null; // default property which is used if none of the rule-dependent in the group was used
3258 bool ruleSpecificExpanded = false;
3259 #endif
3260 const TPropertyDefinition *expandPropP;
3261 while (propP) {
3262 // check for mode dependency
3263 if (!mimeModeMatch(propP->modeDependency)) {
3264 // no mode match -> just skip this one
3265 propP=propP->next;
3266 continue;
3267 }
3268 #ifndef NO_REMOTE_RULES
3269 // check for beginning of new group (no or different property group number)
3270 if (propP->propGroup==0 || propP->propGroup!=propGroup) {
3271 // end of last group - start of new group
3272 propGroup = propP->propGroup; // remember new group number
3273 // expand "other"-rule dependent variant from last group
3274 if (!ruleSpecificExpanded && otherRulePropP) {
3275 expandProperty(
3276 aItem,
3277 aString,
3278 TCFG_CSTR(otherRulePropP->propname)otherRulePropP->propname.c_str(), // the prefix consists of the property name
3279 otherRulePropP, // the property definition
3280 fMimeDirMode // MIME-DIR mode
3281 );
3282 }
3283 // for next group, no rule-specific version has been expanded yet
3284 ruleSpecificExpanded = false;
3285 // for next group, we don't have a "other"-rule variant
3286 otherRulePropP=NULL__null;
3287 }
3288 // check if entry is rule-specific
3289 expandPropP=NULL__null; // do not expand by default
3290 if (propP->dependsOnRemoterule) {
3291 // check if depends on current rule
3292 if (propP->ruleDependency==NULL__null) {
3293 // this is the "other"-rule dependent variant
3294 // - just remember
3295 otherRulePropP=propP;
3296 }
3297 else if (isActiveRule(propP->ruleDependency)) {
3298 // specific for the applied rule
3299 expandPropP=propP; // default to expand current prop
3300 // now we have expanded a rule-specific property (blocks expanding of "other"-rule dependent prop)
3301 ruleSpecificExpanded=true;
3302 }
3303 }
3304 else {
3305 // does not depend on rule, expand anyway
3306 expandPropP=propP;
3307 }
3308 // check if this is last prop of list
3309 propP=propP->next;
3310 if (!propP && otherRulePropP && !ruleSpecificExpanded) {
3311 // End of prop list, no rule-specific expand yet, and there is a otherRuleProp
3312 // expand "other"-rule's property instead
3313 expandPropP=otherRulePropP;
3314 }
3315 #else
3316 // simply expand it
3317 expandPropP=propP;
3318 propP=propP->next;
3319 #endif
3320 // now expand if selected
3321 if (expandPropP)
3322 {
3323 // recursively generate all properties that expand from this entry
3324 // (includes extendsfieldid-parameters and repetitions
3325 expandProperty(
3326 aItem,
3327 aString,
3328 TCFG_CSTR(expandPropP->propname)expandPropP->propname.c_str(), // the prefix consists of the property name
3329 expandPropP, // the property definition
3330 fMimeDirMode // MIME-DIR mode
3331 );
3332 }
3333 } // properties loop
3334 // generate sublevels, if any
3335 const TProfileDefinition *subprofileP = aProfileP->subLevels;
3336 while (subprofileP) {
3337 // generate sublevels (possibly, none is generated)
3338 generateLevels(aItem,aString,subprofileP);
3339 // next
3340 subprofileP=subprofileP->next;
3341 }
3342 // generate level end
3343 s="END:";
3344 s.append(aProfileP->levelName);
3345 finalizeProperty(s.c_str(),aString,fMimeDirMode,false,false);
3346 } // normal level
3347 } // if level must be generated
3348} // TMimeDirProfileHandler::generateLevels
3349
3350
3351// Convert string from MIME-format into field value(s).
3352// - the string passed to this function is already a translated value
3353// list if combinesep is set, and every single value is already
3354// enum-translated if enums are defined.
3355// - returns false if field(s) could not be assigned because aText has
3356// a bad syntax.
3357// - returns true if field(s) assigned something useful or no field is
3358// available to assign anything to.
3359bool TMimeDirProfileHandler::MIMEStringToField(
3360 const char *aText, // the value text to assign or add to the field
3361 const TConversionDef *aConvDefP, // the conversion definition record
3362 TMultiFieldItem &aItem, // the item where data goes to
3363 sInt16 aFid, // the field ID (can be NULL for special conversion modes)
3364 sInt16 aArrIndex // the repeat offset to handle array fields
3365)
3366{
3367 sInt16 moffs;
3368 uInt16 offs,n;
3369 bool isBitMap;
3370 fieldinteger_t flags = 0;
3371 TTimestampField *tsFldP;
3372 timecontext_t tctx;
3373 TParsedTzidSet::iterator tz;
3374 string s;
3375 // RRULE
3376 lineartime_t dtstart;
3377 timecontext_t startcontext = 0, untilcontext = 0;
3378 char freq;
3379 char freqmod;
3380 sInt16 interval;
3381 fieldinteger_t firstmask;
3382 fieldinteger_t lastmask;
3383 lineartime_t until;
3384 bool dostore;
3385
3386 // get pointer to leaf field
3387 TItemField *fldP = aItem.getArrayField(aFid,aArrIndex);
3388 sInt16 convmode = aConvDefP->convmode & CONVMODE_MASK0xFF;
3389 switch (convmode) {
3390 case CONVMODE_MAILTO13:
3391 // remove the mailto: prefix if there is one
3392 if (strucmp(aText,"mailto:",7)==0)
3393 aText+=7; // remove leading "mailto:"
3394 goto normal;
3395 case CONVMODE_EMPTYONLY10:
3396 // same as CONVMODE_NONE, but assigns only first occurrence (that is,
3397 // when field is still empty)
3398 if (!fldP) return true; // no field, assignment "ok" (=nop)
3399 if (!fldP->isEmpty()) return true; // field not empty, discard new assignment
3400 case CONVMODE_TIMESTAMP3: // nothing special for parsing
3401 case CONVMODE_AUTODATE5: // nothing special for parsing
3402 case CONVMODE_AUTOENDDATE6: // check for "last minute of the day"
3403 case CONVMODE_DATE4: // dates will be made floating
3404 case CONVMODE_NONE0:
3405 normal:
3406 if (!fldP) return true; // no field, assignment "ok" (=nop)
3407 // just set as string or add if combine mode
3408 if (aConvDefP->combineSep) {
3409 // combine mode
3410 if (!fldP->isEmpty()) {
3411 // not empty, append with separator
3412 char cs[2];
3413 cs[0]=aConvDefP->combineSep;
3414 cs[1]=0;
3415 fldP->appendString(cs);
3416 }
3417 fldP->appendString(aText);
3418 }
3419 else {
3420 // for non-strings, skip leading spaces before trying to parse
3421 if (!fldP->isBasedOn(fty_string)) {
3422 while (*aText && *aText==' ') aText++; // skip leading spaces
3423 }
3424 // simple assign mode
3425 if (fldP->isBasedOn(fty_timestamp)) {
3426 // read as ISO8601 timestamp
3427 tsFldP = static_cast<TTimestampField *>(fldP);
3428 // if field already has a non-unknown context (e.g. set via TZID, or TZ/DAYLIGHT),
3429 // use that as context for floating ISO date (i.e. no "Z" or "+/-hh:mm" suffix)
3430 if (!tsFldP->isFloating()) {
3431 // field already has a TZ specified (e.g. by a TZID param), use that instead of item level context
3432 tctx = tsFldP->getTimeContext();
3433 }
3434 else {
3435 // no pre-known zone for this specific field, check if property has a specific zone
3436 if (!TCTX_IS_UNKNOWN(fPropTZIDtctx)) {
3437 // property has a specified time zone context from a TZID, use it
3438 tctx = fPropTZIDtctx; // default to property's TZID (if one was parsed, otherwise this will be left floating)
3439 }
3440 else if (fHasExplicitTZ) {
3441 // item has an explicitly specified time zone context (e.g. set via TZ: property),
3442 // treat all timestamps w/o own time zone ("Z" suffix) in that context
3443 tctx = fItemTimeContext;
3444 }
3445 else {
3446 // item has no explicitly specified time zone context,
3447 // parse and leave floating float for now
3448 tctx = TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)); // default to floating
3449 }
3450 }
3451 // Now tctx is the default zone to bet set for ALL values that are in floating notation
3452 // - check for special handling of misbehaving remotes
3453 if (fTreatRemoteTimeAsLocal || fTreatRemoteTimeAsUTC) {
3454 // ignore time zone specs which might be present possibly
3455 tsFldP->setAsISO8601(aText, tctx, true);
3456 // now force time zone to item/user context or UTC depending on flag settings
3457 tctx = fTreatRemoteTimeAsLocal ? fItemTimeContext : TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ));
3458 // set it
3459 tsFldP->setTimeContext(tctx);
3460 }
3461 else {
3462 // read with time zone, if present, and default to tctx set above
3463 tsFldP->setAsISO8601(aText, tctx, false);
3464 // check if still floating now
3465 if (tsFldP->isFloating()) {
3466 // unfloat only if remote cannot handle UTC and therefore ALWAYS uses localtime.
3467 // otherwise, assume that floating status is intentional and must be retained.
3468 // Note: TZID and TZ, if present, are already applied by now
3469 // Note: DURATION and DATE floating will always be retained, as they are always intentional
3470 if ((!fReceiverCanHandleUTC || fProfileCfgP->fUnfloatFloating) && !TCTX_IS_DATEONLY(tsFldP->getTimeContext()) && !tsFldP->isDuration()) {
3471 // not intentionally floating, but just not capable otherwise
3472 // - put it into context of item (which is in this case session's user context)
3473 tsFldP->setTimeContext(fItemTimeContext);
3474 }
3475 }
3476 else {
3477 // non-floating
3478 if (fHasExplicitTZ) {
3479 // item has explicit zone - move timestamp to it (e.g. if timestamps are sent
3480 // in ISO8601 Z notation, but a TZ/DAYLIGHT or TZID is present)
3481 tsFldP->moveToContext(tctx,false);
3482 }
3483 }
3484 }
3485 // special conversions
3486 if (convmode==CONVMODE_DATE4) {
3487 tsFldP->makeFloating(); // date-only is forced floating
3488 }
3489 else if (convmode==CONVMODE_AUTOENDDATE6 && fMimeDirMode==mimo_old) {
3490 // check if this could be a 23:59 type end-of-day
3491 lineartime_t ts = tsFldP->getTimestampAs(fItemTimeContext,&tctx); // get in item context or floating
3492 lineartime_t ts0 = lineartime2dateonlyTime(ts);
3493 if (ts0!=ts && AlldayCount(ts0,ts)>0) { // only if not already a 0:00
3494 // this is a 23:59 type end-of-day, convert it to midnight of next day (AND adjust time context, in case it is now different from original)
3495 tsFldP->setTimestampAndContext(lineartime2dateonlyTime(ts)+linearDateToTimeFactor,tctx);
3496 }
3497 }
3498 }
3499 else {
3500 // read as text
3501 fldP->setAsString(aText);
3502 }
3503 }
3504 return true; // found
3505
3506 // Time zones
3507 case CONVMODE_TZ7:
3508 // parse time zone
3509 if (ISO8601StrToContext(aText, tctx)!=0) {
3510 // Note: this is always global for the entire item, so set the item context
3511 // (which is then used when parsing dates (which should be delayed to make sure TZ is seen first)
3512 fItemTimeContext = tctx;
3513 if (!TCTX_IS_TZ(tctx)) {
3514 // only offset. Try to symbolize it by passing a DAYLIGHT:FALSE and the offset
3515 if (TzDaylightToContext("FALSE", fItemTimeContext, tctx, getSessionZones(), fReceiverTimeContext))
3516 fItemTimeContext = tctx; // there is a symbolized context, keep that
3517 }
3518 fHasExplicitTZ = true; // zone explicitly set, not only copied from session's user zone
3519 goto timecontext;
3520 }
3521 return true; // not set, is ok
3522 case CONVMODE_DAYLIGHT8:
3523 // parse DAYLIGHT zone description property, prefer user zone (among multiple zones matching the Tz/daylight info)
3524 // - resolve to offset (assuming that item context came from a TZ property, so it will
3525 // be one of the non-DST zones, so reftime does not matter)
3526 tctx = fItemTimeContext;
3527 TzResolveContext(tctx, getSystemNowAs(TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), getSessionZones()), true, getSessionZones());
3528 // - now find matching zone for given offset and DAYLIGHT property string
3529 if (TzDaylightToContext(aText,tctx,tctx,getSessionZones(),fReceiverTimeContext)) {
3530 // this is always global for the entire item, so set the item context
3531 // (which is then used when parsing dates (which should be delayed to make sure TZ is seen first)
3532 fItemTimeContext = tctx;
3533 fHasExplicitTZ = true; // zone explicitly set, not only copied from session's user zone
3534 goto timecontext;
3535 }
3536 return true; // not set, is ok
3537 case CONVMODE_TZID9:
3538 // try to get context for named zone
3539 // - look up in TZIDs we've parsed so far from VTIMEZONE
3540 tz = fParsedTzidSet.find(aText);
3541 if (tz!=fParsedTzidSet.end()) {
3542 tctx = tz->second; // get tctx resolved from VTIMEZONE
3543 // use tctx for all values from this property
3544 fPropTZIDtctx = tctx;
3545 goto timecontext;
3546 }
3547 else if (TimeZoneNameToContext(aText, tctx, getSessionZones(), true)) {
3548 // found valid TZID property, save it so we can use it for all values of this property that don't specify their own TZ
3549 PDEBUGPRINTFX(DBG_ERROR,("Warning: TZID %s could be resolved against internal name, but appropriate VTIMEZONE is missing",aText)){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("Warning: TZID %s could be resolved against internal name, but appropriate VTIMEZONE is missing"
,aText); }
;
3550 fPropTZIDtctx=tctx;
3551 goto timecontext;
3552 }
3553 else {
3554 PDEBUGPRINTFX(DBG_ERROR,("Invalid TZID value '%s' found (no related VTIMEZONES found and not referring to an internal time zone name)",aText)){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("Invalid TZID value '%s' found (no related VTIMEZONES found and not referring to an internal time zone name)"
,aText); }
;
3555 }
3556 return true; // not set, is ok
3557 timecontext:
3558 // if no field, we still have the zone as fItemTimeContext
3559 if (!fldP) return true; // no field, is ok
3560 else if (fldP->isBasedOn(fty_timestamp)) {
3561 // based on timestamp, assign context to that timestamp
3562 tsFldP = static_cast<TTimestampField *>(fldP);
3563 tsFldP->setTimeContext(tctx);
3564 }
3565 else if (fldP->getCalcType()==fty_integer || !TCTX_IS_TZ(tctx)) {
3566 // integer field or non-symbolic time zone:
3567 // assign minute offset as number (calculated for now)
3568 TzResolveToOffset(tctx, moffs, getSession()->getSystemNowAs(TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ))), true, getSessionZones());
3569 fldP->setAsInteger(moffs);
3570 }
3571 else {
3572 // assign symbolic time zone name
3573 TimeZoneContextToName(tctx, s, getSessionZones());
3574 fldP->setAsString(s);
3575 }
3576 return true;
3577
3578 case CONVMODE_MULTIMIX15:
3579 case CONVMODE_BITMAP11:
3580 while (*aText && *aText==' ') aText++; // skip leading spaces
3581 if (convmode==CONVMODE_MULTIMIX15) {
3582 // parse value to determine field
3583 if (!mixvalparse(aText, offs, isBitMap, n)) return true; // syntax not ok, nop
3584 fldP = aItem.getArrayField(aFid+offs,aArrIndex);
3585 }
3586 else {
3587 // just bit number
3588 isBitMap=true;
3589 if (StrToUShort(aText,n,2)<1) return true; // no integer convertible value, nop
3590 }
3591 if (!fldP) return true; // no field, assignment "ok" (=nop)
3592 if (isBitMap) {
3593 // store or add to bitmap
3594 // - get current bitmap value if we have a spearator (means that we can have multiple values)
3595 if (aConvDefP->combineSep)
3596 flags=fldP->getAsInteger();
3597 flags = flags | ((fieldinteger_t)1<<n);
3598 // - save updated flags
3599 fldP->setAsInteger(flags);
3600 }
3601 else {
3602 // store as literal
3603 fldP->setAsString(aText+n);
3604 }
3605 return true; // ok
3606 case CONVMODE_VERSION1:
3607 // version string
3608 // - return true if correct version string
3609 return strucmp(aText,aItem.getItemType()->getTypeVers(fProfileMode))==0;
3610 case CONVMODE_PRODID2:
3611 case CONVMODE_VALUETYPE14:
3612 case CONVMODE_FULLVALUETYPE16:
3613 return true; // simply ignore, always ok
3614 case CONVMODE_RRULE20 +0:
3615 // helpers
3616 TTimestampField *tfP;
3617 TIntegerField *ifP;
3618 TStringField *sfP;
3619 if (aFid<0) return true; // no field block, assignment "ok" (=nop)
3620 // read DTSTART (last=6th field in block) as reference for converting count to end time point
3621 dtstart=0; // start date/time, as reference
3622 if (!(tfP = ITEMFIELD_DYNAMIC_CAST_PTR(TTimestampField,fty_timestamp,aItem.getArrayField(aFid+5,aArrIndex))(aItem.getArrayField(aFid+5,aArrIndex)->isBasedOn(fty_timestamp
) ? static_cast<TTimestampField *>(aItem.getArrayField(
aFid+5,aArrIndex)) : __null)
)) return false;
3623 // TZ and TZID should be applied to dates by now, so dtstart should be in right zone
3624 dtstart = tfP->getTimestampAs(TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)),&startcontext);
3625 if (TCTX_IS_UTC(startcontext)) {
3626 // UTC is probably not the correct zone to resolve weekdays -> convert to item zone
3627 dtstart = tfP->getTimestampAs(fItemTimeContext,&startcontext);
3628 }
3629 // init field block values
3630 freq='0'; // frequency
3631 freqmod=' '; // frequency modifier
3632 interval=0; // unspecified interval
3633 firstmask=0; // day mask counted from the first day of the period
3634 lastmask=0; // day mask counted from the last day of the period
3635 until=0; // last day
3636 // do the conversion here
3637 dostore=false;
3638 if (fMimeDirMode==mimo_old) {
3639 // vCalendar 1.0 type RRULE
3640 dostore=RRULE1toInternal(
3641 aText, // RRULE string to be parsed
3642 dtstart, // reference date for parsing RRULE
3643 startcontext,
3644 freq,
3645 freqmod,
3646 interval,
3647 firstmask,
3648 lastmask,
3649 until,
3650 untilcontext,
3651 GETDBGLOGGER(getDbgLogger())
3652 );
3653 }
3654 else {
3655 // iCalendar 2.0 type RRULE
3656 dostore=RRULE2toInternal(
3657 aText, // RRULE string to be parsed
3658 dtstart, // reference date for parsing RRULE
3659 startcontext,
3660 freq,
3661 freqmod,
3662 interval,
3663 firstmask,
3664 lastmask,
3665 until,
3666 untilcontext,
3667 GETDBGLOGGER(getDbgLogger())
3668 );
3669 }
3670 if (dostore) {
3671 // store values into field block
3672 // - freq/freqmod
3673 if (!(sfP = ITEMFIELD_DYNAMIC_CAST_PTR(TStringField,fty_string,aItem.getArrayField(aFid,aArrIndex))(aItem.getArrayField(aFid,aArrIndex)->isBasedOn(fty_string
) ? static_cast<TStringField *>(aItem.getArrayField(aFid
,aArrIndex)) : __null)
)) return false;
3674 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
3675 sfP->assignEmpty();
3676 if (freq!='0') {
3677 sfP->appendChar(freq);
3678 sfP->appendChar(freqmod);
3679 }
3680 // - interval
3681 if (!(ifP = ITEMFIELD_DYNAMIC_CAST_PTR(TIntegerField,fty_integer,aItem.getArrayField(aFid,aArrIndex))(aItem.getArrayField(aFid,aArrIndex)->isBasedOn(fty_integer
) ? static_cast<TIntegerField *>(aItem.getArrayField(aFid
,aArrIndex)) : __null)
)) return false;
3682 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
3683 ifP->setAsInteger(interval);
3684 // - firstmask
3685 if (!(ifP = ITEMFIELD_DYNAMIC_CAST_PTR(TIntegerField,fty_integer,aItem.getArrayField(aFid,aArrIndex))(aItem.getArrayField(aFid,aArrIndex)->isBasedOn(fty_integer
) ? static_cast<TIntegerField *>(aItem.getArrayField(aFid
,aArrIndex)) : __null)
)) return false;
3686 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
3687 ifP->setAsInteger(firstmask);
3688 // - lastmask
3689 if (!(ifP = ITEMFIELD_DYNAMIC_CAST_PTR(TIntegerField,fty_integer,aItem.getArrayField(aFid,aArrIndex))(aItem.getArrayField(aFid,aArrIndex)->isBasedOn(fty_integer
) ? static_cast<TIntegerField *>(aItem.getArrayField(aFid
,aArrIndex)) : __null)
)) return false;
3690 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
3691 ifP->setAsInteger(lastmask);
3692 // - until
3693 if (!(tfP = ITEMFIELD_DYNAMIC_CAST_PTR(TTimestampField,fty_timestamp,aItem.getArrayField(aFid,aArrIndex))(aItem.getArrayField(aFid,aArrIndex)->isBasedOn(fty_timestamp
) ? static_cast<TTimestampField *>(aItem.getArrayField(
aFid,aArrIndex)) : __null)
)) return false;
3694 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
3695 tfP->setTimestampAndContext(until,untilcontext);
3696 // - dtstart is not stored, but only read above for reference
3697 // done
3698 return true;
3699 }
3700 else {
3701 return false;
3702 }
3703 break; // just in case
3704 default:
3705 // unknown mode, cannot convert
3706 return false;
3707 }
3708 return false;
3709} // TMimeDirProfileHandler::MIMEStringToField
3710
3711
3712// helper for parseMimeDir()
3713// - parse parameter or property value(list), returns false if no value(list)
3714bool TMimeDirProfileHandler::parseValue(
3715 const string &aText, // string to parse as value (could be binary content)
3716 const TConversionDef *aConvDefP,
3717 sInt16 aBaseOffset, // base offset
3718 sInt16 aRepOffset, // repeat offset, adds to aBaseOffset for non-array fields, is array index for array fileds
3719 TMultiFieldItem &aItem, // the item where data goes to
3720 bool &aNotEmpty, // is set true (but never set false) if property contained any (non-positional) values
3721 char aSeparator, // separator between values
3722 TMimeDirMode aMimeMode, // MIME mode (older or newer vXXX format compatibility)
3723 bool aParamValue, // set if parsing parameter value (different escaping rules)
3724 bool aStructured, // set if value consists of multiple values (has semicolon content escaping)
3725 bool aOnlyDeEscLF // set if de-escaping only for \n -> LF, but all visible char escapes should be left intact
3726)
3727{
3728 string val,val2;
3729 char c;
3730 const char *p;
3731
3732 // determine field ID
3733 sInt16 fid=aConvDefP->fieldid;
3734 if (fid>=0) {
3735 // value has field where it can be stored
3736 // - fid is ALWAYS offset by baseoffset
3737 fid += aBaseOffset;
3738 // - adjust fid and repoffset (add them and reset aRepOffset if no array field)
3739 aItem.adjustFidAndIndex(fid,aRepOffset);
3740 // find out if value exists (available in source and target)
3741 if (isFieldAvailable(aItem,fid)) {
3742 // parse only if field available in both source and target
3743 if (
3744 (aConvDefP->convmode & CONVMODE_MASK0xFF)==CONVMODE_BLOB_B6412 ||
3745 (aConvDefP->convmode & CONVMODE_MASK0xFF)==CONVMODE_BLOB_AUTO17
3746 ) {
3747 // move 1:1 into field
3748 // - get pointer to leaf field
3749 TItemField *fldP = aItem.getArrayField(fid,aRepOffset);
3750 // - directly set field with entire (possiby binary) string content
3751 if (fldP) fldP->setAsString(aText);
3752 // parsed successfully
3753 return true;
3754 }
3755 // normal text value, apply de-escaping, charset transformation, value list and enum conversion
3756 p = aText.c_str(); // start here
3757 while (*p) {
3758 // value list loop
3759 // - get next value
3760 val.erase();
3761 while ((c=*p)!=0) {
3762 // check for field list separator (if field allows list at all)
3763 if (c==aSeparator && aConvDefP->combineSep) {
3764 p++; // skip separator
3765 break;
3766 }
3767 // check for escaped chars
3768 if (!aParamValue && c=='\\') {
3769 p++;
3770 c=*p;
3771 if (!c) break; // half escape sequence, ignore
3772 else if (c=='n' || c=='N') c='\n';
3773 else if (aOnlyDeEscLF) val+='\\'; // if deescaping only for \n, transfer this non-LF escape into output
3774 // other escaped chars are shown as themselves
3775 }
3776 // add char
3777 val+=c;
3778 // next
3779 p++;
3780 }
3781 // find first non-space and number of chars excluding leading and trailing spaces
3782 const char* valnospc = val.c_str();
3783 size_t numnospc=val.size();
3784 while (*valnospc && *valnospc==' ') { valnospc++; numnospc--; }
3785 while (*(valnospc+numnospc-1)==' ') { numnospc--; }
3786 // - counts as non-empty if there is a non-empty (and not space-only) value string (even if
3787 // it might be converted to empty-value in enum conversion)
3788 if (*valnospc) aNotEmpty=true;
3789 // - apply enum translation if any
3790 const TEnumerationDef *enumP = aConvDefP->findEnumByName(valnospc,numnospc);
3791 if (enumP) {
3792 // we have an explicit value (can be default if there is a enm_defaultvalue enum)
3793 if (enumP->enummode==enm_ignore)
3794 continue; // do not assign anything, get next value
3795 else {
3796 if (enumP->enummode==enm_prefix) {
3797 // append original value minus prefix to translation
3798 size_t n=TCFG_SIZE(enumP->enumtext)enumP->enumtext.size();
3799 val2.assign(valnospc+n,numnospc-n); // copying from original val
3800 val=enumP->enumval; // assign the prefix
3801 val+=val2; // and append the original value sans prefix
3802 }
3803 else {
3804 val=enumP->enumval; // just use translated value
3805 }
3806 }
3807 }
3808 // assign (or add) value to field
3809 if (!MIMEStringToField(
3810 val.c_str(), // the value text to assign or add to the field
3811 aConvDefP, // the conversion definition
3812 aItem,
3813 fid, // field ID, can be -1
3814 aRepOffset // 0 or array index
3815 )) {
3816 // field conversion error
3817 PDEBUGPRINTFX(DBG_ERROR,({ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseValue: MIMEStringToField assignment (fid=%hd, arrindex=%hd) failed"
, fid, aRepOffset ); }
3818 "TMimeDirProfileHandler::parseValue: MIMEStringToField assignment (fid=%hd, arrindex=%hd) failed",{ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseValue: MIMEStringToField assignment (fid=%hd, arrindex=%hd) failed"
, fid, aRepOffset ); }
3819 fid,{ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseValue: MIMEStringToField assignment (fid=%hd, arrindex=%hd) failed"
, fid, aRepOffset ); }
3820 aRepOffset{ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseValue: MIMEStringToField assignment (fid=%hd, arrindex=%hd) failed"
, fid, aRepOffset ); }
3821 )){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseValue: MIMEStringToField assignment (fid=%hd, arrindex=%hd) failed"
, fid, aRepOffset ); }
;
3822 return false;
3823 }
3824 } // while(more chars in value text)
3825 } // if source and target fields available
3826 else {
3827 // show this in log, as most probably it's a remote devInf bug
3828 PDEBUGPRINTFX(DBG_PARSE,("No value stored for field index %hd because remote indicates not supported in devInf",fid)){ if (((0x00000200) & getDbgMask()) == (0x00000200)) getDbgLogger
()->setNextMask(0x00000200).DebugPrintfLastMask ("No value stored for field index %hd because remote indicates not supported in devInf"
,fid); }
;
3829 }
3830 } // if fieldid exists
3831 else {
3832 // could be special conversion using no data or data from
3833 // internal object variables (such as VERSION value)
3834 if (!MIMEStringToField(
3835 aText.c_str(), // the value text to process
3836 aConvDefP, // the conversion definition
3837 aItem,
3838 FID_NOT_SUPPORTED-128,
3839 0
3840 )) {
3841 // field conversion error
3842 PDEBUGPRINTFX(DBG_ERROR,({ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseValue: MIMEStringToField in check mode (no field) failed with val=%s"
, aText.c_str() ); }
3843 "TMimeDirProfileHandler::parseValue: MIMEStringToField in check mode (no field) failed with val=%s",{ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseValue: MIMEStringToField in check mode (no field) failed with val=%s"
, aText.c_str() ); }
3844 aText.c_str(){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseValue: MIMEStringToField in check mode (no field) failed with val=%s"
, aText.c_str() ); }
3845 )){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseValue: MIMEStringToField in check mode (no field) failed with val=%s"
, aText.c_str() ); }
;
3846 return false;
3847 }
3848 }
3849 // parsed successfully
3850 return true;
3851} // TMimeDirProfileHandler::parseValue
3852
3853
3854
3855// parse given property
3856bool TMimeDirProfileHandler::parseProperty(
3857 cAppCharP &aText, // where to start interpreting property, will be updated past end of what was scanned
3858 TMultiFieldItem &aItem, // item to store data into
3859 const TPropertyDefinition *aPropP, // the property definition
3860 sInt16 *aRepArray, // array[repeatID], holding current repetition COUNT for a certain nameExts entry
3861 sInt16 aRepArraySize, // size of array (for security)
3862 TMimeDirMode aMimeMode, // MIME mode (older or newer vXXX format compatibility)
3863 cAppCharP aGroupName, // property group ("a" in "a.TEL:131723612")
3864 size_t aGroupNameLen,
3865 cAppCharP aFullPropName, // entire property name (excluding group) - might be needed in case of wildcard property match
3866 size_t aFullNameLen
3867)
3868{
3869 TNameExtIDMap nameextmap;
3870 const TParameterDefinition *paramP;
3871 const char *p,*ep,*vp;
3872 char c;
3873 string pname;
3874 string val;
3875 string unprocessedVal;
3876 bool defaultparam;
3877 bool fieldoffsetfound;
3878 bool notempty = false;
3879 bool valuelist;
3880 sInt16 pidx; // parameter index
3881 TEncodingTypes encoding;
3882 TCharSets charset;
3883 // field storage info vars, defaults are used if property has no TPropNameExtension
3884 sInt16 baseoffset = 0;
3885 sInt16 repoffset = 0;
3886 sInt16 maxrep = 1; // no repeat by default
3887 sInt16 repinc = 1; // inc by 1
3888 sInt16 repid = -1; // invalid by default
3889 bool overwriteempty = false; // do not overwrite empty values by default
3890 bool repoffsByGroup = false;
3891
3892 // init
3893 encoding = enc_none; // no encoding by default
3894 charset = aMimeMode==mimo_standard ? chs_utf8 : fDefaultInCharset; // always UTF8 for real MIME-DIR (same as enclosing SyncML doc), for mimo_old depends on <inputcharset> remote rule option (normally UTF-8)
3895 nameextmap = 0; // no name extensions detected so far
3896 fieldoffsetfound = (aPropP->nameExts==NULL__null); // no first pass needed at all w/o nameExts, just use offs=0
3897 valuelist = aPropP->valuelist; // cache flag
3898 // prepare storage as unprocessed value
3899 if (aPropP->unprocessed) {
3900 if (aGroupName && *aGroupName) {
3901 unprocessedVal.assign(aGroupName, aGroupNameLen);
3902 unprocessedVal += '.';
3903 }
3904 unprocessedVal.append(aFullPropName, aFullNameLen);
3905 }
3906 // scan parameter list (even if unprocessed, to catch ENCODING and CHARSET)
3907 do {
3908 p=aText;
3909 while (*p==';') {
3910 // param follows
3911 defaultparam=false;
3912 pname.erase();
3913 p=nextunfolded(p,aMimeMode);
3914 // parameter expected here
3915 // - find end of parameter name
3916 vp=NULL__null; // no param name found
3917 for (ep=p; *ep; ep=nextunfolded(ep,aMimeMode)) {
3918 if (*ep=='=') {
3919 // param value follows at vp
3920 vp=nextunfolded(ep,aMimeMode);
3921 break;
3922 }
3923 else if (*ep==':' || *ep==';') {
3924 // end of parameter name w/o equal sign
3925 if (aMimeMode!=mimo_old) {
3926 // only mimo_old allows default params, but as e.g. Nokia Intellisync (Synchrologic) does this completely wrong, we now tolerate it
3927 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,({ if ((getSession()) && (((0x00000002) & (getSession
())->getDbgMask()) == (0x00000002))) (getSession())->getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "Parameter without value: %s - is wrong in MIME-DIR, but we tolerate it and parse as default param name"
, pname.c_str() ); }
3928 "Parameter without value: %s - is wrong in MIME-DIR, but we tolerate it and parse as default param name",{ if ((getSession()) && (((0x00000002) & (getSession
())->getDbgMask()) == (0x00000002))) (getSession())->getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "Parameter without value: %s - is wrong in MIME-DIR, but we tolerate it and parse as default param name"
, pname.c_str() ); }
3929 pname.c_str(){ if ((getSession()) && (((0x00000002) & (getSession
())->getDbgMask()) == (0x00000002))) (getSession())->getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "Parameter without value: %s - is wrong in MIME-DIR, but we tolerate it and parse as default param name"
, pname.c_str() ); }
3930 )){ if ((getSession()) && (((0x00000002) & (getSession
())->getDbgMask()) == (0x00000002))) (getSession())->getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "Parameter without value: %s - is wrong in MIME-DIR, but we tolerate it and parse as default param name"
, pname.c_str() ); }
;
3931 }
3932 // treat this as a value of the default parameter (correct syntax in old vCard 2.1/vCal 1.0, wrong in MIME-DIR)
3933 defaultparam=true; // default param
3934 // value is equal to param name and starts at p
3935 vp=p;
3936 break;
3937 }
3938 // add char to param name (unfolded!)
3939 pname+=*ep;
3940 }
3941 if (!vp) {
3942 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("parseProperty: bad parameter %s (missing value)",pname.c_str())){ if ((getSession()) && (((0x00000002) & (getSession
())->getDbgMask()) == (0x00000002))) (getSession())->getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("parseProperty: bad parameter %s (missing value)"
,pname.c_str()); }
;
3943 return false;
3944 }
3945 // parameter name & value isolated, pname=name (if not defaultparam), vp points to value
3946 // - obtain unfolded value
3947 val.erase();
3948 bool dquoted = false;
3949 bool wasdquoted = false;
3950 // - note: we allow quoted params even with mimo_old, as the chance is much higher that a param value
3951 // beginning with doublequote is actually a quoted string than a value containing a doublequote at the beginning
3952 if (*vp=='"') {
3953 dquoted = true;
3954 wasdquoted = true;
3955 vp=nextunfolded(vp,aMimeMode);
3956 }
3957 do {
3958 c=*vp;
3959 if (isEndOfLineOrText(c)) break;
3960 if (dquoted) {
3961 // within double quoted value, only closing dquote can end it
3962 if (c=='"') {
3963 // swallow closing double quote and proceed (next should be end of value anyway)
3964 vp = nextunfolded(vp,aMimeMode);
3965 dquoted = false;
3966 continue;
3967 }
3968 }
3969 else {
3970 // not within double quoted value
3971 if (c==':' || c==';') break; // end of value
3972 }
3973 val+=c;
3974 // cancel QP softbreaks if encoding is already switched to QP at this point
3975 vp=nextunfolded(vp,aMimeMode,encoding==enc_quoted_printable);
3976 } while(true);
3977 // - processing of next param starts here
3978 p=vp;
3979 // check for global parameters
3980 bool storeUnprocessed = true; // in case this is a unprocessed property, flag will be cleared to prevent storing params that still ARE processed
3981 if ((aMimeMode==mimo_old && defaultparam) || strucmp(pname.c_str(),"ENCODING")==0) {
3982 // get encoding
3983 // Note: always process ENCODING, as QP is mimo-old specific and must be removed for normalized storage
3984 for (sInt16 k=0; k<numMIMEencodings; k++) {
3985 if (strucmp(val.c_str(),MIMEEncodingNames[k])==0) {
3986 encoding=static_cast <TEncodingTypes> (k);
3987 }
3988 }
3989 if (aPropP->unprocessed) {
3990 if (encoding==enc_quoted_printable)
3991 storeUnprocessed = false; // QP will be decoded (for unprocessed properties), so param must not be stored
3992 else
3993 encoding = enc_none; // other encodings will not be processed for unprocessed properties
3994 }
3995 }
3996 else if (strucmp(pname.c_str(),"CHARSET")==0) {
3997 // charset specified (mimo_old value-only not supported)
3998 // Note: always process CHARSET, because non-UTF8 cannot be safely passed to DBs, so we need
3999 // to convert in case it's not UTF-8 even for "unprocessed" properties
4000 sInt16 k;
4001 for (k=1; k<numCharSets; k++) {
4002 if (strucmp(val.c_str(),MIMECharSetNames[k])==0) {
4003 // charset found
4004 charset=TCharSets(k);
4005 break;
4006 }
4007 }
4008 if (k>=numCharSets) {
4009 // unknown charset
4010 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("========== WARNING: Unknown Charset '%s'",val.c_str())){ if ((getSession()) && (((0x00000002) & (getSession
())->getDbgMask()) == (0x00000002))) (getSession())->getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("========== WARNING: Unknown Charset '%s'"
,val.c_str()); }
;
4011 // %%% replace 8bit chars with underscore
4012 charset=chs_unknown;
4013 }
4014 storeUnprocessed = false; // CHARSET is never included in unprocessed property, as we always store UTF-8
4015 }
4016 if (aPropP->unprocessed && storeUnprocessed && fieldoffsetfound) {
4017 // append in reconstructed form for storing "unprocessed" (= lightly normalized)
4018 unprocessedVal += ';';
4019 unprocessedVal += pname;
4020 if (!defaultparam) {
4021 unprocessedVal += '=';
4022 if (wasdquoted) unprocessedVal += '"';
4023 unprocessedVal += val;
4024 if (wasdquoted) unprocessedVal += '"';
4025 }
4026 }
4027 // find param in list now
4028 paramP = aPropP->parameterDefs;
4029 pidx=0; // parameter index
4030 while (paramP) {
4031 // check for match
4032 if (
4033 mimeModeMatch(paramP->modeDependency) &&
4034 #ifndef NO_REMOTE_RULES
4035 (!paramP->ruleDependency || isActiveRule(paramP->ruleDependency)) &&
4036 #endif
4037 ((defaultparam && paramP->defaultparam) || strucmp(pname.c_str(),TCFG_CSTR(paramP->paramname)paramP->paramname.c_str())==0)
4038 ) {
4039 // param name found
4040 // - process value (list)
4041 if (!fieldoffsetfound) {
4042 // first pass, check for extendsname parameters
4043 if (paramP->extendsname) {
4044 // - for each value in the value list, check if it has a nameextid
4045 if (!paramP->convdef.enumdefs) {
4046 DEBUGPRINTFX(DBG_PARSE,({ if (((0x00000200) & getDbgMask()) == (0x00000200)) getDbgLogger
()->setNextMask(0x00000200).DebugPrintfLastMask ( "parseProperty: extendsname param w/o enum : %s;%s"
, aPropP->propname.c_str(), paramP->paramname.c_str() )
; }
4047 "parseProperty: extendsname param w/o enum : %s;%s",{ if (((0x00000200) & getDbgMask()) == (0x00000200)) getDbgLogger
()->setNextMask(0x00000200).DebugPrintfLastMask ( "parseProperty: extendsname param w/o enum : %s;%s"
, aPropP->propname.c_str(), paramP->paramname.c_str() )
; }
4048 TCFG_CSTR(aPropP->propname),{ if (((0x00000200) & getDbgMask()) == (0x00000200)) getDbgLogger
()->setNextMask(0x00000200).DebugPrintfLastMask ( "parseProperty: extendsname param w/o enum : %s;%s"
, aPropP->propname.c_str(), paramP->paramname.c_str() )
; }
4049 TCFG_CSTR(paramP->paramname){ if (((0x00000200) & getDbgMask()) == (0x00000200)) getDbgLogger
()->setNextMask(0x00000200).DebugPrintfLastMask ( "parseProperty: extendsname param w/o enum : %s;%s"
, aPropP->propname.c_str(), paramP->paramname.c_str() )
; }
4050 )){ if (((0x00000200) & getDbgMask()) == (0x00000200)) getDbgLogger
()->setNextMask(0x00000200).DebugPrintfLastMask ( "parseProperty: extendsname param w/o enum : %s;%s"
, aPropP->propname.c_str(), paramP->paramname.c_str() )
; }
;
4051 return false;
4052 }
4053 // - loop through value list
4054 ep=val.c_str();
4055 while (*ep) {
4056 sInt32 n;
4057 const char *pp;
4058 // find end of next value in list
4059 for (n=0,pp=ep; *pp; pp++) {
4060 if (*pp==',') {
4061 pp++; // skip the comma
4062 break;
4063 }
4064 n++;
4065 }
4066 // search in enums list
4067 const TEnumerationDef *enumP = paramP->convdef.findEnumByName(ep,n);
4068 if (enumP && enumP->nameextid>=0) {
4069 // set name extension map bit
4070 nameextmap |= ((TNameExtIDMap)1<<enumP->nameextid);
4071 }
4072 // next value in list
4073 ep=pp;
4074 }
4075 } // if extendsname
4076 } // first pass
4077 else {
4078 // second pass: read param value(s)
4079 if (!parseValue(
4080 val, // input string, possibly binary (e.g. in case of B64 encoded PHOTO)
4081 &(paramP->convdef),
4082 baseoffset, // base offset (as determined by position)
4083 repoffset, // repetition offset or array index
4084 aItem, // the item where data goes to
4085 notempty, // set true if value(s) parsed are not all empty
4086 defaultparam ? ';' : ',', // value list separator
4087 aMimeMode, // MIME mode (older or newer vXXX format compatibility)
4088 true, // parsing a parameter
4089 false, // no structured value
4090 false // normal, full de-escaping
4091 )) {
4092 DEBUGPRINTFX(DBG_PARSE,({ if (((0x00000200) & getDbgMask()) == (0x00000200)) getDbgLogger
()->setNextMask(0x00000200).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseProperty: %s: value not parsed: %s"
, pname.c_str(), val.c_str() ); }
4093 "TMimeDirProfileHandler::parseProperty: %s: value not parsed: %s",{ if (((0x00000200) & getDbgMask()) == (0x00000200)) getDbgLogger
()->setNextMask(0x00000200).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseProperty: %s: value not parsed: %s"
, pname.c_str(), val.c_str() ); }
4094 pname.c_str(),{ if (((0x00000200) & getDbgMask()) == (0x00000200)) getDbgLogger
()->setNextMask(0x00000200).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseProperty: %s: value not parsed: %s"
, pname.c_str(), val.c_str() ); }
4095 val.c_str(){ if (((0x00000200) & getDbgMask()) == (0x00000200)) getDbgLogger
()->setNextMask(0x00000200).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseProperty: %s: value not parsed: %s"
, pname.c_str(), val.c_str() ); }
4096 )){ if (((0x00000200) & getDbgMask()) == (0x00000200)) getDbgLogger
()->setNextMask(0x00000200).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseProperty: %s: value not parsed: %s"
, pname.c_str(), val.c_str() ); }
;
4097 return false;
4098 }
4099 } // second pass
4100 } // if (param known)
4101 // test next param
4102 paramP=paramP->next;
4103 pidx++;
4104 } // while more params
4105 // p points to ';' of next param or ':' of value
4106 } // while more parameters (*p==';')
4107 // check if both passes done or if property storage is explicitly blocked already (baseoffset=-1)
4108 if (fieldoffsetfound) break;
4109 // start second pass
4110 fieldoffsetfound=true;
4111 // - assume empty to start with
4112 notempty=false;
4113 // - prepare for second pass: check if set of param values match
4114 // an entry in the nameexts list
4115 TPropNameExtension *propnameextP = aPropP->nameExts;
4116 if (propnameextP) {
4117 repoffsByGroup = false;
4118 bool dostore = false;
4119 while (propnameextP) {
4120 // check if entry matches parsed extendsname param values
4121 if (
4122 ((propnameextP->musthave_ids & nameextmap) == propnameextP->musthave_ids) && // needed there
4123 ((propnameextP->forbidden_ids & nameextmap) == 0) // none of the forbidden ones there
4124 ) {
4125 // found match, get offset
4126 baseoffset=propnameextP->fieldidoffs;
4127 if (baseoffset==OFFS_NOSTORE-9999) break; // abort with dostore=false
4128 // check if repeat needed/allowed
4129 maxrep=propnameextP->maxRepeat;
4130 if (maxrep==REP_REWRITE0) {
4131 dostore=true; // we can store
4132 break; // unlimited repeat allowed but stored in same fields (overwrite), no need for index search by group
4133 }
4134 // find index where to store this repetition
4135 repid=propnameextP->repeatID;
4136 if (repid>=aRepArraySize)
4137 SYSYNC_THROW(TSyncException(DEBUGTEXT("TMimeDirProfileHandler::parseProperty: repID too high","mdit11")))throw TSyncException("TMimeDirProfileHandler::parseProperty: repID too high"
)
;
4138 // check if a group ID determines the repoffset (not possible for valuelists)
4139 if (aPropP->groupFieldID!=FID_NOT_SUPPORTED-128 && !valuelist) {
4140 // search in group field
4141 string s;
4142 bool someGroups = false;
4143 for (sInt16 n=0; n<maxrep || maxrep==REP_ARRAY32767; n++) {
4144 sInt16 g_repoffset = n*propnameextP->repeatInc; // original repeatoffset (not adjusted yet)
4145 TItemField *g_fldP = aItem.getArrayFieldAdjusted(aPropP->groupFieldID+baseoffset,g_repoffset,true); // get leaf field, if it exists
4146 if (!g_fldP) break; // group field for that repetition does not (yet) exist, array exhausted
4147 // compare group name
4148 if (g_fldP->isAssigned()) {
4149 someGroups = someGroups || !g_fldP->isEmpty(); // when we find a non-empty group field, we have at least one group detected
4150 if (someGroups) {
4151 // don't use repetitions already used by SOME of the fields in the group
4152 // for auto-assigning new groups (or ungrouped occurrences)
4153 if (aRepArray[repid]<n+1)
4154 aRepArray[repid] = n+1;
4155 }
4156 // check if group matches (only if there is a group at all)
4157 g_fldP->getAsString(s);
4158 if (aGroupName && strucmp(aGroupName,s.c_str(),aGroupNameLen)==0) {
4159 repoffsByGroup = true;
4160 dostore = true;
4161 repoffset = g_repoffset;
4162 PDEBUGPRINTFX(DBG_PARSE+DBG_EXOTIC,("parseProperty: found group '%s' at repoffset=%d (repcount=%d)",s.c_str(),repoffset,n)){ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ("parseProperty: found group '%s' at repoffset=%d (repcount=%d)"
,s.c_str(),repoffset,n); }
;
4163 break;
4164 }
4165 }
4166 } // for all possible repetitions
4167 // minrep now contains minimal repetition count for !repoffsByGroup case
4168 } // if grouped property
4169 if (!repoffsByGroup) {
4170 if (aRepArray[repid]<maxrep || maxrep==REP_ARRAY32767) {
4171 // not exhausted, we can use this entry
4172 // - calculate repeat offset to be used
4173 repinc=propnameextP->repeatInc;
4174 // note: repArray will be updated below (if property not empty or !overwriteempty)
4175 do {
4176 repoffset = aRepArray[repid]*repinc;
4177 // - set flag if repeat offset should be incremented after storing an empty property or not
4178 overwriteempty = propnameextP->overwriteEmpty;
4179 // - check if target property main value is empty (must be, or we will skip that repetition)
4180 dostore = false; // if no field exists, we do not store
4181 for (sInt16 e=0; e<aPropP->numValues; e++) {
4182 if (aPropP->convdefs[e].fieldid==FID_NOT_SUPPORTED-128)
4183 continue; // no field, no need to check it
4184 sInt16 e_fid = aPropP->convdefs[e].fieldid+baseoffset;
4185 sInt16 e_rep = repoffset;
4186 aItem.adjustFidAndIndex(e_fid,e_rep);
4187 // - get base field
4188 TItemField *e_basefldP = aItem.getField(e_fid);
4189 TItemField *e_fldP = NULL__null;
4190 if (e_basefldP)
4191 e_fldP=e_basefldP->getArrayField(e_rep,true); // get leaf field, if it exists
4192 if (!e_basefldP || (e_fldP && e_fldP->isAssigned()) ||
4193 (aPropP->groupFieldID!=FID_NOT_SUPPORTED-128 && !valuelist &&
4194 aItem.getArrayFieldAdjusted(aPropP->groupFieldID+baseoffset,e_rep,true))) {
4195 // base field of one of the main fields does not exist or leaf field is already assigned,
4196 // or the group field entry is already in use (doesn't matter whether it is empty)
4197 // -> skip that repetition
4198 dostore = false;
4199 break;
4200 }
4201 else
4202 dostore = true; // at least one field exists, we might store
4203 }
4204 // - check if shared fields used for parameters are available;
4205 // must be enabled explicitly with <parameter ... sharedfield="yes">
4206 const TParameterDefinition *paramP = aPropP->parameterDefs;
4207 while (paramP) {
4208 if (paramP->sharedField &&
4209 mimeModeMatch(paramP->modeDependency)
4210#ifndef NO_REMOTE_RULES
4211 && (!paramP->ruleDependency || isActiveRule(paramP->ruleDependency))
4212#endif
4213 ) {
4214 if (paramP->convdef.fieldid==FID_NOT_SUPPORTED-128)
4215 continue; // no field, no need to check it
4216 sInt16 e_fid = paramP->convdef.fieldid /* +baseoffset */;
4217 sInt16 e_rep = repoffset;
4218 aItem.adjustFidAndIndex(e_fid,e_rep);
4219 // - get base field
4220 TItemField *e_basefldP = aItem.getField(e_fid);
4221 TItemField *e_fldP = NULL__null;
4222 if (e_basefldP)
4223 e_fldP=e_basefldP->getArrayField(e_rep,true); // get leaf field, if it exists
4224 if (!e_basefldP || e_fldP) {
4225 // base field or leaf field is already in use
4226 // (unassigned is not good enough, otherwise we might end up adding information
4227 // to some other, previously parsed property using the same array field)
4228 // -> skip that repetition
4229 dostore=false;
4230 break;
4231 }
4232 }
4233 paramP=paramP->next;
4234 }
4235 // check if we can test more repetitions
4236 if (!dostore) {
4237 if (aRepArray[repid]+1<maxrep || maxrep==REP_ARRAY32767) {
4238 // we can increment and try next repetition
4239 aRepArray[repid]++;
4240 }
4241 else
4242 break; // no more possible repetitions with this position rule (check next rule)
4243 }
4244 } while (!dostore);
4245 if (dostore) break; // we can store now
4246 } // if repeat not yet exhausted
4247 } // if repoffset no already found
4248 } // if position rule matches
4249 // next
4250 propnameextP=propnameextP->next;
4251 } // while search for matching nameExts entry
4252 // abort if we can't store
4253 if (!dostore) {
4254 aText=p; // this is what we've read so far
4255 return false;
4256 }
4257 } // if name extension list not empty
4258 // Now baseoffset/repoffset are valid to be used for storage
4259 } while(true); // until parameter pass 1 & pass 2 done
4260 // parameters are all processed by now, decision made to store data (if !dostore, routine exits above)
4261 // - store the group tag value if we have one
4262 if (aPropP->groupFieldID!=FID_NOT_SUPPORTED-128) {
4263 TItemField *g_fldP = aItem.getArrayFieldAdjusted(aPropP->groupFieldID+baseoffset,repoffset,false);
4264 if (g_fldP)
4265 g_fldP->setAsString(aGroupName,aGroupNameLen); // store the group name (aGroupName might be NULL, that's ok)
4266 }
4267 if (aPropP->unprocessed) {
4268 if (*p==':') {
4269 // there is a value
4270 p++;
4271 // - get entire property value part, not checking for any separators, but converting to appchar (UTF-8) and unfolding
4272 decodeValue(encoding==enc_quoted_printable ? encoding : enc_none, charset, aMimeMode, 0, 0, p, val);
4273 // - add it to "unprocessed" value representation
4274 unprocessedVal += ':';
4275 unprocessedVal += val;
4276 // - process this as a whole (de-escaping ONLY CRLFs) and assign to field
4277 if (!parseValue(
4278 unprocessedVal,
4279 &(aPropP->convdefs[0]), // the conversion definition
4280 baseoffset, // identifies base field
4281 repoffset, // repeat offset to base field / array index
4282 aItem, // the item where data goes to
4283 notempty, // set true if value(s) parsed are not all empty
4284 0, // no value list separator
4285 aMimeMode, // MIME mode (older or newer vXXX format compatibility)
4286 false, // no parameter
4287 false, // not structured
4288 true // only de-escape linefeeds, but nothing else
4289 )) {
4290 return false;
4291 }
4292 } // if property has a value part
4293 } // if unprocessed property
4294 else {
4295 // - read and decode value(s)
4296 char sep=':'; // first value starts with colon
4297 // repeat until we have all values
4298 for (sInt16 i=0; i<aPropP->numValues || valuelist; i++) {
4299 if (*p!=sep && (aPropP->altvaluesep==0 || *p!=aPropP->altvaluesep)) {
4300 #ifdef SYDEBUG2
4301 // Note: for valuelists, this is the normal loop exit case as we are not limited by numValues
4302 if (!valuelist) {
4303 // New behaviour: omitting values is ok (needed e.g. for T39m)
4304 DEBUGPRINTFX(DBG_PARSE,("TMimeDirProfileHandler::parseProperty: %s does not specify all values",TCFG_CSTR(aPropP->propname))){ if (((0x00000200) & getDbgMask()) == (0x00000200)) getDbgLogger
()->setNextMask(0x00000200).DebugPrintfLastMask ("TMimeDirProfileHandler::parseProperty: %s does not specify all values"
,aPropP->propname.c_str()); }
;
4305 }
4306 #endif
4307 break; // all available values read
4308 }
4309 // skip separator
4310 p++;
4311 // get value(list) unfolded
4312 decodeValue(encoding,charset,aMimeMode,aPropP->numValues > 1 || valuelist ? aPropP->valuesep : 0,aPropP->altvaluesep,p,val);
4313 // check if we can store, otherwise just read over value
4314 // - get the conversion def for the value
4315 TConversionDef *convDef = &(aPropP->convdefs[valuelist ? 0 : i]); // always use convdef[0] for value lists
4316 // - store value if not a value list (but simple value or part of structured value), or store if
4317 // valuelist and repeat not yet exhausted, or if valuelist without repetition but combination separator
4318 // which allows to put multiple values into a single field
4319 if (!valuelist || repoffset<maxrep*repinc || maxrep==REP_ARRAY32767 || (valuelist && convDef->combineSep)) {
4320 // convert and store value (or comma separated value-list, not to mix with valuelist-property!!)
4321 if (!parseValue(
4322 val,
4323 convDef,
4324 baseoffset, // identifies base field
4325 repoffset, // repeat offset to base field / array index
4326 aItem, // the item where data goes to
4327 notempty, // set true if value(s) parsed are not all empty
4328 ',',
4329 aMimeMode, // MIME mode (older or newer vXXX format compatibility)
4330 false, // no parameter
4331 aPropP->numValues > 1, // structured if multiple values
4332 false // normal, full de-escaping
4333 )) {
4334 PDEBUGPRINTFX(DBG_PARSE+DBG_EXOTIC,({ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseProperty: %s: value not parsed: %s"
, aPropP->propname.c_str(), val.c_str() ); }
4335 "TMimeDirProfileHandler::parseProperty: %s: value not parsed: %s",{ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseProperty: %s: value not parsed: %s"
, aPropP->propname.c_str(), val.c_str() ); }
4336 TCFG_CSTR(aPropP->propname),{ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseProperty: %s: value not parsed: %s"
, aPropP->propname.c_str(), val.c_str() ); }
4337 val.c_str(){ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseProperty: %s: value not parsed: %s"
, aPropP->propname.c_str(), val.c_str() ); }
4338 )){ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseProperty: %s: value not parsed: %s"
, aPropP->propname.c_str(), val.c_str() ); }
;
4339 return false;
4340 }
4341 // update repeat offset and repeat count if this is a value list
4342 if (valuelist && convDef->combineSep==0 && (notempty || !overwriteempty)) {
4343 // - update count for every non-empty value (for empty values only if overwriteempty is not set)
4344 if (repid>=0)
4345 aRepArray[repid]++; // next repetition
4346 repoffset+=repinc; // also update repeat offset
4347 }
4348 }
4349 else {
4350 // value cannot be stored
4351 PDEBUGPRINTFX(DBG_PARSE+DBG_EXOTIC,({ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseProperty: %s: value not stored because repeat exhausted: %s"
, aPropP->propname.c_str(), val.c_str() ); }
4352 "TMimeDirProfileHandler::parseProperty: %s: value not stored because repeat exhausted: %s",{ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseProperty: %s: value not stored because repeat exhausted: %s"
, aPropP->propname.c_str(), val.c_str() ); }
4353 TCFG_CSTR(aPropP->propname),{ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseProperty: %s: value not stored because repeat exhausted: %s"
, aPropP->propname.c_str(), val.c_str() ); }
4354 val.c_str(){ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseProperty: %s: value not stored because repeat exhausted: %s"
, aPropP->propname.c_str(), val.c_str() ); }
4355 )){ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ( "TMimeDirProfileHandler::parseProperty: %s: value not stored because repeat exhausted: %s"
, aPropP->propname.c_str(), val.c_str() ); }
;
4356 }
4357 // more values must be separated by the value sep char (default=';' but can be ',' e.g. for iCalendar 2.0 CATEGORIES)
4358 sep = aPropP->valuesep;
4359 } // for all values
4360 } // process values
4361 if (notempty && !valuelist) {
4362 // at least one of the components is not empty. Make sure all components are "touched" such that
4363 // in case of arrays, these are assigned even if empty
4364 for (sInt16 j=0; j<aPropP->numValues; j++) {
4365 sInt16 fid=aPropP->convdefs[j].fieldid;
4366 if (fid>=0) {
4367 // requesting the pointer creates the field if it does not already exist
4368 aItem.getArrayFieldAdjusted(fid+baseoffset,repoffset,false);
4369 }
4370 }
4371 const TParameterDefinition *paramP = aPropP->parameterDefs;
4372 while (paramP) {
4373 if (paramP->sharedField &&
4374 mimeModeMatch(paramP->modeDependency)
4375#ifndef NO_REMOTE_RULES
4376 && (!paramP->ruleDependency || isActiveRule(paramP->ruleDependency))
4377#endif
4378 ) {
4379 sInt16 fid=paramP->convdef.fieldid;
4380 if (fid>=0) {
4381 aItem.getArrayFieldAdjusted(fid+baseoffset,repoffset,false);
4382 }
4383 }
4384 paramP=paramP->next;
4385 }
4386 }
4387 if (!valuelist && repid>=0 && (notempty || !overwriteempty) && !repoffsByGroup) {
4388 // we have used this repetition and actually stored values, so count it now
4389 // (unless we have stored an empty value only and overwriteempty is true, in
4390 // this case we don't increment, so next value found for this repetition will
4391 // overwrite empty value
4392 // Also, if repeat offset was found by group name, don't increment (aRepArray
4393 // is already updated in this case)
4394 aRepArray[repid]++;
4395 }
4396 // update read pointer past end of what we've scanned (but not necessarily up
4397 // to next property beginning)
4398 aText=p;
4399 // done, ok
4400 return true;
4401} // TMimeDirProfileHandler::parseProperty
4402
4403
4404// parse MIME-DIR from specified string into item
4405bool TMimeDirProfileHandler::parseMimeDir(const char *aText, TMultiFieldItem &aItem)
4406{
4407 // start with empty item
4408 aItem.cleardata();
4409 // reset item time zone before parsing
4410 fHasExplicitTZ = false; // none set explicitly
4411 fItemTimeContext = fReceiverTimeContext; // default to user context
4412 fDelayedProps.clear(); // start w/o delayed props
4413 fParsedTzidSet.clear(); // start w/o time zones
4414 // start parsing on root level
4415 if (parseLevels(aText,aItem,fProfileDefinitionP,true)) {
4416 // make sure all supported (=available) fields are at least empty (but not missing!)
4417 aItem.assignAvailables();
4418 return true;
4419 }
4420 else
4421 return false;
4422} // TMimeDirProfileHandler::parseMimeDir
4423
4424
4425// parameter string for QP encoding. Needed when skipping otherwise unknown properties
4426#define QP_ENCODING_PARAM"ENCODING=QUOTED-PRINTABLE" "ENCODING=QUOTED-PRINTABLE"
4427
4428// parse MIME-DIR level from specified string into item
4429bool TMimeDirProfileHandler::parseLevels(
4430 const char *&aText,
4431 TMultiFieldItem &aItem,
4432 const TProfileDefinition *aProfileP,
4433 bool aRootLevel
4434)
4435{
4436 appChar c;
4437 cAppCharP p, propname, groupname;
4438 sInt32 n, gn;
4439 sInt16 foundmandatory=0;
4440 const sInt16 maxreps = 50;
4441 sInt16 repArray[maxreps];
4442 bool atStart = aRootLevel;
4443
4444 // reset repetition counts
4445 for (sInt16 k=0; k<maxreps; k++) repArray[k]=0;
4446 // level is known
4447 sInt16 disabledLevels=0;
4448 // set level marker field, if any is defined
4449 sInt16 fid=aProfileP->levelConvdef.fieldid;
4450 if (fid>=0) {
4451 // field defined for level entry
4452 // - make sure field exists and is assigned empty value at least
4453 aItem.getFieldRef(fid).assignEmpty();
4454 const TEnumerationDef *enumP = aProfileP->levelConvdef.enumdefs;
4455 if (enumP) {
4456 // if enumdefs, content is set to first enumdef's enumval (NOT enumtext!!)
4457 aItem.getField(fid)->setAsString(TCFG_CSTR(enumP->enumval)enumP->enumval.c_str());
4458 }
4459 }
4460 // skip possible leading extra LF and CR and whitespace here
4461 // NOTE: Magically server sends XML CDATA with 0x0D 0x0D 0x0A for example
4462 while (isspace(*aText)) aText++;
4463 // parse input text property by property
4464 do {
4465 // start of property parsing
4466 // - reset TZID flag
4467 fPropTZIDtctx = TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ));
4468 // - prepare scanning
4469 p=aText;
4470 propname = p; // assume name starts at beginning of text
4471 n = 0;
4472 groupname = NULL__null; // assume no group
4473 gn = 0;
4474 // determine property name end (and maybe group name)
4475 do {
4476 c=*p;
4477 if (!c) {
4478 // end of text reached w/o property name
4479 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("parseMimeDir: no property name found, text=%s",aText)){ if ((getSession()) && (((0x00000002) & (getSession
())->getDbgMask()) == (0x00000002))) (getSession())->getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("parseMimeDir: no property name found, text=%s"
,aText); }
;
4480 return false;
4481 }
4482 if (c==':' || c==';') break;
4483 // handle grouping
4484 if (c=='.') {
4485 // this is a group name (or element of it)
4486 // - remember the group name
4487 if (!groupname) groupname = propname;
4488 gn = p-groupname; // size of groupname
4489 // - prop name starts after group name (and dot)
4490 propname = ++p; // skip group
4491 n = 0;
4492 continue;
4493 }
4494 // next char
4495 p++; n++;
4496 } while(true);
4497 // propname points to start, p points to end of property name, n=name size
4498 // - search through all properties
4499 bool propparsed=false;
4500 // - check for BEGIN and END
4501 if (strucmp(propname,"BEGIN",n)==0) {
4502 // BEGIN encountered
4503 p = propname+n;
4504 // - skip possible parameters for broken implementations like Intellisync/Synchrologic
4505 if (*p==';') while (*p && *p!=':') p++;
4506 // - isolate value
4507 size_t l=0; const char *lnam=p+1;
4508 while (*(lnam+l)>=0x20) l++; // calculate length of value
4509 p=lnam+l; // advance scanning pointer to terminator
4510 n=0; // prevent false advancing at end of prop loop
4511 if (atStart) {
4512 // value must be level name, else this is a bad profile
4513 if (strucmp(lnam,TCFG_CSTR(aProfileP->levelName)aProfileP->levelName.c_str(),l)!=0) {
4514 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("parseMimeDir: root level BEGIN has bad value: %s",aText)){ if ((getSession()) && (((0x00000002) & (getSession
())->getDbgMask()) == (0x00000002))) (getSession())->getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("parseMimeDir: root level BEGIN has bad value: %s"
,aText); }
;
4515 return false;
4516 }
4517 atStart=false; // no special lead-in check any more
4518 propparsed=true;
4519 }
4520 else {
4521 // value determines new level to enter
4522 if (disabledLevels==0) {
4523 // search for sublevel
4524 const TProfileDefinition *subprofileP = aProfileP->subLevels;
4525 while (subprofileP) {
4526 // check
4527 if (
4528 mimeModeMatch(subprofileP->modeDependency) &&
4529 strucmp(lnam,TCFG_CSTR(subprofileP->levelName)subprofileP->levelName.c_str(),l)==0
4530 ) {
4531 // sublevel found, process
4532 while ((uInt8)(*p)<0x20) p++; // advance scanning pointer to beginning of next property
4533 // check special case first
4534 if (subprofileP->profileMode==profm_vtimezones) {
4535 // vTimeZone is handled specially
4536 string s2;
4537 string s = "END:";
4538 s.append(subprofileP->levelName);
4539 n = s.size(); // size of lead-out
4540 cAppCharP e = strstr(p,s.c_str());
4541 if (e==NULL__null) return false; // unterminated vTimeZone sublevel
4542 s.assign(p,e-p); // everything between lead-in and lead-out
4543 p = e+n; // advance pointer beyond VTIMEZONES
4544 appendStringAsUTF8(s.c_str(), s2, chs_utf8, lem_cstr, false);
4545 timecontext_t tctx;
4546 // identify or add this in the session zones
4547 string tzid;
4548 if (VTIMEZONEtoInternal(s2.c_str(), tctx, getSessionZones(), getDbgLogger(), &tzid)) {
4549 // time zone identified
4550 #ifdef SYDEBUG2
4551 string tzname;
4552 TimeZoneContextToName(tctx, tzname, getSessionZones());
4553 PDEBUGPRINTFX(DBG_PARSE+DBG_EXOTIC,("parseMimeDir: VTIMEZONE with ID='%s' parsed to internal time zone '%s'",tzid.c_str(),tzname.c_str())){ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ("parseMimeDir: VTIMEZONE with ID='%s' parsed to internal time zone '%s'"
,tzid.c_str(),tzname.c_str()); }
;
4554 #endif
4555 // remember it by original name for TZID parsing
4556 fParsedTzidSet[tzid] = tctx;
4557 }
4558 else {
4559 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("parseMimeDir: could not parse VTIMEZONE: %s",s.c_str())){ if ((getSession()) && (((0x00000002) & (getSession
())->getDbgMask()) == (0x00000002))) (getSession())->getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("parseMimeDir: could not parse VTIMEZONE: %s"
,s.c_str()); }
;
4560 }
4561 }
4562 else {
4563 // ordinary non-root level
4564 if (!parseLevels(p,aItem,subprofileP,false)) return false;
4565 }
4566 // - now continue on this level
4567 propparsed=true;
4568 break;
4569 }
4570 // next
4571 subprofileP=subprofileP->next;
4572 }
4573 if (!propparsed) {
4574 // no matching sublevel found, disable this level
4575 disabledLevels=1;
4576 }
4577 }
4578 else {
4579 // already disabled, just nest
4580 disabledLevels++;
4581 }
4582 } // BEGIN not on rootlevel
4583 } // BEGIN found
4584 else if (strucmp(propname,"END",n)==0) {
4585 // END encountered
4586 p = propname+n;
4587 // - skip possible parameters for broken implementations like Intellisync/Synchrologic
4588 if (*p==';') while (*p && *p!=':') p++;
4589 // - isolate value
4590 size_t l=0; const char *lnam=p+1;
4591 while (*(lnam+l)>=0x20) l++; // calculate length of value
4592 p=lnam+l; // advance scanning pointer to terminator
4593 n=0; // prevent false advancing at end of prop loop
4594 // check
4595 if (disabledLevels>0) {
4596 // end of a disabled level, just un-nest
4597 disabledLevels--;
4598 }
4599 else {
4600 // should be end of active level, check name
4601 if (strucmp(lnam,TCFG_CSTR(aProfileP->levelName)aProfileP->levelName.c_str(),l)!=0) {
4602 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("parseMimeDir: unexpected END value: %s",aText)){ if ((getSession()) && (((0x00000002) & (getSession
())->getDbgMask()) == (0x00000002))) (getSession())->getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("parseMimeDir: unexpected END value: %s"
,aText); }
;
4603 return false;
4604 }
4605 // correct end of level
4606 aText=p; // points to terminator, which is correct for end-of-level
4607 // break scanner loop
4608 break;
4609 }
4610 } // END found
4611 else if (disabledLevels==0) {
4612 if (atStart) {
4613 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("parseMimeDir: root level does not start with BEGIN: %s",aText)){ if ((getSession()) && (((0x00000002) & (getSession
())->getDbgMask()) == (0x00000002))) (getSession())->getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("parseMimeDir: root level does not start with BEGIN: %s"
,aText); }
;
4614 return false;
4615 }
4616 // not disabled level
4617 const TPropertyDefinition *propP = aProfileP->propertyDefs;
4618 #ifndef NO_REMOTE_RULES
4619 const TPropertyDefinition *otherRulePropP = NULL__null; // default property which is used if none of the rule-dependent in the group was used
4620 bool ruleSpecificParsed = false;
4621 uInt16 propGroup=0; // group identifier (all props with same name have same group ID)
4622 #endif
4623 const TPropertyDefinition *parsePropP;
4624 while(propP) {
4625 // compare
4626 if (
4627 mimeModeMatch(propP->modeDependency) && // none or matching mode dependency
4628 strwildcmp(propname,TCFG_CSTR(propP->propname)propP->propname.c_str(),n)==0 // wildcards allowed (for unprocessed properties for example)
4629 ) {
4630 // found property def with matching name (and MIME mode)
4631 // check all in group (=all subsequent with same name)
4632 #ifndef NO_REMOTE_RULES
4633 propGroup=propP->propGroup;
4634 ruleSpecificParsed=false;
4635 otherRulePropP=NULL__null;
4636 while (propP && propP->propGroup==propGroup && propP->propGroup!=0)
4637 #else
4638 do
4639 #endif
4640 {
4641 // still in same group (= same name)
4642 #ifndef NO_REMOTE_RULES
4643 // check if this property should be used for parsing
4644 parsePropP=NULL__null; // do not parse by default
4645 if (propP->dependsOnRemoterule) {
4646 // check if depends on current rule
4647 if (propP->ruleDependency==NULL__null) {
4648 // this is the "other"-rule dependent variant
4649 // - just remember for now
4650 otherRulePropP=propP;
4651 }
4652 else if (isActiveRule(propP->ruleDependency)) {
4653 // specific for the applied rule
4654 parsePropP=propP; // default to expand current prop
4655 // now we have expanded a rule-specific property (blocks parsing of "other"-rule dependent prop)
4656 ruleSpecificParsed=true;
4657 }
4658 }
4659 else {
4660 // does not depend on rule, parse anyway
4661 parsePropP=propP;
4662 }
4663 // check if this is last prop of list
4664 propP=propP->next;
4665 if (!(propP && propP->propGroup==propGroup) && otherRulePropP && !ruleSpecificParsed) {
4666 // End of alternatives for parsing this property, no rule-specific parsed yet, and there is a otherRuleProp
4667 // parse "other"-rule's property instead
4668 parsePropP=otherRulePropP;
4669 }
4670 #else
4671 // simply parse it
4672 parsePropP=propP;
4673 propP=propP->next;
4674 #endif
4675 // now parse (or save for delayed parsing later)
4676 if (parsePropP) {
4677 if (parsePropP->delayedProcessing) {
4678 // buffer parameters needed to parse later
4679 PDEBUGPRINTFX(DBG_PARSE+DBG_EXOTIC,("parseMimeDir: property %s parsing delayed, rank=%hd",TCFG_CSTR(parsePropP->propname),parsePropP->delayedProcessing)){ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ("parseMimeDir: property %s parsing delayed, rank=%hd"
,parsePropP->propname.c_str(),parsePropP->delayedProcessing
); }
;
4680 TDelayedPropParseParams dppp;
4681 dppp.delaylevel = parsePropP->delayedProcessing;
4682 dppp.start = p;
4683 dppp.groupname = groupname;
4684 dppp.groupnameLen = gn;
4685 dppp.propDefP = parsePropP;
4686 TDelayedParsingPropsList::iterator pos;
4687 for (pos=fDelayedProps.begin(); pos!=fDelayedProps.end(); pos++) {
4688 // insert at end or before first occurrence of higer delay
4689 if ((*pos).delaylevel>dppp.delaylevel) {
4690 fDelayedProps.insert(pos,dppp);
4691 break;
4692 }
4693 }
4694 if (pos==fDelayedProps.end())
4695 fDelayedProps.push_back(dppp);
4696 // update mandatory count (even if we haven't parsed it yet)
4697 if (parsePropP->mandatory) foundmandatory++;
4698 // skip for now
4699 p=propname+n;
4700 propparsed=true; // but is "parsed" for loop
4701 break; // parse next
4702 }
4703 if (parseProperty(
4704 p, // where to start interpreting property, will be updated past end of poperty
4705 aItem, // item to store data into
4706 parsePropP, // the (matching) property definition
4707 repArray,
4708 maxreps,
4709 fMimeDirMode, // MIME-DIR mode
4710 groupname,
4711 gn,
4712 propname,
4713 n
4714 )) {
4715 // property parsed successfully
4716 propparsed=true;
4717 // count mandarory properties found
4718 if (parsePropP->mandatory) foundmandatory++;
4719 break; // parse next
4720 }
4721 // if not successfully parsed, continue with next property which
4722 // can have the same name, but possibly different parameter definitions
4723 } // if parseProp
4724 } // while same property group (poperties with same name)
4725 #ifdef NO_REMOTE_RULES
4726 while(false); // if no remote rules, we do not loop
4727 #endif
4728 if (propparsed) break; // do not continue outer loop if inner loop has parsed a prop successfully
4729 } // if name matches (=start of group found)
4730 else {
4731 // not start of group
4732 // - next property
4733 propP=propP->next;
4734 }
4735 } // while all properties
4736 } // else: neither BEGIN nor END
4737 if (!propparsed) {
4738 // unknown property
4739 PDEBUGPRINTFX(DBG_PARSE,("parseMimeDir: property not parsed (unknown or not storable): %" FMT_LENGTH(".30") "s",FMT_LENGTH_LIMITED(30,aText))){ if (((0x00000200) & getDbgMask()) == (0x00000200)) getDbgLogger
()->setNextMask(0x00000200).DebugPrintfLastMask ("parseMimeDir: property not parsed (unknown or not storable): %"
".30" "s",aText); }
;
4740 // skip parsed part (the name)
4741 p=propname+n;
4742 }
4743 // p is now end of parsed part
4744 // - skip rest up to EOLN (=any ctrl char)
4745 // Note: we need to check if this is quoted-printable, otherwise we might NOT cancel soft breaks
4746 bool isqp = false;
4747 while ((c=*p)!=0) {
4748 if (isEndOfLineOrText(c)) break; // end of line or string
4749 if (c==';' && *(p+1)) {
4750 if (strucmp(p+1, QP_ENCODING_PARAM"ENCODING=QUOTED-PRINTABLE", strlen(QP_ENCODING_PARAM"ENCODING=QUOTED-PRINTABLE"))==0) {
4751 c = *(p+1+strlen(QP_ENCODING_PARAM"ENCODING=QUOTED-PRINTABLE"));
4752 isqp = c==':' || c==';'; // the property is QP encoded, we need to cancel QP softbreaks while looking for end of property
4753 }
4754 }
4755 p=nextunfolded(p,fMimeDirMode,isqp); // cancel soft breaks if we are in QP encoded property
4756 }
4757 // - skip entire EOLN (=all control chars in sequence %%%)
4758 while (*p && (uInt8)(*p)<'\x20') p=nextunfolded(p,fMimeDirMode);
4759 // set next property start point
4760 aText=p;
4761 } while (*aText); // exit if end of string
4762 // now parse delayed ones (list is in delay order already)
4763 if (aRootLevel) {
4764 // process delayed properties only after entire record is parsed (i.e. when we are at root level here)
4765 TDelayedParsingPropsList::iterator pos;
4766 for (pos=fDelayedProps.begin(); pos!=fDelayedProps.end(); pos++) {
4767 p = (*pos).start; // where to start parsing
4768 PDEBUGPRINTFX(DBG_PARSE+DBG_EXOTIC,({ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ( "parseMimeDir: now parsing delayed property rank=%hd: %"
".30" "s", (*pos).delaylevel, (*pos).start ); }
4769 "parseMimeDir: now parsing delayed property rank=%hd: %" FMT_LENGTH(".30") "s",{ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ( "parseMimeDir: now parsing delayed property rank=%hd: %"
".30" "s", (*pos).delaylevel, (*pos).start ); }
4770 (*pos).delaylevel,{ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ( "parseMimeDir: now parsing delayed property rank=%hd: %"
".30" "s", (*pos).delaylevel, (*pos).start ); }
4771 FMT_LENGTH_LIMITED(30,(*pos).start){ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ( "parseMimeDir: now parsing delayed property rank=%hd: %"
".30" "s", (*pos).delaylevel, (*pos).start ); }
4772 )){ if (((0x00000200 +0x80000000) & getDbgMask()) == (0x00000200
+0x80000000)) getDbgLogger()->setNextMask(0x00000200 +0x80000000
).DebugPrintfLastMask ( "parseMimeDir: now parsing delayed property rank=%hd: %"
".30" "s", (*pos).delaylevel, (*pos).start ); }
;
4773 if (parseProperty(
4774 p, // where to start interpreting property, will be updated past end of property
4775 aItem, // item to store data into
4776 (*pos).propDefP, // the (matching) property definition
4777 repArray,
4778 maxreps,
4779 fMimeDirMode, // MIME-DIR mode
4780 (*pos).groupname,
4781 (*pos).groupnameLen,
4782 "X-delayed", 9 // dummy, unprocessed properties must not be parsed in delayed mode!
4783 )) {
4784 // count mandarory properties found
4785 //%%% moved this to when we queue the delayed props, as mandatory count is per-profile
4786 //if ((*pos).propDefP->mandatory) foundmandatory++;
4787 }
4788 else {
4789 // delayed parsing failed
4790 PDEBUGPRINTFX(DBG_PARSE,("parseMimeDir: failed delayed parsing of property %" FMT_LENGTH(".30") "s",FMT_LENGTH_LIMITED(30,(*pos).start))){ if (((0x00000200) & getDbgMask()) == (0x00000200)) getDbgLogger
()->setNextMask(0x00000200).DebugPrintfLastMask ("parseMimeDir: failed delayed parsing of property %"
".30" "s",(*pos).start); }
;
4791 }
4792 }
4793 // we don't need them any more - clear delayed props
4794 fDelayedProps.clear();
4795 }
4796 // verify integrity
4797 if (foundmandatory<aProfileP->numMandatoryProperties) {
4798 // not all mandatory properties found
4799 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,({ if ((getSession()) && (((0x00000002) & (getSession
())->getDbgMask()) == (0x00000002))) (getSession())->getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "parseMimeDir: missing %d of %hd mandatory properies"
, aProfileP->numMandatoryProperties-foundmandatory, aProfileP
->numMandatoryProperties ); }
4800 "parseMimeDir: missing %d of %hd mandatory properies",{ if ((getSession()) && (((0x00000002) & (getSession
())->getDbgMask()) == (0x00000002))) (getSession())->getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "parseMimeDir: missing %d of %hd mandatory properies"
, aProfileP->numMandatoryProperties-foundmandatory, aProfileP
->numMandatoryProperties ); }
4801 aProfileP->numMandatoryProperties-foundmandatory,{ if ((getSession()) && (((0x00000002) & (getSession
())->getDbgMask()) == (0x00000002))) (getSession())->getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "parseMimeDir: missing %d of %hd mandatory properies"
, aProfileP->numMandatoryProperties-foundmandatory, aProfileP
->numMandatoryProperties ); }
4802 aProfileP->numMandatoryProperties{ if ((getSession()) && (((0x00000002) & (getSession
())->getDbgMask()) == (0x00000002))) (getSession())->getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "parseMimeDir: missing %d of %hd mandatory properies"
, aProfileP->numMandatoryProperties-foundmandatory, aProfileP
->numMandatoryProperties ); }
4803 )){ if ((getSession()) && (((0x00000002) & (getSession
())->getDbgMask()) == (0x00000002))) (getSession())->getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "parseMimeDir: missing %d of %hd mandatory properies"
, aProfileP->numMandatoryProperties-foundmandatory, aProfileP
->numMandatoryProperties ); }
;
4804 // unsuccessful parsing
4805 return false;
4806 }
4807 // successful parsing done
4808 return true;
4809 // %%%%% NOTE: exactly those fields in aItem should be assigned
4810 // which are available in source and target.
4811 // possibly this should be done in prepareForSendTo (o.‰) of
4812 // MultiFieldItem...
4813} // TMimeDirProfileHandler::parseLevels
4814
4815
4816void TMimeDirProfileHandler::getOptionsFromDatastore(void)
4817{
4818 // get options datastore if one is related;
4819 // ignore the session from getSession() here because we need
4820 // to distinguish between script context and normal sync context
4821 // (the former has no datastore, the latter has)
4822 TSyncSession *sessionP = fRelatedDatastoreP ? fRelatedDatastoreP->getSession() : NULL__null;
4823 if (sessionP) {
4824 fReceiverCanHandleUTC = sessionP->fRemoteCanHandleUTC;
4825 fVCal10EnddatesSameDay = sessionP->fVCal10EnddatesSameDay;
4826 fReceiverTimeContext = sessionP->fUserTimeContext; // default to user context
4827 fDontSendEmptyProperties = sessionP->fDontSendEmptyProperties;
4828 fDefaultOutCharset = sessionP->fDefaultOutCharset;
4829 fDefaultInCharset = sessionP->fDefaultInCharset;
4830 fDoQuote8BitContent = sessionP->fDoQuote8BitContent;
4831 fDoNotFoldContent = sessionP->fDoNotFoldContent;
4832 fTreatRemoteTimeAsLocal = sessionP->fTreatRemoteTimeAsLocal;
4833 fTreatRemoteTimeAsUTC = sessionP->fTreatRemoteTimeAsUTC;
4834 #ifndef NO_REMOTE_RULES
4835 fActiveRemoteRules = sessionP->fActiveRemoteRules; // copy the list
4836 #endif
4837 }
4838}
4839
4840
4841// generate Data item (includes header and footer)
4842void TMimeDirProfileHandler::generateText(TMultiFieldItem &aItem, string &aString)
4843{
4844 // get options datastore if one is related
4845 getOptionsFromDatastore();
4846 #ifdef SYDEBUG2
4847 PDEBUGPRINTFX(DBG_GEN+DBG_HOT,("Generating....")){ if (((0x00000400 +0x00000001) & getDbgMask()) == (0x00000400
+0x00000001)) getDbgLogger()->setNextMask(0x00000400 +0x00000001
).DebugPrintfLastMask ("Generating...."); }
;
4848 aItem.debugShowItem(DBG_DATA0x00000080+DBG_GEN0x00000400);
4849 #endif
4850 // baseclass just generates MIME-DIR
4851 fBeginEndNesting=0; // no BEGIN out yet
4852 generateMimeDir(aItem,aString);
4853 #ifdef SYDEBUG2
4854 if (PDEBUGTEST(DBG_GEN+DBG_USERDATA)(((0x00000400 +0x01000000) & getDbgMask()) == (0x00000400
+0x01000000))
) {
4855 // note, do not use debugprintf because string is too long
4856 PDEBUGPRINTFX(DBG_GEN,("Generated: ")){ if (((0x00000400) & getDbgMask()) == (0x00000400)) getDbgLogger
()->setNextMask(0x00000400).DebugPrintfLastMask ("Generated: "
); }
;
4857 PDEBUGPUTSXX(DBG_GEN+DBG_USERDATA,aString.c_str(),0,true){ if (((0x00000400 +0x01000000) & getDbgMask()) == (0x00000400
+0x01000000)) getDbgLogger()->DebugPuts( 0x00000400 +0x01000000
,aString.c_str(),0,true); }
;
4858 }
4859 #endif
4860} // TMimeDirProfileHandler::generateText
4861
4862
4863// parse Data item (includes header and footer)
4864bool TMimeDirProfileHandler::parseText(const char *aText, stringSize aTextSize, TMultiFieldItem &aItem)
4865{
4866 //#warning "aTextSize must be checked!"
4867 // get options datastore if one is related
4868 getOptionsFromDatastore();
4869 // baseclass just parses MIME-DIR
4870 fBeginEndNesting = 0; // no BEGIN found yet
4871 #ifdef SYDEBUG2
4872 if (PDEBUGTEST(DBG_PARSE)(((0x00000200) & getDbgMask()) == (0x00000200))) {
4873 // very detailed, show item being parsed
4874 PDEBUGPRINTFX(DBG_PARSE+DBG_HOT,("Parsing: ")){ if (((0x00000200 +0x00000001) & getDbgMask()) == (0x00000200
+0x00000001)) getDbgLogger()->setNextMask(0x00000200 +0x00000001
).DebugPrintfLastMask ("Parsing: "); }
;
4875 PDEBUGPUTSXX(DBG_PARSE+DBG_USERDATA,aText,0,true){ if (((0x00000200 +0x01000000) & getDbgMask()) == (0x00000200
+0x01000000)) getDbgLogger()->DebugPuts( 0x00000200 +0x01000000
,aText,0,true); }
;
4876 }
4877 #endif
4878 if (parseMimeDir(aText,aItem)) {
4879 if (fBeginEndNesting) {
4880 PDEBUGPRINTFX(DBG_ERROR,("TMimeDirProfileHandler parsing ended with NestCount<>0: %hd",fBeginEndNesting)){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("TMimeDirProfileHandler parsing ended with NestCount<>0: %hd"
,fBeginEndNesting); }
;
4881 return false; // unmatched BEGIN/END
4882 }
4883 #ifdef SYDEBUG2
4884 PDEBUGPRINTFX(DBG_PARSE,("Successfully parsed: ")){ if (((0x00000200) & getDbgMask()) == (0x00000200)) getDbgLogger
()->setNextMask(0x00000200).DebugPrintfLastMask ("Successfully parsed: "
); }
;
4885 aItem.debugShowItem(DBG_DATA0x00000080+DBG_PARSE0x00000200);
4886 #endif
4887 return true;
4888 }
4889 else {
4890 PDEBUGPRINTFX(DBG_ERROR,("Failed parsing item")){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("Failed parsing item"
); }
;
4891 return false;
4892 }
4893} // TMimeDirProfileHandler::parseText
4894
4895
4896bool TMimeDirProfileHandler::parseForProperty(SmlItemPtr_t aItemP, const char *aPropName, string &aString)
4897{
4898 if (aItemP && aItemP->data)
4899 return parseForProperty(smlPCDataToCharP(aItemP->data),aPropName,aString);
4900 else
4901 return false;
4902} // TMimeDirProfileHandler::parseForProperty
4903
4904
4905// scan Data item for specific property (used for quick type tests)
4906bool TMimeDirProfileHandler::parseForProperty(const char *aText, const char *aPropName, string &aString)
4907{
4908 uInt16 n=strlen(aPropName);
4909 while (*aText) {
4910 const char *p=aText;
4911 // find property end
4912 do {
4913 p=nextunfolded(p,fMimeDirMode,true);
4914 } while ((*p)>=0x20);
4915 // p now points to property end
4916 if (strucmp(aText,aPropName,n)==0 && aText[n]==':') {
4917 aText+=n+1; // start of value
4918 aString.assign(aText,p-aText); // save value
4919 return true;
4920 }
4921 // find next property beginning
4922 do {
4923 p=nextunfolded(p,fMimeDirMode,true);
4924 } while (*p && ((*p)<0x20));
4925 // set to beginning of next
4926 aText=p;
4927 }
4928 // not found
4929 return false;
4930} // TMimeDirProfileHandler::parseForProperty
4931
4932
4933
4934// helper for newCTDataPropList
4935void TMimeDirProfileHandler::enumerateLevels(const TProfileDefinition *aProfileP, SmlPcdataListPtr_t *&aPcdataListPP, const TProfileDefinition *aSelectedProfileP, TMimeDirItemType *aItemTypeP)
4936{
4937 // only if mode matches
4938 if (!mimeModeMatch(aProfileP->modeDependency)) return;
4939 // add name of this profile if...
4940 // ...generally enabled for CTCap (shownIfSelectedOnly=false), independent of what other profiles might be selected (e.g. VALARM)
4941 // ...this is the explicitly selected profile (like VTODO while creating DS 1.2 devinf for the tasks datastor)
4942 // ...no profile is specifically selected, which means we want to see ALL profiles (like a DS 1.1 vCalendar type outside <datastore>)
4943 // This means, the only case a name is NOT added are those with those having showlevel="no" when ANOTHER profile is explicitly selected.
4944 if (!aProfileP->shownIfSelectedOnly || aProfileP==aSelectedProfileP || aSelectedProfileP==NULL__null) {
4945 aPcdataListPP = addPCDataStringToList(TCFG_CSTR(aProfileP->levelName)aProfileP->levelName.c_str(),aPcdataListPP);
4946 // check for special subprofiles
4947 if (aProfileP->profileMode==profm_vtimezones) {
4948 // has STANDARD and DAYLIGHT subprofiles
4949 aPcdataListPP = addPCDataStringToList("STANDARD",aPcdataListPP);
4950 aPcdataListPP = addPCDataStringToList("DAYLIGHT",aPcdataListPP);
4951 }
4952 // add names of subprofiles, if any
4953 const TProfileDefinition *subprofileP = aProfileP->subLevels;
4954 while (subprofileP) {
4955 // If this profile is the selected profile, ALL subprofiles must be shown in all cases (so we pass NULL)
4956 enumerateLevels(subprofileP,aPcdataListPP,aProfileP==aSelectedProfileP ? NULL__null : aSelectedProfileP, aItemTypeP);
4957 // next
4958 subprofileP=subprofileP->next;
4959 }
4960 }
4961} // TMimeDirProfileHandler::enumerateLevels
4962
4963
4964
4965// add a CTDataProp item to a CTDataPropList
4966static void addCTDataPropToListIfNotExists(
4967 SmlDevInfCTDataPropPtr_t aCTDataPropP, // existing CTDataProp item data structure, ownership is passed to list
4968 SmlDevInfCTDataPropListPtr_t *aCTDataPropListPP // adress of list root pointer (which points to existing item list or NULL)
4969)
4970{
4971 // add it to the list (but only if we don't already have it)
4972 while (*aCTDataPropListPP) {
4973 // check name
4974 if (strcmp(smlPCDataToCharP(aCTDataPropP->prop->name),smlPCDataToCharP((*aCTDataPropListPP)->data->prop->name))==0) {
4975 //%%% we can add merging parameters here as well
4976 // same property already exists, forget this one
4977 smlFreeDevInfCTDataProp(aCTDataPropP);
4978 aCTDataPropP = NULL__null;
4979 break;
4980 }
4981 aCTDataPropListPP = &((*aCTDataPropListPP)->next);
4982 }
4983 // if not detected duplicate, add it now
4984 if (aCTDataPropP) {
4985 addCTDataPropToList(aCTDataPropP,aCTDataPropListPP);
4986 }
4987} // addCTDataPropToListIfNotExists
4988
4989
4990// add a CTData describing a property (as returned by newDevInfCTData())
4991// as a new property without parameters to a CTDataPropList
4992static void addNewPropToListIfNotExists(
4993 SmlDevInfCTDataPtr_t aPropCTData, // CTData describing property
4994 SmlDevInfCTDataPropListPtr_t *aCTDataPropListPP // adress of list root pointer (which points to existing item list or NULL)
4995)
4996{
4997 SmlDevInfCTDataPropPtr_t propdataP = SML_NEW(SmlDevInfCTDataProp_t)((SmlDevInfCTDataProp_t*) _smlMalloc(sizeof(SmlDevInfCTDataProp_t
)))
;
4998 propdataP->param = NULL__null; // no params
4999 propdataP->prop = aPropCTData;
5000 addCTDataPropToListIfNotExists(propdataP, aCTDataPropListPP);
5001} // addNewPropToListIfNotExists
5002
5003
5004
5005// helper for newCTDataPropList
5006void TMimeDirProfileHandler::enumerateProperties(const TProfileDefinition *aProfileP, SmlDevInfCTDataPropListPtr_t *&aPropListPP, const TProfileDefinition *aSelectedProfileP, TMimeDirItemType *aItemTypeP)
5007{
5008 // remember start of properties
5009 // add all properties of this level (if enabled)
5010 // Note: if this is the explicitly selected (sub)profile, it will be shown under any circumstances
5011 if ((!aProfileP->shownIfSelectedOnly || aProfileP==aSelectedProfileP || aSelectedProfileP==NULL__null) && mimeModeMatch(aProfileP->modeDependency)) {
5012 if (aProfileP->profileMode==profm_vtimezones) {
5013 // Add properties of VTIMEZONE here
5014 addNewPropToListIfNotExists(newDevInfCTData("TZID"),aPropListPP);
5015 addNewPropToListIfNotExists(newDevInfCTData("DTSTART"),aPropListPP);
5016 addNewPropToListIfNotExists(newDevInfCTData("RRULE"),aPropListPP);
5017 addNewPropToListIfNotExists(newDevInfCTData("TZOFFSETFROM"),aPropListPP);
5018 addNewPropToListIfNotExists(newDevInfCTData("TZOFFSETTO"),aPropListPP);
5019 addNewPropToListIfNotExists(newDevInfCTData("TZNAME"),aPropListPP);
5020 }
5021 else {
5022 // normal profile defined in config, add properties as defined in profile, avoid duplicates
5023 const TPropertyDefinition *propP = aProfileP->propertyDefs;
5024 while (propP) {
5025 if (propP->showInCTCap && mimeModeMatch(propP->modeDependency)) {
5026 // - new list entry in CTCap (if property to be shown)
5027 SmlDevInfCTDataPropPtr_t propdataP = SML_NEW(SmlDevInfCTDataProp_t)((SmlDevInfCTDataProp_t*) _smlMalloc(sizeof(SmlDevInfCTDataProp_t
)))
;
5028 propdataP->param = NULL__null; // default to no params
5029 // - add params, if needed
5030 SmlDevInfCTDataListPtr_t *nextParamPP = &(propdataP->param);
5031 const TParameterDefinition *paramP = propP->parameterDefs;
5032 while(paramP) {
5033 // check if parameter is enabled for being shown in CTCap
5034 if (paramP->showInCTCap && mimeModeMatch(paramP->modeDependency)) {
5035 // For some older 1.1 devices (in particular Nokia 7610), enum values of default params
5036 // in pre-MIME-DIR must be shown as param NAMES (not enums).
5037 // But newer 1.2 Nokias like E90 need proper TYPE param with valEnums (when run in 1.2 mode. E90 is fine with 7610 style for 1.1)
5038 // So: normally (fEnumDefaultPropParams==undefined==-1), we show 7610 style for 1.1 and E90 style for 1.2.
5039 // <enumdefaultpropparams> and ENUMDEFAULTPROPPARAMS() can be used to control this behaviour when needed
5040 if (
5041 paramP->defaultparam &&
5042 fMimeDirMode==mimo_old &&
5043 (
5044 (getSession()->fEnumDefaultPropParams==-1 && getSession()->getSyncMLVersion()<syncml_vers_1_2) || // auto mode and SyncML 1.1 or older
5045 (getSession()->fEnumDefaultPropParams==1) // ..or explicitly enabled
5046 )
5047 ) {
5048 // add the name extending enum values as param names
5049 TEnumerationDef *enumP = paramP->convdef.enumdefs;
5050 while(enumP) {
5051 if (!TCFG_ISEMPTY(enumP->enumtext)enumP->enumtext.empty() && enumP->enummode==enm_translate) {
5052 // create new param list entry
5053 nextParamPP = addCTDataToList(newDevInfCTData(TCFG_CSTR(enumP->enumtext)enumP->enumtext.c_str()),nextParamPP);
5054 }
5055 enumP=enumP->next;
5056 }
5057 }
5058 else {
5059 // - proper parameter with valEnum list
5060 SmlDevInfCTDataPtr_t paramdataP = newDevInfCTData(TCFG_CSTR(paramP->paramname)paramP->paramname.c_str());
5061 // - add valenums if any
5062 SmlPcdataListPtr_t *nextValenumPP = &(paramdataP->valenum);
5063 TEnumerationDef *enumP = paramP->convdef.enumdefs;
5064 while(enumP) {
5065 if (!TCFG_ISEMPTY(enumP->enumtext)enumP->enumtext.empty() && enumP->enummode==enm_translate) {
5066 // create new valenum list entry
5067 nextValenumPP = addPCDataStringToList(TCFG_CSTR(enumP->enumtext)enumP->enumtext.c_str(),nextValenumPP);
5068 }
5069 enumP=enumP->next;
5070 }
5071 // - add it to the params list
5072 nextParamPP = addCTDataToList(paramdataP,nextParamPP);
5073 }
5074 } // if param to be shown
5075 paramP=paramP->next;
5076 }
5077 // - get possible size limit and notruncate flag
5078 uInt32 sz=0; // no size limit by default
5079 bool noTruncate=false; // by default, truncation is ok
5080 TFieldDefinition *fieldDefP = NULL__null;
5081 for (sInt16 i=0; i<propP->numValues; i++) {
5082 sInt16 fid=propP->convdefs[0].fieldid;
5083 if (fid>=0) {
5084 // Field type (we need it later when we have a maxsize, which is only allowed together with a datatype in 1.1 DTD)
5085 if (!fieldDefP)
5086 fieldDefP = fItemTypeP->getFieldDefinition(fid);
5087 // Size
5088 uInt32 fsz = fItemTypeP->getFieldOptions(fid)->maxsize; // only if related datastore (i.e. SyncML context)
5089 // - smallest non-fieldblock (excludes RRULE-type special conversions), not-unknown and not-unlimited maxsize is used
5090 if (fieldBlockSize(propP->convdefs[0])==1 && (sz==0 || sz>fsz) && fsz!=FIELD_OPT_MAXSIZE_NONE0 && sInt32(fsz)!=FIELD_OPT_MAXSIZE_UNKNOWN-1)
5091 sz=fsz;
5092 // If any field requests no truncation, report noTruncate
5093 if (getSession()->getSyncMLVersion()>=syncml_vers_1_2 && fItemTypeP->getFieldOptions(fid)->notruncate)
5094 noTruncate=true;
5095 }
5096 }
5097 // - calculate our own maxoccur (value in our field options is not used for now %%%)
5098 uInt32 maxOccur=0;
5099 if (getSession()->getSyncMLVersion()>=syncml_vers_1_2) {
5100 if (propP->nameExts) {
5101 // name extensions determine repeat count
5102 TPropNameExtension *extP = propP->nameExts;
5103 while (extP) {
5104 if (!extP->readOnly) {
5105 if (extP->maxRepeat==REP_ARRAY32767) {
5106 // no limit
5107 maxOccur=0; // unlimited
5108 break; // prevent other name extensions to intervene
5109 }
5110 else {
5111 // limited number of occurrences, add to count
5112 maxOccur+=extP->maxRepeat;
5113 }
5114 }
5115 // next
5116 extP=extP->next;
5117 }
5118 }
5119 else {
5120 // not repeating: property may not occur more than once
5121 maxOccur=1;
5122 }
5123 }
5124 // - some SyncML 1.0 clients crash when they see type/size
5125 if (!(getSession()->fShowTypeSzInCTCap10) && getSession()->getSyncMLVersion()<=syncml_vers_1_0) {
5126 sz = 0; // prevent size/type in SyncML 1.0 (as old clients like S55 crash if it is included)
5127 }
5128 // - find out if we need to show the type (before SyncML 1.2, Size MUST be preceeded by DataType)
5129 // On the other hand, DataType MUST NOT be used in 1.2 for VersIt types!!!
5130 cAppCharP dataType=NULL__null;
5131 if (sz!=0 && fieldDefP && getSession()->getSyncMLVersion()<syncml_vers_1_2) {
5132 // we have a size, so we NEED a datatype
5133 TPropDataTypes dt = devInfPropTypes[fieldDefP->type];
5134 if (dt==proptype_text) dt=proptype_chr; // SyncML 1.1 does not have "text" type
5135 if (dt!=proptype_unknown)
5136 dataType = propDataTypeNames[dt];
5137 }
5138 // - add property data descriptor
5139 propdataP->prop = newDevInfCTData(TCFG_CSTR(propP->propname)propP->propname.c_str(),sz,noTruncate,maxOccur,dataType);
5140 if (propP->convdefs && (propP->convdefs->convmode & CONVMODE_MASK0xFF)==CONVMODE_VERSION1) {
5141 // special case: add version valenum
5142 addPCDataStringToList(aItemTypeP->getTypeVers(),&(propdataP->prop->valenum));
5143 }
5144 // add it if not already same-named property in the list, otherwise discard it
5145 addCTDataPropToListIfNotExists(propdataP,aPropListPP);
5146 } // if to be shown in CTCap
5147 propP=propP->next;
5148 } // while properties
5149 } // normal profile defined in config
5150 // add properties of other levels
5151 const TProfileDefinition *subprofileP = aProfileP->subLevels;
5152 while (subprofileP) {
5153 // only if the current profile is the selected profile, properties of ALL contained subprofiles will be shown
5154 // (otherwise, selection might be within the current profile, so we need to pass on the selection)
5155 enumerateProperties(subprofileP,aPropListPP,aProfileP==aSelectedProfileP ? NULL__null : aSelectedProfileP, aItemTypeP);
5156 // next
5157 subprofileP=subprofileP->next;
5158 }
5159 }
5160} // TMimeDirProfileHandler::enumerateProperties
5161
5162
5163// helper: enumerate filter properties
5164void TMimeDirProfileHandler::enumeratePropFilters(const TProfileDefinition *aProfileP, SmlPcdataListPtr_t &aFilterProps, const TProfileDefinition *aSelectedProfileP, TMimeDirItemType *aItemTypeP)
5165{
5166 // add all properties of this level (if enabled)
5167 // Note: if this is the explicitly selected (sub)profile, it will be shown under any circumstances
5168 if (!aProfileP->shownIfSelectedOnly || aProfileP==aSelectedProfileP) {
5169 const TPropertyDefinition *propP = aProfileP->propertyDefs;
5170 while (propP) {
5171 if (
5172 propP->canFilter &&
5173 (propP->showInCTCap || aProfileP==aSelectedProfileP) &&
5174 propP->convdefs && (propP->convdefs[0].convmode & CONVMODE_MASK0xFF)!=CONVMODE_VERSION1 && (propP->convdefs[0].convmode & CONVMODE_MASK0xFF)!=CONVMODE_PRODID2
5175 ) {
5176 // Note: properties of explicitly selected (sub)profiles will be shown anyway,
5177 // as only purpose of suppressing properties in devInf is to avoid
5178 // duplicate listing in case of multiple subprofiles in ONE CTCap.
5179 // - add property name to filter property list
5180 addPCDataStringToList(TCFG_CSTR(propP->propname)propP->propname.c_str(), &aFilterProps);
5181 } // if to be shown in filterCap
5182 propP=propP->next;
5183 }
5184 }
5185 // add properties of other levels
5186 const TProfileDefinition *subprofileP = aProfileP->subLevels;
5187 while (subprofileP) {
5188 if (aSelectedProfileP==NULL__null || subprofileP==aSelectedProfileP) {
5189 // only if the current profile is the selected profile, filter properties of ALL contained subprofiles will be shown
5190 enumeratePropFilters(subprofileP,aFilterProps,aProfileP==aSelectedProfileP ? NULL__null : aSelectedProfileP, aItemTypeP);
5191 }
5192 // next
5193 subprofileP=subprofileP->next;
5194 }
5195} // TMimeDirProfileHandler::enumeratePropFilters
5196
5197
5198#ifdef OBJECT_FILTERING1
5199
5200// Filtering: add keywords and property names to filterCap
5201void TMimeDirProfileHandler::addFilterCapPropsAndKeywords(SmlPcdataListPtr_t &aFilterKeywords, SmlPcdataListPtr_t &aFilterProps, TTypeVariantDescriptor aVariantDescriptor, TSyncItemType *aItemTypeP)
5202{
5203 // get pointer to selected variant (if none, all variants will be shown)
5204 const TProfileDefinition *selectedSubprofileP = (const TProfileDefinition *)aVariantDescriptor;
5205 // get pointer to mimedir item type
5206 TMimeDirItemType *mimeDirItemTypeP;
5207 GET_CASTED_PTR(mimeDirItemTypeP,TMimeDirItemType,aItemTypeP,"MIME-DIR profile used with non-MIME-DIR type"){ mimeDirItemTypeP = dynamic_cast<TMimeDirItemType *>(aItemTypeP
); if (!mimeDirItemTypeP) { throw TSyncException("MIME-DIR profile used with non-MIME-DIR type"
); } }
;
5208 // add name of all properties that have canFilter attribute set
5209 enumeratePropFilters(fProfileDefinitionP,aFilterProps,selectedSubprofileP, mimeDirItemTypeP);
5210} // TMimeDirProfileHandler::addFilterCapPropsAndKeywords
5211
5212#endif // OBJECT_FILTERING
5213
5214
5215
5216// generates SyncML-Devinf property list for type
5217SmlDevInfCTDataPropListPtr_t TMimeDirProfileHandler::newCTDataPropList(TTypeVariantDescriptor aVariantDescriptor, TSyncItemType *aItemTypeP)
5218{
5219 TMimeDirItemType *itemTypeP = static_cast<TMimeDirItemType *>(aItemTypeP);
5220 // get pointer to selected variant (if none, all variants will be shown)
5221 const TProfileDefinition *selectedSubprofileP = (const TProfileDefinition *)aVariantDescriptor;
5222 // generate new list
5223 SmlDevInfCTDataPropListPtr_t proplistP = SML_NEW(SmlDevInfCTDataPropList_t)((SmlDevInfCTDataPropList_t*) _smlMalloc(sizeof(SmlDevInfCTDataPropList_t
)))
;
5224 SmlDevInfCTDataPropListPtr_t nextpropP = proplistP;
5225 // generate BEGIN property
5226 // - add property contents
5227 nextpropP->data = SML_NEW(SmlDevInfCTDataProp_t)((SmlDevInfCTDataProp_t*) _smlMalloc(sizeof(SmlDevInfCTDataProp_t
)))
;
5228 nextpropP->data->param=NULL__null; // no params
5229 // - property data descriptor
5230 SmlDevInfCTDataPtr_t pdataP = newDevInfCTData("BEGIN");
5231 nextpropP->data->prop = pdataP;
5232 // - add valenums for all profiles and subprofiles
5233 SmlPcdataListPtr_t *liststartPP = &pdataP->valenum;
5234 enumerateLevels(fProfileDefinitionP,liststartPP,selectedSubprofileP,itemTypeP);
5235 // generate END property
5236 nextpropP->next = SML_NEW(SmlDevInfCTDataPropList_t)((SmlDevInfCTDataPropList_t*) _smlMalloc(sizeof(SmlDevInfCTDataPropList_t
)))
;
5237 nextpropP=nextpropP->next;
5238 // - add property contents
5239 nextpropP->data = SML_NEW(SmlDevInfCTDataProp_t)((SmlDevInfCTDataProp_t*) _smlMalloc(sizeof(SmlDevInfCTDataProp_t
)))
;
5240 nextpropP->data->param=NULL__null; // no params
5241 // - property data descriptor
5242 pdataP = newDevInfCTData("END");
5243 nextpropP->data->prop = pdataP;
5244 // - add valenums for all profiles and subprofiles
5245 liststartPP = &pdataP->valenum;
5246 enumerateLevels(fProfileDefinitionP,liststartPP,selectedSubprofileP,itemTypeP);
5247 // generate all other properties of all levels
5248 nextpropP->next=NULL__null; // in case no properties are found
5249 SmlDevInfCTDataPropListPtr_t *propstartPP = &nextpropP->next;
5250 enumerateProperties(fProfileDefinitionP,propstartPP,selectedSubprofileP,itemTypeP);
5251 // done
5252 return proplistP;
5253} // TMimeDirProfileHandler::newCTDataPropList
5254
5255
5256// Analyze CTCap part of devInf
5257bool TMimeDirProfileHandler::analyzeCTCap(SmlDevInfCTCapPtr_t aCTCapP, TSyncItemType *aItemTypeP)
5258{
5259 TMimeDirItemType *itemTypeP = static_cast<TMimeDirItemType *>(aItemTypeP);
5260 // assume all sublevels enabled (as long as we don't get a
5261 // BEGIN CTCap listing all the available levels.
5262 //aItemTypeP->setLevelOptions(NULL,true);
5263 // check details
5264 SmlDevInfCTDataPropListPtr_t proplistP = aCTCapP->prop;
5265 if (proplistP) {
5266 if (!itemTypeP->fReceivedFieldDefs) {
5267 // there is a propList, and we haven't scanned one already for this type
5268 // (could be the case for DS 1.2 vCalendar where we get events & tasks separately)
5269 // so disable all non-mandatory fields first (available ones will be re-enabled)
5270 for (sInt16 i=0; i<itemTypeP->fFieldDefinitionsP->numFields(); i++) {
5271 itemTypeP->getFieldOptions(i)->available=false;
5272 }
5273 // force mandatory properties to be always "available"
5274 setfieldoptions(NULL__null,fProfileDefinitionP,itemTypeP);
5275 }
5276 // now we have received fields
5277 itemTypeP->fReceivedFieldDefs=true;
5278 }
5279 while (proplistP) {
5280 // get property descriptor
5281 SmlDevInfCTDataPtr_t propP = proplistP->data->prop;
5282 // see if we have this property in any of the levels
5283 setfieldoptions(propP,fProfileDefinitionP,itemTypeP);
5284 // next property in CTCap
5285 proplistP=proplistP->next;
5286 } // properties in CTCap
5287 return true;
5288} // TMimeDirProfileHandler::analyzeCTCap
5289
5290
5291
5292// %%%%% dummy for now
5293bool TMimeDirProfileHandler::setLevelOptions(const char *aLevelName, bool aEnable, TMimeDirItemType *aItemTypeP)
5294{
5295 // do it recursively.
5296 // %%% we need to have a flag somewhere for these
5297 // don't we have one already???
5298 // YES, its fSubLevelRestrictions (supposedly a bitmask for max 32 levels)
5299 // %%% checking the levels and ignoring incoming/outgoing items with
5300 // wrong levels must be added later
5301 return true;
5302} // TMimeDirProfileHandler::setLevelOptions
5303
5304
5305
5306
5307// enable fields related to aPropP property in profiles recursively
5308// or (if aPropP is NULL), enable fields of all mandatory properties
5309void TMimeDirProfileHandler::setfieldoptions(
5310 const SmlDevInfCTDataPtr_t aPropP, // property to enable fields for, NULL if all mandatory properties should be enabled
5311 const TProfileDefinition *aProfileP,
5312 TMimeDirItemType *aItemTypeP
5313)
5314{
5315 // set defaults
5316 sInt32 propsize = FIELD_OPT_MAXSIZE_NONE0;
5317 sInt32 maxOccur = 0; // none by default
5318 bool noTruncate=false;
5319 const char* propname = NULL__null;
5320 TFieldOptions *fo;
5321 // get params from CTCap property definition (if any)
5322 if (aPropP) {
5323 // get name of CTCap property
5324 propname = smlPCDataToCharP(aPropP->name);
5325 // get possible maxSize
5326 if (aPropP->maxsize) {
5327 if (getSession()->fIgnoreDevInfMaxSize) {
5328 // remote rule flags maxsize as invalid (like in E90), flag it as unknown (but possibly limited)
5329 propsize = FIELD_OPT_MAXSIZE_UNKNOWN-1;
5330 }
5331 else {
5332 // treat as valid
5333 StrToLong(smlPCDataToCharP(aPropP->maxsize),propsize);
5334 }
5335 }
5336 // get possible maxOccur
5337 if (aPropP->maxoccur) {
5338 StrToLong(smlPCDataToCharP(aPropP->maxoccur),maxOccur);
5339 }
5340 // get possible noTruncate
5341 if (aPropP->flags & SmlDevInfNoTruncate_f0x0020)
5342 noTruncate=true;
5343 // check for BEGIN to check for enabled sublevels
5344 if (strucmp(propname,"BEGIN")==0) {
5345 // check ValEnums that denote supported levels
5346 SmlPcdataListPtr_t valenumP = aPropP->valenum;
5347 if (valenumP) {
5348 // we HAVE supported BEGINs listed, so disable all levels first
5349 // and have them individually enabled below according to ValEnums
5350 setLevelOptions(NULL__null,false,aItemTypeP);
5351 }
5352 while (valenumP) {
5353 // get sublevel name
5354 const char *slname = smlPCDataToCharP(valenumP->data);
5355 setLevelOptions(slname,true,aItemTypeP); // enable this one
5356 // check next
5357 valenumP=valenumP->next;
5358 }
5359 }
5360 } // if enabling for specified property
5361 // enable all fields related to this property and set options
5362 const TPropertyDefinition *propdefP = aProfileP->propertyDefs;
5363 sInt16 j,q,i,o,r,bs;
5364 while (propdefP) {
5365 // compare
5366 if (
5367 (propname==NULL__null && propdefP->mandatory) ||
5368 (propname && (strucmp(propname,TCFG_CSTR(propdefP->propname)propdefP->propname.c_str())==0))
5369 ) {
5370 // match (or enabling mandatory) -> enable all fields that are related to this property
5371 // - values
5372 for (i=0; i<propdefP->numValues; i++) {
5373 // base field ID
5374 j=propdefP->convdefs[i].fieldid;
5375 bs=fieldBlockSize(propdefP->convdefs[i]);
5376 if (j>=0) {
5377 // field supported
5378 TPropNameExtension *pneP = propdefP->nameExts;
5379 if (pneP) {
5380 while(pneP) {
5381 o = pneP->fieldidoffs;
5382 if (o>=0) {
5383 r=0;
5384 // for all repetitions (but only for first if mode is REP_ARRAY
5385 // or field is an array)
5386 do {
5387 // make entire field block addressed by this convdef available
5388 // and set maxoccur/notruncate
5389 for (q=0; q<bs; q++) {
5390 // flag available
5391 fo = aItemTypeP->getFieldOptions(j+o+q);
5392 if (fo) fo->available=true;
5393 }
5394 // set size if specified (only for first field in block)
5395 fo = aItemTypeP->getFieldOptions(j+o);
5396 if (fo) {
5397 if (propsize!=FIELD_OPT_MAXSIZE_NONE0) fo->maxsize=propsize;
5398 // set maxoccur if specified
5399 if (maxOccur!=0) fo->maxoccur=maxOccur;
5400 // set noTruncate
5401 if (noTruncate) fo->notruncate=true;
5402 }
5403 // next
5404 o+=pneP->repeatInc;
5405 } while (
5406 ++r < pneP->maxRepeat &&
5407 pneP->maxRepeat!=REP_ARRAY32767
5408 #ifdef ARRAYFIELD_SUPPORT1
5409 && !aItemTypeP->getFieldDefinition(j)->array
5410 #endif
5411 );
5412 }
5413 pneP=pneP->next;
5414 }
5415 }
5416 else {
5417 // single variant, non-repeating property
5418 // make entire field block addressed by this convdef available
5419 for (q=0; q<bs; q++) { fo = aItemTypeP->getFieldOptions(j+q); if (fo) fo->available=true; }
5420 // set size if specified
5421 fo = aItemTypeP->getFieldOptions(j);
5422 if (fo) {
5423 if (propsize!=FIELD_OPT_MAXSIZE_NONE0) fo->maxsize=propsize;
5424 // set maxoccur if specified
5425 if (maxOccur!=0) fo->maxoccur=maxOccur;
5426 // set noTruncate
5427 if (noTruncate) fo->notruncate=true;
5428 }
5429 }
5430 }
5431 } // enable values
5432 // - parameter values
5433 const TParameterDefinition *paramdefP = propdefP->parameterDefs;
5434 while(paramdefP) {
5435 // base field ID
5436 j=paramdefP->convdef.fieldid;
5437 bs=fieldBlockSize(paramdefP->convdef);
5438 if (j>=0) {
5439 // field supported
5440 TPropNameExtension *pneP = propdefP->nameExts;
5441 if (pneP) {
5442 while(pneP) {
5443 o = pneP->fieldidoffs;
5444 if (o>=0) {
5445 r=0;
5446 // for all repetitions
5447 do {
5448 // make entire field block addressed by this convdef available
5449 for (q=0; q<bs; q++) { fo = aItemTypeP->getFieldOptions(j+o+q); if (fo) fo->available=true; }
5450 // set size if specified
5451 fo = aItemTypeP->getFieldOptions(j+o);
5452 if (propsize!=FIELD_OPT_MAXSIZE_NONE0 && fo) fo->maxsize=propsize;
5453 // Note: MaxOccur and NoTruncate are not relevant for parameter values
5454 // next
5455 o+=pneP->repeatInc;
5456 } while (
5457 ++r < pneP->maxRepeat &&
5458 pneP->maxRepeat!=REP_ARRAY32767
5459 #ifdef ARRAYFIELD_SUPPORT1
5460 && !aItemTypeP->getFieldDefinition(j)->array
5461 #endif
5462 );
5463 }
5464 pneP=pneP->next;
5465 }
5466 }
5467 else {
5468 // single variant, non-repeating property
5469 // make entire field block addressed by this convdef available
5470 for (q=0; q<bs; q++) { fo=aItemTypeP->getFieldOptions(j+q); if (fo) fo->available=true; }
5471 // set size if specified
5472 fo = aItemTypeP->getFieldOptions(j);
5473 if (propsize!=FIELD_OPT_MAXSIZE_NONE0 && fo) fo->maxsize=propsize;
5474 }
5475 }
5476 paramdefP=paramdefP->next;
5477 } // while
5478 } // if known property
5479 propdefP=propdefP->next;
5480 }
5481 // now enable fields in all subprofiles
5482 const TProfileDefinition *subprofileP = aProfileP->subLevels;
5483 while (subprofileP) {
5484 setfieldoptions(aPropP,subprofileP,aItemTypeP);
5485 // next
5486 subprofileP=subprofileP->next;
5487 }
5488} // TMimeDirProfileHandler::setfieldoptions
5489
5490
5491
5492// set mode (for those profiles that have more than one, like MIME-DIR's old/standard)
5493void TMimeDirProfileHandler::setProfileMode(sInt32 aMode)
5494{
5495 fProfileMode = aMode;
5496 // determine derived mime mode
5497 switch (aMode) {
5498 case PROFILEMODE_OLD : fMimeDirMode=mimo_old; break; // 1 = old = vCard 2.1 / vCalendar 1.0
5499 default : fMimeDirMode=mimo_standard; break; // anything else = standard = vCard 3.0 / iCalendar 2.0 style
5500 }
5501} // TMimeDirProfileHandler::setProfileMode
5502
5503
5504#ifndef NO_REMOTE_RULES
5505
5506void TMimeDirProfileHandler::setRemoteRule(const string &aRemoteRuleName)
5507{
5508 TSessionConfig *scP = getSession()->getSessionConfig();
5509 TRemoteRulesList::iterator pos;
5510 for(pos=scP->fRemoteRulesList.begin();pos!=scP->fRemoteRulesList.end();pos++) {
5511 if((*pos)->fElementName == aRemoteRuleName) {
5512 // only this rule and all rules included by it rule must be active
5513 fActiveRemoteRules.clear();
5514 activateRemoteRule(*pos);
5515 break;
5516 }
5517 }
5518} // TMimeDirProfileHandler::setRemoteRule
5519
5520void TMimeDirProfileHandler::activateRemoteRule(TRemoteRuleConfig *aRuleP)
5521{
5522 // activate this rule (similar code as in TSyncSession::checkRemoteSpecifics()
5523 fActiveRemoteRules.push_back(aRuleP);
5524 // - apply options that have a value
5525 //if (aRuleP->fLegacyMode>=0) fLegacyMode = aRuleP->fLegacyMode;
5526 //if (aRuleP->fLenientMode>=0) fLenientMode = aRuleP->fLenientMode;
5527 //if (aRuleP->fLimitedFieldLengths>=0) fLimitedRemoteFieldLengths = aRuleP->fLimitedFieldLengths;
5528 if (aRuleP->fDontSendEmptyProperties>=0) fDontSendEmptyProperties = aRuleP->fDontSendEmptyProperties;
5529 if (aRuleP->fDoQuote8BitContent>=0) fDoQuote8BitContent = aRuleP->fDoQuote8BitContent;
5530 if (aRuleP->fDoNotFoldContent>=0) fDoNotFoldContent = aRuleP->fDoNotFoldContent;
5531 //if (aRuleP->fNoReplaceInSlowsync>=0) fNoReplaceInSlowsync = aRuleP->fNoReplaceInSlowsync;
5532 if (aRuleP->fTreatRemoteTimeAsLocal>=0) fTreatRemoteTimeAsLocal = aRuleP->fTreatRemoteTimeAsLocal;
5533 if (aRuleP->fTreatRemoteTimeAsUTC>=0) fTreatRemoteTimeAsUTC = aRuleP->fTreatRemoteTimeAsUTC;
5534 if (aRuleP->fVCal10EnddatesSameDay>=0) fVCal10EnddatesSameDay = aRuleP->fVCal10EnddatesSameDay;
5535 //if (aRuleP->fIgnoreDevInfMaxSize>=0) fIgnoreDevInfMaxSize = aRuleP->fIgnoreDevInfMaxSize;
5536 //if (aRuleP->fIgnoreCTCap>=0) fIgnoreCTCap = aRuleP->fIgnoreCTCap;
5537 //if (aRuleP->fDSPathInDevInf>=0) fDSPathInDevInf = aRuleP->fDSPathInDevInf;
5538 //if (aRuleP->fDSCgiInDevInf>=0) fDSCgiInDevInf = aRuleP->fDSCgiInDevInf;
5539 //if (aRuleP->fUpdateClientDuringSlowsync>=0) fUpdateClientDuringSlowsync = aRuleP->fUpdateClientDuringSlowsync;
5540 //if (aRuleP->fUpdateServerDuringSlowsync>=0) fUpdateServerDuringSlowsync = aRuleP->fUpdateServerDuringSlowsync;
5541 //if (aRuleP->fAllowMessageRetries>=0) fAllowMessageRetries = aRuleP->fAllowMessageRetries;
5542 //if (aRuleP->fStrictExecOrdering>=0) fStrictExecOrdering = aRuleP->fStrictExecOrdering;
5543 //if (aRuleP->fTreatCopyAsAdd>=0) fTreatCopyAsAdd = aRuleP->fTreatCopyAsAdd;
5544 //if (aRuleP->fCompleteFromClientOnly>=0) fCompleteFromClientOnly = aRuleP->fCompleteFromClientOnly;
5545 //if (aRuleP->fRequestMaxTime>=0) fRequestMaxTime = aRuleP->fRequestMaxTime;
5546 if (aRuleP->fDefaultOutCharset!=chs_unknown) fDefaultOutCharset = aRuleP->fDefaultOutCharset;
5547 if (aRuleP->fDefaultInCharset!=chs_unknown) fDefaultInCharset = aRuleP->fDefaultInCharset;
5548 // - possibly override decisions that are otherwise made by session
5549 // Note: this is not a single option because we had this before rule options were tristates.
5550 //if (aRuleP->fForceUTC>0) fRemoteCanHandleUTC=true;
5551 //if (aRuleP->fForceLocaltime>0) fRemoteCanHandleUTC=false;
5552
5553 // now recursively activate included rules
5554 TRemoteRulesList::iterator pos;
5555 for(pos=aRuleP->fSubRulesList.begin();pos!=aRuleP->fSubRulesList.end();pos++) {
5556 activateRemoteRule(*pos);
5557 }
5558}
5559
5560// check if given rule (by name, or if aRuleName=NULL by rule pointer) is active
5561bool TMimeDirProfileHandler::isActiveRule(TRemoteRuleConfig *aRuleP)
5562{
5563 TRemoteRulesList::iterator pos;
5564 for(pos=fActiveRemoteRules.begin();pos!=fActiveRemoteRules.end();pos++) {
5565 if ((*pos)==aRuleP)
5566 return true;
5567 }
5568 // no match
5569 return false;
5570} // TMimeDirProfileHandler::isActiveRule
5571
5572#endif // NO_REMOTE_RULES
5573
5574
5575// - check mode
5576bool TMimeDirProfileHandler::mimeModeMatch(TMimeDirMode aMimeMode)
5577{
5578 return
5579 aMimeMode==numMimeModes || // not dependent on MIME mode
5580 aMimeMode==fMimeDirMode;
5581} // TMimeDirProfileHandler::mimeModeMatch
5582
5583
5584
5585/* end of TMimeDirProfileHandler implementation */
5586
5587
5588// Utility functions
5589// -----------------
5590
5591
5592/// @brief checks two timestamps if they represent an all-day event
5593/// @param[in] aStart start time
5594/// @param[in] aEnd end time
5595/// @return 0 if not allday, x=1..n if allday (spanning x days) by one of the
5596/// following criteria:
5597/// - both start and end at midnight of the same day (= 1 day)
5598/// - both start and end at midnight of different days (= 1..n days)
5599/// - start at midnight and end between 23:59:00 and 23:59:59 of
5600/// same or different days (= 1..n days)
5601uInt16 AlldayCount(lineartime_t aStart, lineartime_t aEnd)
5602{
5603 lineartime_t startTime = lineartime2timeonly(aStart);
5604 if (startTime!=0) return 0; // start not at midnight -> no allday
5605 lineartime_t endTime = lineartime2timeonly(aEnd);
5606 if (endTime==0) {
5607 if (aStart==aEnd) aEnd += linearDateToTimeFactor; // one day
5608 }
5609 else if (endTime>= (23*MinsPerHour+59)*SecsPerMin*secondToLinearTimeFactor) {
5610 // add one minute to make sure we reach into next day
5611 aEnd += SecsPerMin*secondToLinearTimeFactor;
5612 }
5613 else
5614 return 0; // allday criteria not met
5615 // now calculate number of days
5616 return (aEnd-aStart) / linearDateToTimeFactor;
5617} // AlldayCount
5618
5619
5620/// @brief checks two timestamps if they represent an all-day event
5621/// @param[in] aStartFldP start time field
5622/// @param[in] aEndFldP end time field
5623/// @param[in] aTimecontext context to use to check allday criteria for all non-floating timestamps
5624/// or UTC timestamps only (if aContextForUTC is set).
5625/// @param[in] aContextForUTC if set, context is only applied for UTC timestamps, other non-floatings are checked as-is
5626/// @return 0 if not allday, x=1..n if allday (spanning x days)
5627uInt16 AlldayCount(TItemField *aStartFldP, TItemField *aEndFldP, timecontext_t aTimecontext, bool aContextForUTC)
5628{
5629 if (!aStartFldP->isBasedOn(fty_timestamp)) return 0;
5630 if (!aEndFldP->isBasedOn(fty_timestamp)) return 0;
5631 TTimestampField *startFldP = static_cast<TTimestampField *>(aStartFldP);
5632 TTimestampField *endFldP = static_cast<TTimestampField *>(aEndFldP);
5633 // check in specified time zone if originally UTC (or aContextForUTC not set), otherwise check as-is
5634 timecontext_t tctx;
5635 lineartime_t start = startFldP->getTimestampAs(!aContextForUTC || TCTX_IS_UTC(startFldP->getTimeContext()) ? aTimecontext : TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)), &tctx);
5636 lineartime_t end = endFldP->getTimestampAs(!aContextForUTC || TCTX_IS_UTC(endFldP->getTimeContext()) ? aTimecontext : TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)), &tctx);
5637 return AlldayCount(start,end);
5638} // AlldayCount
5639
5640
5641/// @brief makes two timestamps represent an all-day event
5642/// @param[in/out] aStart start time within the first day, will be set to midnight (00:00:00)
5643/// @param[in/out] aEnd end time within the last day or at midnight of the next day,
5644/// will be set to midnight of the next day
5645/// @param[in] aDays if>0, this is used to calculate the aEnd timestamp (aEnd input is
5646/// ignored then)
5647void MakeAllday(lineartime_t &aStart, lineartime_t &aEnd, sInt16 aDays)
5648{
5649 lineartime_t duration = 0;
5650
5651 // first calculate duration (assuming that even if there's a timezone problem, both
5652 // timestamps will be affected so duration is still correct)
5653 if (aDays<=0) {
5654 // use implicit duration
5655 duration = aEnd-aStart;
5656 } else {
5657 // use explicit duration
5658 duration = aDays * linearDateToTimeFactor;
5659 }
5660 // truncate start to midnight
5661 aStart = lineartime2dateonlyTime(aStart);
5662 // calculate timestamp that for sure is in next day
5663 aEnd = aStart + duration + linearDateToTimeFactor-1; // one unit less than a full day, ensures that 00:00:00 input will remain same day
5664 // make day-only of next day
5665 aEnd = lineartime2dateonlyTime(aEnd);
5666} // MakeAllday
5667
5668
5669/// @brief makes two timestamp fields represent an all-day event
5670/// @param[in/out] aStartFldP start time within the first day, will be set to dateonly
5671/// @param[in/out] aEndFldP end time within the last day or at midnight of the next day, will be set to dateonly of the next day
5672/// @param[in] aTimecontext context to calculate day boundaries in (if timestamp is not already floating), can be floating to treat in context of start date
5673/// @param[in] aDays if>0, this is used to calculate the aEnd timestamp (aEnd input is
5674/// ignored then)
5675/// @note fields will be made floating and dateonly
5676void MakeAllday(TItemField *aStartFldP, TItemField *aEndFldP, timecontext_t aTimecontext, sInt16 aDays)
5677{
5678 if (!aStartFldP->isBasedOn(fty_timestamp)) return;
5679 if (!aEndFldP->isBasedOn(fty_timestamp)) return;
5680 TTimestampField *startFldP = static_cast<TTimestampField *>(aStartFldP);
5681 TTimestampField *endFldP = static_cast<TTimestampField *>(aEndFldP);
5682 // adjust in specified time zone (or floating)
5683 timecontext_t tctx;
5684 lineartime_t start = startFldP->getTimestampAs(aTimecontext,&tctx);
5685 // context must match, unless either requested-as-is or timestamp is already floating
5686 if (tctx!=aTimecontext && !TCTX_IS_UNKNOWN(aTimecontext) && !TCTX_IS_UNKNOWN(tctx)) return; // cannot do anything
5687 // get end in same context as start is
5688 lineartime_t end = endFldP->getTimestampAs(tctx);
5689 // make allday
5690 MakeAllday(start,end,aDays);
5691 // store back and floating + dateonly
5692 tctx = TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)) | TCTX_DATEONLY;
5693 // for output format capable of date-only
5694 startFldP->setTimestampAndContext(start,tctx);
5695 endFldP->setTimestampAndContext(end,tctx);
5696} // MakeAllday
5697
5698
5699
5700} // namespace sysync
5701
5702
5703// eof