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