2 * File: mimedirprofile.cpp
4 * Author: Lukas Zeller (luz@synthesis.ch)
7 * base class for MIME DIR based content types (vCard, vCalendar...)
9 * Copyright (c) 2001-2009 by Synthesis AG (www.synthesis.ch)
11 * 2009-01-09 : luz : created from mimediritemtype.cpp
16 #include "prefix_file.h"
18 #include "vtimezone.h"
21 #include "mimedirprofile.h"
22 #include "mimediritemtype.h"
24 #include "syncagent.h"
27 using namespace sysync;
33 // mime-DIR mode names
34 const char * const mimeModeNames[numMimeModes] = {
40 // VTIMEZONE generation modes
41 const char * const VTimeZoneGenModes[numVTimeZoneGenModes] = {
50 // VTIMEZONE generation modes
51 const char * const VTzIdGenModes[numTzIdGenModes] = {
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
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
77 #pragma exceptions off
81 TMIMEProfileConfig::TMIMEProfileConfig(const char* aName, TConfigElement *aParentElement) :
82 TProfileConfig(aName,aParentElement)
84 fRootProfileP=NULL; // no profile yet
86 } // TMIMEProfileConfig::TMIMEProfileConfig
89 TMIMEProfileConfig::~TMIMEProfileConfig()
92 } // TMIMEProfileConfig::~TMIMEProfileConfig
96 void TMIMEProfileConfig::clear(void)
100 delete fRootProfileP;
101 fRootProfileP=NULL; // no profile any more
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)
114 } // TMIMEProfileConfig::clear
116 #pragma exceptions reset
120 TProfileHandler *TMIMEProfileConfig::newProfileHandler(TMultiFieldItemType *aItemTypeP)
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
127 // our handler is the text profile handler
128 return (TProfileHandler *)(new TMimeDirProfileHandler(this,aItemTypeP));
132 #ifdef CONFIGURABLE_TYPE_SUPPORT
135 // get conversion mode, virtual, can be overridden by derivates
136 bool TMIMEProfileConfig::getConvMode(const char *aText, sInt16 &aConvMode)
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;
176 fail("'conversion' value '%s' is invalid",aText);
180 } // TMIMEProfileConfig::getConvMode
186 bool TMIMEProfileConfig::getConvAttrs(const char **aAttributes, sInt16 &aFid, sInt16 &aConvMode, char &aCombSep)
189 const char *fnam = getAttr(aAttributes,"field");
190 if (fnam && *fnam!=0) {
193 aFid = fFieldListP->fieldIndex(fnam);
194 if (aFid==VARIDX_UNDEFINED) {
195 fail("'field' '%s' does not exist in field list '%s'",fnam,fFieldListP->getName());
199 // - get conversion mode
200 const char *conv = getAttr(aAttributes,"conversion");
202 if (!getConvMode(conv,aConvMode)) return false;
204 // - get combination char
205 const char *comb = getAttr(aAttributes,"combine");
207 if (strucmp(comb,"no")==0)
209 else if (strucmp(comb,"lines")==0)
211 else if (strlen(comb)==1)
214 fail("'combine' value '%s' is invalid",comb);
219 } // TMIMEProfileConfig::getConvAttrs
222 bool TMIMEProfileConfig::getMask(const char **aAttributes, const char *aName, TParameterDefinition *aParamP, TNameExtIDMap &aMask)
224 const char *m=getAttr(aAttributes,aName);
227 // skip comma separators and spaces
228 if (*m==',' || *m<=0x20) { m++; continue; }
230 TParameterDefinition *paramP = aParamP; // default param
232 while(m[n]>0x20 && m[n]!=',') {
234 // qualified enum, search param
235 paramP=fOpenProperty->findParameter(m,n);
237 fail("Unknown param '%s' referenced in '%s'",m,aName);
240 m+=n+1; // set start to enum name
242 continue; // prevent increment
247 fail("Enum value must be qualified with parameter name: '%s'",m);
250 TNameExtIDMap msk = paramP->getExtIDbit(m,n);
252 fail("'%s' is not an enum of parameter '%s'",m,paramP->paramname.c_str());
256 m+=n; // advance pointer
260 } // TMIMEProfileConfig::getMask
263 bool TMIMEProfileConfig::processPosition(TParameterDefinition *aParamP, const char **aAttributes)
265 // <position has="TYPE.HOME" hasnot="FAX,CELL" shows="VOICE"
266 // field="TEL_HOME" repeat="4" increment="1" minshow="0"/>
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
275 sInt16 fid=FID_NOT_SUPPORTED;
276 const char *fnam = getAttr(aAttributes,"field");
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());
282 // - calculate offset from first specified value field in property
283 sInt16 fidoffs=FID_NOT_SUPPORTED;
285 for (sInt16 k=0; k<fOpenProperty->numValues; k++) {
286 fidoffs=fOpenProperty->convdefs[k].fieldid;
287 if (fidoffs>=0) break; // found field offset
290 return !fail("property '%s' does not have any field assigned, cannot use 'position'",fOpenProperty->propname.c_str());
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");
304 if (strucmp(repval,"rewrite")==0) repeat=REP_REWRITE;
305 #ifdef ARRAYFIELD_SUPPORT
306 else if (strucmp(repval,"array")==0) repeat=REP_ARRAY;
308 else if (!StrToShort(repval,repeat))
309 return !fail("expected number, 'rewrite' or 'array' in 'repeat'");
311 // - increment and minshow
313 !getAttrShort(aAttributes,"increment",incr,true) ||
314 !getAttrShort(aAttributes,"minshow",minshow,true) ||
315 !getAttrShort(aAttributes,"sharepreviouscount",sharecountoffs,true)
317 return !fail("number expected in 'increment', 'minshow' and 'sharepreviouscount'");
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
329 expectEmpty(); // no contents (and not a separte nest level)
331 } // TMIMEProfileConfig::processPosition
334 // called at end of nested parsing level
335 void TMIMEProfileConfig::nestedElementEnd(void)
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
346 } // TMIMEProfileConfig::nestedElementEnd
349 // config element parsing
350 bool TMIMEProfileConfig::localStartElement(const char *aElementName, const char **aAttributes, sInt32 aLine)
360 // MIME profile. This is multi-level and therefore needs
361 // complicated parser.
363 // reset to root level
370 // - get MIME-DIR type dependency
371 TMimeDirMode modeDep = numMimeModes; // no mode dependency by default
373 cAppCharP modeDepName = getAttr(aAttributes,"onlyformode");
375 if (!StrToEnum(mimeModeNames,numMimeModes,m,modeDepName))
376 return fail("unknown 'onlyformode' attribute value '%s'",modeDepName);
378 modeDep=(TMimeDirMode)m;
380 // - now parse specifics
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");
388 TEnumMode mode=enm_translate; // default to translate
389 const char *mod=getAttr(aAttributes,"mode");
391 if (!StrToEnum(EnumModeNames,numEnumModes,m,mod))
392 return fail("unknown 'mode' '%s'",mod);
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");
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");
413 fOpenConvDef->addEnumNameExt(fOpenProperty, nam,val,mode);
415 fOpenConvDef->addEnum(nam,val,mode);
416 expectEmpty(); // no contents (and not a separate nest level)
420 return false; // parent is TConfigElement, no need to call inherited
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;
429 // - get other options of convdef
430 if (!getConvAttrs(aAttributes,fid,convmode,combsep)) return true; // failed
432 fOpenConvDef=fOpenParameter->setConvDef(fid,convmode,combsep);
433 startNestedParsing();
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
445 return inherited::localStartElement(aElementName, aAttributes, aLine); // call inherited
447 else if (fOpenProperty) {
448 if (strucmp(aElementName,"value")==0) {
449 // <value index="1" field="N_FIRST" conversion="none" combine="no"/>
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;
460 // - get other options of convdef
461 if (!getConvAttrs(aAttributes,fid,convmode,combsep))
462 return true; // failed
464 fOpenConvDef=fOpenProperty->setConvDef(idx,fid,convmode,combsep);
465 startNestedParsing();
467 else if (strucmp(aElementName,"parameter")==0) {
468 // <parameter name="TYPE" default="yes" positional="yes">
470 nam = getAttr(aAttributes,"name");
472 return fail("'parameter' must have 'name' attribute");
474 bool positional=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
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!)
485 return fail("bad boolean value");
487 fOpenParameter = fOpenProperty->addParam(nam,defparam,positional,shownonempty,showinctcap,modeDep);
488 startNestedParsing();
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
499 return inherited::localStartElement(aElementName, aAttributes, aLine); // call inherited
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
508 nam = getAttr(aAttributes,"name");
510 return fail("'subprofile' must have 'name' attribute");
511 // - get profile mode
512 TProfileModes mode = profm_custom;
513 cAppCharP pfmode = getAttr(aAttributes,"mode");
515 if (!StrToEnum(ProfileModeNames,numProfileModes,m,pfmode))
516 return fail("unknown profile 'mode' '%s'",pfmode);
518 mode=(TProfileModes)m;
520 // - check mode dependent params
521 TProfileDefinition *profileP = NULL; // no foreign properties by default
522 if (mode==profm_custom) {
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");
530 profileP = fRootProfileP->findProfile(use);
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)
536 // parsing nested elements in this TConfigElement
537 startNestedParsing();
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)
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");
555 // - "value" is optional, without a value subprofile is activated if field is non-empty
556 val = getAttr(aAttributes,"value");
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
564 cdP->addEnum("",val,enm_translate); // set value to be set into level control field when level is entered
567 else if (strucmp(aElementName,"property")==0) {
568 // <property name="VERSION" rule="other" values="1" mandatory="no" show="yes" suppressempty="no" delayedparsing="0">
570 nam = getAttr(aAttributes,"name");
572 return fail("'property' must have 'name' attribute");
574 if (!fLastProperty || strucmp(fLastProperty->TCFG_CSTR(propname),nam)!=0) {
575 // first property in group
576 fPropertyGroupID++; // new group ID
578 #ifndef NO_REMOTE_RULES
579 // - get rule dependency
580 bool isRuleDep=false;
581 const char *depRuleName = getAttr(aAttributes,"rule");
584 if (strucmp(depRuleName,"other")==0) {
585 // "other" rule (property is active if no other property from the group gets active)
590 // - get number of values
591 sInt16 numval=1; // default to 1
592 const char *nvs = getAttr(aAttributes,"values");
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");
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)
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
627 startNestedParsing();
631 return inherited::localStartElement(aElementName, aAttributes, aLine); // call inherited
634 if (strucmp(aElementName,"profile")==0) {
635 // <profile name="VCARD" nummandatory="2">
637 return fail("'profile' cannot be defined more than once");
638 // new profile starts new property group
641 nam = getAttr(aAttributes,"name");
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();
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);
661 return inherited::localStartElement(aElementName, aAttributes, aLine);
665 } // TMIMEProfileConfig::localStartElement
669 #ifndef NO_REMOTE_RULES
670 // resolve remote rule dependencies in profile (recursive)
671 static void resolveRemoteRuleDeps(TProfileDefinition *aProfileP, TAgentConfig *aSessionConfigP)
673 TProfileDefinition *profileP = aProfileP;
675 // resolve properties
676 TPropertyDefinition *propP = profileP->propertyDefs;
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)) {
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);
691 if (propP->ruleDependency==NULL) {
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()));
701 // resolve subprofiles
702 resolveRemoteRuleDeps(profileP->subLevels,aSessionConfigP);
704 profileP=profileP->next;
706 } // resolveRemoteRuleDeps
710 void TMIMEProfileConfig::localResolve(bool aLastPass)
713 // check for required settings
715 SYSYNC_THROW(TConfigParseException("empty 'mimeprofile' not allowed"));
716 #ifndef NO_REMOTE_RULES
717 // recursively resolve remote rule dependencies in all properties
718 resolveRemoteRuleDeps(
720 static_cast<TAgentConfig *>(static_cast<TRootConfig *>(getRootElement())->fAgentConfigP)
725 inherited::localResolve(aLastPass);
726 } // TMIMEProfileConfig::localResolve
728 #endif // CONFIGURABLE_TYPE_SUPPORT
735 // implementation of MIME-DIR info classes
737 #pragma exceptions off
738 #define EXCEPTIONS_HERE 0
741 TEnumerationDef::TEnumerationDef(const char *aEnumName, const char *aEnumVal, TEnumMode aMode, sInt16 aNameExtID)
744 TCFG_ASSIGN(enumtext,aEnumName);
745 TCFG_ASSIGN(enumval,aEnumVal);
747 nameextid=aNameExtID;
748 } // TEnumerationDef::TEnumerationDef
751 TEnumerationDef::~TEnumerationDef()
753 // make sure entire chain gets deleted
754 if (next) delete next;
755 } // TEnumerationDef::~TEnumerationDef
759 TConversionDef::TConversionDef()
761 fieldid=FID_NOT_SUPPORTED;
765 } // TConversionDef::TConversionDef
768 TConversionDef::~TConversionDef()
770 // make sure enum list gets deleted
771 if (enumdefs) delete enumdefs;
772 } // TEnumerationDef::~TEnumerationDef
775 TConversionDef *TConversionDef::setConvDef(
785 } // TConversionDef::setConvDef
788 const TEnumerationDef *TConversionDef::findEnumByName(const char *aName, sInt16 n)
791 TEnumerationDef *enumP = enumdefs;
792 TEnumerationDef *defaultenumP = NULL;
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
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
815 return enumP ? enumP : defaultenumP;
816 } // TConversionDef::findEnumByName
819 const TEnumerationDef *TConversionDef::findEnumByVal(const char *aVal, sInt16 n)
822 TEnumerationDef *enumP = enumdefs;
823 TEnumerationDef *defaultenumP = NULL;
827 (enumP->enummode==enm_translate || enumP->enummode==enm_ignore) &&
828 strucmp(aVal,TCFG_CSTR(enumP->enumval),n)==0
830 // check prefix match
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
840 return enumP ? enumP : defaultenumP;
841 } // TConversionDef::findEnumByVal
844 void TConversionDef::addEnum(const char *aEnumName, const char *aEnumVal, TEnumMode aMode)
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
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)
856 TEnumerationDef **enumPP = &enumdefs;
857 while(*enumPP!=NULL) enumPP=&((*enumPP)->next); // find last in chain
858 if (aProp->nextNameExt>31)
860 SYSYNC_THROW(TSyncException(DEBUGTEXT("more than 32 name extensions","mdit3")));
862 return; // silently ignore
864 *enumPP = new TEnumerationDef(aEnumName,aEnumVal, aMode, aProp->nextNameExt++);
865 } // TConversionDef::addEnumNameExt
868 TParameterDefinition::TParameterDefinition(
869 const char *aName, bool aDefault, bool aExtendsName, bool aShowNonEmpty, bool aShowInCTCap, TMimeDirMode aModeDep
872 TCFG_ASSIGN(paramname,aName);
873 defaultparam=aDefault;
874 extendsname=aExtendsName;
875 shownonempty=aShowNonEmpty;
876 showInCTCap=aShowInCTCap;
877 modeDependency=aModeDep;
878 } // TParameterDefinition::TParameterDefinition
881 TParameterDefinition::~TParameterDefinition()
883 if (next) delete next;
884 } // TParameterDefinition::~TParameterDefinition
887 TNameExtIDMap TParameterDefinition::getExtIDbit(const char *aEnumName, sInt16 n)
889 const TEnumerationDef *enumP=convdef.findEnumByName(aEnumName,n);
891 return ((TNameExtIDMap)1<<enumP->nameextid);
894 } // TParameterDefinition::getExtIDbit
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
903 musthave_ids=aMusthave_ids;
904 forbidden_ids=aForbidden_ids;
905 addtlSend_ids=aAddtlSend_ids;
906 fieldidoffs=aFieldidoffs;
907 maxRepeat=aMaxRepeat;
908 repeatInc=aRepeatInc;
910 overwriteEmpty=aOverwriteEmpty;
913 } // TPropNameExtension::TPropNameExtension
916 TPropNameExtension::~TPropNameExtension()
918 if (next) delete next;
919 } // TPropNameExtension::~TPropNameExtension
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)
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) {
936 expandlist = aNumVals==NUMVAL_REP_LIST;
937 numValues = 1; // we accept a single convdef only
941 numValues = aNumVals;
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;
958 } // TPropertyDefinition::TPropertyDefinition
961 TPropertyDefinition::~TPropertyDefinition()
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
974 TConversionDef *TPropertyDefinition::setConvDef(sInt16 aValNum, sInt16 aFieldId,sInt16 aConvMode,char aCombSep)
976 if (aValNum<0 || aValNum>=numValues)
978 SYSYNC_THROW(TSyncException(DEBUGTEXT("setConvDef for Property with bad value number","mdit4")));
980 return NULL; // silently ignore
982 return convdefs[aValNum].setConvDef(aFieldId,aConvMode,aCombSep);
983 }; // TPropertyDefinition::TConversionDef
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
992 TPropNameExtension **namextPP = &nameExts;
993 while(*namextPP!=NULL) namextPP=&((*namextPP)->next); // find last in chain
995 if (aMaxRepeat==REP_ARRAY)
996 aMinShow=0; // by default, show nothing if array is empty
998 aMinShow=aMaxRepeat; // auto mode, show all repetitions
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++
1007 } // TPropertyDefinition::addNameExt
1010 TParameterDefinition *TPropertyDefinition::addParam(
1011 const char *aName, bool aDefault, bool aExtendsName, bool aShowNonEmpty, bool aShowInCTCap, TMimeDirMode aModeDep
1014 TParameterDefinition **paramPP = ¶meterDefs;
1015 while(*paramPP!=NULL) paramPP=&((*paramPP)->next); // find last in chain
1016 *paramPP = new TParameterDefinition(aName,aDefault,aExtendsName,aShowNonEmpty,aShowInCTCap, aModeDep);
1018 } // TPropertyDefinition::addParam
1021 // find parameter by name
1022 TParameterDefinition *TPropertyDefinition::findParameter(const char *aNam, sInt16 aLen)
1024 TParameterDefinition *paramP = parameterDefs;
1026 if (strucmp(aNam,paramP->TCFG_CSTR(paramname),aLen)==0)
1027 return paramP; // found
1028 paramP=paramP->next; // next
1032 } // TPropertyDefinition::findParameter
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
1044 parentProfile=aParentProfileP; // NULL if root
1047 TCFG_ASSIGN(levelName,aProfileName);
1048 shownIfSelectedOnly = aShowInCTCapIfSelectedOnly;
1049 profileMode = aProfileMode;
1050 modeDependency = aModeDep;
1052 numMandatoryProperties=aNumMandatory;
1057 } // TProfileDefinition::TProfileDefinition
1060 TProfileDefinition::~TProfileDefinition()
1062 if (propertyDefs && ownsProps) delete propertyDefs;
1063 if (subLevels) delete subLevels;
1064 if (next) delete next;
1065 } // TProfileDefinition::~TProfileDefinition
1068 TProfileDefinition *TProfileDefinition::addSubProfile(
1069 const char *aProfileName, // name
1070 sInt16 aNumMandatory,
1071 bool aShowInCTCapIfSelectedOnly,
1072 TProfileModes aProfileMode,
1073 TMimeDirMode aModeDep
1076 TProfileDefinition **profilePP=&subLevels;
1077 while (*profilePP!=NULL) profilePP=&((*profilePP)->next);
1078 *profilePP=new TProfileDefinition(this,aProfileName,aNumMandatory,aShowInCTCapIfSelectedOnly,aProfileMode,aModeDep);
1080 } // TProfileDefinition::addSubProfile
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)
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
1102 } // TProfileDefinition::addProperty
1105 void TProfileDefinition::usePropertiesOf(TProfileDefinition *aProfile)
1108 propertyDefs=aProfile->propertyDefs;
1109 } // TProfileDefinition::usePropertiesOf
1112 // find (sub)profile by name, recursively
1113 TProfileDefinition *TProfileDefinition::findProfile(const char *aNam)
1116 if (levelName==aNam) return this;
1118 TProfileDefinition *lvlP = subLevels;
1119 TProfileDefinition *foundlvlP;
1121 foundlvlP=lvlP->findProfile(aNam);
1122 if (foundlvlP) return foundlvlP;
1125 // does not match myself nor one of my sublevels
1127 } // TProfileDefinition::findProfile
1129 #pragma exceptions reset
1130 #undef EXCEPTIONS_HERE
1131 #define EXCEPTIONS_HERE TARGET_HAS_EXCEPTIONS
1134 #ifdef OBJECT_FILTERING
1136 // get property definition of given filter expression identifier.
1137 TPropertyDefinition *TProfileDefinition::getPropertyDef(const char *aPropName)
1139 TPropertyDefinition *propP = NULL;
1141 if (!aPropName) return propP; // no name, no fid
1142 // Depth first: search in subprofiles, if any
1143 TProfileDefinition *profileP = subLevels;
1145 // search depth first
1146 if ((propP=profileP->getPropertyDef(aPropName))!=NULL)
1147 return propP; // found
1148 // test next profile
1149 profileP=profileP->next;
1151 // now search my own properties
1152 propP = propertyDefs;
1155 if (strucmp(aPropName,TCFG_CSTR(propP->propname))==0) {
1158 // test next property
1163 } // TProfileDefinition::getPropertyDef
1166 // get field index of given filter expression identifier.
1167 sInt16 TProfileDefinition::getPropertyMainFid(const char *aPropName, uInt16 aIndex)
1169 sInt16 fid = VARIDX_UNDEFINED;
1171 // search property definition with matching name
1172 TPropertyDefinition *propP = getPropertyDef(aPropName);
1173 // search for first value with a field assigned
1175 // found property with matching name
1176 if (propP->convdefs) {
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
1185 // index specified for multivalued properties -> return specified value's ID
1186 if (aIndex<=propP->numValues) {
1187 return propP->convdefs[aIndex-1].fieldid;
1193 return VARIDX_UNDEFINED;
1194 } // TProfileDefinition::getPropertyMainFid
1197 #endif // OBJECT_FILTERING
1202 * Implementation of TMimeDirProfileHandler
1206 TMimeDirProfileHandler::TMimeDirProfileHandler(
1207 TMIMEProfileConfig *aMIMEProfileCfgP,
1208 TMultiFieldItemType *aItemTypeP
1209 ) : TProfileHandler(aMIMEProfileCfgP, aItemTypeP)
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
1229 TMimeDirProfileHandler::~TMimeDirProfileHandler()
1232 } // TMimeDirProfileHandler::~TTextProfileHandler
1236 #ifdef OBJECT_FILTERING
1238 // get field index of given filter expression identifier.
1239 sInt16 TMimeDirProfileHandler::getFilterIdentifierFieldIndex(const char *aIdentifier, uInt16 aIndex)
1241 // search properties for field index
1242 return fProfileDefinitionP->getPropertyMainFid(aIdentifier, aIndex);
1243 } // TMimeDirProfileHandler::getFilterIdentifierFieldIndex
1245 #endif // OBJECT_FILTERING
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)
1255 cAppCharP p = aMixVal;
1256 // check offset (2 digit max)
1258 p+=StrToUShort(p,aOffs,2);
1259 if (*p++ != '.') return false; // wrong syntax
1265 if (StrToUShort(p+1,aN,2)<1) return false; // wrong syntax
1268 // literal, return position within string
1270 aN = p+1-aMixVal; // literal starts at this position
1273 return false; // unknown command
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)
1283 if (aConvDef.convmode==CONVMODE_RRULE)
1284 return 6; // RRULE fieldblock: DTSTART,FREQ,INTERVAL,FIRSTMASK,LASTMASK,UNTIL = 6 fields
1286 return 1; // single field
1287 } // TMimeDirProfileHandler::fieldBlockSize
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
1303 const int maxmix = 10;
1304 uInt16 mixOffs[maxmix];
1305 bool mixIsFlags[maxmix];
1306 TEnumerationDef *enumP;
1307 uInt16 offs; bool isFlags;
1309 fieldinteger_t flags;
1311 TTimestampField *tsFldP;
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;
1327 // get pointer to leaf field
1328 TItemField *fldP = aItem.getArrayField(aFid,aArrIndex,true); // existing array elements only
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) {
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
1344 goto timestamp; // others are treated as timestamps
1345 // date & time modes
1346 case CONVMODE_DATE: // always show as date
1348 dateonly = true; // render as date in all cases
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
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
1364 if (TCTX_IS_DATEONLY(tctx))
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);
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);
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);
1386 tsFldP->getAsISO8601(aString, TCTX_UTC, true, false, false, false);
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)
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
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);
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);
1415 return true; // found
1418 fldP->getAsString(aString);
1419 return true; // found
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
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;
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
1440 tctx = tsFldP->getTimeContext(); // get the context
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
1446 else if (fldP->getCalcType()==fty_integer) {
1447 // integer field is simply a time zone offset in minutes
1448 tctx = TCTX_MINOFFSET(fldP->getAsInteger());
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());
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
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
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
1481 if (fEarliestTZDate==noLinearTime || fEarliestTZDate>ts) fEarliestTZDate = ts; // new minimum
1482 if (fLatestTZDate==noLinearTime || fLatestTZDate<ts) fLatestTZDate = ts; // new maximum
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
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
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
1506 // - return true only if we actually have a TZ
1507 return ContextToISO8601StrAppend(aString, tctx, true);
1509 else if (aConvDefP->convmode==CONVMODE_DAYLIGHT) {
1510 // TZ and DAYLIGHT property for vCal 1.0
1512 // - return true only if we actually have a DAYLIGHT
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);
1523 if (strucmp(s.c_str(),"mailto:",7)!=0 && s.size()>0)
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";
1538 // only show type if full value type requested
1539 if (aConvDefP->convmode==CONVMODE_FULLVALUETYPE)
1540 aString="DATE-TIME";
1542 return false; // we don't need a VALUE param for normal datetimes
1546 return false; // no field type that needs VALUE param
1547 // valuetype generated
1549 case CONVMODE_VERSION:
1551 aString=aItem.getItemType()->getTypeVers(fProfileMode);
1553 case CONVMODE_PRODID:
1554 // PRODID ISO9070 non-registered FPI
1555 // -//ABC Corporation//NONSGML My Product//EN
1556 aString = SYSYNC_FPI;
1558 case CONVMODE_BITMAP:
1559 // bitmap is a special case of multimix, set up params
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
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
1572 enumP = aConvDefP->enumdefs;
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
1579 // is a new field, add it to list
1580 mixOffs[nummix] = offs;
1581 mixIsFlags[nummix] = isFlags;
1583 if (nummix>=maxmix) break; // no more mixes allowed, stop scanning
1590 // now generate strings from collected data
1592 for (i=0; i<nummix; i++) {
1594 fldP = aItem.getArrayField(aFid+mixOffs[i],aArrIndex,true); // existing array elements only
1596 if (mixIsFlags[i]) {
1597 // use target as bitmask to create bit numbers
1598 flags=fldP->getAsInteger();
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
1608 StringObjAppendPrintf(aString,"%d.",mixOffs[i]);
1612 StringObjAppendPrintf(aString,"%hd",bitNo);
1614 flags >>= 1; // consume this one
1620 if (!fldP->isEmpty()) {
1621 if (!aString.empty() && aConvDefP->combineSep)
1622 aString+=aConvDefP->combineSep; // append separator if there are more flags
1624 StringObjAppendPrintf(aString,"%d.",mixOffs[i]);
1625 aString += 'L'; // literal
1626 fldP->appendToString(aString);
1629 } // field available
1632 case CONVMODE_RRULE: {
1633 // get values from field block
1634 if (aFid<0) return false; // no field, no string
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);
1640 freqmod=' '; // no modifier
1641 if (s.size()>0) freq=s[0];
1642 if (s.size()>1) freqmod=s[1];
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();
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();
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();
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
1659 // - UTC preferred as output format if basically possible and not actively disabled
1661 fReceiverCanHandleUTC && getSession()->canHandleUTC() ?
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);
1671 // Now do the conversion
1673 if (fMimeDirMode==mimo_old) {
1674 // vCalendar 1.0 type RRULE
1675 ok = internalToRRULE1(
1688 // iCalendar 2.0 type RRULE
1689 ok = internalToRRULE2(
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;
1708 break; // just in case
1711 // unknown mode, no value
1715 } // TMimeDirProfileHandler::fieldToMIMEString
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)
1724 return (aChar=='\x0D') || (aChar=='\x0A');
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)
1733 return (aChar==0) || isLineEndChar(aChar);
1734 } // isEndOfLineOrText
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)
1743 cAppCharP p = aText;
1744 bool crFound = false;
1745 // skip sequence of CRs
1746 while (*p=='\x0D') {
1750 // past all CRs in a row
1752 // independent of the number of CRs preceeding, this is a line end including the LF
1753 aText = p+1; // past LF
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
1764 } // testAndSkipLineEnd
1768 // return incremented pointer pointing to original char or next non-folded char
1769 static cAppCharP skipfolded(cAppCharP aText, TMimeDirMode aMimeMode, bool qpSoftBreakCancel=false)
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)
1781 // old folding type, LWSP must be preserved
1786 else if (qpSoftBreakCancel && *p=='=') {
1787 // could be soft break sequence, check for line end
1789 if (testAndSkipLineEnd(p)) {
1793 // not folding sequence, return ptr to char as is
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)
1803 if (*p==0) return p; // at end of string, do not advance
1804 p++; // point to next
1805 return skipfolded(p,aMimeMode,qpSoftBreakCancel);
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)
1821 const int maxseqlen=6;
1823 char c,chrs[maxseqlen];
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)
1832 // decode standard content
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
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
1852 c='\n'; // make newline
1854 else if (c=='\x0A') {
1855 p=nextunfolded(p,aMimeMode,true); // advance to char after second digit
1856 continue; // ignore LF
1860 seqlen=1; // assume logical char consists of single byte
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);
1871 p=nextunfolded(p,aMimeMode,true);
1873 } // quoted printable
1874 else if (aEncoding==enc_base64 || aEncoding==enc_b) {
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
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);
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
1894 for (cAppCharP r2=r; *r2 && !isLineEndChar(*r2); r2++) {
1895 if (*r2==':' || *r2==';') {
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
1903 // skip to continuation of B64 string
1911 uInt8 *binP = b64::decode(p, q-p, &binsz);
1912 aVal.append((const char *)binP,binsz);
1914 // - continue at next char after b64 value
1918 // no (known) encoding
1919 p = skipfolded(aText,aMimeMode,false); // get unfolded start point (in case value starts with folding sequence
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
1926 seqlen=1; // assume logical char consists of single byte
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);
1937 p=nextunfolded(p,aMimeMode,false);
1940 // return pointer to terminating char
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
1955 const uInt8 *valPtr = (const uInt8 *)aValuedata.c_str();
1956 size_t valSz = aValuedata.size();
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();
1964 // - apply encoding if needed
1968 aPropertytext, // append output here
1969 aEncoding, // desired encoding
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)
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,
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)
1996 // make sure that allocation does not increase char by char
1997 aString.reserve(aString.size()+strlen(proptext)+100);
2000 ssize_t lastlwsp=-1; // no linear white space found so far
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;
2007 // Note: we prevent folding within UTF8 sequences as result string would become inconvertible e.g. into UTF16
2009 cAppCharP nP = UTF8toUCS4(proptext, uc);
2011 // UTF-8 compliant byte (or byte sequence), skip as an entiety
2016 // Not UTF-8 compliant, simply one byte
2020 // check for optional break indicator
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
2028 // update line length
2030 // explicit linefeed flag
2031 explf=(c=='\n' || c=='\r');
2033 // prohibit folding for ugly devices like V3i
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
2041 firstunwritten=proptext;
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
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
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);
2074 // we are on a new line now
2078 firstunwritten=proptext;
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
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
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
2092 aString+=' '; // not only soft line break, but MIMD-DIR type folding
2095 firstunwritten=proptext;
2100 aString.append(firstunwritten,n);
2101 // terminate property
2102 aString.append("\x0D\x0A"); // CRLF
2103 } // finalizeProperty
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
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)
2133 string vallist; // as received from fieldToMIMEString()
2134 string val; // single value
2135 string outval; // entire value (list) escaped
2138 // determine field ID
2139 bool isarray = false; // no array by default
2140 sInt16 fid=aConvDefP->fieldid;
2142 // field has storage
2143 // - fid is always offset by baseoffset
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
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
2174 // append to existing string
2175 fldP->appendToString(outval,maxSiz);
2176 // force B64 encoding
2177 aEncoding=enc_base64;
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)
2187 return GENVALUE_EXHAUSTED; // no leaf field - must be exhausted array
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)
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;
2198 // - n=size of input value, p=ptr to end of value (0 or sep)
2200 // perform enum translation if needed
2201 if (aConvDefP->enumdefs) {
2202 const TEnumerationDef *enumP = aConvDefP->findEnumByVal(val.c_str());
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
2213 // simply use translated value
2214 val=enumP->enumtext;
2218 PDEBUGPRINTFX(DBG_GEN+DBG_EXOTIC,("No translation found for Val='%s'", val.c_str()));
2221 // - val is now translated enum (or original value if value does not match any enum text)
2223 // perform escaping and determine need for encoding
2224 bool spaceonly = true;
2225 bool firstchar = true;
2227 for (const char *p=val.c_str();(c=*p)!=0 && (c!=aConvDefP->combineSep);p++) {
2229 // - check for whitespace
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;
2239 wordSize = 0; // new word starts
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
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
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;
2253 // always escape colon in parameters
2254 if (!aParamValue) goto add_char;
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
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;
2265 // escape chars with backslash
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;
2278 // just pass it, will be encoded later
2282 // MIME-DIR: use quoted C-style notation
2283 outval.append("\\n");
2288 // prevent adding space-only for params
2289 if (spaceonly && aParamValue) break; // just check next
2291 // check for non ASCII and set flag if found
2292 if ((uInt8)c > 0x7F) aNonASCII=true;
2293 // just copy to output
2295 firstchar = false; // first char is out
2298 } // for all chars in val item
2299 // go to next item in the val list (if any)
2301 // more items in the list
2302 // - add separator if previous one is not empty param value
2303 if (!(spaceonly && aParamValue)) {
2305 valsiz++; // count it as part of the value
2307 lp++; // skip input list separator
2309 // check for truncation needs (do not truncate parameters, ever)
2310 if (maxSiz && valsiz>maxSiz && !aParamValue) {
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()),
2319 // treat it as if field was not supported locally
2320 return GENVALUE_NOTSUPPORTED;
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()),
2330 // do not add more chars
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
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
2343 } // source and target both support the field (or field belongs to mandatory property)
2345 return GENVALUE_NOTSUPPORTED; // field not supported by either source or target (and not mandatory) -> do not generate value
2346 } // if fieldid exists
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
2355 // no value, no output
2356 return GENVALUE_NOTSUPPORTED; // field not supported
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;
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
2367 aString.append(outval);
2369 return outval.empty()
2370 ? (isarray ? GENVALUE_EMPTYELEMENT : GENVALUE_EMPTY) // empty
2371 : (isarray ? GENVALUE_ELEMENT : GENVALUE_NONEMPTY); // non empty
2372 } // TMimeDirProfileHandler::generateValue
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)
2385 TPropNameExtension *aPropNameExt // propname extension for generating musthave param values
2388 const TParameterDefinition *paramP;
2390 char sep=0; // separator for value lists
2392 TEncodingTypes encoding;
2394 bool showalways=false;
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;
2404 // parameter not started yet
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;
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) {
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
2424 // normal parameter, first add param separator and name
2426 aString.append(paramP->paramname);
2428 // - separator, in case other values follow
2429 sep=','; // value list separator is comma by default
2433 // add separator for one more 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
2442 } // while enum values
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 ";"
2451 if (paramP->defaultparam && (aMimeMode==mimo_old)) {
2452 // default param, values are written like a list of params
2456 // normal parameter, first add name
2457 paramstr.append(paramP->paramname);
2459 sep=','; // value list separator is comma by default
2463 // already started values, just add more
2464 // - next value starts with a separator
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
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
2485 paramP=paramP->next;
2488 } // TMimeDirProfileHandler::generateParams
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
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)
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)
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
2524 // scan name extensions
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
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
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;
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;
2564 maxOccur = 0; // no limit
2565 // Note: all value fields of the property will have the same maxOccur, so we can stop here
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
2583 if (genres==GENPROP_NONEMPTY) {
2584 // generated a property
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
2593 // one more generated of the maximum possible (note: REP_ARRAY=32k, so this will not limit an array)
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",
2601 TCFG_CSTR(aPropP->propname)
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)
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;
2618 } // if nameexts at all
2619 } // TMimeDirProfileHandler::expandProperty
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
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;
2642 // - reset TZID presence flag
2643 fPropTZIDtctx = TCTX_UNKNOWN;
2644 // - init string with name and (eventually) parameters that are constant over all repetitions
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
2659 // - append value(s)
2660 encoding=enc_none; // default is no encoding
2661 sInt16 v=0; // value counter
2662 nonasc=false; // assume plain ASCII
2666 const TConversionDef *convP;
2667 sInt16 maxrep=1,repinc=1;
2669 maxrep=aPropNameExt->maxRepeat;
2670 repinc=aPropNameExt->repeatInc;
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
2682 genres=generateValue(
2685 aBaseOffset, // offset relative to base field
2686 aRepeatOffset, // additional offset or array index
2688 aPropP->valuesep, // use valuelist separator between multiple values eventually generated from a list in a single field (e.g. CATEGORIES)
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
2695 v>0 ? aPropP->valuesep : 0 // separate with specified multi-value-delimiter if not first value
2697 // check if something was generated
2698 if (genres>=GENVALUE_ELEMENT) {
2699 // generated something, might have caused encoding/noasc change
2701 nonasc=nonasc || na;
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
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
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;
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
2728 convP = &(aPropP->convdefs[v]);
2729 // generate one value
2732 genres=generateValue(
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)
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)
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
2751 nonasc=nonasc || na;
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
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
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
2771 if (v>=aPropP->numValues) break; // done with all values
2772 // add delimiter for next value
2773 elemtext+=aPropP->valuesep;
2774 // add break indicator
2777 // if none of the data sources is an array, we can't be exhausted.
2778 if (!somearrays) arrayexhausted = false;
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;
2788 // add the parameter
2789 proptext.append(";ENCODING=");
2790 proptext.append(MIMEEncodingNames[encoding]);
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]);
2799 // - separate value from property text
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;
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
2818 ? (arrayexhausted ? GENPROP_EXHAUSTED : GENPROP_EMPTY) // no property generated
2819 : GENPROP_EXHAUSTED; // no values supported means "exhausted" as well
2821 } // TMimeDirProfileHandler::generateProperty
2825 // generate MIME-DIR from item into string object
2826 void TMimeDirProfileHandler::generateMimeDir(TMultiFieldItem &aItem, string &aString)
2829 aString.reserve(3000); // not too small
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) {
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) {
2856 endYear = startYear; // only show for start of range
2859 startYear = endYear; // only show for end of range
2862 // pass both start and end year
2864 case vtzgen_openend:
2865 // pass start year but request that all rules from start up to the current date are inlcuded
2868 case vtzgen_current:
2869 case numVTimeZoneGenModes:
2870 // case statement to keep gcc happy, will not be reached because of if() above
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);
2882 while (val.size()>n) {
2883 i = val.find('\n',n); // next line end
2884 if (i==string::npos) i=val.size();
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
2896 s.append(fVTimeZonePendingProfileP->levelName);
2897 finalizeProperty(s.c_str(),vtz,fMimeDirMode,false,false);
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);
2903 fVTimeZonePendingProfileP = NULL;
2904 } // if pending VTIMEZONE
2905 } // TMimeDirProfileHandler::generateMimeDir
2908 // generate nested levels of MIME-DIR content
2909 void TMimeDirProfileHandler::generateLevels(
2910 TMultiFieldItem &aItem,
2912 const TProfileDefinition *aProfileP
2915 //* %%% */ PDEBUGBLOCKDESC("generateLevels",TCFG_CSTR(aProfileP->levelName));
2916 // check if level must be generated
2919 sInt16 fid=aProfileP->levelConvdef.fieldid;
2920 if (fid<0) dolevel=true; // if no controlling field there, generate anyway
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);
2927 // if enumdefs, content must match first enumdef's enumval (NOT enumtext!!)
2928 dolevel = strucmp(val.c_str(),enumP->TCFG_CSTR(enumval))==0;
2931 // just being not empty enables level
2932 dolevel = !aItem.getField(fid)->isEmpty();
2936 // check for MIME mode dependency
2937 dolevel = dolevel && mimeModeMatch(aProfileP->modeDependency);
2938 // generate level if enabled
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();
2948 // standard custom level
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;
2959 const TPropertyDefinition *expandPropP;
2961 // check for mode dependency
2962 if (!mimeModeMatch(propP->modeDependency)) {
2963 // no mode match -> just skip this one
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) {
2978 TCFG_CSTR(otherRulePropP->propname), // the prefix consists of the property name
2979 otherRulePropP, // the property definition
2980 fMimeDirMode // MIME-DIR mode
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;
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
2995 otherRulePropP=propP;
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;
3005 // does not depend on rule, expand anyway
3008 // check if this is last prop of list
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;
3020 // now expand if selected
3023 // recursively generate all properties that expand from this entry
3024 // (includes extendsfieldid-parameters and repetitions
3028 expandPropP->TCFG_CSTR(propname), // the prefix consists of the property name
3029 expandPropP, // the property definition
3030 fMimeDirMode // MIME-DIR mode
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);
3041 subprofileP=subprofileP->next;
3043 // generate level end
3045 s.append(aProfileP->levelName);
3046 finalizeProperty(s.c_str(),aString,fMimeDirMode,false,false);
3048 } // if level must be generated
3049 //* %%% */ PDEBUGENDBLOCK("generateLevels");
3050 } // TMimeDirProfileHandler::generateLevels
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
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
3072 fieldinteger_t flags = 0;
3073 TTimestampField *tsFldP;
3075 TParsedTzidSet::iterator tz;
3078 lineartime_t dtstart;
3079 timecontext_t startcontext = 0, untilcontext = 0;
3083 fieldinteger_t firstmask;
3084 fieldinteger_t lastmask;
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:"
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
3107 if (!fldP) return true; // no field, assignment "ok" (=nop)
3108 // just set as string or add if combine mode
3109 if (aConvDefP->combineSep) {
3111 if (!fldP->isEmpty()) {
3112 // not empty, append with separator
3114 cs[0]=aConvDefP->combineSep;
3116 fldP->appendString(cs);
3118 fldP->appendString(aText);
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
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();
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)
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;
3147 // item has no explicitly specified time zone context,
3148 // parse and leave floating float for now
3149 tctx = TCTX_UNKNOWN; // default to floating
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;
3160 tsFldP->setTimeContext(tctx);
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);
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);
3186 // special conversions
3187 if (aConvDefP->convmode==CONVMODE_DATE) {
3188 tsFldP->makeFloating(); // date-only is forced floating
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);
3202 fldP->setAsString(aText);
3205 return true; // found
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
3219 fHasExplicitTZ = true; // zone explicitly set, not only copied from session's user zone
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
3237 return true; // not set, is ok
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;
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));
3255 PDEBUGPRINTFX(DBG_ERROR,("Invalid TZID value '%s' found (no related VTIMEZONES found and not referring to an internal time zone name)",aText));
3257 return true; // not set, is ok
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);
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);
3273 // assign symbolic time zone name
3274 TimeZoneContextToName(tctx, s, getSessionZones());
3275 fldP->setAsString(s);
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);
3290 if (StrToUShort(aText,n,2)<1) return true; // no integer convertible value, nop
3292 if (!fldP) return true; // no field, assignment "ok" (=nop)
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);
3304 fldP->setAsString(aText+n);
3307 case CONVMODE_VERSION:
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:
3317 TTimestampField *tfP;
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);
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
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
3356 // iCalendar 2.0 type RRULE
3357 dostore=RRULE2toInternal(
3358 aText, // RRULE string to be parsed
3359 dtstart, // reference date for parsing RRULE
3372 // store values into field block
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
3378 sfP->appendChar(freq);
3379 sfP->appendChar(freqmod);
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);
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);
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);
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
3404 break; // just in case
3406 // unknown mode, cannot convert
3410 } // TMimeDirProfileHandler::MIMEStringToField
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)
3432 // determine field ID
3433 sInt16 fid=aConvDefP->fieldid;
3435 // value has field where it can be stored
3436 // - fid is ALWAYS offset by baseoffset
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
3452 // normal text value, apply de-escaping, charset transformation, value list and enum conversion
3453 p = aText.c_str(); // start here
3459 // check for field list separator (if field allows list at all)
3460 if (c==aSeparator && aConvDefP->combineSep) {
3461 p++; // skip separator
3464 // check for escaped chars
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
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);
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
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
3500 val=enumP->enumval; // just use translated value
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
3509 fid, // field ID, can be -1
3510 aRepOffset // 0 or array index
3512 // field conversion error
3513 PDEBUGPRINTFX(DBG_ERROR,(
3514 "TMimeDirProfileHandler::parseValue: MIMEStringToField assignment (fid=%hd, arrindex=%hd) failed",
3520 } // while(more chars in value text)
3521 } // if source and target fields available
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));
3526 } // if fieldid exists
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
3537 // field conversion error
3538 PDEBUGPRINTFX(DBG_ERROR,(
3539 "TMimeDirProfileHandler::parseValue: MIMEStringToField in check mode (no field) failed with val=%s",
3545 // parsed successfully
3547 } // TMimeDirProfileHandler::parseValue
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)
3561 TNameExtIDMap nameextmap;
3562 const TParameterDefinition *paramP;
3563 const char *p,*ep,*vp;
3568 bool fieldoffsetfound;
3569 bool notempty = false;
3571 sInt16 pidx; // parameter index
3572 TEncodingTypes encoding;
3574 // field storage info vars, defaults are used if property has no TPropNameExtension
3575 sInt16 baseoffset=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
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
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)) {
3601 // param value follows at vp
3602 vp=nextunfolded(ep,aMimeMode);
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",
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
3620 // add char to param name (unfolded!)
3624 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("parseProperty: bad parameter %s (missing value)",pname.c_str()));
3627 // parameter name & value isolated, pname=name (if not defaultparam), vp points to value
3628 // - obtain unfolded value
3630 bool dquoted = false;
3631 if (*vp=='"' && aMimeMode==mimo_standard) {
3633 vp=nextunfolded(vp,aMimeMode);
3637 if (isEndOfLineOrText(c)) break;
3639 // within double quoted value, only closing dquote can end it
3641 // swallow closing double quote and proceed (next should be end of value anyway)
3642 vp = nextunfolded(vp,aMimeMode);
3648 // not within double quoted value
3649 if (c==':' || c==';') break; // end of value
3650 // check escaped characters
3652 // escape char, do not check next char for end-of-value (but DO NOT expand \-escaped chars here!!)
3653 vp=nextunfolded(vp,aMimeMode);
3656 val+='\\'; // keep the escaped sequence for later when value is actually processed!
3659 // half-finished escape at end of value, ignore
3665 // cancel QP softbreaks if encoding is already switched to QP at this point
3666 vp=nextunfolded(vp,aMimeMode,encoding==enc_quoted_printable);
3668 // - processing of next param starts here
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);
3679 else if (strucmp(pname.c_str(),"CHARSET")==0) {
3680 // charset specified (mimo_old value-only not supported)
3682 for (k=1; k<numCharSets; k++) {
3683 if (strucmp(val.c_str(),MIMECharSetNames[k])==0) {
3685 charset=TCharSets(k);
3689 if (k>=numCharSets) {
3691 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("========== WARNING: Unknown Charset '%s'",val.c_str()));
3692 // %%% replace 8bit chars with underscore
3693 charset=chs_unknown;
3696 // find param in list now
3697 paramP = aPropP->parameterDefs;
3698 pidx=0; // parameter index
3702 mimeModeMatch(paramP->modeDependency) &&
3703 ((defaultparam && paramP->defaultparam) || strucmp(pname.c_str(),paramP->TCFG_CSTR(paramname))==0)
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)
3719 // - loop through value list
3724 // find end of next value in list
3725 for (n=0,pp=ep; *pp; pp++) {
3727 pp++; // skip the comma
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);
3738 // next value in list
3744 // second pass: read param value(s)
3746 val, // input string, possibly binary (e.g. in case of B64 encoded PHOTO)
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
3757 DEBUGPRINTFX(DBG_PARSE,(
3758 "TMimeDirProfileHandler::parseProperty: %s: value not parsed: %s",
3765 } // if (param known)
3767 paramP=paramP->next;
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
3778 // - prepare for second pass: check if set of param values match
3779 // an entry in the nameexts list
3780 TPropNameExtension *propnameextP = aPropP->nameExts;
3783 while (propnameextP) {
3784 // check if entry matches parsed extendsname param values
3786 ((propnameextP->musthave_ids & nameextmap) == propnameextP->musthave_ids) && // needed there
3787 ((propnameextP->forbidden_ids & nameextmap) == 0) // none of the forbidden ones there
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)
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
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);
3821 TItemField *e_basefldP = aItem.getField(e_fid);
3822 TItemField *e_fldP = NULL;
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
3832 dostore=true; // at least one field exists, we might store
3834 // check if we can test more repetitions
3836 if (aRepArray[repid]+1<maxrep || maxrep==REP_ARRAY) {
3837 // we can increment and try next repetition
3841 break; // no more possible repetitions with this position rule (check next rule)
3844 if (dostore) break; // we can store now
3845 } // if repeat not yet exhausted
3846 } // if position rule matches
3848 propnameextP=propnameextP->next;
3849 } // while search for matching nameExts entry
3850 // abort if we can't store
3852 aText=p; // this is what we've read so far
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
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)) {
3865 // Note: for valuelists, this is the normal loop exit case as we are not limited by numValues
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)));
3871 break; // all available values read
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!!)
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
3893 aMimeMode, // MIME mode (older or newer vXXX format compatibility)
3894 false, // no parameter
3895 aPropP->numValues > 1 // structured if multiple values
3897 PDEBUGPRINTFX(DBG_PARSE+DBG_EXOTIC,(
3898 "TMimeDirProfileHandler::parseProperty: %s: value not parsed: %s",
3899 aPropP->TCFG_CSTR(propname),
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
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),
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;
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;
3929 aItem.adjustFidAndIndex(fid,repoffset);
3930 // requesting the pointer creates the field if it does not already exist
3931 aItem.getArrayField(fid,repoffset,false);
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
3942 // update read pointer past end of what we've scanned (but not necessarily up
3943 // to next property beginning)
3947 } // TMimeDirProfileHandler::parseProperty
3950 // parse MIME-DIR from specified string into item
3951 bool TMimeDirProfileHandler::parseMimeDir(const char *aText, TMultiFieldItem &aItem)
3953 // start with empty item
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();
3968 } // TMimeDirProfileHandler::parseMimeDir
3971 // parameter string for QP encoding. Needed when skipping otherwise unknown properties
3972 #define QP_ENCODING_PARAM "ENCODING=QUOTED-PRINTABLE"
3974 // parse MIME-DIR level from specified string into item
3975 bool TMimeDirProfileHandler::parseLevels(
3977 TMultiFieldItem &aItem,
3978 const TProfileDefinition *aProfileP,
3983 cAppCharP p, propname;
3985 sInt16 foundmandatory=0;
3986 const sInt16 maxreps = 50;
3987 sInt16 repArray[maxreps];
3988 bool atStart = aRootLevel;
3990 // reset repetition counts
3991 for (sInt16 k=0; k<maxreps; k++) repArray[k]=0;
3993 sInt16 disabledLevels=0;
3994 // set level marker field, if any is defined
3995 sInt16 fid=aProfileP->levelConvdef.fieldid;
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;
4002 // if enumdefs, content is set to first enumdef's enumval (NOT enumtext!!)
4003 aItem.getField(fid)->setAsString(enumP->TCFG_CSTR(enumval));
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
4011 // start of property parsing
4012 // - reset TZID flag
4013 fPropTZIDtctx = TCTX_UNKNOWN;
4014 // - prepare scanning
4016 propname = p; // assume name starts at beginning of text
4018 // determine property name end
4022 // end of text reached w/o property name
4023 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("parseMimeDir: no property name found, text=%s",aText));
4026 if (c==':' || c==';') break;
4029 // %%% add capability to save group names in fields
4030 propname = p+1; // skip group
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
4043 // - skip eventual parameters for broken implementations like Intellisync/Synchrologic
4044 if (*p==';') while (*p && *p!=':') p++;
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
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));
4056 atStart=false; // no special lead-in check any more
4060 // value determines new level to enter
4061 if (disabledLevels==0) {
4062 // search for sublevel
4063 const TProfileDefinition *subprofileP = aProfileP->subLevels;
4064 while (subprofileP) {
4067 mimeModeMatch(subprofileP->modeDependency) &&
4068 strucmp(lnam,subprofileP->TCFG_CSTR(levelName),l)==0
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
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);
4085 // identify or add this in the session zones
4087 if (VTIMEZONEtoInternal(s2.c_str(), tctx, getSessionZones(), NULL, &tzid)) {
4088 // time zone identified
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()));
4094 // remember it by original name for TZID parsing
4095 fParsedTzidSet[tzid] = tctx;
4098 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("parseMimeDir: could not parse VTIMEZONE: %s",s.c_str()));
4102 // ordinary non-root level
4103 if (!parseLevels(p,aItem,subprofileP,false)) return false;
4105 // - now continue on this level
4110 subprofileP=subprofileP->next;
4113 // no matching sublevel found, disable this level
4118 // already disabled, just nest
4121 } // BEGIN not on rootlevel
4123 else if (strucmp(propname,"END",n)==0) {
4126 // - skip eventual parameters for broken implementations like Intellisync/Synchrologic
4127 if (*p==';') while (*p && *p!=':') p++;
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
4134 if (disabledLevels>0) {
4135 // end of a disabled level, just un-nest
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));
4144 // correct end of level
4145 aText=p; // points to terminator, which is correct for end-of-level
4146 // break scanner loop
4150 else if (disabledLevels==0) {
4152 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("parseMimeDir: root level does not start with BEGIN: %s",aText));
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)
4162 const TPropertyDefinition *parsePropP;
4166 mimeModeMatch(propP->modeDependency) && // none or matching mode dependency
4167 strucmp(propname,propP->TCFG_CSTR(propname),n)==0
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)
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;
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;
4199 // does not depend on rule, parse anyway
4202 // check if this is last prop of list
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;
4214 // now parse (or save for delayed parsing later)
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;
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);
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++;
4237 propparsed=true; // but is "parsed" for loop
4238 break; // parse next
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
4246 fMimeDirMode // MIME-DIR mode
4248 // property parsed successfully
4250 // count mandarory properties found
4251 if (parsePropP->mandatory) foundmandatory++;
4252 break; // parse next
4254 // if not successfully parsed, continue with next property which
4255 // can have the same name, but different parameter definitions
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
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)
4265 // not start of group
4269 } // while all properties
4270 } // else: neither BEGIN nor END
4273 PDEBUGPRINTFX(DBG_PARSE,("parseMimeDir: property unknown: %" FMT_LENGTH(".30") "s",FMT_LENGTH_LIMITED(30,aText)));
4274 // skip parsed part (the name)
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
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
4289 p=nextunfolded(p,fMimeDirMode,isqp); // cancel soft breaks if we are in QP encoded property
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
4295 } while (*aText); // exit if end of string
4296 // now parse delayed ones (list is in delay order already)
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",
4305 FMT_LENGTH_LIMITED(30,(*pos).start)
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
4313 fMimeDirMode // MIME-DIR mode
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++;
4320 // delayed parsing failed
4321 PDEBUGPRINTFX(DBG_PARSE,("parseMimeDir: failed delayed parsing of property %" FMT_LENGTH(".30") "s",FMT_LENGTH_LIMITED(30,(*pos).start)));
4324 // we don't need them any more - clear delayed props
4325 fDelayedProps.clear();
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
4335 // unsuccessful parsing
4338 // successful parsing done
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
4347 void TMimeDirProfileHandler::getOptionsFromDatastore(void)
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;
4370 // generate Data item (includes header and footer)
4371 void TMimeDirProfileHandler::generateText(TMultiFieldItem &aItem, string &aString)
4373 // get options datastore if one is related
4374 getOptionsFromDatastore();
4376 PDEBUGPRINTFX(DBG_GEN+DBG_HOT,("Generating...."));
4377 aItem.debugShowItem(DBG_DATA+DBG_GEN);
4379 // baseclass just generates MIME-DIR
4380 fBeginEndNesting=0; // no BEGIN out yet
4381 generateMimeDir(aItem,aString);
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);
4389 } // TMimeDirProfileHandler::generateText
4392 // parse Data item (includes header and footer)
4393 bool TMimeDirProfileHandler::parseText(const char *aText, stringSize aTextSize, TMultiFieldItem &aItem)
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
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);
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
4413 PDEBUGPRINTFX(DBG_PARSE,("Successfully parsed: "));
4414 aItem.debugShowItem(DBG_DATA+DBG_PARSE);
4419 PDEBUGPRINTFX(DBG_ERROR,("Failed parsing item"));
4422 } // TMimeDirProfileHandler::parseText
4425 bool TMimeDirProfileHandler::parseForProperty(SmlItemPtr_t aItemP, const char *aPropName, string &aString)
4427 if (aItemP && aItemP->data)
4428 return parseForProperty(smlPCDataToCharP(aItemP->data),aPropName,aString);
4431 } // TMimeDirProfileHandler::parseForProperty
4434 // scan Data item for specific property (used for quick type tests)
4435 bool TMimeDirProfileHandler::parseForProperty(const char *aText, const char *aPropName, string &aString)
4437 uInt16 n=strlen(aPropName);
4439 const char *p=aText;
4440 // find property end
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
4450 // find next property beginning
4452 p=nextunfolded(p,fMimeDirMode,true);
4453 } while (*p && ((*p)<0x20));
4454 // set to beginning of next
4459 } // TMimeDirProfileHandler::parseForProperty
4463 // helper for newCTDataPropList
4464 void TMimeDirProfileHandler::enumerateLevels(const TProfileDefinition *aProfileP, SmlPcdataListPtr_t *&aPcdataListPP, const TProfileDefinition *aSelectedProfileP, TMimeDirItemType *aItemTypeP)
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);
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);
4487 subprofileP=subprofileP->next;
4490 } // TMimeDirProfileHandler::enumerateLevels
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)
4500 // add it to the list (but only if we don't already have it)
4501 while (*aCTDataPropListPP) {
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;
4510 aCTDataPropListPP = &((*aCTDataPropListPP)->next);
4512 // if not detected duplicate, add it now
4514 addCTDataPropToList(aCTDataPropP,aCTDataPropListPP);
4516 } // addCTDataPropToListIfNotExists
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)
4526 SmlDevInfCTDataPropPtr_t propdataP = SML_NEW(SmlDevInfCTDataProp_t);
4527 propdataP->param = NULL; // no params
4528 propdataP->prop = aPropCTData;
4529 addCTDataPropToListIfNotExists(propdataP, aCTDataPropListPP);
4530 } // addNewPropToListIfNotExists
4534 // helper for newCTDataPropList
4535 void TMimeDirProfileHandler::enumerateProperties(const TProfileDefinition *aProfileP, SmlDevInfCTDataPropListPtr_t *&aPropListPP, const TProfileDefinition *aSelectedProfileP, TMimeDirItemType *aItemTypeP)
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);
4551 // normal profile defined in config, add properties as defined in profile, avoid duplicates
4552 const TPropertyDefinition *propP = aProfileP->propertyDefs;
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;
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
4570 paramP->defaultparam &&
4571 fMimeDirMode==mimo_old &&
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
4577 // add the name extending enum values as param names
4578 TEnumerationDef *enumP = paramP->convdef.enumdefs;
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);
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;
4594 if (!TCFG_ISEMPTY(enumP->enumtext) && enumP->enummode==enm_translate) {
4595 // create new valenum list entry
4596 nextValenumPP = addPCDataStringToList(TCFG_CSTR(enumP->enumtext),nextValenumPP);
4600 // - add it to the params list
4601 nextParamPP = addCTDataToList(paramdataP,nextParamPP);
4603 } // if param to be shown
4604 paramP=paramP->next;
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;
4613 // Field type (we need it later when we have a maxsize, which is only allowed together with a datatype in 1.1 DTD)
4615 fieldDefP = fItemTypeP->getFieldDefinition(fid);
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)
4621 // If any field requests no truncation, report noTruncate
4622 if (getSession()->getSyncMLVersion()>=syncml_vers_1_2 && fItemTypeP->getFieldOptions(fid)->notruncate)
4626 // - calculate our own maxoccur (value in our field options is not used for now %%%)
4628 if (getSession()->getSyncMLVersion()>=syncml_vers_1_2) {
4629 if (propP->nameExts) {
4630 // name extensions determine repeat count
4631 TPropNameExtension *extP = propP->nameExts;
4633 if (!extP->readOnly) {
4634 if (extP->maxRepeat==REP_ARRAY) {
4636 maxOccur=0; // unlimited
4637 break; // prevent other name extensions to intervene
4640 // limited number of occurrences, add to count
4641 maxOccur+=extP->maxRepeat;
4649 // not repeating: property may not occur more than once
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)
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];
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));
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
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);
4686 subprofileP=subprofileP->next;
4689 } // TMimeDirProfileHandler::enumerateProperties
4692 // helper: enumerate filter properties
4693 void TMimeDirProfileHandler::enumeratePropFilters(const TProfileDefinition *aProfileP, SmlPcdataListPtr_t &aFilterProps, const TProfileDefinition *aSelectedProfileP, TMimeDirItemType *aItemTypeP)
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;
4702 (propP->showInCTCap || aProfileP==aSelectedProfileP) &&
4703 propP->convdefs && propP->convdefs[0].convmode!=CONVMODE_VERSION && propP->convdefs[0].convmode!=CONVMODE_PRODID
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
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);
4722 subprofileP=subprofileP->next;
4724 } // TMimeDirProfileHandler::enumeratePropFilters
4727 #ifdef OBJECT_FILTERING
4729 // Filtering: add keywords and property names to filterCap
4730 void TMimeDirProfileHandler::addFilterCapPropsAndKeywords(SmlPcdataListPtr_t &aFilterKeywords, SmlPcdataListPtr_t &aFilterProps, TTypeVariantDescriptor aVariantDescriptor, TSyncItemType *aItemTypeP)
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
4741 #endif // OBJECT_FILTERING
4745 // generates SyncML-Devinf property list for type
4746 SmlDevInfCTDataPropListPtr_t TMimeDirProfileHandler::newCTDataPropList(TTypeVariantDescriptor aVariantDescriptor, TSyncItemType *aItemTypeP)
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);
4782 } // TMimeDirProfileHandler::newCTDataPropList
4785 // Analyze CTCap part of devInf
4786 bool TMimeDirProfileHandler::analyzeCTCap(SmlDevInfCTCapPtr_t aCTCapP, TSyncItemType *aItemTypeP)
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);
4793 SmlDevInfCTDataPropListPtr_t proplistP = aCTCapP->prop;
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;
4802 // force mandatory properties to be always "available"
4803 setfieldoptions(NULL,fProfileDefinitionP,itemTypeP);
4805 // now we have received fields
4806 itemTypeP->fReceivedFieldDefs=true;
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
4817 } // TMimeDirProfileHandler::analyzeCTCap
4821 // %%%%% dummy for now
4822 bool TMimeDirProfileHandler::setLevelOptions(const char *aLevelName, bool aEnable, TMimeDirItemType *aItemTypeP)
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
4831 } // TMimeDirProfileHandler::setLevelOptions
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
4845 sInt32 propsize = FIELD_OPT_MAXSIZE_NONE;
4846 sInt32 maxOccur = 0; // none by default
4847 bool noTruncate=false;
4848 const char* propname = NULL;
4850 // get params from CTCap property definition (if any)
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;
4862 StrToLong(smlPCDataToCharP(aPropP->maxsize),propsize);
4865 // get eventual maxOccur
4866 if (aPropP->maxoccur) {
4867 StrToLong(smlPCDataToCharP(aPropP->maxoccur),maxOccur);
4869 // get eventual noTruncate
4870 if (aPropP->flags & SmlDevInfNoTruncate_f)
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;
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);
4882 // get sublevel name
4883 const char *slname = smlPCDataToCharP(valenumP->data);
4884 setLevelOptions(slname,true,aItemTypeP); // enable this one
4886 valenumP=valenumP->next;
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;
4896 (propname==NULL && propdefP->mandatory) ||
4897 (propname && (strucmp(propname,propdefP->TCFG_CSTR(propname))==0))
4899 // match (or enabling mandatory) -> enable all fields that are related to this property
4901 for (i=0; i<propdefP->numValues; i++) {
4903 j=propdefP->convdefs[i].fieldid;
4904 bs=fieldBlockSize(propdefP->convdefs[i]);
4907 TPropNameExtension *pneP = propdefP->nameExts;
4910 o = pneP->fieldidoffs;
4913 // for all repetitions (but only for first if mode is REP_ARRAY
4914 // or field is an array)
4916 // make entire field block addressed by this convdef available
4917 // and set maxoccur/notruncate
4918 for (q=0; q<bs; q++) {
4920 fo = aItemTypeP->getFieldOptions(j+o+q);
4921 if (fo) fo->available=true;
4923 // set size if specified (only for first field in block)
4924 fo = aItemTypeP->getFieldOptions(j+o);
4926 if (propsize!=FIELD_OPT_MAXSIZE_NONE) fo->maxsize=propsize;
4927 // set maxoccur if specified
4928 if (maxOccur!=0) fo->maxoccur=maxOccur;
4930 if (noTruncate) fo->notruncate=true;
4935 ++r < pneP->maxRepeat &&
4936 pneP->maxRepeat!=REP_ARRAY
4937 #ifdef ARRAYFIELD_SUPPORT
4938 && !aItemTypeP->getFieldDefinition(j)->array
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);
4952 if (propsize!=FIELD_OPT_MAXSIZE_NONE) fo->maxsize=propsize;
4953 // set maxoccur if specified
4954 if (maxOccur!=0) fo->maxoccur=maxOccur;
4956 if (noTruncate) fo->notruncate=true;
4961 // - parameter values
4962 const TParameterDefinition *paramdefP = propdefP->parameterDefs;
4965 j=paramdefP->convdef.fieldid;
4966 bs=fieldBlockSize(paramdefP->convdef);
4969 TPropNameExtension *pneP = propdefP->nameExts;
4972 o = pneP->fieldidoffs;
4975 // for all repetitions
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
4986 ++r < pneP->maxRepeat &&
4987 pneP->maxRepeat!=REP_ARRAY
4988 #ifdef ARRAYFIELD_SUPPORT
4989 && !aItemTypeP->getFieldDefinition(j)->array
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;
5005 paramdefP=paramdefP->next;
5007 } // if known property
5008 propdefP=propdefP->next;
5010 // now enable fields in all subprofiles
5011 const TProfileDefinition *subprofileP = aProfileP->subLevels;
5012 while (subprofileP) {
5013 setfieldoptions(aPropP,subprofileP,aItemTypeP);
5015 subprofileP=subprofileP->next;
5017 } // TMimeDirProfileHandler::setfieldoptions
5021 // set mode (for those profiles that have more than one, like MIME-DIR's old/standard)
5022 void TMimeDirProfileHandler::setProfileMode(sInt32 aMode)
5024 fProfileMode = aMode;
5025 // determine derived mime mode
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
5030 } // TMimeDirProfileHandler::setProfileMode
5033 #ifndef NO_REMOTE_RULES
5034 void TMimeDirProfileHandler::setRemoteRule(const string &aRemoteRuleName)
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;
5044 } // TMimeDirProfileHandler::setRemoteRule
5049 bool TMimeDirProfileHandler::mimeModeMatch(TMimeDirMode aMimeMode)
5052 aMimeMode==numMimeModes || // not dependent on MIME mode
5053 aMimeMode==fMimeDirMode;
5054 } // TMimeDirProfileHandler::mimeModeMatch
5058 /* end of TMimeDirProfileHandler implementation */
5061 // Utility functions
5062 // -----------------
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)
5076 lineartime_t startTime = lineartime2timeonly(aStart);
5077 if (startTime!=0) return 0; // start not at midnight -> no allday
5078 lineartime_t endTime = lineartime2timeonly(aEnd);
5080 if (aStart==aEnd) aEnd += linearDateToTimeFactor; // one day
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;
5087 return 0; // allday criteria not met
5088 // now calculate number of days
5089 return (aEnd-aStart) / linearDateToTimeFactor;
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)
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
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);
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
5120 void MakeAllday(lineartime_t &aStart, lineartime_t &aEnd, sInt16 aDays)
5122 lineartime_t duration = 0;
5124 // first calculate duration (assuming that even if there's a timezone problem, both
5125 // timestamps will be affected so duration is still correct)
5127 // use implicit duration
5128 duration = aEnd-aStart;
5130 // use explicit duration
5131 duration = aDays * linearDateToTimeFactor;
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);
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
5148 /// @note fields will be made floating and dateonly
5149 void MakeAllday(TItemField *aStartFldP, TItemField *aEndFldP, timecontext_t aTimecontext, sInt16 aDays)
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)
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);
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);
5173 } // namespace sysync