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