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