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(TCFG_CSTR(fLastProperty->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
615 sInt16 groupFieldID = FID_NOT_SUPPORTED; // no group field ID by default
616 cAppCharP gfin = getAttr(aAttributes, "groupfield");
618 groupFieldID = fFieldListP->fieldIndex(gfin);
619 if (groupFieldID==VARIDX_UNDEFINED) {
620 fail("'groupfield' '%s' does not exist in field list '%s'",gfin,fFieldListP->getName());
624 // - delayed processing
625 sInt16 delayedprocessing=0; // default to 0
626 if (!getAttrShort(aAttributes,"delayedparsing",delayedprocessing,true))
627 return fail ("bad 'delayedparsing' specification");
628 // - create property now and open new level of parsing
629 fOpenProperty=fOpenProfile->addProperty(nam,numval,mandatory,showprop,suppressempty,delayedprocessing,*valsep,fPropertyGroupID,canfilter,modeDep, *altvalsep, groupFieldID);
630 fLastProperty=fOpenProperty; // for group checking
631 #ifndef NO_REMOTE_RULES
632 // - add rule dependency (pointer will be resolved later)
633 fOpenProperty->dependsOnRemoterule=isRuleDep;
634 fOpenProperty->ruleDependency=NULL; // not known yet
635 TCFG_ASSIGN(fOpenProperty->dependencyRuleName,depRuleName); // save name for later resolving
637 startNestedParsing();
641 return inherited::localStartElement(aElementName, aAttributes, aLine); // call inherited
644 if (strucmp(aElementName,"profile")==0) {
645 // <profile name="VCARD" nummandatory="2">
647 return fail("'profile' cannot be defined more than once");
648 // new profile starts new property group
651 nam = getAttr(aAttributes,"name");
653 return fail("'profile' must have 'name' attribute");
654 // - get number of mandatory properties
655 if (!getAttrShort(aAttributes,"nummandatory",nummand,false))
656 return fail ("missing or bad 'nummandatory' specification");
657 // create root profile
658 fRootProfileP = new TProfileDefinition(NULL,nam,nummand,false,profm_custom,numMimeModes); // root needs no selection to be shown, is always a custom profile, and not mode dependent
659 // parsing nested elements in this TConfigElement
660 fOpenProfile=fRootProfileP; // current open profile
661 startNestedParsing();
663 else if (strucmp(aElementName,"unfloattimestamps")==0)
664 expectBool(fUnfloatFloating);
665 else if (strucmp(aElementName,"vtimezonegenmode")==0)
666 expectEnum(sizeof(fVTimeZoneGenMode),&fVTimeZoneGenMode,VTimeZoneGenModes,numVTimeZoneGenModes);
667 else if (strucmp(aElementName,"tzidgenmode")==0)
668 expectEnum(sizeof(fTzIdGenMode),&fTzIdGenMode,VTzIdGenModes,numTzIdGenModes);
671 return inherited::localStartElement(aElementName, aAttributes, aLine);
675 } // TMIMEProfileConfig::localStartElement
679 #ifndef NO_REMOTE_RULES
680 // resolve remote rule dependencies in profile (recursive)
681 static void resolveRemoteRuleDeps(TProfileDefinition *aProfileP, TAgentConfig *aSessionConfigP)
683 TProfileDefinition *profileP = aProfileP;
685 // resolve properties
686 TPropertyDefinition *propP = profileP->propertyDefs;
688 // check for rule-dependent props
689 if (propP->dependsOnRemoterule) {
690 propP->ruleDependency=NULL; // assume the "other" rule entry
691 if (!TCFG_ISEMPTY(propP->dependencyRuleName)) {
693 TRemoteRulesList::iterator pos;
694 for(pos=aSessionConfigP->fRemoteRulesList.begin();pos!=aSessionConfigP->fRemoteRulesList.end();pos++) {
695 if (strucmp(TCFG_CSTR(propP->dependencyRuleName),(*pos)->getName())==0) {
696 // found rule by name
697 propP->ruleDependency=(*pos);
701 if (propP->ruleDependency==NULL) {
703 StringObjPrintf(s,"property '%s' depends on unknown rule '%s'",TCFG_CSTR(propP->propname),TCFG_CSTR(propP->dependencyRuleName));
704 SYSYNC_THROW(TConfigParseException(s.c_str()));
711 // resolve subprofiles
712 resolveRemoteRuleDeps(profileP->subLevels,aSessionConfigP);
714 profileP=profileP->next;
716 } // resolveRemoteRuleDeps
720 void TMIMEProfileConfig::localResolve(bool aLastPass)
723 // check for required settings
725 SYSYNC_THROW(TConfigParseException("empty 'mimeprofile' not allowed"));
726 #ifndef NO_REMOTE_RULES
727 // recursively resolve remote rule dependencies in all properties
728 resolveRemoteRuleDeps(
730 static_cast<TAgentConfig *>(static_cast<TRootConfig *>(getRootElement())->fAgentConfigP)
735 inherited::localResolve(aLastPass);
736 } // TMIMEProfileConfig::localResolve
738 #endif // CONFIGURABLE_TYPE_SUPPORT
745 // implementation of MIME-DIR info classes
747 #pragma exceptions off
748 #define EXCEPTIONS_HERE 0
751 TEnumerationDef::TEnumerationDef(const char *aEnumName, const char *aEnumVal, TEnumMode aMode, sInt16 aNameExtID)
754 TCFG_ASSIGN(enumtext,aEnumName);
755 TCFG_ASSIGN(enumval,aEnumVal);
757 nameextid=aNameExtID;
758 } // TEnumerationDef::TEnumerationDef
761 TEnumerationDef::~TEnumerationDef()
763 // make sure entire chain gets deleted
764 if (next) delete next;
765 } // TEnumerationDef::~TEnumerationDef
769 TConversionDef::TConversionDef()
771 fieldid=FID_NOT_SUPPORTED;
775 } // TConversionDef::TConversionDef
778 TConversionDef::~TConversionDef()
780 // make sure enum list gets deleted
781 if (enumdefs) delete enumdefs;
782 } // TEnumerationDef::~TEnumerationDef
785 TConversionDef *TConversionDef::setConvDef(
795 } // TConversionDef::setConvDef
798 const TEnumerationDef *TConversionDef::findEnumByName(const char *aName, sInt16 n)
801 TEnumerationDef *enumP = enumdefs;
802 TEnumerationDef *defaultenumP = NULL;
806 (enumP->enummode==enm_translate || enumP->enummode==enm_ignore) &&
807 strucmp(aName,TCFG_CSTR(enumP->enumtext),n)==0
808 ) break; // found full match
809 // check prefix match
811 enumP->enummode==enm_prefix &&
812 (TCFG_SIZE(enumP->enumtext)==0 || strucmp(aName,TCFG_CSTR(enumP->enumtext),TCFG_SIZE(enumP->enumtext))==0)
813 ) break; // found prefix match (or prefix entry with no text, which means match as well)
814 // otherwise: remember if this is a default
815 else if (enumP->enummode==enm_default_value) {
816 // default value entry
817 defaultenumP=enumP; // anyway: remember default value entry
818 // allow searching default value by name (for "has","hasnot" parsing via getExtIDbit())
819 if (!(TCFG_ISEMPTY(enumP->enumtext)) && strucmp(aName,TCFG_CSTR(enumP->enumtext),n)==0)
820 break; // found named default value
825 return enumP ? enumP : defaultenumP;
826 } // TConversionDef::findEnumByName
829 const TEnumerationDef *TConversionDef::findEnumByVal(const char *aVal, sInt16 n)
832 TEnumerationDef *enumP = enumdefs;
833 TEnumerationDef *defaultenumP = NULL;
837 (enumP->enummode==enm_translate || enumP->enummode==enm_ignore) &&
838 strucmp(aVal,TCFG_CSTR(enumP->enumval),n)==0
840 // check prefix match
842 enumP->enummode==enm_prefix &&
843 (TCFG_SIZE(enumP->enumval)==0 || strucmp(aVal,TCFG_CSTR(enumP->enumval),TCFG_SIZE(enumP->enumval))==0)
844 ) break; // found prefix match (or prefix entry with no value, which means match as well)
845 // remember if this is a default
846 else if (enumP->enummode == enm_default_name) defaultenumP=enumP; // remember default
850 return enumP ? enumP : defaultenumP;
851 } // TConversionDef::findEnumByVal
854 void TConversionDef::addEnum(const char *aEnumName, const char *aEnumVal, TEnumMode aMode)
856 TEnumerationDef **enumPP = &enumdefs;
857 while(*enumPP!=NULL) enumPP=&((*enumPP)->next); // find last in chain
858 *enumPP = new TEnumerationDef(aEnumName,aEnumVal,aMode); // w/o name extension
859 } // TConversionDef::addEnum
863 // add enum for name extension, auto-creates property-unique name extension ID
864 void TConversionDef::addEnumNameExt(TPropertyDefinition *aProp, const char *aEnumName, const char *aEnumVal, TEnumMode aMode)
866 TEnumerationDef **enumPP = &enumdefs;
867 while(*enumPP!=NULL) enumPP=&((*enumPP)->next); // find last in chain
868 if (aProp->nextNameExt>31)
870 SYSYNC_THROW(TSyncException(DEBUGTEXT("more than 32 name extensions","mdit3")));
872 return; // silently ignore
874 *enumPP = new TEnumerationDef(aEnumName,aEnumVal, aMode, aProp->nextNameExt++);
875 } // TConversionDef::addEnumNameExt
878 TParameterDefinition::TParameterDefinition(
879 const char *aName, bool aDefault, bool aExtendsName, bool aShowNonEmpty, bool aShowInCTCap, TMimeDirMode aModeDep
882 TCFG_ASSIGN(paramname,aName);
883 defaultparam=aDefault;
884 extendsname=aExtendsName;
885 shownonempty=aShowNonEmpty;
886 showInCTCap=aShowInCTCap;
887 modeDependency=aModeDep;
888 } // TParameterDefinition::TParameterDefinition
891 TParameterDefinition::~TParameterDefinition()
893 if (next) delete next;
894 } // TParameterDefinition::~TParameterDefinition
897 TNameExtIDMap TParameterDefinition::getExtIDbit(const char *aEnumName, sInt16 n)
899 const TEnumerationDef *enumP=convdef.findEnumByName(aEnumName,n);
901 return ((TNameExtIDMap)1<<enumP->nameextid);
904 } // TParameterDefinition::getExtIDbit
907 TPropNameExtension::TPropNameExtension(
908 TNameExtIDMap aMusthave_ids, TNameExtIDMap aForbidden_ids, TNameExtIDMap aAddtlSend_ids,
909 sInt16 aFieldidoffs, sInt16 aMaxRepeat, sInt16 aRepeatInc, sInt16 aMinShow,
910 bool aOverwriteEmpty, bool aReadOnly, sInt16 aRepeatID
913 musthave_ids=aMusthave_ids;
914 forbidden_ids=aForbidden_ids;
915 addtlSend_ids=aAddtlSend_ids;
916 fieldidoffs=aFieldidoffs;
917 maxRepeat=aMaxRepeat;
918 repeatInc=aRepeatInc;
920 overwriteEmpty=aOverwriteEmpty;
923 } // TPropNameExtension::TPropNameExtension
926 TPropNameExtension::~TPropNameExtension()
928 if (next) delete next;
929 } // TPropNameExtension::~TPropNameExtension
932 TPropertyDefinition::TPropertyDefinition(const char* aName, sInt16 aNumVals, bool aMandatory, bool aShowInCTCap, bool aSuppressEmpty, uInt16 aDelayedProcessing, char aValuesep, char aAltValuesep, uInt16 aPropertyGroupID, bool aCanFilter, TMimeDirMode aModeDep, sInt16 aGroupFieldID)
935 TCFG_ASSIGN(propname,aName);
936 nameExts = NULL; // none yet
937 nextNameExt = 0; // no enums with name extensions defined yet for this property
938 valuelist = false; // no value list by default
939 expandlist = false; // not expanding value list into repeating property by default
940 valuesep = aValuesep; // separator for structured-value and value-list properties
941 altvaluesep = aAltValuesep; // alternate separator for structured-value and value-list properties (for parsing only)
942 groupFieldID = aGroupFieldID; // fid for field that contains the group tag (prefix to the property name, like "a" in "a.TEL:079122327")
943 propGroup = aPropertyGroupID; // property group ID
944 if (aNumVals==NUMVAL_LIST || aNumVals==NUMVAL_REP_LIST) {
947 expandlist = aNumVals==NUMVAL_REP_LIST;
948 numValues = 1; // we accept a single convdef only
952 numValues = aNumVals;
954 // create convdefs array
955 convdefs = new TConversionDef[numValues];
956 parameterDefs = NULL; // none yet
957 mandatory = aMandatory;
958 showInCTCap = aShowInCTCap;
959 canFilter = aCanFilter;
960 suppressEmpty = aSuppressEmpty;
961 delayedProcessing = aDelayedProcessing;
962 modeDependency = aModeDep;
963 #ifndef NO_REMOTE_RULES
964 // not dependent on rule yet (as rules do not exists at TPropertyDefinition creation,
965 // dependency will be added later, if any)
966 dependsOnRemoterule = false;
967 ruleDependency = NULL;
969 } // TPropertyDefinition::TPropertyDefinition
972 TPropertyDefinition::~TPropertyDefinition()
974 // delete name extensions
975 if (nameExts) delete nameExts;
976 // delete convdefs array
977 if (convdefs) delete [] convdefs;
978 // delete parameter definitions
979 if (parameterDefs) delete parameterDefs;
980 // delete rest of chain
981 if (next) delete next;
982 } // TPropertyDefinition::~TPropertyDefinition
985 TConversionDef *TPropertyDefinition::setConvDef(sInt16 aValNum, sInt16 aFieldId,sInt16 aConvMode,char aCombSep)
987 if (aValNum<0 || aValNum>=numValues)
989 SYSYNC_THROW(TSyncException(DEBUGTEXT("setConvDef for Property with bad value number","mdit4")));
991 return NULL; // silently ignore
993 return convdefs[aValNum].setConvDef(aFieldId,aConvMode,aCombSep);
994 }; // TPropertyDefinition::TConversionDef
997 void TPropertyDefinition::addNameExt(TProfileDefinition *aRootProfile, // for profile-global RepID generation
998 TNameExtIDMap aMusthave_ids, TNameExtIDMap aForbidden_ids, TNameExtIDMap aAddtlSend_ids,
999 sInt16 aFieldidoffs, sInt16 aMaxRepeat, sInt16 aRepeatInc, sInt16 aMinShow,
1000 bool aOverwriteEmpty, bool aReadOnly, sInt16 aShareCountOffs
1003 TPropNameExtension **namextPP = &nameExts;
1004 while(*namextPP!=NULL) namextPP=&((*namextPP)->next); // find last in chain
1006 if (aMaxRepeat==REP_ARRAY)
1007 aMinShow=0; // by default, show nothing if array is empty
1009 aMinShow=aMaxRepeat; // auto mode, show all repetitions
1011 *namextPP = new TPropNameExtension(
1012 aMusthave_ids,aForbidden_ids,aAddtlSend_ids,aFieldidoffs,
1013 aMaxRepeat,aRepeatInc,aMinShow,aOverwriteEmpty,aReadOnly,
1014 // readOnly alternative parsing <position> might want to share the
1015 // repeat count with previous <position> occurrences
1016 aShareCountOffs ? aRootProfile->nextRepID-aShareCountOffs : aRootProfile->nextRepID++
1018 } // TPropertyDefinition::addNameExt
1021 TParameterDefinition *TPropertyDefinition::addParam(
1022 const char *aName, bool aDefault, bool aExtendsName, bool aShowNonEmpty, bool aShowInCTCap, TMimeDirMode aModeDep
1025 TParameterDefinition **paramPP = ¶meterDefs;
1026 while(*paramPP!=NULL) paramPP=&((*paramPP)->next); // find last in chain
1027 *paramPP = new TParameterDefinition(aName,aDefault,aExtendsName,aShowNonEmpty,aShowInCTCap, aModeDep);
1029 } // TPropertyDefinition::addParam
1032 // find parameter by name
1033 TParameterDefinition *TPropertyDefinition::findParameter(const char *aNam, sInt16 aLen)
1035 TParameterDefinition *paramP = parameterDefs;
1037 if (strucmp(aNam,TCFG_CSTR(paramP->paramname),aLen)==0)
1038 return paramP; // found
1039 paramP=paramP->next; // next
1043 } // TPropertyDefinition::findParameter
1046 TProfileDefinition::TProfileDefinition(
1047 TProfileDefinition *aParentProfileP, // parent profile
1048 const char *aProfileName, // name
1049 sInt16 aNumMandatory,
1050 bool aShowInCTCapIfSelectedOnly,
1051 TProfileModes aProfileMode,
1052 TMimeDirMode aModeDep
1055 parentProfile=aParentProfileP; // NULL if root
1058 TCFG_ASSIGN(levelName,aProfileName);
1059 shownIfSelectedOnly = aShowInCTCapIfSelectedOnly;
1060 profileMode = aProfileMode;
1061 modeDependency = aModeDep;
1063 numMandatoryProperties=aNumMandatory;
1068 } // TProfileDefinition::TProfileDefinition
1071 TProfileDefinition::~TProfileDefinition()
1073 if (propertyDefs && ownsProps) delete propertyDefs;
1074 if (subLevels) delete subLevels;
1075 if (next) delete next;
1076 } // TProfileDefinition::~TProfileDefinition
1079 TProfileDefinition *TProfileDefinition::addSubProfile(
1080 const char *aProfileName, // name
1081 sInt16 aNumMandatory,
1082 bool aShowInCTCapIfSelectedOnly,
1083 TProfileModes aProfileMode,
1084 TMimeDirMode aModeDep
1087 TProfileDefinition **profilePP=&subLevels;
1088 while (*profilePP!=NULL) profilePP=&((*profilePP)->next);
1089 *profilePP=new TProfileDefinition(this,aProfileName,aNumMandatory,aShowInCTCapIfSelectedOnly,aProfileMode,aModeDep);
1091 } // TProfileDefinition::addSubProfile
1094 TPropertyDefinition *TProfileDefinition::addProperty(
1095 const char *aName, // name
1096 sInt16 aNumValues, // number of values
1097 bool aMandatory, // mandatory
1098 bool aShowInCTCap, // show in CTCap
1099 bool aSuppressEmpty, // suppress empty ones on send
1100 uInt16 aDelayedProcessing, // delayed processing when parsed, 0=immediate processing, 1..n=delayed
1101 char aValuesep, // value separator
1102 uInt16 aPropertyGroupID, // property group ID (alternatives for same-named properties should have same ID>0)
1103 bool aCanFilter, // can be filtered -> show in filter cap
1104 TMimeDirMode aModeDep, // property valid only for specific MIME mode
1105 char aAltValuesep, // alternate separator (for parsing)
1106 sInt16 aGroupFieldID // group field ID
1109 TPropertyDefinition **propPP=&propertyDefs;
1110 while (*propPP!=NULL) propPP=&((*propPP)->next);
1111 *propPP=new TPropertyDefinition(aName,aNumValues,aMandatory,aShowInCTCap,aSuppressEmpty,aDelayedProcessing,aValuesep,aAltValuesep,aPropertyGroupID,aCanFilter,aModeDep,aGroupFieldID);
1112 // return new property
1114 } // TProfileDefinition::addProperty
1117 void TProfileDefinition::usePropertiesOf(TProfileDefinition *aProfile)
1120 propertyDefs=aProfile->propertyDefs;
1121 } // TProfileDefinition::usePropertiesOf
1124 // find (sub)profile by name, recursively
1125 TProfileDefinition *TProfileDefinition::findProfile(const char *aNam)
1128 if (levelName==aNam) return this;
1130 TProfileDefinition *lvlP = subLevels;
1131 TProfileDefinition *foundlvlP;
1133 foundlvlP=lvlP->findProfile(aNam);
1134 if (foundlvlP) return foundlvlP;
1137 // does not match myself nor one of my sublevels
1139 } // TProfileDefinition::findProfile
1141 #pragma exceptions reset
1142 #undef EXCEPTIONS_HERE
1143 #define EXCEPTIONS_HERE TARGET_HAS_EXCEPTIONS
1146 #ifdef OBJECT_FILTERING
1148 // get property definition of given filter expression identifier.
1149 TPropertyDefinition *TProfileDefinition::getPropertyDef(const char *aPropName)
1151 TPropertyDefinition *propP = NULL;
1153 if (!aPropName) return propP; // no name, no fid
1154 // Depth first: search in subprofiles, if any
1155 TProfileDefinition *profileP = subLevels;
1157 // search depth first
1158 if ((propP=profileP->getPropertyDef(aPropName))!=NULL)
1159 return propP; // found
1160 // test next profile
1161 profileP=profileP->next;
1163 // now search my own properties
1164 propP = propertyDefs;
1167 if (strucmp(aPropName,TCFG_CSTR(propP->propname))==0) {
1170 // test next property
1175 } // TProfileDefinition::getPropertyDef
1178 // get field index of given filter expression identifier.
1179 sInt16 TProfileDefinition::getPropertyMainFid(const char *aPropName, uInt16 aIndex)
1181 sInt16 fid = VARIDX_UNDEFINED;
1183 // search property definition with matching name
1184 TPropertyDefinition *propP = getPropertyDef(aPropName);
1185 // search for first value with a field assigned
1187 // found property with matching name
1188 if (propP->convdefs) {
1190 // no index specified -> search first with a valid FID
1191 for (uInt16 i=0; i<propP->numValues; i++) {
1192 if ((fid=propP->convdefs[i].fieldid)!=VARIDX_UNDEFINED)
1193 return fid; // found a field index
1197 // index specified for multivalued properties -> return specified value's ID
1198 if (aIndex<=propP->numValues) {
1199 return propP->convdefs[aIndex-1].fieldid;
1205 return VARIDX_UNDEFINED;
1206 } // TProfileDefinition::getPropertyMainFid
1209 #endif // OBJECT_FILTERING
1214 * Implementation of TMimeDirProfileHandler
1218 TMimeDirProfileHandler::TMimeDirProfileHandler(
1219 TMIMEProfileConfig *aMIMEProfileCfgP,
1220 TMultiFieldItemType *aItemTypeP
1221 ) : TProfileHandler(aMIMEProfileCfgP, aItemTypeP)
1223 // save profile config pointer
1224 fProfileCfgP = aMIMEProfileCfgP;
1225 fProfileDefinitionP = fProfileCfgP->fRootProfileP;
1226 // settable options defaults
1227 fMimeDirMode=mimo_standard;
1228 fReceiverCanHandleUTC = true;
1229 fVCal10EnddatesSameDay = false; // avoid 23:59:59 style end date by default
1230 fReceiverTimeContext = TCTX_UNKNOWN; // none in particular
1231 fDontSendEmptyProperties = false; // send all defined properties
1232 fDefaultOutCharset = chs_utf8; // standard
1233 fDefaultInCharset = chs_utf8; // standard
1234 fDoQuote8BitContent = false; // no quoting needed per se
1235 fDoNotFoldContent = false; // standard requires folding
1236 fTreatRemoteTimeAsLocal = false; // only for broken implementations
1237 fTreatRemoteTimeAsUTC = false; // only for broken implementations
1238 fActiveRemoteRules.clear(); // no dependency on certain remote rules
1239 } // TMimeDirProfileHandler::TMimeDirProfileHandler
1242 TMimeDirProfileHandler::~TMimeDirProfileHandler()
1245 } // TMimeDirProfileHandler::~TTextProfileHandler
1249 #ifdef OBJECT_FILTERING
1251 // get field index of given filter expression identifier.
1252 sInt16 TMimeDirProfileHandler::getFilterIdentifierFieldIndex(const char *aIdentifier, uInt16 aIndex)
1254 // search properties for field index
1255 return fProfileDefinitionP->getPropertyMainFid(aIdentifier, aIndex);
1256 } // TMimeDirProfileHandler::getFilterIdentifierFieldIndex
1258 #endif // OBJECT_FILTERING
1262 // parses enum value for CONVMODE_MULTIMIX
1263 // [offs.](Bx|Lzzzzzzz)
1264 // aN returns the bit number or the offset of the zzzzz literal within aMixVal, depending on aIsBitMap
1265 static bool mixvalparse(cAppCharP aMixVal, uInt16 &aOffs, bool &aIsBitMap, uInt16 &aN)
1268 cAppCharP p = aMixVal;
1269 // check offset (2 digit max)
1271 p+=StrToUShort(p,aOffs,2);
1272 if (*p++ != '.') return false; // wrong syntax
1278 if (StrToUShort(p+1,aN,2)<1) return false; // wrong syntax
1281 // literal, return position within string
1283 aN = p+1-aMixVal; // literal starts at this position
1286 return false; // unknown command
1292 // returns the size of the field block (how many fids in sequence) related
1293 // to a given convdef (for multi-field conversion modes such as CONVMODE_RRULE
1294 sInt16 TMimeDirProfileHandler::fieldBlockSize(const TConversionDef &aConvDef)
1296 if (aConvDef.convmode==CONVMODE_RRULE)
1297 return 6; // RRULE fieldblock: DTSTART,FREQ,INTERVAL,FIRSTMASK,LASTMASK,UNTIL = 6 fields
1299 return 1; // single field
1300 } // TMimeDirProfileHandler::fieldBlockSize
1304 // special field translation (to be extended in derived classes)
1305 // Note: the string returned by this function will be scanned as a
1306 // value list if combinesep is set, and every single value will be
1307 // enum-translated if enums defined.
1308 bool TMimeDirProfileHandler::fieldToMIMEString(
1309 TMultiFieldItem &aItem, // the item where data comes from
1310 sInt16 aFid, // the field ID (can be NULL for special conversion modes)
1311 sInt16 aArrIndex, // the repeat offset to handle array fields
1312 const TConversionDef *aConvDefP, // the conversion definition record
1313 string &aString // output string
1316 const int maxmix = 10;
1317 uInt16 mixOffs[maxmix];
1318 bool mixIsFlags[maxmix];
1319 TEnumerationDef *enumP;
1320 uInt16 offs; bool isFlags;
1322 fieldinteger_t flags;
1324 TTimestampField *tsFldP;
1330 // RRULE field block values
1331 char freq; // frequency
1332 char freqmod; // frequency modifier
1333 sInt16 interval; // interval
1334 fieldinteger_t firstmask; // day mask counted from the first day of the period
1335 fieldinteger_t lastmask; // day mask counted from the last day of the period
1336 lineartime_t until; // last day
1337 timecontext_t untilcontext;
1340 // get pointer to leaf field
1341 TItemField *fldP = aItem.getArrayField(aFid,aArrIndex,true); // existing array elements only
1343 bool dateonly=false; // assume timestamp mode
1344 bool autodate=true; // show date-only values automatically as date-only, even if stored in a timestamp field
1345 switch (aConvDefP->convmode) {
1348 case CONVMODE_EMPTYONLY:
1349 // just get field as string
1350 if (!fldP) return false; // no field, no value
1351 if (!fldP->isBasedOn(fty_timestamp)) goto normal;
1352 // Based on timestamp
1353 // - handle date-only specially
1354 if (fldP->getType()==fty_date)
1355 goto dateonly; // date-only
1357 goto timestamp; // others are treated as timestamps
1358 // date & time modes
1359 case CONVMODE_DATE: // always show as date
1361 dateonly = true; // render as date in all cases
1363 case CONVMODE_AUTOENDDATE:
1364 case CONVMODE_AUTODATE: // show date-only as date in iCal 2.0 (mimo_standard), but always as timestamp for vCal 1.0 (mimo_old)
1365 if (fMimeDirMode==mimo_standard) goto timestamp; // use autodate if MIME-DIR format is not vCal 1.0 style
1366 case CONVMODE_TIMESTAMP: // always show as timestamp
1367 // get explictly as timestamp (even if field or field contents is date)
1368 autodate = false; // do not show as date, even if it is a date-only
1370 if (!fldP) return false; // no field, no value
1371 if (!fldP->isBasedOn(fty_timestamp)) goto normal;
1372 // show as timestamp
1373 tsFldP = static_cast<TTimestampField *>(fldP);
1374 tctx = tsFldP->getTimeContext();
1375 // check for auto-date
1377 if (TCTX_IS_DATEONLY(tctx))
1380 // check for special cases
1381 if (TCTX_IS_DURATION(tctx)) {
1382 // duration is shown as such
1383 tsFldP->getAsISO8601(aString, TCTX_UNKNOWN | TCTX_DURATION, false, false, false, false);
1385 else if (dateonly) {
1386 // date-only are either floating or shown as date-only part of original timestamp
1387 tsFldP->getAsISO8601(aString, TCTX_UNKNOWN | TCTX_DATEONLY, false, false, false, false);
1389 else if (fReceiverCanHandleUTC && !tsFldP->isFloating()) {
1390 // remote can handle UTC and the timestamp is not floating
1391 if (!TCTX_IS_UNKNOWN(fPropTZIDtctx)) {
1392 // if we have rendered a TZID for this property, this means that apparently the remote
1393 // supports TZID (otherwise the field would not be marked available in the devInf).
1394 // - show it as floating, explicitly with both date AND time (both flags set)
1395 tsFldP->getAsISO8601(aString, TCTX_UNKNOWN | TCTX_TIMEONLY | TCTX_DATEONLY, false, false, false, false);
1399 tsFldP->getAsISO8601(aString, TCTX_UTC, true, false, false, false);
1403 // remote cannot handle UTC or time is floating (possibly dateonly or duration)
1404 if (tsFldP->isFloating()) {
1405 // floating, show as-is
1406 ts = tsFldP->getTimestampAs(TCTX_UNKNOWN);
1407 if (ts==noLinearTime)
1410 if (TCTX_IS_DATEONLY(tctx)) {
1411 // value is a date-only, but we must render it a datetime
1412 ts=lineartime2dateonlyTime(ts); // make time part 0:00:00
1414 // first check for auto-end-date (which must be floating)
1415 // Note: we don't get here with a date only mimo_standard because it will be catched above, so test is not really needed
1416 if (aConvDefP->convmode==CONVMODE_AUTOENDDATE && fVCal10EnddatesSameDay && TCTX_IS_DATEONLY(tctx) && fMimeDirMode==mimo_old)
1417 ts-=1; // subtract one unit to make end show last time unit of previous day
1418 // now show as floating ISO8601
1419 TimestampToISO8601Str(aString, ts, TCTX_UNKNOWN, false, false);
1423 // not floating (=not a enddateonly), but we can't send UTC - render as localtime
1424 // in item time zone (which defaults to session time zone)
1425 tsFldP->getAsISO8601(aString, fItemTimeContext, false, false, false, false);
1428 return true; // found
1431 fldP->getAsString(aString);
1432 return true; // found
1435 case CONVMODE_DAYLIGHT:
1436 // use now as default point in time for possible offset calculations
1437 ts = getSession()->getSystemNowAs(TCTX_SYSTEM);
1438 // if no field is specified, the item context is used (which defaults to
1439 // the session's user context)
1440 // Note that testing fldP is not enough, because an empty array will also cause fldP==NULL
1442 if (aFid!=FID_NOT_SUPPORTED)
1443 return false; // field not available (but conversion definition DOES refer to a field --> no time zone)
1444 // conversion definition does not refer to a field: use item context
1445 tctx = fItemTimeContext;
1447 else if (fldP->isBasedOn(fty_timestamp)) {
1448 // time zone of a timestamp
1449 tsFldP = static_cast<TTimestampField *>(fldP);
1450 // - if floating time, we have no time zone
1451 if (tsFldP->isFloating() || tsFldP->isDuration()) return false; // floating or duration -> no time zone
1453 tctx = tsFldP->getTimeContext(); // get the context
1455 ts = tsFldP->getTimestampAs(TCTX_UNKNOWN);
1456 // prevent generating TZID (and associated VTIMEZONES later) for empty timestamp
1457 if (ts==noLinearTime) return false; // no timestamp -> no time zone
1459 else if (fldP->getCalcType()==fty_integer) {
1460 // integer field is simply a time zone offset in minutes
1461 tctx = TCTX_MINOFFSET(fldP->getAsInteger());
1463 else if (!fldP->isEmpty()) {
1464 // string field can be timezone name or numeric minute offset
1465 fldP->getAsString(s);
1466 if (!TimeZoneNameToContext(s.c_str(),tctx,getSessionZones())) {
1467 // if not recognized as time zone name, use integer value
1468 tctx = TCTX_MINOFFSET(fldP->getAsInteger());
1472 return false; // no TZ to show
1473 // if remote cannot handle UTC (i.e. only understands localtime), then make sure
1474 // the time zone shown is the general item zone (user zone).
1475 if (!fReceiverCanHandleUTC) {
1476 TzConvertTimestamp(ts,tctx,fItemTimeContext,getSessionZones());
1477 tctx = fItemTimeContext; // use item zone
1479 // now render context as selected
1480 if (aConvDefP->convmode==CONVMODE_TZID) {
1481 // time zone ID for iCal 2.0 TZID parameter
1482 // - make sure meta context is resolved (we don't want "SYSTEM" as TZID!)
1483 if (!TzResolveMetaContext(tctx, getSessionZones())) return false; // cannot resolve, no time zone ID
1484 // - if time zone is not UTC (which is represented as "Z" and needs no TZID), show name
1485 if (!TCTX_IS_UTC(tctx) && !TCTX_IS_UNKNOWN(tctx) && !TCTX_IS_DATEONLY(tctx)) {
1486 // - show name of zone as TZID
1487 if (!TimeZoneContextToName(tctx, aString, getSessionZones(), fProfileCfgP->fTzIdGenMode==tzidgen_olson ? "o" : NULL)) return false; // cannot get name/ID
1488 // - flag property-level TZID generated now
1490 // - add to set of TZID-referenced time zones (for vTimezone generation)
1491 fUsedTCtxSet.insert(fUsedTCtxSet.end(),tctx);
1492 // - update range of time covered for generating VTIMEZONE later
1494 if (fEarliestTZDate==noLinearTime || fEarliestTZDate>ts) fEarliestTZDate = ts; // new minimum
1495 if (fLatestTZDate==noLinearTime || fLatestTZDate<ts) fLatestTZDate = ts; // new maximum
1500 // CONVMODE_TZ or CONVMODE_DAYLIGHT
1501 // - there's only one TZ/DAYLIGHT per item, so set it as item context
1502 if (!fReceiverCanHandleUTC) {
1503 // devices that can't handle UTC should not be bothered with TZ info
1504 // (e.g. for N-Gage/3650 presence of a TZ shifts the data by the TZ value!?)
1505 return false; // prevent generation of TZ or DAYLIGHT props
1508 // only if remote can handle UTC we may change the item time context
1509 // (otherwise, the timestamp must be rendered in itemzone/userzone)
1510 fItemTimeContext = tctx;
1511 fHasExplicitTZ = true; // flag setting explicit time zone for item
1513 // - get resolved TZ offset and DAYLIGHT string for vCal 1.0
1514 ContextToTzDaylight(tctx,ts,s,tctx,getSessionZones());
1515 if (aConvDefP->convmode==CONVMODE_TZ) {
1516 // time zone in +/-hh[:mm] format for vCal 1.0 TZ property
1517 // - render offset in extended format
1519 // - return true only if we actually have a TZ
1520 return ContextToISO8601StrAppend(aString, tctx, true);
1522 else if (aConvDefP->convmode==CONVMODE_DAYLIGHT) {
1523 // TZ and DAYLIGHT property for vCal 1.0
1525 // - return true only if we actually have a DAYLIGHT
1531 case CONVMODE_MAILTO:
1532 // make sure we have a mailto: prefix (but not if string is empty)
1533 if (!fldP) return false; // no field, no value
1534 fldP->getAsString(s);
1536 if (strucmp(s.c_str(),"mailto:",7)!=0 && s.size()>0)
1540 case CONVMODE_VALUETYPE:
1541 case CONVMODE_FULLVALUETYPE:
1542 // specify value type of field if needed
1543 if (!fldP) return false; // no field -> no VALUE param
1544 if (fldP->isBasedOn(fty_timestamp)) {
1545 // show VALUE=DATE if we have date-only or time-only
1546 tctx = static_cast<TTimestampField *>(fldP)->getTimeContext();
1547 if (TCTX_IS_DURATION(tctx)) aString="DURATION";
1548 else if (TCTX_IS_DATEONLY(tctx)) aString="DATE";
1549 else if (TCTX_IS_TIMEONLY(tctx)) aString="TIME";
1551 // only show type if full value type requested
1552 if (aConvDefP->convmode==CONVMODE_FULLVALUETYPE)
1553 aString="DATE-TIME";
1555 return false; // we don't need a VALUE param for normal datetimes
1559 return false; // no field type that needs VALUE param
1560 // valuetype generated
1562 case CONVMODE_VERSION:
1564 aString=aItem.getItemType()->getTypeVers(fProfileMode);
1566 case CONVMODE_PRODID:
1567 // PRODID ISO9070 non-registered FPI
1568 // -//ABC Corporation//NONSGML My Product//EN
1569 aString = SYSYNC_FPI;
1571 case CONVMODE_BITMAP:
1572 // bitmap is a special case of multimix, set up params
1577 case CONVMODE_MULTIMIX:
1578 // list of special values that can be either literals or bit masks, and can optionally affect more than one field
1580 // Bx : Bit number x (like in CONVMODE_BITMAP, x = 0..63)
1581 // Lxxxx : Literal xxxxx (xxxxx will just be copied from the source field)
1582 // y.Bx or y.Lxxxx : use y as field offset to use (no y means 0 offset)
1583 // - collect parameters to generate mix from enums
1585 enumP = aConvDefP->enumdefs;
1587 if (mixvalparse(TCFG_CSTR(enumP->enumval),offs,isFlags,bitNo)) {
1588 // check if this field is in list already
1589 for (i=0; i<nummix; i++) {
1590 if (mixOffs[i] == offs) goto next; // referring to same field again, skip
1592 // is a new field, add it to list
1593 mixOffs[nummix] = offs;
1594 mixIsFlags[nummix] = isFlags;
1596 if (nummix>=maxmix) break; // no more mixes allowed, stop scanning
1603 // now generate strings from collected data
1605 for (i=0; i<nummix; i++) {
1607 fldP = aItem.getArrayField(aFid+mixOffs[i],aArrIndex,true); // existing array elements only
1609 if (mixIsFlags[i]) {
1610 // use target as bitmask to create bit numbers
1611 flags=fldP->getAsInteger();
1615 // create bit representation
1616 if (!aString.empty() && aConvDefP->combineSep)
1617 aString+=aConvDefP->combineSep; // separator first if not first item
1618 if (aConvDefP->convmode==CONVMODE_MULTIMIX) {
1619 // multimix mode, use full syntax
1621 StringObjAppendPrintf(aString,"%d.",mixOffs[i]);
1625 StringObjAppendPrintf(aString,"%hd",bitNo);
1627 flags >>= 1; // consume this one
1633 if (!fldP->isEmpty()) {
1634 if (!aString.empty() && aConvDefP->combineSep)
1635 aString+=aConvDefP->combineSep; // append separator if there are more flags
1637 StringObjAppendPrintf(aString,"%d.",mixOffs[i]);
1638 aString += 'L'; // literal
1639 fldP->appendToString(aString);
1642 } // field available
1645 case CONVMODE_RRULE: {
1646 // get values from field block
1647 if (aFid<0) return false; // no field, no string
1649 if (!(sfP = ITEMFIELD_DYNAMIC_CAST_PTR(TStringField,fty_string,aItem.getArrayField(aFid,aArrIndex,true)))) return false;
1650 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
1651 sfP->getAsString(s);
1653 freqmod=' '; // no modifier
1654 if (s.size()>0) freq=s[0];
1655 if (s.size()>1) freqmod=s[1];
1657 if (!(ifP = ITEMFIELD_DYNAMIC_CAST_PTR(TIntegerField,fty_integer,aItem.getArrayField(aFid,aArrIndex,true)))) return false;
1658 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
1659 interval=(sInt16)ifP->getAsInteger();
1661 if (!(ifP = ITEMFIELD_DYNAMIC_CAST_PTR(TIntegerField,fty_integer,aItem.getArrayField(aFid,aArrIndex,true)))) return false;
1662 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
1663 firstmask=ifP->getAsInteger();
1665 if (!(ifP = ITEMFIELD_DYNAMIC_CAST_PTR(TIntegerField,fty_integer,aItem.getArrayField(aFid,aArrIndex,true)))) return false;
1666 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
1667 lastmask=ifP->getAsInteger();
1669 if (!(tsFldP = ITEMFIELD_DYNAMIC_CAST_PTR(TTimestampField,fty_timestamp,aItem.getArrayField(aFid,aArrIndex,true)))) return false;
1670 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
1672 // - UTC preferred as output format if basically possible and not actively disabled
1674 fReceiverCanHandleUTC && getSession()->canHandleUTC() ?
1677 // - get in preferred zone (or floating)
1678 until=tsFldP->getTimestampAs(untilcontext,&untilcontext);
1679 lineartime_t tzend = until;
1680 // A RRULE with no end extends at least into current time (for tz range update, see below)
1681 if (until==noLinearTime) {
1682 tzend = getSession()->getSystemNowAs(TCTX_UTC);
1684 // Now do the conversion
1686 if (fMimeDirMode==mimo_old) {
1687 // vCalendar 1.0 type RRULE
1688 ok = internalToRRULE1(
1701 // iCalendar 2.0 type RRULE
1702 ok = internalToRRULE2(
1714 // if we actually generated a RRULE, the range of used time zones must be updated according
1715 // to the recurrence end (date or open end, see tzend calculation above)
1716 if (!aString.empty()) {
1717 if (fEarliestTZDate==noLinearTime || tzend<fEarliestTZDate) fEarliestTZDate = tzend;
1718 if (fLatestTZDate==noLinearTime || tzend>fLatestTZDate) fLatestTZDate = tzend;
1721 break; // just in case
1724 // unknown mode, no value
1728 } // TMimeDirProfileHandler::fieldToMIMEString
1732 /// @brief test if char is part of a line end
1733 /// @return true if aChar is a line end char
1734 /// @param [in] aChar charcter to check
1735 static bool isLineEndChar(appChar aChar)
1737 return (aChar=='\x0D') || (aChar=='\x0A');
1741 /// @brief test if char is end of a line or end of the text (NUL)
1742 /// @return true if aChar is a line end char or NUL
1743 /// @param [in] aChar charcter to check
1744 static bool isEndOfLineOrText(appChar aChar)
1746 return (aChar==0) || isLineEndChar(aChar);
1747 } // isEndOfLineOrText
1750 /// @brief test if a line end of any kind is at aText
1751 /// @note CR,LF,CRLF and CR...CRLF sequences are all considered one line end
1752 /// @return true if line end found
1753 /// @param [in/out] aText advance past line end sequence
1754 static bool testAndSkipLineEnd(cAppCharP &aText)
1756 cAppCharP p = aText;
1757 bool crFound = false;
1758 // skip sequence of CRs
1759 while (*p=='\x0D') {
1763 // past all CRs in a row
1765 // independent of the number of CRs preceeding, this is a line end including the LF
1766 aText = p+1; // past LF
1770 // we previously found at least one CR at the beginning, but no LF is following
1771 // -> assume CR only line ends, consider first CR as a line end by itself
1772 aText++; // skip first CR
1777 } // testAndSkipLineEnd
1781 // return incremented pointer pointing to original char or next non-folded char
1782 static cAppCharP skipfolded(cAppCharP aText, TMimeDirMode aMimeMode, bool qpSoftBreakCancel=false)
1784 cAppCharP p = aText;
1785 if (testAndSkipLineEnd(p)) {
1786 // check for folding sequence
1787 if (*p==' ' || *p=='\x09') {
1788 // line end followed by space: folding sequence
1789 if (aMimeMode==mimo_standard) {
1790 // ignore entire sequence (CR,LF,SPACE/TAB)
1794 // old folding type, LWSP must be preserved
1799 else if (qpSoftBreakCancel && *p=='=') {
1800 // could be soft break sequence, check for line end
1802 if (testAndSkipLineEnd(p)) {
1806 // not folding sequence, return ptr to char as is
1811 // get next character, while skipping MIME-DIR folding sequences
1812 // if qpSoftBreakCancel, QUOTED-PRINTABLE encoding style soft-line-break sequences
1813 // will be eliminated
1814 static const char *nextunfolded(const char *p, TMimeDirMode aMimeMode, bool qpSoftBreakCancel=false)
1816 if (*p==0) return p; // at end of string, do not advance
1817 p++; // point to next
1818 return skipfolded(p,aMimeMode,qpSoftBreakCancel);
1822 // helper for MIME DIR generation:
1823 // - apply encoding and charset conversion to values part of property if needed
1824 static void decodeValue(
1825 TEncodingTypes aEncoding, // the encoding to be used
1826 TCharSets aCharset, // charset to be applied to 8-bit chars
1827 TMimeDirMode aMimeMode, // the MIME mode
1828 char aStructSep, // input is structured value, stop when aStructSep is encountered
1829 char aAltSep, // alternate separator, also stop when encountering this one (but only if aStructSep is !=0)
1830 const char *&aText, // where to start decoding, updated past last char added to aVal
1831 string &aVal // decoded data is stored here (possibly some binary data)
1834 const int maxseqlen=6;
1836 char c,chrs[maxseqlen];
1841 if (aEncoding==enc_quoted_printable) {
1842 // decode quoted-printable content
1843 p = skipfolded(aText,aMimeMode,true); // get unfolded start point (in case value starts with folding sequence)
1845 // decode standard content
1847 if (isEndOfLineOrText(c) || (!escaped && aStructSep!=0 && (c==aStructSep || c==aAltSep))) break; // EOLN and struct separators terminate value
1848 // test if escape char (but do not filter it out, as actual de-escaping is done in parseValue() later
1849 escaped=(!escaped) && (c=='\\'); // escape next only if we are not escaped already
1855 s=nextunfolded(p,aMimeMode,true);
1856 if (*s==0) break; // end of string
1857 hex[0]=*s; // first digit
1858 s=nextunfolded(s,aMimeMode,true);
1859 if (*s==0) break; // end of string
1860 hex[1]=*s; // second digit
1861 if (HexStrToUShort(hex,code,2)==2) {
1862 p=s; // continue with next char after second digit
1863 c=code; // decoded char
1865 c='\n'; // make newline
1867 else if (c=='\x0A') {
1868 p=nextunfolded(p,aMimeMode,true); // advance to char after second digit
1869 continue; // ignore LF
1873 seqlen=1; // assume logical char consists of single byte
1876 seqlen=appendCharsAsUTF8(chrs,aVal,aCharset,seqlen); // add char (possibly with UTF8 expansion) to aVal
1877 if (seqlen<=1) break; // done
1878 // need more bytes to encode entire char
1879 for (int i=1;i<seqlen;i++) {
1880 p=nextunfolded(p,aMimeMode,true);
1884 p=nextunfolded(p,aMimeMode,true);
1886 } // quoted printable
1887 else if (aEncoding==enc_base64 || aEncoding==enc_b) {
1889 // - find start of property value
1890 p = skipfolded(aText,aMimeMode,false); // get unfolded start point (in case value starts with folding sequence
1891 // - find end of property value
1894 if (aStructSep!=0 && (*q==aStructSep || *q==aAltSep))
1895 break; // structure separator terminates B64 as well (colon, semicolon and comma never appear in B64)
1896 if (isLineEndChar(*q)) {
1897 // end of line. Check if this is folding or end of property
1898 cAppCharP r=skipfolded(q,aMimeMode,false);
1900 // no folding skipped -> this appears to be the end of the property
1901 // Now for ill-encoded vCard 2.1 which chop B64 into lines, but do not prefix continuation
1902 // lines with some whitespace, make sure the next line contains a colon
1903 // - skip that line end
1904 while (isLineEndChar(*r)) r++;
1905 // - examine next line
1907 for (cAppCharP r2=r; *r2 && !isLineEndChar(*r2); r2++) {
1908 if (*r2==':' || *r2==';') {
1913 if (eob64) break; // q is end of B64 string -> go decode it
1914 // there's more to the b64 string at r, continue looking for end
1916 // skip to continuation of B64 string
1924 uInt8 *binP = b64::decode(p, q-p, &binsz);
1925 aVal.append((const char *)binP,binsz);
1927 // - continue at next char after b64 value
1931 // no (known) encoding
1932 p = skipfolded(aText,aMimeMode,false); // get unfolded start point (in case value starts with folding sequence
1935 if (isEndOfLineOrText(c) || (!escaped && aStructSep!=0 && (c==aStructSep || c==aAltSep))) break; // EOLN and structure-sep (usually ;) terminate value
1936 // test if escape char (but do not filter it out, as actual de-escaping is done in parseValue() later
1937 escaped=(!escaped) && (c=='\\'); // escape next only if we are not escaped already
1939 seqlen=1; // assume logical char consists of single byte
1942 seqlen=appendCharsAsUTF8(chrs,aVal,aCharset,seqlen); // add char (possibly with UTF8 expansion) to aVal
1943 if (seqlen<=1) break; // done
1944 // need more bytes to encode entire char
1945 for (int i=1;i<seqlen;i++) {
1946 p=nextunfolded(p,aMimeMode,false);
1950 p=nextunfolded(p,aMimeMode,false);
1953 // return pointer to terminating char
1958 // helper for MIME DIR generation:
1959 // - apply encoding to values part of property if needed
1960 static void encodeValues(
1961 TEncodingTypes aEncoding, // the encoding to be used
1962 TCharSets aCharSet, // charset to be applied to 8-bit chars
1963 const string &aValuedata, // the data to be encoded (possibly some binary data)
1964 string &aPropertytext, // the property string where encoded data is appended
1965 bool aDoNotFoldContent // special override for folding
1968 const uInt8 *valPtr = (const uInt8 *)aValuedata.c_str();
1969 size_t valSz = aValuedata.size();
1971 if (aCharSet!=chs_utf8) {
1972 // we need to convert to target charset first
1973 appendUTF8ToString((const char *)valPtr,s,aCharSet,lem_none,qm_none);
1974 valPtr = (const uInt8 *)s.c_str();
1977 // - apply encoding if needed
1981 aPropertytext, // append output here
1982 aEncoding, // desired encoding
1984 0 // disable insertion of soft line breaks
1985 : MIME_MAXLINESIZE-1, // limit to standard MIME-linesize, leave one free for possible extra folding space
1986 aPropertytext.size() % MIME_MAXLINESIZE, // current line size
1987 true // insert CRs only for softbreaks (for post-processing by folding)
1992 // helper for MIME DIR generation:
1993 // - fold, copy and terminate (CRLF) property into aString output
1994 // - \n in input is explicit "fold here" indicator
1995 // - \b in input is an optional "fold here" indicator, which will appear as space in the
1996 // output when needed, but will otherwise be discarded
1997 // - \r in input indicates that a line end must be inserted
1998 // if aDoSoftBreak==true, only a line break is inserted (QUOTED-PRINTABLE soft line break)
1999 // otherwise, a full folding sequence (CRLF + space) is inserted. In case of MIME-DIR,
2000 // QP softbreaks are nothing special, and still need an extra space (as this is reversed on parsing).
2001 static void finalizeProperty(
2002 const char *proptext,
2004 TMimeDirMode aMimeMode, // MIME mode (older or newer vXXX format compatibility)
2005 bool aDoNotFold, // set to prevent folding
2006 bool aDoSoftBreak // set to insert QP-softbreaks when \r is encountered, otherwise do a full hard break (which essentially inserts a space for mimo_old)
2009 // make sure that allocation does not increase char by char
2010 aString.reserve(aString.size()+strlen(proptext)+100);
2013 ssize_t lastlwsp=-1; // no linear white space found so far
2015 cAppCharP firstunwritten=proptext; // none written yet
2016 while (proptext && (c=*proptext)!=0) {
2017 // remember position of last lwsp (space or TAB)
2018 if (c==' ' || c==0x09) lastlwsp=n;
2020 // Note: we prevent folding within UTF8 sequences as result string would become inconvertible e.g. into UTF16
2022 cAppCharP nP = UTF8toUCS4(proptext, uc);
2024 // UTF-8 compliant byte (or byte sequence), skip as an entiety
2029 // Not UTF-8 compliant, simply one byte
2033 // check for optional break indicator
2035 aString.append(firstunwritten,n-1); // copy what we have up to that '\b'
2036 firstunwritten+=n; // now pointing to next char after '\b'
2037 lastlwsp=0; // usually now pointing to a NON-LWSP, except if by accident a LWSP follows, which is ok as well
2038 n=0; // continue checking from here
2039 continue; // check next
2041 // update line length
2043 // explicit linefeed flag
2044 explf=(c=='\n' || c=='\r');
2046 // prohibit folding for ugly devices like V3i
2048 // append what we have until here
2049 n--; // explicit \n or \r is ignored
2050 aString.append(firstunwritten,n);
2051 // forget the explicit linefeed - and continue
2054 firstunwritten=proptext;
2057 else if ((llen>=MIME_MAXLINESIZE && *proptext) || explf) { // avoid unnecessary folding (there must be something more coming)
2058 // folding needed (line gets longer than MIME_MAXLINESIZE or '\n' found in input string)
2059 if (aMimeMode==mimo_old && !explf) {
2060 // vCard 2.1 type folding, must occur before an LWSP
2061 #ifdef DONT_FORCE_FOLD_ITEMS_WITHOUT_LWSP
2062 if (lastlwsp<0) continue; // no LWSP found, cannot fold
2065 // emergency force fold and accept data being shredded
2066 // - copy all we have by now
2067 aString.append(firstunwritten,n);
2068 firstunwritten+=n; // now pointing to next
2069 n=0; // none left (not needed, would be reset below anyway)
2070 // - insert line break
2071 aString.append("\x0D\x0A "); // line break AND an extra shredding space
2076 // - copy all up to (but not including) last LWSP
2077 aString.append(firstunwritten,lastlwsp);
2078 firstunwritten+=lastlwsp; // now pointing to LWSP (or non-LWSP in case of '\b')
2079 n-=lastlwsp; // number of chars left (including LWSP)
2080 // - insert line break
2081 aString.append("\x0D\x0A"); // line break
2082 if (*firstunwritten!=' ' && *firstunwritten!=0x09)
2083 aString+=' '; // breaking at location indicated by '\b', LWSP must be added
2084 // - copy rest scanned so far (except in '\b' case, this begins with an LWSP)
2085 aString.append(firstunwritten,n);
2087 // we are on a new line now
2091 firstunwritten=proptext;
2094 // MIME-DIR type folding, can occur anywhere and *adds* a LWSP (which is removed at unfolding later)
2095 // or mimo-old type folding containing explicit CR(LF)s -> break here
2096 // - copy line so far to output
2098 n--; // explicit \n or \r is not copied, but only causes line break to occur
2099 aString.append(firstunwritten,n);
2100 aString.append("\x0D\x0A"); // line break
2102 (c!='\r' && aMimeMode==mimo_standard) || // folding indicator and MIME-DIR -> folding always must insert extra space
2103 (c=='\r' && !aDoSoftBreak) // soft-break indicator, but not in softbreak mode (i.e. B64 input) -> always insert extra space
2105 aString+=' '; // not only soft line break, but MIMD-DIR type folding
2108 firstunwritten=proptext;
2113 aString.append(firstunwritten,n);
2114 // terminate property
2115 aString.append("\x0D\x0A"); // CRLF
2116 } // finalizeProperty
2119 // results for generateValue:
2120 #define GENVALUE_NOTSUPPORTED 0 // field not supported
2121 #define GENVALUE_EXHAUSTED 1 // array field exhausted
2122 #define GENVALUE_EMPTYELEMENT 2 // array field empty
2123 #define GENVALUE_EMPTY 3 // non-array field empty
2124 #define GENVALUE_ELEMENT 4 // non-empty array element
2125 #define GENVALUE_NONEMPTY 5 // non-empty non-array value
2127 // helper for generateMimeDir()
2128 // - generate parameter or property value(list),
2129 // returns: GENVALUE_xxx
2130 sInt16 TMimeDirProfileHandler::generateValue(
2131 TMultiFieldItem &aItem, // the item where data comes from
2132 const TConversionDef *aConvDefP,
2133 sInt16 aBaseOffset, // basic fid offset to use
2134 sInt16 aRepOffset, // repeat offset, adds to aBaseOffset for non-array fields, is array index for array fields
2135 string &aString, // where value is ADDED
2136 char aSeparator, // separator to be used between values if field contains multiple values in a list separated by confdef->combineSep
2137 TMimeDirMode aMimeMode, // MIME mode (older or newer vXXX format compatibility)
2138 bool aParamValue, // set if generating parameter value (different escaping rules, i.e. colon must be escaped, or entire value double-quoted)
2139 bool aStructured, // set if value consists of multiple values (needs semicolon content escaping)
2140 bool aCommaEscape, // set if "," content escaping is needed (for values in valuelists like TYPE=TEL,WORK etc.)
2141 TEncodingTypes &aEncoding, // modified if special value encoding is required
2142 bool &aNonASCII, // set if any non standard 7bit ASCII-char is contained
2143 char aFirstChar // will be appended before value if there is any value (and a '\b' optional break indicator is appended as well)
2146 string vallist; // as received from fieldToMIMEString()
2147 string val; // single value
2148 string outval; // entire value (list) escaped
2151 // determine field ID
2152 bool isarray = false; // no array by default
2153 sInt16 fid=aConvDefP->fieldid;
2155 // field has storage
2156 // - fid is always offset by baseoffset
2159 isarray = aItem.adjustFidAndIndex(fid,aRepOffset);
2160 // generate only if available in both source and target (or non-SyncML context)
2161 if (isFieldAvailable(aItem,fid)) {
2162 // find out if value exists
2163 if (aItem.isAssigned(fid)) {
2164 // - field has a value assigned (altough this might be empty string)
2165 // determine max size to truncate value if needed
2167 sInt32 valsiz=0; // net size of value
2168 //%%%% getTargetItemType???
2169 sInt32 maxSiz=aItem.getTargetItemType()->getFieldOptions(fid)->maxsize;
2170 if (maxSiz==FIELD_OPT_MAXSIZE_UNKNOWN || maxSiz==FIELD_OPT_MAXSIZE_NONE)
2171 maxSiz = 0; // no size restriction
2172 bool noTruncate=aItem.getTargetItemType()->getFieldOptions(fid)->notruncate;
2173 // check for BLOB values
2174 if (aConvDefP->convmode==CONVMODE_BLOB_B64) {
2175 // no value lists, escaping, enums. Simply set value and encoding
2176 TItemField *fldP = aItem.getArrayField(fid,aRepOffset,true); // existing array elements only
2177 if (!fldP) return GENVALUE_EXHAUSTED; // no leaf field - must be exhausted array (fldP==NULL is not possible here for non-arrays)
2178 if (fldP->isUnassigned()) return GENVALUE_EMPTYELEMENT; // must be empty element empty element, but field supported (fldP==NULL is not possible here for non-arrays)
2179 // check max size and truncate if needed
2180 if (maxSiz && sInt32(fldP->getStringSize())>maxSiz) {
2181 if (noTruncate || getSession()->getSyncMLVersion()<syncml_vers_1_2) {
2182 // truncate not allowed (default for pre-SyncML 1.2 for BLOB fields)
2183 PDEBUGPRINTFX(DBG_ERROR+DBG_GEN,("BLOB value exceeds max size (%ld) and cannot be truncated -> omit", (long)maxSiz));
2184 return GENVALUE_NOTSUPPORTED; // treat it as if field was not supported locally
2187 // append to existing string
2188 fldP->appendToString(outval,maxSiz);
2189 // force B64 encoding
2190 aEncoding=enc_base64;
2194 // apply custom field(s)-to-string translation if needed
2195 if (!fieldToMIMEString(aItem,fid,aRepOffset,aConvDefP,vallist)) {
2196 // check if no value because array was exhausted
2197 if (aItem.getArrayField(fid,aRepOffset,true))
2198 return isarray ? GENVALUE_EMPTYELEMENT : GENVALUE_EMPTY; // no value (but field supported)
2200 return GENVALUE_EXHAUSTED; // no leaf field - must be exhausted array
2202 // separate value list into multiple values if needed
2203 const char *lp = vallist.c_str(); // list item pointer
2204 const char *sp; // start of item pointer (helper)
2207 // find (single) input value string's end
2208 for (sp=lp,n=0; (c=*lp)!=0; lp++, n++) {
2209 if (c==aConvDefP->combineSep) break;
2211 // - n=size of input value, p=ptr to end of value (0 or sep)
2213 // perform enum translation if needed
2214 if (aConvDefP->enumdefs) {
2215 const TEnumerationDef *enumP = aConvDefP->findEnumByVal(val.c_str());
2217 PDEBUGPRINTFX(DBG_GEN+DBG_EXOTIC,("Val='%s' translated to enumName='%s' mode=%s", val.c_str(), TCFG_CSTR(enumP->enumtext), EnumModeNames[enumP->enummode]));
2218 if (enumP->enummode==enm_ignore)
2219 val.erase(); // ignore -> make value empty as empty values are never stored
2220 else if (enumP->enummode==enm_prefix) {
2221 // replace value prefix by text prefix
2222 n=TCFG_SIZE(enumP->enumval);
2223 val.replace(0,n,TCFG_CSTR(enumP->enumtext)); // replace val prefix by text prefix
2226 // simply use translated value
2227 val=enumP->enumtext;
2231 PDEBUGPRINTFX(DBG_GEN+DBG_EXOTIC,("No translation found for Val='%s'", val.c_str()));
2234 // - val is now translated enum (or original value if value does not match any enum text)
2236 // perform escaping and determine need for encoding
2237 bool spaceonly = true;
2238 bool firstchar = true;
2240 for (const char *p=val.c_str();(c=*p)!=0 && (c!=aConvDefP->combineSep);p++) {
2242 // - check for whitespace
2244 spaceonly = false; // does not consist of whitespace only
2245 wordSize++; // count consecutive non-spaces
2246 if (aMimeMode==mimo_old && aEncoding==enc_none && wordSize>MIME_MAXLINESIZE/2) {
2247 // If text contains words with critical (probably unfoldable) size in mimo-old, select quoted printable encoding
2248 aEncoding=enc_quoted_printable;
2252 wordSize = 0; // new word starts
2254 // only text must be fully escaped, turn escaping off for RRULE (RECUR type)
2255 bool noescape = aConvDefP->convmode==CONVMODE_RRULE;
2256 // escape reserved chars
2259 if (firstchar && aParamValue && aMimeMode==mimo_standard) goto do_escape; // if param value starts with a double quote, we need to escape it because param value can be in double-quote-enclosed form
2260 goto add_char; // otherwise, just add
2262 // in MIME-DIR, always escape commas, in pre-MIME-DIR only if usage in value list requires it
2263 if (noescape || (!aCommaEscape && aMimeMode==mimo_old)) goto add_char;
2266 // always escape colon in parameters
2267 if (!aParamValue) goto add_char;
2270 // Backslash must always be escaped
2271 // - for MIMO-old: at least Nokia 9210 does it this way
2272 // - for MIME-DIR: specified in the standard
2275 // in MIME-DIR, always escape semicolons, in pre-MIME-DIR only in parameters and structured values
2276 if (noescape || (!aParamValue && !aStructured && aMimeMode==mimo_old)) goto add_char;
2278 // escape chars with backslash
2286 if (aMimeMode==mimo_old) {
2287 if (aEncoding==enc_none) {
2288 // For line ends in mimo_old: select quoted printable encoding
2289 aEncoding=enc_quoted_printable;
2291 // just pass it, will be encoded later
2295 // MIME-DIR: use quoted C-style notation
2296 outval.append("\\n");
2301 // prevent adding space-only for params
2302 if (spaceonly && aParamValue) break; // just check next
2304 // check for non ASCII and set flag if found
2305 if ((uInt8)c > 0x7F) aNonASCII=true;
2306 // just copy to output
2308 firstchar = false; // first char is out
2311 } // for all chars in val item
2312 // go to next item in the val list (if any)
2314 // more items in the list
2315 // - add separator if previous one is not empty param value
2316 if (!(spaceonly && aParamValue)) {
2318 valsiz++; // count it as part of the value
2320 lp++; // skip input list separator
2322 // check for truncation needs (do not truncate parameters, ever)
2323 if (maxSiz && valsiz>maxSiz && !aParamValue) {
2326 // truncate not allowed
2327 PDEBUGPRINTFX(DBG_ERROR+DBG_GEN,(
2328 "Value '%" FMT_LENGTH(".40") "s' exceeds %ld chars net length but is noTruncate -> omit",
2329 FMT_LENGTH_LIMITED(40,outval.c_str()),
2332 // treat it as if field was not supported locally
2333 return GENVALUE_NOTSUPPORTED;
2336 // truncate allowed, shorten output accordingly
2337 outval.erase(outval.size()-(valsiz-maxSiz));
2338 PDEBUGPRINTFX(DBG_GEN,(
2339 "Truncated value '%" FMT_LENGTH(".40") "s' to %ld chars net length (maxSize)",
2340 FMT_LENGTH_LIMITED(40,outval.c_str()),
2343 // do not add more chars
2347 } // while value chars available
2348 } // not BLOB conversion
2349 // value generated in outval (altough it might be an empty string)
2350 } // if field assigned
2352 // not assigned. However a not assigned array means an array with no elements, which
2353 // is the same as an exhausted array
2354 return isarray ? GENVALUE_EXHAUSTED : GENVALUE_NOTSUPPORTED; // array is exhaused, non-array unassigned means not available
2356 } // source and target both support the field (or field belongs to mandatory property)
2358 return GENVALUE_NOTSUPPORTED; // field not supported by either source or target (and not mandatory) -> do not generate value
2359 } // if fieldid exists
2361 // could be special conversion using no data or data from
2362 // internal object variables (such as VERSION value)
2363 if (fieldToMIMEString(aItem,FID_NOT_SUPPORTED,0,aConvDefP,vallist)) {
2364 // got some output, use it as value
2368 // no value, no output
2369 return GENVALUE_NOTSUPPORTED; // field not supported
2371 // now we have a value in outval, check if encoding needs to be applied
2372 // - check if we should select QUOTED-PRINTABLE because of nonASCII
2373 if (aNonASCII && fDoQuote8BitContent && aEncoding==enc_none)
2374 aEncoding=enc_quoted_printable;
2376 if (!outval.empty() && aFirstChar!=0) {
2377 aString+=aFirstChar; // we have a value, add sep char first
2378 aString+='\b'; // and an optional break indicator
2380 aString.append(outval);
2382 return outval.empty()
2383 ? (isarray ? GENVALUE_EMPTYELEMENT : GENVALUE_EMPTY) // empty
2384 : (isarray ? GENVALUE_ELEMENT : GENVALUE_NONEMPTY); // non empty
2385 } // TMimeDirProfileHandler::generateValue
2389 // generate parameters for one property instance
2390 // - returns true if parameters with shownonempty=true were generated
2391 bool TMimeDirProfileHandler::generateParams(
2392 TMultiFieldItem &aItem, // the item where data comes from
2393 string &aString, // the string to add parameters to
2394 const TPropertyDefinition *aPropP, // the property to generate (all instances)
2395 TMimeDirMode aMimeMode, // MIME mode (older or newer vXXX format compatibility)
2398 TPropNameExtension *aPropNameExt // propname extension for generating musthave param values
2401 const TParameterDefinition *paramP;
2403 char sep=0; // separator for value lists
2405 TEncodingTypes encoding;
2407 bool showalways=false;
2409 // Generate parameters
2410 // Note: altough positional values are always the same, non-positional values
2411 // can vary from repetition to repetition and can be mixed with the
2412 // positional values. So we must generate the mixture again for
2413 // every repetition.
2414 // - check all parameters for musthave values
2415 paramP = aPropP->parameterDefs;
2417 // parameter not started yet
2419 // process param only if matching mode
2420 if (mimeModeMatch(paramP->modeDependency)) {
2421 // first append extendsname param values
2422 if (paramP->extendsname && aPropNameExt) {
2423 const TEnumerationDef *enumP = paramP->convdef.enumdefs;
2425 if (enumP->nameextid>=0) {
2426 // value is relevant for name extension, check if required for this param
2427 if ((((TNameExtIDMap)1<<enumP->nameextid) & (aPropNameExt->musthave_ids | aPropNameExt->addtlSend_ids))!=0) {
2428 // found param value which is required or flagged to be sent additionally as name extension
2429 if (!paramstarted) {
2431 aString+=';'; // param always starts with ;
2432 if (paramP->defaultparam && (aMimeMode==mimo_old)) {
2433 // default param, values are written like a list of params
2434 sep=';'; // separator, in case other values follow
2437 // normal parameter, first add param separator and name
2439 aString.append(paramP->paramname);
2441 // - separator, in case other values follow
2442 sep=','; // value list separator is comma by default
2446 // add separator for one more value
2450 aString.append(enumP->enumtext);
2451 } // if enum value is a "must have" value for name extension
2452 } // if enum value is relevant to name extension
2455 } // while enum values
2457 // append value(s) if there is an associated field
2458 paramstr.erase(); // none to start with
2459 if (paramP->convdef.fieldid!=FID_NOT_SUPPORTED) {
2460 if (!paramstarted) {
2461 // Note: paramstarted must not be set here, as empty value might prevent param from being written
2462 // parameter starts with ";"
2464 if (paramP->defaultparam && (aMimeMode==mimo_old)) {
2465 // default param, values are written like a list of params
2469 // normal parameter, first add name
2470 paramstr.append(paramP->paramname);
2472 sep=','; // value list separator is comma by default
2476 // already started values, just add more
2477 // - next value starts with a separator
2480 // add parameter value(list)
2481 encoding=enc_none; // parameters are not encoded
2482 // NOTE: only non-empty parameters are generated
2483 // NOTE: parameters themselves cannot have a value list that is stored in an array,
2484 // but parameters of repeating properties can be stored in array elements (using the
2485 // same index as for the property itself)
2486 // Note: Escape commas if separator is a comma
2487 if (generateValue(aItem,&(paramP->convdef),aBaseOffset,aRepOffset,paramstr,sep,aMimeMode,true,false,sep==',',encoding,nonasc)>=GENVALUE_ELEMENT) {
2488 // value generated, add parameter name/value (or separator/value for already started params)
2489 aString.append(paramstr);
2490 paramstarted=true; // started only if we really have appended something at all
2492 } // if field defined for this param
2493 // update show status
2494 if (paramP->shownonempty && paramstarted)
2495 showalways=true; // param has a value and must make property show
2498 paramP=paramP->next;
2501 } // TMimeDirProfileHandler::generateParams
2504 // generateProperty return codes:
2505 #define GENPROP_EXHAUSTED 0 // nothing generated because data source exhausted (or field not supported)
2506 #define GENPROP_EMPTY 1 // nothing generated because empty value (but field supported)
2507 #define GENPROP_NONEMPTY 2 // something generated
2511 // helper for generateMimeDir(), expansion of property according to nameExts
2512 void TMimeDirProfileHandler::expandProperty(
2513 TMultiFieldItem &aItem, // the item where data comes from
2514 string &aString, // the string to add properties to
2515 const char *aPrefix, // the prefix (property name)
2516 const TPropertyDefinition *aPropP, // the property to generate (all instances)
2517 TMimeDirMode aMimeMode // MIME mode (older or newer vXXX format compatibility)
2520 // scan nameExts to generate name-extended variants and repetitions
2521 TPropNameExtension *propnameextP = aPropP->nameExts;
2522 if (!propnameextP) {
2523 // no name extensions -> this is a non-repeating property
2524 // just generate once, even if empty (except if it has suppressempty set)
2526 aItem, // the item where data comes from
2527 aString, // the string to add properties to
2528 aPrefix, // the prefix (property name)
2529 aPropP, // the property to generate
2530 0, // field ID offset to be used
2531 0, // additional repeat offset / array index
2532 aMimeMode, // MIME mode (older or newer vXXX format compatibility)
2533 false // if set, a property with only empty values will never be generated
2537 // scan name extensions
2539 sInt16 maxOccur=0; // default to no limit
2540 while (propnameextP) {
2541 sInt16 baseoffs=propnameextP->fieldidoffs;
2542 sInt16 repoffs=0; // no repeat offset yet
2543 if (baseoffs!=OFFS_NOSTORE && !propnameextP->readOnly) {
2544 // we can address fields for this property and it's not readonly (parsing variant)
2545 // generate value part
2546 sInt16 n=propnameextP->maxRepeat;
2547 // check for value list
2548 if (aPropP->valuelist && !aPropP->expandlist) {
2549 // property contains a value list -> all repetitions are shown within ONE property instance
2550 // NOTE: generateProperty will exhaust possible repeats
2552 aItem, // the item where data comes from
2553 aString, // the string to add properties to
2554 aPrefix, // the prefix (property name)
2555 aPropP, // the property to generate
2556 baseoffs, // field ID offset to be used
2557 repoffs, // additional repeat offset / array index
2558 aMimeMode, // MIME mode (older or newer vXXX format compatibility)
2559 propnameextP->minShow<1, // suppress if fewer to show than 1 (that is, like suppressempty in this case)
2560 propnameextP // propname extension for generating musthave param values and maxrep/repinc for valuelists
2564 // now generate separate properties for all repetitions
2565 // Note: strategy is to keep order as much as possible (completely if
2566 // minShow is >= maxRepeat
2567 sInt16 emptyRepOffs=-1;
2568 // get occurrence limit as provided by remote
2569 for (sInt16 i=0; i<aPropP->numValues; i++) {
2570 sInt16 fid=aPropP->convdefs[0].fieldid;
2572 if (fRelatedDatastoreP) {
2573 // only if datastore is related we are in SyncML context, otherwise we should not check maxOccur
2574 maxOccur = aItem.getItemType()->getFieldOptions(fid)->maxoccur;
2577 maxOccur = 0; // no limit
2578 // Note: all value fields of the property will have the same maxOccur, so we can stop here
2583 // generate property for this repetition
2584 // - no repeating within generateProperty takes place!
2585 sInt16 genres = generateProperty(
2586 aItem, // the item where data comes from
2587 aString, // the string to add properties to
2588 aPrefix, // the prefix (property name)
2589 aPropP, // the property to generate
2590 baseoffs, // field ID offset to be used
2591 repoffs, // additional repeat offset / array index
2592 aMimeMode, // MIME mode (older or newer vXXX format compatibility)
2593 propnameextP->minShow-generated<n, // suppress if fewer to show than remaining repeats
2594 propnameextP // propname extension for generating musthave param values
2596 if (genres==GENPROP_NONEMPTY) {
2597 // generated a property
2601 // nothing generated
2602 if (emptyRepOffs<0) emptyRepOffs=repoffs; // remember empty
2603 // check for array repeat, in which case exhausted array or non-supported field will stop generating
2604 if (propnameextP->maxRepeat==REP_ARRAY && genres==GENPROP_EXHAUSTED) break; // exit loop if any only if array exhausted
2606 // one more generated of the maximum possible (note: REP_ARRAY=32k, so this will not limit an array)
2608 repoffs+=propnameextP->repeatInc;
2609 // end generation if remote's maxOccur limit is reached
2610 if (maxOccur && generated>=maxOccur) {
2611 PDEBUGPRINTFX(DBG_GEN,(
2612 "maxOccur (%hd) for Property '%s' reached - no more instances will be generated",
2614 TCFG_CSTR(aPropP->propname)
2619 // add empty ones if needed
2620 while (generated<propnameextP->minShow && emptyRepOffs>=0 && !(maxOccur && generated>=maxOccur)) {
2621 // generate empty ones (no suppression)
2622 generateProperty(aItem,aString,aPrefix,aPropP,baseoffs,emptyRepOffs,aMimeMode,false);
2623 generated++; // count as generated anyway (even in case generation of empty is globally turned off)
2625 } // repeat properties when we have repeating enabled
2626 } // if name extension is stored
2627 propnameextP=propnameextP->next;
2628 // stop if maxOccur reached
2629 if (maxOccur && generated>=maxOccur) break;
2631 } // if nameexts at all
2632 } // TMimeDirProfileHandler::expandProperty
2635 // helper for expandProperty: generates property
2636 // returns: GENPROP_xxx
2637 sInt16 TMimeDirProfileHandler::generateProperty(
2638 TMultiFieldItem &aItem, // the item where data comes from
2639 string &aString, // the string to add properties to
2640 const char *aPrefix, // the prefix (property name)
2641 const TPropertyDefinition *aPropP, // the property to generate (all instances)
2642 sInt16 aBaseOffset, // field ID offset to be used
2643 sInt16 aRepeatOffset, // additional repeat offset / array index
2644 TMimeDirMode aMimeMode, // MIME mode (older or newer vXXX format compatibility)
2645 bool aSuppressEmpty, // if set, a property with only empty values will not be generated
2646 TPropNameExtension *aPropNameExt // propname extension for generating musthave param values and maxrep/repinc for valuelists
2649 string proptext; // unfolded property text
2650 proptext.reserve(300); // not too small
2651 string elemtext; // single element (value or param) text
2652 TEncodingTypes encoding;
2655 // - reset TZID presence flag
2656 fPropTZIDtctx = TCTX_UNKNOWN;
2657 // - start with empty text
2659 // - first set group if there is one
2660 if (aPropP->groupFieldID!=FID_NOT_SUPPORTED) {
2662 TItemField *g_fldP = aItem.getArrayFieldAdjusted(aPropP->groupFieldID+aBaseOffset, aRepeatOffset, true);
2663 if (g_fldP && !g_fldP->isEmpty()) {
2664 g_fldP->appendToString(proptext);
2665 proptext += '.'; // group separator
2668 // - append name and (possibly) parameters that are constant over all repetitions
2669 proptext += aPrefix;
2670 bool anyvaluessupported=false; // at least one of the main values must be supported by the remote in order to generate property at all
2671 bool arrayexhausted=false; // flag will be set if a main value was not generated because array exhausted
2672 // - append parameter values
2673 // anyvalues gets set if a parameter with shownonempty attribute was generated
2674 bool anyvalues=generateParams(
2675 aItem, // the item where data comes from
2676 proptext, // where params will be appended
2677 aPropP, // the property definition
2683 // - append value(s)
2684 encoding=enc_none; // default is no encoding
2685 sInt16 v=0; // value counter
2686 nonasc=false; // assume plain ASCII
2690 const TConversionDef *convP;
2691 sInt16 maxrep=1,repinc=1;
2693 maxrep=aPropNameExt->maxRepeat;
2694 repinc=aPropNameExt->repeatInc;
2696 // generate property contents
2697 if (aPropP->valuelist && !aPropP->expandlist) {
2698 // property with value list
2699 // NOTE: convdef[0] is used for all values, aRepeatOffset changes
2700 convP = &(aPropP->convdefs[0]);
2701 // - now iterate over available repeats or array contents
2702 while(aRepeatOffset<maxrep*repinc || maxrep==REP_ARRAY) {
2703 // generate one value
2706 genres=generateValue(
2709 aBaseOffset, // offset relative to base field
2710 aRepeatOffset, // additional offset or array index
2712 aPropP->valuesep, // use valuelist separator between multiple values possibly generated from a list in a single field (e.g. CATEGORIES)
2714 false, // not a param
2715 true, // always escape ; in valuelist properties
2716 aPropP->valuesep==',' || aPropP->altvaluesep==',', // escape commas if one of the separators is a comma
2719 v>0 ? aPropP->valuesep : 0 // separate with specified multi-value-delimiter if not first value
2721 // check if something was generated
2722 if (genres>=GENVALUE_ELEMENT) {
2723 // generated something, might have caused encoding/noasc change
2725 nonasc=nonasc || na;
2727 // update if we have at least one value of this property supported (even if empty) by the remote party
2728 if (genres>GENVALUE_NOTSUPPORTED) anyvaluessupported=true;
2729 if (genres==GENVALUE_EXHAUSTED) arrayexhausted=true; // for at least one component of the property, the array is exhausted
2730 // update if we have any value now (even if only empty)
2731 // - generate empty property according to
2733 // - session-global fDontSendEmptyProperties
2734 // - supressEmpty property flag in property definition
2735 // - if no repeat (i.e. no aPropNameExt), exhausted array is treated like empty value (i.e. rendered unless suppressempty set)
2736 anyvalues = anyvalues ||
2737 (genres>=(aSuppressEmpty || fDontSendEmptyProperties || aPropP->suppressEmpty ? GENVALUE_ELEMENT : (aPropNameExt ? GENVALUE_EMPTYELEMENT : GENVALUE_EXHAUSTED)));
2738 // count effective value appended
2740 // update repeat offset
2741 aRepeatOffset+=repinc;
2742 // check for array mode - stop if array is exhausted or field not supported
2743 if (maxrep==REP_ARRAY && genres<=GENVALUE_EXHAUSTED) break;
2747 // property with individual values (like N)
2748 // NOTE: field changes with different convdefs, offsets remain stable
2749 arrayexhausted = true; // assume all arrays exhausted unless we find at least one non-exhausted array
2750 bool somearrays = false; // no arrays yet
2752 convP = &(aPropP->convdefs[v]);
2753 // generate one value
2756 genres=generateValue(
2759 aBaseOffset, // base offset, relative to
2760 aRepeatOffset, // repeat offset or array index
2761 elemtext, // value will be stored here (might be binary in case of BLOBs, but then encoding will be set)
2762 ',', // should for some exotic reason values consist of a list, separate it by "," (";" is reserved for structured values)
2765 aPropP->numValues>1, // structured value, escape ";"
2766 aPropP->altvaluesep==',', // escape commas if alternate separator is a comma
2767 enc, // will receive needed encoding (usually B64 for binary values)
2770 //* %%% */ PDEBUGPRINTFX(DBG_EXOTIC,("generateValue #%hd for property '%s' returns genres==%hd",v,TCFG_CSTR(aPropP->propname),genres));
2771 // check if something was generated
2772 if (genres>=GENVALUE_ELEMENT) {
2773 // generated something, might have caused encoding/noasc change
2775 nonasc=nonasc || na;
2777 // update if we have at least one value of this property supported (even if empty) by the remote party
2778 if (genres>GENVALUE_NOTSUPPORTED) anyvaluessupported=true;
2779 if (genres==GENVALUE_ELEMENT || genres==GENVALUE_EMPTYELEMENT) {
2780 arrayexhausted = false; // there is at least one non-exhausted array we're reading from (even if only empty value)
2781 somearrays = true; // generating from array
2783 else if (genres==GENVALUE_EXHAUSTED)
2784 somearrays = true; // generating from array
2785 // update if we have any value now (even if only empty)
2786 // - generate empty property according to
2788 // - session-global fDontSendEmptyProperties
2789 // - supressEmpty property flag in property definition
2790 // - if no repeat (i.e. no aPropNameExt), exhausted array is treated like empty value (i.e. rendered unless suppressempty set)
2791 anyvalues = anyvalues ||
2792 (genres>=(aSuppressEmpty || fDontSendEmptyProperties || aPropP->suppressEmpty ? GENVALUE_ELEMENT : (aPropNameExt ? GENVALUE_EMPTYELEMENT : GENVALUE_EXHAUSTED)));
2793 // insert delimiter if not last value
2795 if (v>=aPropP->numValues) break; // done with all values
2796 // add delimiter for next value
2797 elemtext+=aPropP->valuesep;
2798 // add break indicator
2801 // if none of the data sources is an array, we can't be exhausted.
2802 if (!somearrays) arrayexhausted = false;
2804 // - finalize property if it contains supported fields at all (or is mandatory)
2805 if ((anyvaluessupported && anyvalues) || aPropP->mandatory) {
2806 // - generate encoding parameter if needed
2807 if (encoding!=enc_none) {
2808 // in MIME-DIR, only "B" is allowed for binary, for vCard 2.1 it is "BASE64"
2809 if (encoding==enc_base64 || encoding==enc_b) {
2810 encoding = aMimeMode==mimo_standard ? enc_b : enc_base64;
2812 // add the parameter
2813 proptext.append(";ENCODING=");
2814 proptext.append(MIMEEncodingNames[encoding]);
2816 // - generate charset parameter if needed
2817 // NOTE: MIME-DIR based formats do NOT have the CHARSET attribute any more!
2818 if (nonasc && aMimeMode==mimo_old && fDefaultOutCharset!=chs_ansi) {
2819 // non-ASCII chars contained, generate property telling what charset is used
2820 proptext.append(";CHARSET=");
2821 proptext.append(MIMECharSetNames[fDefaultOutCharset]);
2823 // - separate value from property text
2825 // - append (probably encoded) values now, always in UTF-8
2826 encodeValues(encoding,fDefaultOutCharset,elemtext,proptext,fDoNotFoldContent);
2827 // - fold, copy and terminate (CRLF) property into aString output
2828 finalizeProperty(proptext.c_str(),aString,aMimeMode,fDoNotFoldContent,encoding==enc_quoted_printable);
2829 // - special case: base64 (but not B) encoded value must have an extra CRLF even if folding is
2830 // disabled, so we need to insert it here (because non-folding mode eliminates it from being
2831 // generated automatically in encodeValues/finalizeProperty)
2832 if (fDoNotFoldContent && encoding==enc_base64)
2833 aString.append("\x0D\x0A"); // extra CRLF terminating a base64 encoded property (note, base64 only occurs in mimo_old)
2834 // - property generated
2835 return GENPROP_NONEMPTY;
2838 // Note: it is essential to return GENPROP_EXHAUSTED if no values are supported for this property at
2839 // all (otherwise caller might loop endless trying to generate a non-empty property
2842 ? (arrayexhausted ? GENPROP_EXHAUSTED : GENPROP_EMPTY) // no property generated
2843 : GENPROP_EXHAUSTED; // no values supported means "exhausted" as well
2845 } // TMimeDirProfileHandler::generateProperty
2849 // generate MIME-DIR from item into string object
2850 void TMimeDirProfileHandler::generateMimeDir(TMultiFieldItem &aItem, string &aString)
2853 aString.reserve(3000); // not too small
2855 // reset item time zone before generating
2856 fHasExplicitTZ = false; // none set explicitly
2857 fItemTimeContext = fReceiverTimeContext; // default to receiver context
2858 fUsedTCtxSet.clear(); // no TZIDs used yet
2859 fEarliestTZDate = noLinearTime; // reset range of generated timestamps related to a TZID or TZ/DAYLIGHT
2860 fLatestTZDate = noLinearTime;
2861 fVTimeZonePendingProfileP = NULL; // no VTIMEZONE pending for generation
2862 fVTimeZoneInsertPos = 0; // no insert position yet
2863 // recursively generate levels
2864 generateLevels(aItem,aString,fProfileDefinitionP);
2865 // now generate VTIMEZONE, if needed
2866 if (fVTimeZonePendingProfileP) {
2869 // generate needed vTimeZones (according to fUsedTCtxSet)
2870 for (TTCtxSet::iterator pos=fUsedTCtxSet.begin(); pos!=fUsedTCtxSet.end(); pos++) {
2871 // - calculate first and last year covered by timestamps in this record
2872 sInt16 startYear=0,endYear=0;
2873 if (fEarliestTZDate && fProfileCfgP->fVTimeZoneGenMode!=vtzgen_current) {
2874 // dependent on actually created dates
2875 lineartime2date(fEarliestTZDate, &startYear, NULL, NULL);
2876 lineartime2date(fLatestTZDate, &endYear, NULL, NULL);
2877 // there is at least one date in the record
2878 switch (fProfileCfgP->fVTimeZoneGenMode) {
2880 endYear = startYear; // only show for start of range
2883 startYear = endYear; // only show for end of range
2886 // pass both start and end year
2888 case vtzgen_openend:
2889 // pass start year but request that all rules from start up to the current date are inlcuded
2892 case vtzgen_current:
2893 case numVTimeZoneGenModes:
2894 // case statement to keep gcc happy, will not be reached because of if() above
2900 s.append(fVTimeZonePendingProfileP->levelName);
2901 finalizeProperty(s.c_str(),vtz,fMimeDirMode,false,false);
2902 // - generate raw string
2903 //%%% endYear is not yet implemented in internalToVTIMEZONE(), fTzIdGenMode has only the olson option for now
2904 internalToVTIMEZONE(*pos, val, getSessionZones(), NULL, startYear, endYear, fProfileCfgP->fTzIdGenMode==tzidgen_olson ? "o" : NULL);
2906 while (val.size()>n) {
2907 i = val.find('\n',n); // next line end
2908 if (i==string::npos) i=val.size();
2910 // more than one char = not only a trailing line end
2911 s.assign(val,n,i-n);
2912 // finalize and add property
2913 finalizeProperty(s.c_str(),vtz,fMimeDirMode,false,false);
2914 // advance cursor beyond terminating LF
2920 s.append(fVTimeZonePendingProfileP->levelName);
2921 finalizeProperty(s.c_str(),vtz,fMimeDirMode,false,false);
2923 // now insert the VTIMEZONE into the output string (so possibly making it appear BEFORE the
2924 // properties that use TZIDs)
2925 aString.insert(fVTimeZoneInsertPos, vtz);
2927 fVTimeZonePendingProfileP = NULL;
2928 } // if pending VTIMEZONE
2929 } // TMimeDirProfileHandler::generateMimeDir
2932 // generate nested levels of MIME-DIR content
2933 void TMimeDirProfileHandler::generateLevels(
2934 TMultiFieldItem &aItem,
2936 const TProfileDefinition *aProfileP
2939 //* %%% */ PDEBUGBLOCKDESC("generateLevels",TCFG_CSTR(aProfileP->levelName));
2940 // check if level must be generated
2943 sInt16 fid=aProfileP->levelConvdef.fieldid;
2944 if (fid<0) dolevel=true; // if no controlling field there, generate anyway
2946 // check field contents to determine if generation is needed
2947 if (aItem.isAssigned(fid)) {
2948 const TEnumerationDef *enumP = aProfileP->levelConvdef.enumdefs;
2949 aItem.getField(fid)->getAsString(val);
2951 // if enumdefs, content must match first enumdef's enumval (NOT enumtext!!)
2952 dolevel = strucmp(val.c_str(),TCFG_CSTR(enumP->enumval))==0;
2955 // just being not empty enables level
2956 dolevel = !aItem.getField(fid)->isEmpty();
2960 // check for MIME mode dependency
2961 dolevel = dolevel && mimeModeMatch(aProfileP->modeDependency);
2962 // generate level if enabled
2964 // generate level start
2965 if (aProfileP->profileMode==profm_vtimezones) {
2966 // don't generate now, just remember the string position where we should add the
2967 // VTIMEZONEs when we're done generating the record.
2968 fVTimeZonePendingProfileP = aProfileP;
2969 fVTimeZoneInsertPos = aString.size();
2972 // standard custom level
2974 s.append(aProfileP->levelName);
2975 finalizeProperty(s.c_str(),aString,fMimeDirMode,false,false);
2976 // loop through all properties of that level
2977 const TPropertyDefinition *propP = aProfileP->propertyDefs;
2978 #ifndef NO_REMOTE_RULES
2979 uInt16 propGroup=0; // group identifier (all props with same name have same group ID)
2980 const TPropertyDefinition *otherRulePropP = NULL; // default property which is used if none of the rule-dependent in the group was used
2981 bool ruleSpecificExpanded = false;
2983 const TPropertyDefinition *expandPropP;
2985 // check for mode dependency
2986 if (!mimeModeMatch(propP->modeDependency)) {
2987 // no mode match -> just skip this one
2991 //* %%% */ PDEBUGBLOCKDESC("expand_property",TCFG_CSTR(propP->propname));
2992 #ifndef NO_REMOTE_RULES
2993 // check for beginning of new group (no or different property group number)
2994 if (propP->propGroup==0 || propP->propGroup!=propGroup) {
2995 // end of last group - start of new group
2996 propGroup = propP->propGroup; // remember new group number
2997 // expand "other"-rule dependent variant from last group
2998 if (!ruleSpecificExpanded && otherRulePropP) {
3002 TCFG_CSTR(otherRulePropP->propname), // the prefix consists of the property name
3003 otherRulePropP, // the property definition
3004 fMimeDirMode // MIME-DIR mode
3007 // for next group, no rule-specific version has been expanded yet
3008 ruleSpecificExpanded = false;
3009 // for next group, we don't have a "other"-rule variant
3010 otherRulePropP=NULL;
3012 // check if entry is rule-specific
3013 expandPropP=NULL; // do not expand by default
3014 if (propP->dependsOnRemoterule) {
3015 // check if depends on current rule
3016 if (propP->ruleDependency==NULL) {
3017 // this is the "other"-rule dependent variant
3019 otherRulePropP=propP;
3021 else if (isActiveRule(propP->ruleDependency)) {
3022 // specific for the applied rule
3023 expandPropP=propP; // default to expand current prop
3024 // now we have expanded a rule-specific property (blocks expanding of "other"-rule dependent prop)
3025 ruleSpecificExpanded=true;
3029 // does not depend on rule, expand anyway
3032 // check if this is last prop of list
3034 if (!propP && otherRulePropP && !ruleSpecificExpanded) {
3035 // End of prop list, no rule-specific expand yet, and there is a otherRuleProp
3036 // expand "other"-rule's property instead
3037 expandPropP=otherRulePropP;
3044 // now expand if selected
3047 // recursively generate all properties that expand from this entry
3048 // (includes extendsfieldid-parameters and repetitions
3052 TCFG_CSTR(expandPropP->propname), // the prefix consists of the property name
3053 expandPropP, // the property definition
3054 fMimeDirMode // MIME-DIR mode
3057 //* %%% */ PDEBUGENDBLOCK("expand_property");
3058 } // properties loop
3059 // generate sublevels, if any
3060 const TProfileDefinition *subprofileP = aProfileP->subLevels;
3061 while (subprofileP) {
3062 // generate sublevels (possibly, none is generated)
3063 generateLevels(aItem,aString,subprofileP);
3065 subprofileP=subprofileP->next;
3067 // generate level end
3069 s.append(aProfileP->levelName);
3070 finalizeProperty(s.c_str(),aString,fMimeDirMode,false,false);
3072 } // if level must be generated
3073 //* %%% */ PDEBUGENDBLOCK("generateLevels");
3074 } // TMimeDirProfileHandler::generateLevels
3077 // Convert string from MIME-format into field value(s).
3078 // - the string passed to this function is already a translated value
3079 // list if combinesep is set, and every single value is already
3080 // enum-translated if enums are defined.
3081 // - returns false if field(s) could not be assigned because aText has
3083 // - returns true if field(s) assigned something useful or no field is
3084 // available to assign anything to.
3085 bool TMimeDirProfileHandler::MIMEStringToField(
3086 const char *aText, // the value text to assign or add to the field
3087 const TConversionDef *aConvDefP, // the conversion definition record
3088 TMultiFieldItem &aItem, // the item where data goes to
3089 sInt16 aFid, // the field ID (can be NULL for special conversion modes)
3090 sInt16 aArrIndex // the repeat offset to handle array fields
3096 fieldinteger_t flags = 0;
3097 TTimestampField *tsFldP;
3099 TParsedTzidSet::iterator tz;
3102 lineartime_t dtstart;
3103 timecontext_t startcontext = 0, untilcontext = 0;
3107 fieldinteger_t firstmask;
3108 fieldinteger_t lastmask;
3112 // get pointer to leaf field
3113 TItemField *fldP = aItem.getArrayField(aFid,aArrIndex);
3114 switch (aConvDefP->convmode) {
3115 case CONVMODE_MAILTO:
3116 // remove the mailto: prefix if there is one
3117 if (strucmp(aText,"mailto:",7)==0)
3118 aText+=7; // remove leading "mailto:"
3120 case CONVMODE_EMPTYONLY:
3121 // same as CONVMODE_NONE, but assigns only first occurrence (that is,
3122 // when field is still empty)
3123 if (!fldP) return true; // no field, assignment "ok" (=nop)
3124 if (!fldP->isEmpty()) return true; // field not empty, discard new assignment
3125 case CONVMODE_TIMESTAMP: // nothing special for parsing
3126 case CONVMODE_AUTODATE: // nothing special for parsing
3127 case CONVMODE_AUTOENDDATE: // check for "last minute of the day"
3128 case CONVMODE_DATE: // dates will be made floating
3131 if (!fldP) return true; // no field, assignment "ok" (=nop)
3132 // just set as string or add if combine mode
3133 if (aConvDefP->combineSep) {
3135 if (!fldP->isEmpty()) {
3136 // not empty, append with separator
3138 cs[0]=aConvDefP->combineSep;
3140 fldP->appendString(cs);
3142 fldP->appendString(aText);
3145 // for non-strings, skip leading spaces before trying to parse
3146 if (!fldP->isBasedOn(fty_string)) {
3147 while (*aText && *aText==' ') aText++; // skip leading spaces
3149 // simple assign mode
3150 if (fldP->isBasedOn(fty_timestamp)) {
3151 // read as ISO8601 timestamp
3152 tsFldP = static_cast<TTimestampField *>(fldP);
3153 // if field already has a non-unknown context (e.g. set via TZID, or TZ/DAYLIGHT),
3154 // use that as context for floating ISO date (i.e. no "Z" or "+/-hh:mm" suffix)
3155 if (!tsFldP->isFloating()) {
3156 // field already has a TZ specified (e.g. by a TZID param), use that instead of item level context
3157 tctx = tsFldP->getTimeContext();
3160 // no pre-known zone for this specific field, check if property has a specific zone
3161 if (!TCTX_IS_UNKNOWN(fPropTZIDtctx)) {
3162 // property has a specified time zone context from a TZID, use it
3163 tctx = fPropTZIDtctx; // default to property's TZID (if one was parsed, otherwise this will be left floating)
3165 else if (fHasExplicitTZ) {
3166 // item has an explicitly specified time zone context (e.g. set via TZ: property),
3167 // treat all timestamps w/o own time zone ("Z" suffix) in that context
3168 tctx = fItemTimeContext;
3171 // item has no explicitly specified time zone context,
3172 // parse and leave floating float for now
3173 tctx = TCTX_UNKNOWN; // default to floating
3176 // Now tctx is the default zone to bet set for ALL values that are in floating notation
3177 // - check for special handling of misbehaving remotes
3178 if (fTreatRemoteTimeAsLocal || fTreatRemoteTimeAsUTC) {
3179 // ignore time zone specs which might be present possibly
3180 tsFldP->setAsISO8601(aText, tctx, true);
3181 // now force time zone to item/user context or UTC depending on flag settings
3182 tctx = fTreatRemoteTimeAsLocal ? fItemTimeContext : TCTX_UTC;
3184 tsFldP->setTimeContext(tctx);
3187 // read with time zone, if present, and default to tctx set above
3188 tsFldP->setAsISO8601(aText, tctx, false);
3189 // check if still floating now
3190 if (tsFldP->isFloating()) {
3191 // unfloat only if remote cannot handle UTC and therefore ALWAYS uses localtime.
3192 // otherwise, assume that floating status is intentional and must be retained.
3193 // Note: TZID and TZ, if present, are already applied by now
3194 // Note: DURATION and DATE floating will always be retained, as they are always intentional
3195 if ((!fReceiverCanHandleUTC || fProfileCfgP->fUnfloatFloating) && !TCTX_IS_DATEONLY(tsFldP->getTimeContext()) && !tsFldP->isDuration()) {
3196 // not intentionally floating, but just not capable otherwise
3197 // - put it into context of item (which is in this case session's user context)
3198 tsFldP->setTimeContext(fItemTimeContext);
3203 if (fHasExplicitTZ) {
3204 // item has explicit zone - move timestamp to it (e.g. if timestamps are sent
3205 // in ISO8601 Z notation, but a TZ/DAYLIGHT or TZID is present)
3206 tsFldP->moveToContext(tctx,false);
3210 // special conversions
3211 if (aConvDefP->convmode==CONVMODE_DATE) {
3212 tsFldP->makeFloating(); // date-only is forced floating
3214 else if (aConvDefP->convmode==CONVMODE_AUTOENDDATE && fMimeDirMode==mimo_old) {
3215 // check if this could be a 23:59 type end-of-day
3216 lineartime_t ts = tsFldP->getTimestampAs(fItemTimeContext,&tctx); // get in item context or floating
3217 lineartime_t ts0 = lineartime2dateonlyTime(ts);
3218 if (ts0!=ts && AlldayCount(ts0,ts)>0) { // only if not already a 0:00
3219 // this is a 23:59 type end-of-day, convert it to midnight of next day (AND adjust time context, in case it is now different from original)
3220 tsFldP->setTimestampAndContext(lineartime2dateonlyTime(ts)+linearDateToTimeFactor,tctx);
3226 fldP->setAsString(aText);
3229 return true; // found
3234 if (ISO8601StrToContext(aText, tctx)!=0) {
3235 // Note: this is always global for the entire item, so set the item context
3236 // (which is then used when parsing dates (which should be delayed to make sure TZ is seen first)
3237 fItemTimeContext = tctx;
3238 if (!TCTX_IS_TZ(tctx)) {
3239 // only offset. Try to symbolize it by passing a DAYLIGHT:FALSE and the offset
3240 if (TzDaylightToContext("FALSE", fItemTimeContext, tctx, getSessionZones(), fReceiverTimeContext))
3241 fItemTimeContext = tctx; // there is a symbolized context, keep that
3243 fHasExplicitTZ = true; // zone explicitly set, not only copied from session's user zone
3246 return true; // not set, is ok
3247 case CONVMODE_DAYLIGHT:
3248 // parse DAYLIGHT zone description property, prefer user zone (among multiple zones matching the Tz/daylight info)
3249 // - resolve to offset (assuming that item context came from a TZ property, so it will
3250 // be one of the non-DST zones, so reftime does not matter)
3251 tctx = fItemTimeContext;
3252 TzResolveContext(tctx, getSystemNowAs(TCTX_UTC, getSessionZones()), true, getSessionZones());
3253 // - now find matching zone for given offset and DAYLIGHT property string
3254 if (TzDaylightToContext(aText,tctx,tctx,getSessionZones(),fReceiverTimeContext)) {
3255 // this is always global for the entire item, so set the item context
3256 // (which is then used when parsing dates (which should be delayed to make sure TZ is seen first)
3257 fItemTimeContext = tctx;
3258 fHasExplicitTZ = true; // zone explicitly set, not only copied from session's user zone
3261 return true; // not set, is ok
3263 // try to get context for named zone
3264 // - look up in TZIDs we've parsed so far from VTIMEZONE
3265 tz = fParsedTzidSet.find(aText);
3266 if (tz!=fParsedTzidSet.end()) {
3267 tctx = tz->second; // get tctx resolved from VTIMEZONE
3268 // use tctx for all values from this property
3269 fPropTZIDtctx = tctx;
3272 else if (TimeZoneNameToContext(aText, tctx, getSessionZones())) {
3273 // found valid TZID property, save it so we can use it for all values of this property that don't specify their own TZ
3274 PDEBUGPRINTFX(DBG_ERROR,("Warning: TZID %s could be resolved against internal name, but appropriate VTIMEZONE is missing",aText));
3279 PDEBUGPRINTFX(DBG_ERROR,("Invalid TZID value '%s' found (no related VTIMEZONES found and not referring to an internal time zone name)",aText));
3281 return true; // not set, is ok
3283 // if no field, we still have the zone as fItemTimeContext
3284 if (!fldP) return true; // no field, is ok
3285 else if (fldP->isBasedOn(fty_timestamp)) {
3286 // based on timestamp, assign context to that timestamp
3287 tsFldP = static_cast<TTimestampField *>(fldP);
3288 tsFldP->setTimeContext(tctx);
3290 else if (fldP->getCalcType()==fty_integer || !TCTX_IS_TZ(tctx)) {
3291 // integer field or non-symbolic time zone:
3292 // assign minute offset as number (calculated for now)
3293 TzResolveToOffset(tctx, moffs, getSession()->getSystemNowAs(TCTX_UTC), true, getSessionZones());
3294 fldP->setAsInteger(moffs);
3297 // assign symbolic time zone name
3298 TimeZoneContextToName(tctx, s, getSessionZones());
3299 fldP->setAsString(s);
3303 case CONVMODE_MULTIMIX:
3304 case CONVMODE_BITMAP:
3305 while (*aText && *aText==' ') aText++; // skip leading spaces
3306 if (aConvDefP->convmode==CONVMODE_MULTIMIX) {
3307 // parse value to determine field
3308 if (!mixvalparse(aText, offs, isBitMap, n)) return true; // syntax not ok, nop
3309 fldP = aItem.getArrayField(aFid+offs,aArrIndex);
3314 if (StrToUShort(aText,n,2)<1) return true; // no integer convertible value, nop
3316 if (!fldP) return true; // no field, assignment "ok" (=nop)
3318 // store or add to bitmap
3319 // - get current bitmap value if we have a spearator (means that we can have multiple values)
3320 if (aConvDefP->combineSep)
3321 flags=fldP->getAsInteger();
3322 flags = flags | ((fieldinteger_t)1<<n);
3323 // - save updated flags
3324 fldP->setAsInteger(flags);
3328 fldP->setAsString(aText+n);
3331 case CONVMODE_VERSION:
3333 // - return true if correct version string
3334 return strucmp(aText,aItem.getItemType()->getTypeVers(fProfileMode))==0;
3335 case CONVMODE_PRODID:
3336 case CONVMODE_VALUETYPE:
3337 case CONVMODE_FULLVALUETYPE:
3338 return true; // simply ignore, always ok
3339 case CONVMODE_RRULE:
3341 TTimestampField *tfP;
3344 if (aFid<0) return true; // no field block, assignment "ok" (=nop)
3345 // read DTSTART (last=6th field in block) as reference for converting count to end time point
3346 dtstart=0; // start date/time, as reference
3347 if (!(tfP = ITEMFIELD_DYNAMIC_CAST_PTR(TTimestampField,fty_timestamp,aItem.getArrayField(aFid+5,aArrIndex)))) return false;
3348 // TZ and TZID should be applied to dates by now, so dtstart should be in right zone
3349 dtstart = tfP->getTimestampAs(TCTX_UNKNOWN,&startcontext);
3350 if (TCTX_IS_UTC(startcontext)) {
3351 // UTC is probably not the correct zone to resolve weekdays -> convert to item zone
3352 dtstart = tfP->getTimestampAs(fItemTimeContext,&startcontext);
3354 // init field block values
3355 freq='0'; // frequency
3356 freqmod=' '; // frequency modifier
3357 interval=0; // unspecified interval
3358 firstmask=0; // day mask counted from the first day of the period
3359 lastmask=0; // day mask counted from the last day of the period
3360 until=0; // last day
3361 // do the conversion here
3363 if (fMimeDirMode==mimo_old) {
3364 // vCalendar 1.0 type RRULE
3365 dostore=RRULE1toInternal(
3366 aText, // RRULE string to be parsed
3367 dtstart, // reference date for parsing RRULE
3380 // iCalendar 2.0 type RRULE
3381 dostore=RRULE2toInternal(
3382 aText, // RRULE string to be parsed
3383 dtstart, // reference date for parsing RRULE
3396 // store values into field block
3398 if (!(sfP = ITEMFIELD_DYNAMIC_CAST_PTR(TStringField,fty_string,aItem.getArrayField(aFid,aArrIndex)))) return false;
3399 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
3402 sfP->appendChar(freq);
3403 sfP->appendChar(freqmod);
3406 if (!(ifP = ITEMFIELD_DYNAMIC_CAST_PTR(TIntegerField,fty_integer,aItem.getArrayField(aFid,aArrIndex)))) return false;
3407 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
3408 ifP->setAsInteger(interval);
3410 if (!(ifP = ITEMFIELD_DYNAMIC_CAST_PTR(TIntegerField,fty_integer,aItem.getArrayField(aFid,aArrIndex)))) return false;
3411 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
3412 ifP->setAsInteger(firstmask);
3414 if (!(ifP = ITEMFIELD_DYNAMIC_CAST_PTR(TIntegerField,fty_integer,aItem.getArrayField(aFid,aArrIndex)))) return false;
3415 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
3416 ifP->setAsInteger(lastmask);
3418 if (!(tfP = ITEMFIELD_DYNAMIC_CAST_PTR(TTimestampField,fty_timestamp,aItem.getArrayField(aFid,aArrIndex)))) return false;
3419 aFid++; // do NOT INCREMENT in macro, as it would get incremented twice
3420 tfP->setTimestampAndContext(until,untilcontext);
3421 // - dtstart is not stored, but only read above for reference
3428 break; // just in case
3430 // unknown mode, cannot convert
3434 } // TMimeDirProfileHandler::MIMEStringToField
3437 // helper for parseMimeDir()
3438 // - parse parameter or property value(list), returns false if no value(list)
3439 bool TMimeDirProfileHandler::parseValue(
3440 const string &aText, // string to parse as value (could be binary content)
3441 const TConversionDef *aConvDefP,
3442 sInt16 aBaseOffset, // base offset
3443 sInt16 aRepOffset, // repeat offset, adds to aBaseOffset for non-array fields, is array index for array fileds
3444 TMultiFieldItem &aItem, // the item where data goes to
3445 bool &aNotEmpty, // is set true (but never set false) if property contained any (non-positional) values
3446 char aSeparator, // separator between values
3447 TMimeDirMode aMimeMode, // MIME mode (older or newer vXXX format compatibility)
3448 bool aParamValue, // set if parsing parameter value (different escaping rules)
3449 bool aStructured // set if value consists of multiple values (has semicolon content escaping)
3456 // determine field ID
3457 sInt16 fid=aConvDefP->fieldid;
3459 // value has field where it can be stored
3460 // - fid is ALWAYS offset by baseoffset
3462 // - adjust fid and repoffset (add them and reset aRepOffset if no array field)
3463 aItem.adjustFidAndIndex(fid,aRepOffset);
3464 // find out if value exists (available in source and target)
3465 if (isFieldAvailable(aItem,fid)) {
3466 // parse only if field available in both source and target
3467 if (aConvDefP->convmode==CONVMODE_BLOB_B64) {
3468 // move 1:1 into field
3469 // - get pointer to leaf field
3470 TItemField *fldP = aItem.getArrayField(fid,aRepOffset);
3471 // - directly set field with entire (possiby binary) string content
3472 if (fldP) fldP->setAsString(aText);
3473 // parsed successfully
3476 // normal text value, apply de-escaping, charset transformation, value list and enum conversion
3477 p = aText.c_str(); // start here
3483 // check for field list separator (if field allows list at all)
3484 if (c==aSeparator && aConvDefP->combineSep) {
3485 p++; // skip separator
3488 // check for escaped chars
3492 if (!c) break; // half escape sequence, ignore
3493 else if (c=='n' || c=='N') c='\n';
3494 // other escaped chars are shown as themselves
3501 // find first non-space and number of chars excluding leading and trailing spaces
3502 const char* valnospc = val.c_str();
3503 size_t numnospc=val.size();
3504 while (*valnospc && *valnospc==' ') { valnospc++; numnospc--; }
3505 while (*(valnospc+numnospc-1)==' ') { numnospc--; }
3506 // - counts as non-empty if there is a non-empty (and not space-only) value string (even if
3507 // it might be converted to empty-value in enum conversion)
3508 if (*valnospc) aNotEmpty=true;
3509 // - apply enum translation if any
3510 const TEnumerationDef *enumP = aConvDefP->findEnumByName(valnospc,numnospc);
3512 // we have an explicit value (can be default if there is a enm_defaultvalue enum)
3513 if (enumP->enummode==enm_ignore)
3514 continue; // do not assign anything, get next value
3516 if (enumP->enummode==enm_prefix) {
3517 // append original value minus prefix to translation
3518 size_t n=TCFG_SIZE(enumP->enumtext);
3519 val2.assign(valnospc+n,numnospc-n); // copying from original val
3520 val=enumP->enumval; // assign the prefix
3521 val+=val2; // and append the original value sans prefix
3524 val=enumP->enumval; // just use translated value
3528 // assign (or add) value to field
3529 if (!MIMEStringToField(
3530 val.c_str(), // the value text to assign or add to the field
3531 aConvDefP, // the conversion definition
3533 fid, // field ID, can be -1
3534 aRepOffset // 0 or array index
3536 // field conversion error
3537 PDEBUGPRINTFX(DBG_ERROR,(
3538 "TMimeDirProfileHandler::parseValue: MIMEStringToField assignment (fid=%hd, arrindex=%hd) failed",
3544 } // while(more chars in value text)
3545 } // if source and target fields available
3547 // show this in log, as most probably it's a remote devInf bug
3548 PDEBUGPRINTFX(DBG_PARSE,("No value stored for field index %hd because remote indicates not supported in devInf",fid));
3550 } // if fieldid exists
3552 // could be special conversion using no data or data from
3553 // internal object variables (such as VERSION value)
3554 if (!MIMEStringToField(
3555 aText.c_str(), // the value text to process
3556 aConvDefP, // the conversion definition
3561 // field conversion error
3562 PDEBUGPRINTFX(DBG_ERROR,(
3563 "TMimeDirProfileHandler::parseValue: MIMEStringToField in check mode (no field) failed with val=%s",
3569 // parsed successfully
3571 } // TMimeDirProfileHandler::parseValue
3575 // parse given property
3576 bool TMimeDirProfileHandler::parseProperty(
3577 cAppCharP &aText, // where to start interpreting property, will be updated past end of what was scanned
3578 TMultiFieldItem &aItem, // item to store data into
3579 const TPropertyDefinition *aPropP, // the property definition
3580 sInt16 *aRepArray, // array[repeatID], holding current repetition COUNT for a certain nameExts entry
3581 sInt16 aRepArraySize, // size of array (for security)
3582 TMimeDirMode aMimeMode, // MIME mode (older or newer vXXX format compatibility)
3583 cAppCharP aGroupName, // property group ("a" in "a.TEL:131723612")
3584 size_t aGroupNameLen
3587 TNameExtIDMap nameextmap;
3588 const TParameterDefinition *paramP;
3589 const char *p,*ep,*vp;
3594 bool fieldoffsetfound;
3595 bool notempty = false;
3597 sInt16 pidx; // parameter index
3598 TEncodingTypes encoding;
3600 // field storage info vars, defaults are used if property has no TPropNameExtension
3601 sInt16 baseoffset = 0;
3602 sInt16 repoffset = 0;
3603 sInt16 maxrep = 1; // no repeat by default
3604 sInt16 repinc = 1; // inc by 1
3605 sInt16 repid = -1; // invalid by default
3606 bool overwriteempty = false; // do not overwrite empty values by default
3607 bool repoffsByGroup = false;
3610 encoding = enc_none; // no encoding by default
3611 charset = aMimeMode==mimo_standard ? chs_utf8 : fDefaultInCharset; // always UTF8 for real MIME-DIR (same as enclosing SyncML doc), for mimo_old depends on <inputcharset> remote rule option (normally UTF-8)
3612 nameextmap = 0; // no name extensions detected so far
3613 fieldoffsetfound = (aPropP->nameExts==NULL); // no first pass needed at all w/o nameExts, just use offs=0
3614 valuelist = aPropP->valuelist; // cache flag
3615 // scan parameter list
3622 p=nextunfolded(p,aMimeMode);
3623 // parameter expected here
3624 // - find end of parameter name
3625 vp=NULL; // no param name found
3626 for (ep=p; *ep; ep=nextunfolded(ep,aMimeMode)) {
3628 // param value follows at vp
3629 vp=nextunfolded(ep,aMimeMode);
3632 else if (*ep==':' || *ep==';') {
3633 // end of parameter name w/o equal sign
3634 if (aMimeMode!=mimo_old) {
3635 // only mimo_old allows default params, but as e.g. Nokia Intellisync (Synchrologic) does this completely wrong, we now tolerate it
3636 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,(
3637 "Parameter without value: %s - is wrong in MIME-DIR, but we tolerate it and parse as default param name",
3641 // treat this as a value of the default parameter (correct syntax in old vCard 2.1/vCal 1.0, wrong in MIME-DIR)
3642 defaultparam=true; // default param
3643 // value is equal to param name and starts at p
3647 // add char to param name (unfolded!)
3651 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("parseProperty: bad parameter %s (missing value)",pname.c_str()));
3654 // parameter name & value isolated, pname=name (if not defaultparam), vp points to value
3655 // - obtain unfolded value
3657 bool dquoted = false;
3658 if (*vp=='"' && aMimeMode==mimo_standard) {
3660 vp=nextunfolded(vp,aMimeMode);
3664 if (isEndOfLineOrText(c)) break;
3666 // within double quoted value, only closing dquote can end it
3668 // swallow closing double quote and proceed (next should be end of value anyway)
3669 vp = nextunfolded(vp,aMimeMode);
3675 // not within double quoted value
3676 if (c==':' || c==';') break; // end of value
3677 // check escaped characters
3679 // escape char, do not check next char for end-of-value (but DO NOT expand \-escaped chars here!!)
3680 vp=nextunfolded(vp,aMimeMode);
3683 val+='\\'; // keep the escaped sequence for later when value is actually processed!
3686 // half-finished escape at end of value, ignore
3692 // cancel QP softbreaks if encoding is already switched to QP at this point
3693 vp=nextunfolded(vp,aMimeMode,encoding==enc_quoted_printable);
3695 // - processing of next param starts here
3697 // check for global parameters
3698 if ((aMimeMode==mimo_old && defaultparam) || strucmp(pname.c_str(),"ENCODING")==0) {
3699 // get encoding (if valid encoding)
3700 for (sInt16 k=0; k<numMIMEencodings; k++) {
3701 if (strucmp(val.c_str(),MIMEEncodingNames[k])==0) {
3702 encoding=static_cast <TEncodingTypes> (k);
3706 else if (strucmp(pname.c_str(),"CHARSET")==0) {
3707 // charset specified (mimo_old value-only not supported)
3709 for (k=1; k<numCharSets; k++) {
3710 if (strucmp(val.c_str(),MIMECharSetNames[k])==0) {
3712 charset=TCharSets(k);
3716 if (k>=numCharSets) {
3718 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("========== WARNING: Unknown Charset '%s'",val.c_str()));
3719 // %%% replace 8bit chars with underscore
3720 charset=chs_unknown;
3723 // find param in list now
3724 paramP = aPropP->parameterDefs;
3725 pidx=0; // parameter index
3729 mimeModeMatch(paramP->modeDependency) &&
3730 ((defaultparam && paramP->defaultparam) || strucmp(pname.c_str(),TCFG_CSTR(paramP->paramname))==0)
3733 // - process value (list)
3734 if (!fieldoffsetfound) {
3735 // first pass, check for extendsname parameters
3736 if (paramP->extendsname) {
3737 // - for each value in the value list, check if it has a nameextid
3738 if (!paramP->convdef.enumdefs) {
3739 DEBUGPRINTFX(DBG_PARSE,(
3740 "parseProperty: extendsname param w/o enum : %s;%s",
3741 TCFG_CSTR(aPropP->propname),
3742 TCFG_CSTR(paramP->paramname)
3746 // - loop through value list
3751 // find end of next value in list
3752 for (n=0,pp=ep; *pp; pp++) {
3754 pp++; // skip the comma
3759 // search in enums list
3760 const TEnumerationDef *enumP = paramP->convdef.findEnumByName(ep,n);
3761 if (enumP && enumP->nameextid>=0) {
3762 // set name extension map bit
3763 nameextmap |= ((TNameExtIDMap)1<<enumP->nameextid);
3765 // next value in list
3771 // second pass: read param value(s)
3773 val, // input string, possibly binary (e.g. in case of B64 encoded PHOTO)
3775 baseoffset, // base offset (as determined by position)
3776 repoffset, // repetition offset or array index
3777 aItem, // the item where data goes to
3778 notempty, // set true if value(s) parsed are not all empty
3779 defaultparam ? ';' : ',', // value list separator
3780 aMimeMode, // MIME mode (older or newer vXXX format compatibility)
3781 true, // parsing a parameter
3782 false // no structured value
3784 DEBUGPRINTFX(DBG_PARSE,(
3785 "TMimeDirProfileHandler::parseProperty: %s: value not parsed: %s",
3792 } // if (param known)
3794 paramP=paramP->next;
3796 } // while more params
3797 // p points to ';' of next param or ':' of value
3798 } // while more parameters (*p==';')
3799 // check if both passes done or if property storage is explicitly blocked already (baseoffset=-1)
3800 if (fieldoffsetfound) break;
3801 // start second pass
3802 fieldoffsetfound=true;
3803 // - assume empty to start with
3805 // - prepare for second pass: check if set of param values match
3806 // an entry in the nameexts list
3807 TPropNameExtension *propnameextP = aPropP->nameExts;
3809 repoffsByGroup = false;
3810 bool dostore = false;
3811 while (propnameextP) {
3812 // check if entry matches parsed extendsname param values
3814 ((propnameextP->musthave_ids & nameextmap) == propnameextP->musthave_ids) && // needed there
3815 ((propnameextP->forbidden_ids & nameextmap) == 0) // none of the forbidden ones there
3817 // found match, get offset
3818 baseoffset=propnameextP->fieldidoffs;
3819 if (baseoffset==OFFS_NOSTORE) break; // abort with dostore=false
3820 // check if repeat needed/allowed
3821 maxrep=propnameextP->maxRepeat;
3822 if (maxrep==REP_REWRITE) {
3823 dostore=true; // we can store
3824 break; // unlimited repeat allowed but stored in same fields (overwrite), no need for index search by group
3826 // find index where to store this repetition
3827 repid=propnameextP->repeatID;
3828 if (repid>=aRepArraySize)
3829 SYSYNC_THROW(TSyncException(DEBUGTEXT("TMimeDirProfileHandler::parseProperty: repID too high","mdit11")));
3830 // check if a group ID determines the repoffset (not possible for valuelists)
3831 if (aPropP->groupFieldID!=FID_NOT_SUPPORTED && !valuelist) {
3832 // search in group field
3834 bool someGroups = false;
3835 for (sInt16 n=0; n<maxrep || maxrep==REP_ARRAY; n++) {
3836 sInt16 g_repoffset = n*propnameextP->repeatInc; // original repeatoffset (not adjusted yet)
3837 TItemField *g_fldP = aItem.getArrayFieldAdjusted(aPropP->groupFieldID+baseoffset,g_repoffset,true); // get leaf field, if it exists
3838 if (!g_fldP) break; // group field for that repetition does not (yet) exist, array exhausted
3839 // compare group name
3840 if (g_fldP->isAssigned()) {
3841 someGroups = someGroups || !g_fldP->isEmpty(); // when we find a non-empty group field, we have at least one group detected
3843 // don't use repetitions already used by SOME of the fields in the group
3844 // for auto-assigning new groups (or ungrouped occurrences)
3845 if (aRepArray[repid]<n+1)
3846 aRepArray[repid] = n+1;
3848 // check if group matches (only if there is a group at all)
3849 g_fldP->getAsString(s);
3850 if (aGroupName && strucmp(aGroupName,s.c_str(),aGroupNameLen)==0) {
3851 repoffsByGroup = true;
3853 repoffset = g_repoffset;
3854 PDEBUGPRINTFX(DBG_PARSE+DBG_EXOTIC,("parseProperty: found group '%s' at repoffset=%d (repcount=%d)",s.c_str(),repoffset,n));
3858 } // for all possible repetitions
3859 // minrep now contains minimal repetition count for !repoffsByGroup case
3860 } // if grouped property
3861 if (!repoffsByGroup) {
3862 if (aRepArray[repid]<maxrep || maxrep==REP_ARRAY) {
3863 // not exhausted, we can use this entry
3864 // - calculate repeat offset to be used
3865 repinc=propnameextP->repeatInc;
3866 // note: repArray will be updated below (if property not empty or !overwriteempty)
3867 dostore=true; // we can store
3869 repoffset = aRepArray[repid]*repinc;
3870 // - set flag if repeat offset should be incremented after storing an empty property or not
3871 overwriteempty = propnameextP->overwriteEmpty;
3872 // - check if target property main value is empty (must be, or we will skip that repetition)
3873 dostore = false; // if no field exists, we do not store
3874 for (sInt16 e=0; e<aPropP->numValues; e++) {
3875 if (aPropP->convdefs[e].fieldid==FID_NOT_SUPPORTED)
3876 continue; // no field, no need to check it
3877 sInt16 e_fid = aPropP->convdefs[e].fieldid+baseoffset;
3878 sInt16 e_rep = repoffset;
3879 aItem.adjustFidAndIndex(e_fid,e_rep);
3881 TItemField *e_basefldP = aItem.getField(e_fid);
3882 TItemField *e_fldP = NULL;
3884 e_fldP=e_basefldP->getArrayField(e_rep,true); // get leaf field, if it exists
3885 if (!e_basefldP || (e_fldP && e_fldP->isAssigned())) {
3886 // base field of one of the main fields does not exist or leaf field is already assigned
3887 // -> skip that repetition
3892 dostore = true; // at least one field exists, we might store
3894 // check if we can test more repetitions
3896 if (aRepArray[repid]+1<maxrep || maxrep==REP_ARRAY) {
3897 // we can increment and try next repetition
3901 break; // no more possible repetitions with this position rule (check next rule)
3904 if (dostore) break; // we can store now
3905 } // if repeat not yet exhausted
3906 } // if repoffset no already found
3907 } // if position rule matches
3909 propnameextP=propnameextP->next;
3910 } // while search for matching nameExts entry
3911 // abort if we can't store
3913 aText=p; // this is what we've read so far
3916 } // if name extension list not empty
3917 // Now baseoffset/repoffset are valid to be used for storage
3918 } while(true); // until parameter pass 1 & pass 2 done
3919 // parameters are all processed by now, decision made to store data (if !dostore, routine exits above)
3920 // - store the group tag value if we have one
3921 if (aPropP->groupFieldID!=FID_NOT_SUPPORTED) {
3922 TItemField *g_fldP = aItem.getArrayFieldAdjusted(aPropP->groupFieldID+baseoffset,repoffset,false);
3924 g_fldP->setAsString(aGroupName,aGroupNameLen); // store the group name (aGroupName might be NULL, that's ok)
3927 char sep=':'; // first value starts with colon
3928 // repeat until we have all values
3929 for (sInt16 i=0; i<aPropP->numValues || valuelist; i++) {
3930 if (*p!=sep && (aPropP->altvaluesep==0 || *p!=aPropP->altvaluesep)) {
3932 // Note: for valuelists, this is the normal loop exit case as we are not limited by numValues
3934 // New behaviour: omitting values is ok (needed e.g. for T39m)
3935 DEBUGPRINTFX(DBG_PARSE,("TMimeDirProfileHandler::parseProperty: %s does not specify all values",TCFG_CSTR(aPropP->propname)));
3938 break; // all available values read
3942 // get value(list) unfolded
3943 decodeValue(encoding,charset,aMimeMode,aPropP->numValues > 1 || valuelist ? aPropP->valuesep : 0,aPropP->altvaluesep,p,val);
3944 // check if we can store, otherwise just read over value
3945 // - get the conversion def for the value
3946 TConversionDef *convDef = &(aPropP->convdefs[valuelist ? 0 : i]); // always use convdef[0] for value lists
3947 // - store value if not a value list (but simple value or part of structured value), or store if
3948 // valuelist and repeat not yet exhausted, or if valuelist without repetition but combination separator
3949 // which allows to put multiple values into a single field
3950 if (!valuelist || repoffset<maxrep*repinc || maxrep==REP_ARRAY || (valuelist && convDef->combineSep)) {
3951 // convert and store value (or comma separated value-list, not to mix with valuelist-property!!)
3955 baseoffset, // identifies base field
3956 repoffset, // repeat offset to base field / array index
3957 aItem, // the item where data goes to
3958 notempty, // set true if value(s) parsed are not all empty
3960 aMimeMode, // MIME mode (older or newer vXXX format compatibility)
3961 false, // no parameter
3962 aPropP->numValues > 1 // structured if multiple values
3964 PDEBUGPRINTFX(DBG_PARSE+DBG_EXOTIC,(
3965 "TMimeDirProfileHandler::parseProperty: %s: value not parsed: %s",
3966 TCFG_CSTR(aPropP->propname),
3971 // update repeat offset and repeat count if this is a value list
3972 if (valuelist && convDef->combineSep==0 && (notempty || !overwriteempty)) {
3973 // - update count for every non-empty value (for empty values only if overwriteempty is not set)
3975 aRepArray[repid]++; // next repetition
3976 repoffset+=repinc; // also update repeat offset
3980 // value cannot be stored
3981 PDEBUGPRINTFX(DBG_PARSE+DBG_EXOTIC,(
3982 "TMimeDirProfileHandler::parseProperty: %s: value not stored because repeat exhausted: %s",
3983 TCFG_CSTR(aPropP->propname),
3987 // more values must be separated by the value sep char (default=';' but can be ',' e.g. for iCalendar 2.0 CATEGORIES)
3988 sep = aPropP->valuesep;
3990 if (notempty && !valuelist) {
3991 // at least one of the components is not empty. Make sure all components are "touched" such that
3992 // in case of arrays, these are assigned even if empty
3993 for (sInt16 j=0; j<aPropP->numValues; j++) {
3994 sInt16 fid=aPropP->convdefs[j].fieldid;
3996 // requesting the pointer creates the field if it does not already exist
3997 aItem.getArrayFieldAdjusted(fid+baseoffset,repoffset,false);
4001 if (!valuelist && repid>=0 && (notempty || !overwriteempty) && !repoffsByGroup) {
4002 // we have used this repetition and actually stored values, so count it now
4003 // (unless we have stored an empty value only and overwriteempty is true, in
4004 // this case we don't increment, so next value found for this repetition will
4005 // overwrite empty value
4006 // Also, if repeat offset was found by group name, don't increment (aRepArray
4007 // is already updated in this case)
4010 // update read pointer past end of what we've scanned (but not necessarily up
4011 // to next property beginning)
4015 } // TMimeDirProfileHandler::parseProperty
4018 // parse MIME-DIR from specified string into item
4019 bool TMimeDirProfileHandler::parseMimeDir(const char *aText, TMultiFieldItem &aItem)
4021 // start with empty item
4023 // reset item time zone before parsing
4024 fHasExplicitTZ = false; // none set explicitly
4025 fItemTimeContext = fReceiverTimeContext; // default to user context
4026 fDelayedProps.clear(); // start w/o delayed props
4027 fParsedTzidSet.clear(); // start w/o time zones
4028 // start parsing on root level
4029 if (parseLevels(aText,aItem,fProfileDefinitionP,true)) {
4030 // make sure all supported (=available) fields are at least empty (but not missing!)
4031 aItem.assignAvailables();
4036 } // TMimeDirProfileHandler::parseMimeDir
4039 // parameter string for QP encoding. Needed when skipping otherwise unknown properties
4040 #define QP_ENCODING_PARAM "ENCODING=QUOTED-PRINTABLE"
4042 // parse MIME-DIR level from specified string into item
4043 bool TMimeDirProfileHandler::parseLevels(
4045 TMultiFieldItem &aItem,
4046 const TProfileDefinition *aProfileP,
4051 cAppCharP p, propname, groupname;
4053 sInt16 foundmandatory=0;
4054 const sInt16 maxreps = 50;
4055 sInt16 repArray[maxreps];
4056 bool atStart = aRootLevel;
4058 // reset repetition counts
4059 for (sInt16 k=0; k<maxreps; k++) repArray[k]=0;
4061 sInt16 disabledLevels=0;
4062 // set level marker field, if any is defined
4063 sInt16 fid=aProfileP->levelConvdef.fieldid;
4065 // field defined for level entry
4066 // - make sure field exists and is assigned empty value at least
4067 aItem.getFieldRef(fid).assignEmpty();
4068 const TEnumerationDef *enumP = aProfileP->levelConvdef.enumdefs;
4070 // if enumdefs, content is set to first enumdef's enumval (NOT enumtext!!)
4071 aItem.getField(fid)->setAsString(TCFG_CSTR(enumP->enumval));
4074 // skip possible leading extra LF and CR and whitespace here
4075 // NOTE: Magically server sends XML CDATA with 0x0D 0x0D 0x0A for example
4076 while (isspace(*aText)) aText++;
4077 // parse input text property by property
4079 // start of property parsing
4080 // - reset TZID flag
4081 fPropTZIDtctx = TCTX_UNKNOWN;
4082 // - prepare scanning
4084 propname = p; // assume name starts at beginning of text
4086 groupname = NULL; // assume no group
4088 // determine property name end (and maybe group name)
4092 // end of text reached w/o property name
4093 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("parseMimeDir: no property name found, text=%s",aText));
4096 if (c==':' || c==';') break;
4099 // this is a group name (or element of it)
4100 // - remember the group name
4101 if (!groupname) groupname = propname;
4102 gn = p-groupname; // size of groupname
4103 // - prop name starts after group name (and dot)
4104 propname = ++p; // skip group
4111 // propname points to start, p points to end of property name, n=name size
4112 // - search through all properties
4113 bool propparsed=false;
4114 // - check for BEGIN and END
4115 if (strucmp(propname,"BEGIN",n)==0) {
4116 // BEGIN encountered
4118 // - skip possible parameters for broken implementations like Intellisync/Synchrologic
4119 if (*p==';') while (*p && *p!=':') p++;
4121 size_t l=0; const char *lnam=p+1;
4122 while (*(lnam+l)>=0x20) l++; // calculate length of value
4123 p=lnam+l; // advance scanning pointer to terminator
4124 n=0; // prevent false advancing at end of prop loop
4126 // value must be level name, else this is a bad profile
4127 if (strucmp(lnam,TCFG_CSTR(aProfileP->levelName),l)!=0) {
4128 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("parseMimeDir: root level BEGIN has bad value: %s",aText));
4131 atStart=false; // no special lead-in check any more
4135 // value determines new level to enter
4136 if (disabledLevels==0) {
4137 // search for sublevel
4138 const TProfileDefinition *subprofileP = aProfileP->subLevels;
4139 while (subprofileP) {
4142 mimeModeMatch(subprofileP->modeDependency) &&
4143 strucmp(lnam,TCFG_CSTR(subprofileP->levelName),l)==0
4145 // sublevel found, process
4146 while ((uInt8)(*p)<0x20) p++; // advance scanning pointer to beginning of next property
4147 // check special case first
4148 if (subprofileP->profileMode==profm_vtimezones) {
4149 // vTimeZone is handled specially
4152 s.append(subprofileP->levelName);
4153 n = s.size(); // size of lead-out
4154 cAppCharP e = strstr(p,s.c_str());
4155 if (e==NULL) return false; // unterminated vTimeZone sublevel
4156 s.assign(p,e-p); // everything between lead-in and lead-out
4157 p = e+n; // advance pointer beyond VTIMEZONES
4158 appendStringAsUTF8(s.c_str(), s2, chs_utf8, lem_cstr, false);
4160 // identify or add this in the session zones
4162 if (VTIMEZONEtoInternal(s2.c_str(), tctx, getSessionZones(), NULL, &tzid)) {
4163 // time zone identified
4166 TimeZoneContextToName(tctx, tzname, getSessionZones());
4167 PDEBUGPRINTFX(DBG_PARSE+DBG_EXOTIC,("parseMimeDir: VTIMEZONE with ID='%s' parsed to internal time zone '%s'",tzid.c_str(),tzname.c_str()));
4169 // remember it by original name for TZID parsing
4170 fParsedTzidSet[tzid] = tctx;
4173 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("parseMimeDir: could not parse VTIMEZONE: %s",s.c_str()));
4177 // ordinary non-root level
4178 if (!parseLevels(p,aItem,subprofileP,false)) return false;
4180 // - now continue on this level
4185 subprofileP=subprofileP->next;
4188 // no matching sublevel found, disable this level
4193 // already disabled, just nest
4196 } // BEGIN not on rootlevel
4198 else if (strucmp(propname,"END",n)==0) {
4201 // - skip possible parameters for broken implementations like Intellisync/Synchrologic
4202 if (*p==';') while (*p && *p!=':') p++;
4204 size_t l=0; const char *lnam=p+1;
4205 while (*(lnam+l)>=0x20) l++; // calculate length of value
4206 p=lnam+l; // advance scanning pointer to terminator
4207 n=0; // prevent false advancing at end of prop loop
4209 if (disabledLevels>0) {
4210 // end of a disabled level, just un-nest
4214 // should be end of active level, check name
4215 if (strucmp(lnam,TCFG_CSTR(aProfileP->levelName),l)!=0) {
4216 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("parseMimeDir: unexpected END value: %s",aText));
4219 // correct end of level
4220 aText=p; // points to terminator, which is correct for end-of-level
4221 // break scanner loop
4225 else if (disabledLevels==0) {
4227 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,("parseMimeDir: root level does not start with BEGIN: %s",aText));
4230 // not disabled level
4231 const TPropertyDefinition *propP = aProfileP->propertyDefs;
4232 #ifndef NO_REMOTE_RULES
4233 const TPropertyDefinition *otherRulePropP = NULL; // default property which is used if none of the rule-dependent in the group was used
4234 bool ruleSpecificParsed = false;
4235 uInt16 propGroup=0; // group identifier (all props with same name have same group ID)
4237 const TPropertyDefinition *parsePropP;
4241 mimeModeMatch(propP->modeDependency) && // none or matching mode dependency
4242 strucmp(propname,TCFG_CSTR(propP->propname),n)==0
4244 // found property def with matching name (and MIME mode)
4245 // check all in group (=all subsequent with same name)
4246 #ifndef NO_REMOTE_RULES
4247 propGroup=propP->propGroup;
4248 ruleSpecificParsed=false;
4249 otherRulePropP=NULL;
4250 while (propP && propP->propGroup==propGroup && propP->propGroup!=0)
4255 // still in same group (= same name)
4256 #ifndef NO_REMOTE_RULES
4257 // check if this property should be used for parsing
4258 parsePropP=NULL; // do not parse by default
4259 if (propP->dependsOnRemoterule) {
4260 // check if depends on current rule
4261 if (propP->ruleDependency==NULL) {
4262 // this is the "other"-rule dependent variant
4263 // - just remember for now
4264 otherRulePropP=propP;
4266 else if (isActiveRule(propP->ruleDependency)) {
4267 // specific for the applied rule
4268 parsePropP=propP; // default to expand current prop
4269 // now we have expanded a rule-specific property (blocks parsing of "other"-rule dependent prop)
4270 ruleSpecificParsed=true;
4274 // does not depend on rule, parse anyway
4277 // check if this is last prop of list
4279 if (!(propP && propP->propGroup==propGroup) && otherRulePropP && !ruleSpecificParsed) {
4280 // End of alternatives for parsing this property, no rule-specific parsed yet, and there is a otherRuleProp
4281 // parse "other"-rule's property instead
4282 parsePropP=otherRulePropP;
4289 // now parse (or save for delayed parsing later)
4291 if (parsePropP->delayedProcessing) {
4292 // buffer parameters needed to parse later
4293 PDEBUGPRINTFX(DBG_PARSE+DBG_EXOTIC,("parseMimeDir: property %s parsing delayed, rank=%hd",TCFG_CSTR(parsePropP->propname),parsePropP->delayedProcessing));
4294 TDelayedPropParseParams dppp;
4295 dppp.delaylevel = parsePropP->delayedProcessing;
4297 dppp.groupname = groupname;
4298 dppp.groupnameLen = gn;
4299 dppp.propDefP = parsePropP;
4300 TDelayedParsingPropsList::iterator pos;
4301 for (pos=fDelayedProps.begin(); pos!=fDelayedProps.end(); pos++) {
4302 // insert at end or before first occurrence of higer delay
4303 if ((*pos).delaylevel>dppp.delaylevel) {
4304 fDelayedProps.insert(pos,dppp);
4308 if (pos==fDelayedProps.end())
4309 fDelayedProps.push_back(dppp);
4310 // update mandatory count (even if we haven't parsed it yet)
4311 if (parsePropP->mandatory) foundmandatory++;
4314 propparsed=true; // but is "parsed" for loop
4315 break; // parse next
4318 p, // where to start interpreting property, will be updated past end of poperty
4319 aItem, // item to store data into
4320 parsePropP, // the (matching) property definition
4323 fMimeDirMode, // MIME-DIR mode
4327 // property parsed successfully
4329 // count mandarory properties found
4330 if (parsePropP->mandatory) foundmandatory++;
4331 break; // parse next
4333 // if not successfully parsed, continue with next property which
4334 // can have the same name, but possibly different parameter definitions
4336 } // while same property group (poperties with same name)
4337 #ifdef NO_REMOTE_RULES
4338 while(false); // if no remote rules, we do not loop
4340 if (propparsed) break; // do not continue outer loop if inner loop has parsed a prop successfully
4341 } // if name matches (=start of group found)
4343 // not start of group
4347 } // while all properties
4348 } // else: neither BEGIN nor END
4351 PDEBUGPRINTFX(DBG_PARSE,("parseMimeDir: property not parsed (unknown or not storable): %" FMT_LENGTH(".30") "s",FMT_LENGTH_LIMITED(30,aText)));
4352 // skip parsed part (the name)
4355 // p is now end of parsed part
4356 // - skip rest up to EOLN (=any ctrl char)
4357 // Note: we need to check if this is quoted-printable, otherwise we might NOT cancel soft breaks
4360 if (isEndOfLineOrText(c)) break; // end of line or string
4361 if (c==';' && *(p+1)) {
4362 if (strucmp(p+1, QP_ENCODING_PARAM, strlen(QP_ENCODING_PARAM))==0) {
4363 c = *(p+1+strlen(QP_ENCODING_PARAM));
4364 isqp = c==':' || c==';'; // the property is QP encoded, we need to cancel QP softbreaks while looking for end of property
4367 p=nextunfolded(p,fMimeDirMode,isqp); // cancel soft breaks if we are in QP encoded property
4369 // - skip entire EOLN (=all control chars in sequence %%%)
4370 while (*p && (uInt8)(*p)<'\x20') p=nextunfolded(p,fMimeDirMode);
4371 // set next property start point
4373 } while (*aText); // exit if end of string
4374 // now parse delayed ones (list is in delay order already)
4376 // process delayed properties only after entire record is parsed (i.e. when we are at root level here)
4377 TDelayedParsingPropsList::iterator pos;
4378 for (pos=fDelayedProps.begin(); pos!=fDelayedProps.end(); pos++) {
4379 p = (*pos).start; // where to start parsing
4380 PDEBUGPRINTFX(DBG_PARSE+DBG_EXOTIC,(
4381 "parseMimeDir: now parsing delayed property rank=%hd: %" FMT_LENGTH(".30") "s",
4383 FMT_LENGTH_LIMITED(30,(*pos).start)
4386 p, // where to start interpreting property, will be updated past end of property
4387 aItem, // item to store data into
4388 (*pos).propDefP, // the (matching) property definition
4391 fMimeDirMode, // MIME-DIR mode
4395 // count mandarory properties found
4396 //%%% moved this to when we queue the delayed props, as mandatory count is per-profile
4397 //if ((*pos).propDefP->mandatory) foundmandatory++;
4400 // delayed parsing failed
4401 PDEBUGPRINTFX(DBG_PARSE,("parseMimeDir: failed delayed parsing of property %" FMT_LENGTH(".30") "s",FMT_LENGTH_LIMITED(30,(*pos).start)));
4404 // we don't need them any more - clear delayed props
4405 fDelayedProps.clear();
4408 if (foundmandatory<aProfileP->numMandatoryProperties) {
4409 // not all mandatory properties found
4410 POBJDEBUGPRINTFX(getSession(),DBG_ERROR,(
4411 "parseMimeDir: missing %hd of %hd mandatory properies",
4412 aProfileP->numMandatoryProperties-foundmandatory,
4413 aProfileP->numMandatoryProperties
4415 // unsuccessful parsing
4418 // successful parsing done
4420 // %%%%% NOTE: exactly those fields in aItem should be assigned
4421 // which are available in source and target.
4422 // possibly this should be done in prepareForSendTo (o.‰) of
4423 // MultiFieldItem...
4424 } // TMimeDirProfileHandler::parseLevels
4427 void TMimeDirProfileHandler::getOptionsFromDatastore(void)
4429 // get options datastore if one is related
4430 if (fRelatedDatastoreP) {
4431 fReceiverCanHandleUTC = fRelatedDatastoreP->getSession()->fRemoteCanHandleUTC;
4432 fVCal10EnddatesSameDay = fRelatedDatastoreP->getSession()->fVCal10EnddatesSameDay;
4433 fReceiverTimeContext = fRelatedDatastoreP->getSession()->fUserTimeContext; // default to user context
4434 fDontSendEmptyProperties = fRelatedDatastoreP->getSession()->fDontSendEmptyProperties;
4435 fDefaultOutCharset = fRelatedDatastoreP->getSession()->fDefaultOutCharset;
4436 fDefaultInCharset = fRelatedDatastoreP->getSession()->fDefaultInCharset;
4437 fDoQuote8BitContent = fRelatedDatastoreP->getSession()->fDoQuote8BitContent;
4438 fDoNotFoldContent = fRelatedDatastoreP->getSession()->fDoNotFoldContent;
4439 fTreatRemoteTimeAsLocal = fRelatedDatastoreP->getSession()->fTreatRemoteTimeAsLocal;
4440 fTreatRemoteTimeAsUTC = fRelatedDatastoreP->getSession()->fTreatRemoteTimeAsUTC;
4441 #ifndef NO_REMOTE_RULES
4442 fActiveRemoteRules = fRelatedDatastoreP->getSession()->fActiveRemoteRules; // copy the list
4448 // generate Data item (includes header and footer)
4449 void TMimeDirProfileHandler::generateText(TMultiFieldItem &aItem, string &aString)
4451 // get options datastore if one is related
4452 getOptionsFromDatastore();
4454 PDEBUGPRINTFX(DBG_GEN+DBG_HOT,("Generating...."));
4455 aItem.debugShowItem(DBG_DATA+DBG_GEN);
4457 // baseclass just generates MIME-DIR
4458 fBeginEndNesting=0; // no BEGIN out yet
4459 generateMimeDir(aItem,aString);
4461 if (PDEBUGTEST(DBG_GEN+DBG_USERDATA)) {
4462 // note, do not use debugprintf because string is too long
4463 PDEBUGPRINTFX(DBG_GEN,("Generated: "));
4464 PDEBUGPUTSXX(DBG_GEN+DBG_USERDATA,aString.c_str(),0,true);
4467 } // TMimeDirProfileHandler::generateText
4470 // parse Data item (includes header and footer)
4471 bool TMimeDirProfileHandler::parseText(const char *aText, stringSize aTextSize, TMultiFieldItem &aItem)
4473 //#warning "aTextSize must be checked!"
4474 // get options datastore if one is related
4475 getOptionsFromDatastore();
4476 // baseclass just parses MIME-DIR
4477 fBeginEndNesting = 0; // no BEGIN found yet
4479 if (PDEBUGTEST(DBG_PARSE)) {
4480 // very detailed, show item being parsed
4481 PDEBUGPRINTFX(DBG_PARSE+DBG_HOT,("Parsing: "));
4482 PDEBUGPUTSXX(DBG_PARSE+DBG_USERDATA,aText,0,true);
4485 if (parseMimeDir(aText,aItem)) {
4486 if (fBeginEndNesting) {
4487 PDEBUGPRINTFX(DBG_ERROR,("TMimeDirProfileHandler parsing ended with NestCount<>0: %hd",fBeginEndNesting));
4488 return false; // unmatched BEGIN/END
4491 PDEBUGPRINTFX(DBG_PARSE,("Successfully parsed: "));
4492 aItem.debugShowItem(DBG_DATA+DBG_PARSE);
4497 PDEBUGPRINTFX(DBG_ERROR,("Failed parsing item"));
4500 } // TMimeDirProfileHandler::parseText
4503 bool TMimeDirProfileHandler::parseForProperty(SmlItemPtr_t aItemP, const char *aPropName, string &aString)
4505 if (aItemP && aItemP->data)
4506 return parseForProperty(smlPCDataToCharP(aItemP->data),aPropName,aString);
4509 } // TMimeDirProfileHandler::parseForProperty
4512 // scan Data item for specific property (used for quick type tests)
4513 bool TMimeDirProfileHandler::parseForProperty(const char *aText, const char *aPropName, string &aString)
4515 uInt16 n=strlen(aPropName);
4517 const char *p=aText;
4518 // find property end
4520 p=nextunfolded(p,fMimeDirMode,true);
4521 } while ((*p)>=0x20);
4522 // p now points to property end
4523 if (strucmp(aText,aPropName,n)==0 && aText[n]==':') {
4524 aText+=n+1; // start of value
4525 aString.assign(aText,p-aText); // save value
4528 // find next property beginning
4530 p=nextunfolded(p,fMimeDirMode,true);
4531 } while (*p && ((*p)<0x20));
4532 // set to beginning of next
4537 } // TMimeDirProfileHandler::parseForProperty
4541 // helper for newCTDataPropList
4542 void TMimeDirProfileHandler::enumerateLevels(const TProfileDefinition *aProfileP, SmlPcdataListPtr_t *&aPcdataListPP, const TProfileDefinition *aSelectedProfileP, TMimeDirItemType *aItemTypeP)
4544 // only if mode matches
4545 if (!mimeModeMatch(aProfileP->modeDependency)) return;
4546 // add name of this profile if...
4547 // ...generally enabled for CTCap (shownIfSelectedOnly=false), independent of what other profiles might be selected (e.g. VALARM)
4548 // ...this is the explicitly selected profile (like VTODO while creating DS 1.2 devinf for the tasks datastor)
4549 // ...no profile is specifically selected, which means we want to see ALL profiles (like a DS 1.1 vCalendar type outside <datastore>)
4550 // This means, the only case a name is NOT added are those with those having showlevel="no" when ANOTHER profile is explicitly selected.
4551 if (!aProfileP->shownIfSelectedOnly || aProfileP==aSelectedProfileP || aSelectedProfileP==NULL) {
4552 aPcdataListPP = addPCDataStringToList(TCFG_CSTR(aProfileP->levelName),aPcdataListPP);
4553 // check for special subprofiles
4554 if (aProfileP->profileMode==profm_vtimezones) {
4555 // has STANDARD and DAYLIGHT subprofiles
4556 aPcdataListPP = addPCDataStringToList("STANDARD",aPcdataListPP);
4557 aPcdataListPP = addPCDataStringToList("DAYLIGHT",aPcdataListPP);
4559 // add names of subprofiles, if any
4560 const TProfileDefinition *subprofileP = aProfileP->subLevels;
4561 while (subprofileP) {
4562 // If this profile is the selected profile, ALL subprofiles must be shown in all cases (so we pass NULL)
4563 enumerateLevels(subprofileP,aPcdataListPP,aProfileP==aSelectedProfileP ? NULL : aSelectedProfileP, aItemTypeP);
4565 subprofileP=subprofileP->next;
4568 } // TMimeDirProfileHandler::enumerateLevels
4572 // add a CTDataProp item to a CTDataPropList
4573 static void addCTDataPropToListIfNotExists(
4574 SmlDevInfCTDataPropPtr_t aCTDataPropP, // existing CTDataProp item data structure, ownership is passed to list
4575 SmlDevInfCTDataPropListPtr_t *aCTDataPropListPP // adress of list root pointer (which points to existing item list or NULL)
4578 // add it to the list (but only if we don't already have it)
4579 while (*aCTDataPropListPP) {
4581 if (strcmp(smlPCDataToCharP(aCTDataPropP->prop->name),smlPCDataToCharP((*aCTDataPropListPP)->data->prop->name))==0) {
4582 //%%% we can add merging parameters here as well
4583 // same property already exists, forget this one
4584 smlFreeDevInfCTDataProp(aCTDataPropP);
4585 aCTDataPropP = NULL;
4588 aCTDataPropListPP = &((*aCTDataPropListPP)->next);
4590 // if not detected duplicate, add it now
4592 addCTDataPropToList(aCTDataPropP,aCTDataPropListPP);
4594 } // addCTDataPropToListIfNotExists
4597 // add a CTData describing a property (as returned by newDevInfCTData())
4598 // as a new property without parameters to a CTDataPropList
4599 static void addNewPropToListIfNotExists(
4600 SmlDevInfCTDataPtr_t aPropCTData, // CTData describing property
4601 SmlDevInfCTDataPropListPtr_t *aCTDataPropListPP // adress of list root pointer (which points to existing item list or NULL)
4604 SmlDevInfCTDataPropPtr_t propdataP = SML_NEW(SmlDevInfCTDataProp_t);
4605 propdataP->param = NULL; // no params
4606 propdataP->prop = aPropCTData;
4607 addCTDataPropToListIfNotExists(propdataP, aCTDataPropListPP);
4608 } // addNewPropToListIfNotExists
4612 // helper for newCTDataPropList
4613 void TMimeDirProfileHandler::enumerateProperties(const TProfileDefinition *aProfileP, SmlDevInfCTDataPropListPtr_t *&aPropListPP, const TProfileDefinition *aSelectedProfileP, TMimeDirItemType *aItemTypeP)
4615 // remember start of properties
4616 // add all properties of this level (if enabled)
4617 // Note: if this is the explicitly selected (sub)profile, it will be shown under any circumstances
4618 if ((!aProfileP->shownIfSelectedOnly || aProfileP==aSelectedProfileP || aSelectedProfileP==NULL) && mimeModeMatch(aProfileP->modeDependency)) {
4619 if (aProfileP->profileMode==profm_vtimezones) {
4620 // Add properties of VTIMEZONE here
4621 addNewPropToListIfNotExists(newDevInfCTData("TZID"),aPropListPP);
4622 addNewPropToListIfNotExists(newDevInfCTData("DTSTART"),aPropListPP);
4623 addNewPropToListIfNotExists(newDevInfCTData("RRULE"),aPropListPP);
4624 addNewPropToListIfNotExists(newDevInfCTData("TZOFFSETFROM"),aPropListPP);
4625 addNewPropToListIfNotExists(newDevInfCTData("TZOFFSETTO"),aPropListPP);
4626 addNewPropToListIfNotExists(newDevInfCTData("TZNAME"),aPropListPP);
4629 // normal profile defined in config, add properties as defined in profile, avoid duplicates
4630 const TPropertyDefinition *propP = aProfileP->propertyDefs;
4632 if (propP->showInCTCap && mimeModeMatch(propP->modeDependency)) {
4633 // - new list entry in CTCap (if property to be shown)
4634 SmlDevInfCTDataPropPtr_t propdataP = SML_NEW(SmlDevInfCTDataProp_t);
4635 propdataP->param = NULL; // default to no params
4636 // - add params, if needed
4637 SmlDevInfCTDataListPtr_t *nextParamPP = &(propdataP->param);
4638 const TParameterDefinition *paramP = propP->parameterDefs;
4640 // check if parameter is enabled for being shown in CTCap
4641 if (paramP->showInCTCap && mimeModeMatch(paramP->modeDependency)) {
4642 // For some older 1.1 devices (in particular Nokia 7610), enum values of default params
4643 // in pre-MIME-DIR must be shown as param NAMES (not enums).
4644 // But newer 1.2 Nokias like E90 need proper TYPE param with valEnums (when run in 1.2 mode. E90 is fine with 7610 style for 1.1)
4645 // So: normally (fEnumDefaultPropParams==undefined==-1), we show 7610 style for 1.1 and E90 style for 1.2.
4646 // <enumdefaultpropparams> and ENUMDEFAULTPROPPARAMS() can be used to control this behaviour when needed
4648 paramP->defaultparam &&
4649 fMimeDirMode==mimo_old &&
4651 (getSession()->fEnumDefaultPropParams==-1 && getSession()->getSyncMLVersion()<syncml_vers_1_2) || // auto mode and SyncML 1.1 or older
4652 (getSession()->fEnumDefaultPropParams==1) // ..or explicitly enabled
4655 // add the name extending enum values as param names
4656 TEnumerationDef *enumP = paramP->convdef.enumdefs;
4658 if (!TCFG_ISEMPTY(enumP->enumtext) && enumP->enummode==enm_translate) {
4659 // create new param list entry
4660 nextParamPP = addCTDataToList(newDevInfCTData(TCFG_CSTR(enumP->enumtext)),nextParamPP);
4666 // - proper parameter with valEnum list
4667 SmlDevInfCTDataPtr_t paramdataP = newDevInfCTData(TCFG_CSTR(paramP->paramname));
4668 // - add valenums if any
4669 SmlPcdataListPtr_t *nextValenumPP = &(paramdataP->valenum);
4670 TEnumerationDef *enumP = paramP->convdef.enumdefs;
4672 if (!TCFG_ISEMPTY(enumP->enumtext) && enumP->enummode==enm_translate) {
4673 // create new valenum list entry
4674 nextValenumPP = addPCDataStringToList(TCFG_CSTR(enumP->enumtext),nextValenumPP);
4678 // - add it to the params list
4679 nextParamPP = addCTDataToList(paramdataP,nextParamPP);
4681 } // if param to be shown
4682 paramP=paramP->next;
4684 // - get possible size limit and notruncate flag
4685 uInt32 sz=0; // no size limit by default
4686 bool noTruncate=false; // by default, truncation is ok
4687 TFieldDefinition *fieldDefP = NULL;
4688 for (sInt16 i=0; i<propP->numValues; i++) {
4689 sInt16 fid=propP->convdefs[0].fieldid;
4691 // Field type (we need it later when we have a maxsize, which is only allowed together with a datatype in 1.1 DTD)
4693 fieldDefP = fItemTypeP->getFieldDefinition(fid);
4695 uInt32 fsz = fItemTypeP->getFieldOptions(fid)->maxsize; // only if related datastore (i.e. SyncML context)
4696 // - smallest non-fieldblock (excludes RRULE-type special conversions), not-unknown and not-unlimited maxsize is used
4697 if (fieldBlockSize(propP->convdefs[0])==1 && (sz==0 || sz>fsz) && fsz!=FIELD_OPT_MAXSIZE_NONE && sInt32(fsz)!=FIELD_OPT_MAXSIZE_UNKNOWN)
4699 // If any field requests no truncation, report noTruncate
4700 if (getSession()->getSyncMLVersion()>=syncml_vers_1_2 && fItemTypeP->getFieldOptions(fid)->notruncate)
4704 // - calculate our own maxoccur (value in our field options is not used for now %%%)
4706 if (getSession()->getSyncMLVersion()>=syncml_vers_1_2) {
4707 if (propP->nameExts) {
4708 // name extensions determine repeat count
4709 TPropNameExtension *extP = propP->nameExts;
4711 if (!extP->readOnly) {
4712 if (extP->maxRepeat==REP_ARRAY) {
4714 maxOccur=0; // unlimited
4715 break; // prevent other name extensions to intervene
4718 // limited number of occurrences, add to count
4719 maxOccur+=extP->maxRepeat;
4727 // not repeating: property may not occur more than once
4731 // - some SyncML 1.0 clients crash when they see type/size
4732 if (!(getSession()->fShowTypeSzInCTCap10) && getSession()->getSyncMLVersion()<=syncml_vers_1_0) {
4733 sz = 0; // prevent size/type in SyncML 1.0 (as old clients like S55 crash if it is included)
4735 // - find out if we need to show the type (before SyncML 1.2, Size MUST be preceeded by DataType)
4736 // On the other hand, DataType MUST NOT be used in 1.2 for VersIt types!!!
4737 cAppCharP dataType=NULL;
4738 if (sz!=0 && fieldDefP && getSession()->getSyncMLVersion()<syncml_vers_1_2) {
4739 // we have a size, so we NEED a datatype
4740 TPropDataTypes dt = devInfPropTypes[fieldDefP->type];
4741 if (dt==proptype_text) dt=proptype_chr; // SyncML 1.1 does not have "text" type
4742 if (dt!=proptype_unknown)
4743 dataType = propDataTypeNames[dt];
4745 // - add property data descriptor
4746 propdataP->prop = newDevInfCTData(TCFG_CSTR(propP->propname),sz,noTruncate,maxOccur,dataType);
4747 if (propP->convdefs && propP->convdefs->convmode==CONVMODE_VERSION) {
4748 // special case: add version valenum
4749 addPCDataStringToList(aItemTypeP->getTypeVers(),&(propdataP->prop->valenum));
4751 // add it if not already same-named property in the list, otherwise discard it
4752 addCTDataPropToListIfNotExists(propdataP,aPropListPP);
4753 } // if to be shown in CTCap
4755 } // while properties
4756 } // normal profile defined in config
4757 // add properties of other levels
4758 const TProfileDefinition *subprofileP = aProfileP->subLevels;
4759 while (subprofileP) {
4760 // only if the current profile is the selected profile, properties of ALL contained subprofiles will be shown
4761 // (otherwise, selection might be within the current profile, so we need to pass on the selection)
4762 enumerateProperties(subprofileP,aPropListPP,aProfileP==aSelectedProfileP ? NULL : aSelectedProfileP, aItemTypeP);
4764 subprofileP=subprofileP->next;
4767 } // TMimeDirProfileHandler::enumerateProperties
4770 // helper: enumerate filter properties
4771 void TMimeDirProfileHandler::enumeratePropFilters(const TProfileDefinition *aProfileP, SmlPcdataListPtr_t &aFilterProps, const TProfileDefinition *aSelectedProfileP, TMimeDirItemType *aItemTypeP)
4773 // add all properties of this level (if enabled)
4774 // Note: if this is the explicitly selected (sub)profile, it will be shown under any circumstances
4775 if (!aProfileP->shownIfSelectedOnly || aProfileP==aSelectedProfileP) {
4776 const TPropertyDefinition *propP = aProfileP->propertyDefs;
4780 (propP->showInCTCap || aProfileP==aSelectedProfileP) &&
4781 propP->convdefs && propP->convdefs[0].convmode!=CONVMODE_VERSION && propP->convdefs[0].convmode!=CONVMODE_PRODID
4783 // Note: properties of explicitly selected (sub)profiles will be shown anyway,
4784 // as only purpose of suppressing properties in devInf is to avoid
4785 // duplicate listing in case of multiple subprofiles in ONE CTCap.
4786 // - add property name to filter property list
4787 addPCDataStringToList(TCFG_CSTR(propP->propname), &aFilterProps);
4788 } // if to be shown in filterCap
4792 // add properties of other levels
4793 const TProfileDefinition *subprofileP = aProfileP->subLevels;
4794 while (subprofileP) {
4795 if (aSelectedProfileP==NULL || subprofileP==aSelectedProfileP) {
4796 // only if the current profile is the selected profile, filter properties of ALL contained subprofiles will be shown
4797 enumeratePropFilters(subprofileP,aFilterProps,aProfileP==aSelectedProfileP ? NULL : aSelectedProfileP, aItemTypeP);
4800 subprofileP=subprofileP->next;
4802 } // TMimeDirProfileHandler::enumeratePropFilters
4805 #ifdef OBJECT_FILTERING
4807 // Filtering: add keywords and property names to filterCap
4808 void TMimeDirProfileHandler::addFilterCapPropsAndKeywords(SmlPcdataListPtr_t &aFilterKeywords, SmlPcdataListPtr_t &aFilterProps, TTypeVariantDescriptor aVariantDescriptor, TSyncItemType *aItemTypeP)
4810 // get pointer to selected variant (if none, all variants will be shown)
4811 const TProfileDefinition *selectedSubprofileP = (const TProfileDefinition *)aVariantDescriptor;
4812 // get pointer to mimedir item type
4813 TMimeDirItemType *mimeDirItemTypeP;
4814 GET_CASTED_PTR(mimeDirItemTypeP,TMimeDirItemType,aItemTypeP,"MIME-DIR profile used with non-MIME-DIR type");
4815 // add name of all properties that have canFilter attribute set
4816 enumeratePropFilters(fProfileDefinitionP,aFilterProps,selectedSubprofileP, mimeDirItemTypeP);
4817 } // TMimeDirProfileHandler::addFilterCapPropsAndKeywords
4819 #endif // OBJECT_FILTERING
4823 // generates SyncML-Devinf property list for type
4824 SmlDevInfCTDataPropListPtr_t TMimeDirProfileHandler::newCTDataPropList(TTypeVariantDescriptor aVariantDescriptor, TSyncItemType *aItemTypeP)
4826 TMimeDirItemType *itemTypeP = static_cast<TMimeDirItemType *>(aItemTypeP);
4827 // get pointer to selected variant (if none, all variants will be shown)
4828 const TProfileDefinition *selectedSubprofileP = (const TProfileDefinition *)aVariantDescriptor;
4829 // generate new list
4830 SmlDevInfCTDataPropListPtr_t proplistP = SML_NEW(SmlDevInfCTDataPropList_t);
4831 SmlDevInfCTDataPropListPtr_t nextpropP = proplistP;
4832 // generate BEGIN property
4833 // - add property contents
4834 nextpropP->data = SML_NEW(SmlDevInfCTDataProp_t);
4835 nextpropP->data->param=NULL; // no params
4836 // - property data descriptor
4837 SmlDevInfCTDataPtr_t pdataP = newDevInfCTData("BEGIN");
4838 nextpropP->data->prop = pdataP;
4839 // - add valenums for all profiles and subprofiles
4840 SmlPcdataListPtr_t *liststartPP = &pdataP->valenum;
4841 enumerateLevels(fProfileDefinitionP,liststartPP,selectedSubprofileP,itemTypeP);
4842 // generate END property
4843 nextpropP->next = SML_NEW(SmlDevInfCTDataPropList_t);
4844 nextpropP=nextpropP->next;
4845 // - add property contents
4846 nextpropP->data = SML_NEW(SmlDevInfCTDataProp_t);
4847 nextpropP->data->param=NULL; // no params
4848 // - property data descriptor
4849 pdataP = newDevInfCTData("END");
4850 nextpropP->data->prop = pdataP;
4851 // - add valenums for all profiles and subprofiles
4852 liststartPP = &pdataP->valenum;
4853 enumerateLevels(fProfileDefinitionP,liststartPP,selectedSubprofileP,itemTypeP);
4854 // generate all other properties of all levels
4855 nextpropP->next=NULL; // in case no properties are found
4856 SmlDevInfCTDataPropListPtr_t *propstartPP = &nextpropP->next;
4857 enumerateProperties(fProfileDefinitionP,propstartPP,selectedSubprofileP,itemTypeP);
4860 } // TMimeDirProfileHandler::newCTDataPropList
4863 // Analyze CTCap part of devInf
4864 bool TMimeDirProfileHandler::analyzeCTCap(SmlDevInfCTCapPtr_t aCTCapP, TSyncItemType *aItemTypeP)
4866 TMimeDirItemType *itemTypeP = static_cast<TMimeDirItemType *>(aItemTypeP);
4867 // assume all sublevels enabled (as long as we don't get a
4868 // BEGIN CTCap listing all the available levels.
4869 //aItemTypeP->setLevelOptions(NULL,true);
4871 SmlDevInfCTDataPropListPtr_t proplistP = aCTCapP->prop;
4873 if (!itemTypeP->fReceivedFieldDefs) {
4874 // there is a propList, and we haven't scanned one already for this type
4875 // (could be the case for DS 1.2 vCalendar where we get events & tasks separately)
4876 // so disable all non-mandatory fields first (available ones will be re-enabled)
4877 for (sInt16 i=0; i<itemTypeP->fFieldDefinitionsP->numFields(); i++) {
4878 itemTypeP->getFieldOptions(i)->available=false;
4880 // force mandatory properties to be always "available"
4881 setfieldoptions(NULL,fProfileDefinitionP,itemTypeP);
4883 // now we have received fields
4884 itemTypeP->fReceivedFieldDefs=true;
4887 // get property descriptor
4888 SmlDevInfCTDataPtr_t propP = proplistP->data->prop;
4889 // see if we have this property in any of the levels
4890 setfieldoptions(propP,fProfileDefinitionP,itemTypeP);
4891 // next property in CTCap
4892 proplistP=proplistP->next;
4893 } // properties in CTCap
4895 } // TMimeDirProfileHandler::analyzeCTCap
4899 // %%%%% dummy for now
4900 bool TMimeDirProfileHandler::setLevelOptions(const char *aLevelName, bool aEnable, TMimeDirItemType *aItemTypeP)
4902 // do it recursively.
4903 // %%% we need to have a flag somewhere for these
4904 // don't we have one already???
4905 // YES, its fSubLevelRestrictions (supposedly a bitmask for max 32 levels)
4906 // %%% checking the levels and ignoring incoming/outgoing items with
4907 // wrong levels must be added later
4909 } // TMimeDirProfileHandler::setLevelOptions
4914 // enable fields related to aPropP property in profiles recursively
4915 // or (if aPropP is NULL), enable fields of all mandatory properties
4916 void TMimeDirProfileHandler::setfieldoptions(
4917 const SmlDevInfCTDataPtr_t aPropP, // property to enable fields for, NULL if all mandatory properties should be enabled
4918 const TProfileDefinition *aProfileP,
4919 TMimeDirItemType *aItemTypeP
4923 sInt32 propsize = FIELD_OPT_MAXSIZE_NONE;
4924 sInt32 maxOccur = 0; // none by default
4925 bool noTruncate=false;
4926 const char* propname = NULL;
4928 // get params from CTCap property definition (if any)
4930 // get name of CTCap property
4931 propname = smlPCDataToCharP(aPropP->name);
4932 // get possible maxSize
4933 if (aPropP->maxsize) {
4934 if (getSession()->fIgnoreDevInfMaxSize) {
4935 // remote rule flags maxsize as invalid (like in E90), flag it as unknown (but possibly limited)
4936 propsize = FIELD_OPT_MAXSIZE_UNKNOWN;
4940 StrToLong(smlPCDataToCharP(aPropP->maxsize),propsize);
4943 // get possible maxOccur
4944 if (aPropP->maxoccur) {
4945 StrToLong(smlPCDataToCharP(aPropP->maxoccur),maxOccur);
4947 // get possible noTruncate
4948 if (aPropP->flags & SmlDevInfNoTruncate_f)
4950 // check for BEGIN to check for enabled sublevels
4951 if (strucmp(propname,"BEGIN")==0) {
4952 // check ValEnums that denote supported levels
4953 SmlPcdataListPtr_t valenumP = aPropP->valenum;
4955 // we HAVE supported BEGINs listed, so disable all levels first
4956 // and have them individually enabled below according to ValEnums
4957 setLevelOptions(NULL,false,aItemTypeP);
4960 // get sublevel name
4961 const char *slname = smlPCDataToCharP(valenumP->data);
4962 setLevelOptions(slname,true,aItemTypeP); // enable this one
4964 valenumP=valenumP->next;
4967 } // if enabling for specified property
4968 // enable all fields related to this property and set options
4969 const TPropertyDefinition *propdefP = aProfileP->propertyDefs;
4970 sInt16 j,q,i,o,r,bs;
4974 (propname==NULL && propdefP->mandatory) ||
4975 (propname && (strucmp(propname,TCFG_CSTR(propdefP->propname))==0))
4977 // match (or enabling mandatory) -> enable all fields that are related to this property
4979 for (i=0; i<propdefP->numValues; i++) {
4981 j=propdefP->convdefs[i].fieldid;
4982 bs=fieldBlockSize(propdefP->convdefs[i]);
4985 TPropNameExtension *pneP = propdefP->nameExts;
4988 o = pneP->fieldidoffs;
4991 // for all repetitions (but only for first if mode is REP_ARRAY
4992 // or field is an array)
4994 // make entire field block addressed by this convdef available
4995 // and set maxoccur/notruncate
4996 for (q=0; q<bs; q++) {
4998 fo = aItemTypeP->getFieldOptions(j+o+q);
4999 if (fo) fo->available=true;
5001 // set size if specified (only for first field in block)
5002 fo = aItemTypeP->getFieldOptions(j+o);
5004 if (propsize!=FIELD_OPT_MAXSIZE_NONE) fo->maxsize=propsize;
5005 // set maxoccur if specified
5006 if (maxOccur!=0) fo->maxoccur=maxOccur;
5008 if (noTruncate) fo->notruncate=true;
5013 ++r < pneP->maxRepeat &&
5014 pneP->maxRepeat!=REP_ARRAY
5015 #ifdef ARRAYFIELD_SUPPORT
5016 && !aItemTypeP->getFieldDefinition(j)->array
5024 // single variant, non-repeating property
5025 // make entire field block addressed by this convdef available
5026 for (q=0; q<bs; q++) { fo = aItemTypeP->getFieldOptions(j+q); if (fo) fo->available=true; }
5027 // set size if specified
5028 fo = aItemTypeP->getFieldOptions(j);
5030 if (propsize!=FIELD_OPT_MAXSIZE_NONE) fo->maxsize=propsize;
5031 // set maxoccur if specified
5032 if (maxOccur!=0) fo->maxoccur=maxOccur;
5034 if (noTruncate) fo->notruncate=true;
5039 // - parameter values
5040 const TParameterDefinition *paramdefP = propdefP->parameterDefs;
5043 j=paramdefP->convdef.fieldid;
5044 bs=fieldBlockSize(paramdefP->convdef);
5047 TPropNameExtension *pneP = propdefP->nameExts;
5050 o = pneP->fieldidoffs;
5053 // for all repetitions
5055 // make entire field block addressed by this convdef available
5056 for (q=0; q<bs; q++) { fo = aItemTypeP->getFieldOptions(j+o+q); if (fo) fo->available=true; }
5057 // set size if specified
5058 fo = aItemTypeP->getFieldOptions(j+o);
5059 if (propsize!=FIELD_OPT_MAXSIZE_NONE && fo) fo->maxsize=propsize;
5060 // Note: MaxOccur and NoTruncate are not relevant for parameter values
5064 ++r < pneP->maxRepeat &&
5065 pneP->maxRepeat!=REP_ARRAY
5066 #ifdef ARRAYFIELD_SUPPORT
5067 && !aItemTypeP->getFieldDefinition(j)->array
5075 // single variant, non-repeating property
5076 // make entire field block addressed by this convdef available
5077 for (q=0; q<bs; q++) { fo=aItemTypeP->getFieldOptions(j+q); if (fo) fo->available=true; }
5078 // set size if specified
5079 fo = aItemTypeP->getFieldOptions(j);
5080 if (propsize!=FIELD_OPT_MAXSIZE_NONE && fo) fo->maxsize=propsize;
5083 paramdefP=paramdefP->next;
5085 } // if known property
5086 propdefP=propdefP->next;
5088 // now enable fields in all subprofiles
5089 const TProfileDefinition *subprofileP = aProfileP->subLevels;
5090 while (subprofileP) {
5091 setfieldoptions(aPropP,subprofileP,aItemTypeP);
5093 subprofileP=subprofileP->next;
5095 } // TMimeDirProfileHandler::setfieldoptions
5099 // set mode (for those profiles that have more than one, like MIME-DIR's old/standard)
5100 void TMimeDirProfileHandler::setProfileMode(sInt32 aMode)
5102 fProfileMode = aMode;
5103 // determine derived mime mode
5105 case PROFILEMODE_OLD : fMimeDirMode=mimo_old; break; // 1 = old = vCard 2.1 / vCalendar 1.0
5106 default : fMimeDirMode=mimo_standard; break; // anything else = standard = vCard 3.0 / iCalendar 2.0 style
5108 } // TMimeDirProfileHandler::setProfileMode
5111 #ifndef NO_REMOTE_RULES
5113 void TMimeDirProfileHandler::setRemoteRule(const string &aRemoteRuleName)
5115 TSessionConfig *scP = getSession()->getSessionConfig();
5116 TRemoteRulesList::iterator pos;
5117 for(pos=scP->fRemoteRulesList.begin();pos!=scP->fRemoteRulesList.end();pos++) {
5118 if((*pos)->fElementName == aRemoteRuleName) {
5119 // only this rule must be active
5120 fActiveRemoteRules.clear();
5121 fActiveRemoteRules.push_back(*pos);
5125 } // TMimeDirProfileHandler::setRemoteRule
5128 // check if given rule (by name, or if aRuleName=NULL by rule pointer) is active
5129 bool TMimeDirProfileHandler::isActiveRule(TRemoteRuleConfig *aRuleP)
5131 TRemoteRulesList::iterator pos;
5132 for(pos=fActiveRemoteRules.begin();pos!=fActiveRemoteRules.end();pos++) {
5138 } // TMimeDirProfileHandler::isActiveRule
5140 #endif // NO_REMOTE_RULES
5144 bool TMimeDirProfileHandler::mimeModeMatch(TMimeDirMode aMimeMode)
5147 aMimeMode==numMimeModes || // not dependent on MIME mode
5148 aMimeMode==fMimeDirMode;
5149 } // TMimeDirProfileHandler::mimeModeMatch
5153 /* end of TMimeDirProfileHandler implementation */
5156 // Utility functions
5157 // -----------------
5160 /// @brief checks two timestamps if they represent an all-day event
5161 /// @param[in] aStart start time
5162 /// @param[in] aEnd end time
5163 /// @return 0 if not allday, x=1..n if allday (spanning x days) by one of the
5164 /// following criteria:
5165 /// - both start and end at midnight of the same day (= 1 day)
5166 /// - both start and end at midnight of different days (= 1..n days)
5167 /// - start at midnight and end between 23:59:00 and 23:59:59 of
5168 /// same or different days (= 1..n days)
5169 uInt16 AlldayCount(lineartime_t aStart, lineartime_t aEnd)
5171 lineartime_t startTime = lineartime2timeonly(aStart);
5172 if (startTime!=0) return 0; // start not at midnight -> no allday
5173 lineartime_t endTime = lineartime2timeonly(aEnd);
5175 if (aStart==aEnd) aEnd += linearDateToTimeFactor; // one day
5177 else if (endTime>= (23*MinsPerHour+59)*SecsPerMin*secondToLinearTimeFactor) {
5178 // add one minute to make sure we reach into next day
5179 aEnd += SecsPerMin*secondToLinearTimeFactor;
5182 return 0; // allday criteria not met
5183 // now calculate number of days
5184 return (aEnd-aStart) / linearDateToTimeFactor;
5188 /// @brief checks two timestamps if they represent an all-day event
5189 /// @param[in] aStartFldP start time field
5190 /// @param[in] aEndFldP end time field
5191 /// @param[in] aTimecontext context to use to check allday criteria for all non-floating timestamps
5192 /// or UTC timestamps only (if aContextForUTC is set).
5193 /// @param[in] aContextForUTC if set, context is only applied for UTC timestamps, other non-floatings are checked as-is
5194 /// @return 0 if not allday, x=1..n if allday (spanning x days)
5195 uInt16 AlldayCount(TItemField *aStartFldP, TItemField *aEndFldP, timecontext_t aTimecontext, bool aContextForUTC)
5197 if (!aStartFldP->isBasedOn(fty_timestamp)) return 0;
5198 if (!aEndFldP->isBasedOn(fty_timestamp)) return 0;
5199 TTimestampField *startFldP = static_cast<TTimestampField *>(aStartFldP);
5200 TTimestampField *endFldP = static_cast<TTimestampField *>(aEndFldP);
5201 // check in specified time zone if originally UTC (or aContextForUTC not set), otherwise check as-is
5203 lineartime_t start = startFldP->getTimestampAs(!aContextForUTC || TCTX_IS_UTC(startFldP->getTimeContext()) ? aTimecontext : TCTX_UNKNOWN, &tctx);
5204 lineartime_t end = endFldP->getTimestampAs(!aContextForUTC || TCTX_IS_UTC(endFldP->getTimeContext()) ? aTimecontext : TCTX_UNKNOWN, &tctx);
5205 return AlldayCount(start,end);
5209 /// @brief makes two timestamps represent an all-day event
5210 /// @param[in/out] aStart start time within the first day, will be set to midnight (00:00:00)
5211 /// @param[in/out] aEnd end time within the last day or at midnight of the next day,
5212 /// will be set to midnight of the next day
5213 /// @param[in] aDays if>0, this is used to calculate the aEnd timestamp (aEnd input is
5215 void MakeAllday(lineartime_t &aStart, lineartime_t &aEnd, sInt16 aDays)
5217 lineartime_t duration = 0;
5219 // first calculate duration (assuming that even if there's a timezone problem, both
5220 // timestamps will be affected so duration is still correct)
5222 // use implicit duration
5223 duration = aEnd-aStart;
5225 // use explicit duration
5226 duration = aDays * linearDateToTimeFactor;
5228 // truncate start to midnight
5229 aStart = lineartime2dateonlyTime(aStart);
5230 // calculate timestamp that for sure is in next day
5231 aEnd = aStart + duration + linearDateToTimeFactor-1; // one unit less than a full day, ensures that 00:00:00 input will remain same day
5232 // make day-only of next day
5233 aEnd = lineartime2dateonlyTime(aEnd);
5237 /// @brief makes two timestamp fields represent an all-day event
5238 /// @param[in/out] aStartFldP start time within the first day, will be set to dateonly
5239 /// @param[in/out] aEndFldP end time within the last day or at midnight of the next day, will be set to dateonly of the next day
5240 /// @param[in] aTimecontext context to calculate day boundaries in (if timestamp is not already floating), can be floating to treat in context of start date
5241 /// @param[in] aDays if>0, this is used to calculate the aEnd timestamp (aEnd input is
5243 /// @note fields will be made floating and dateonly
5244 void MakeAllday(TItemField *aStartFldP, TItemField *aEndFldP, timecontext_t aTimecontext, sInt16 aDays)
5246 if (!aStartFldP->isBasedOn(fty_timestamp)) return;
5247 if (!aEndFldP->isBasedOn(fty_timestamp)) return;
5248 TTimestampField *startFldP = static_cast<TTimestampField *>(aStartFldP);
5249 TTimestampField *endFldP = static_cast<TTimestampField *>(aEndFldP);
5250 // adjust in specified time zone (or floating)
5252 lineartime_t start = startFldP->getTimestampAs(aTimecontext,&tctx);
5253 // context must match, unless either requested-as-is or timestamp is already floating
5254 if (tctx!=aTimecontext && !TCTX_IS_UNKNOWN(aTimecontext) && !TCTX_IS_UNKNOWN(tctx)) return; // cannot do anything
5255 // get end in same context as start is
5256 lineartime_t end = endFldP->getTimestampAs(tctx);
5258 MakeAllday(start,end,aDays);
5259 // store back and floating + dateonly
5260 tctx = TCTX_UNKNOWN | TCTX_DATEONLY;
5261 // for output format capable of date-only
5262 startFldP->setTimestampAndContext(start,tctx);
5263 endFldP->setTimestampAndContext(end,tctx);
5268 } // namespace sysync