• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright 2006 The Android Open Source Project
3  */
4 
5 #include <pim/EventRecurrence.h>
6 #include <utils/String8.h>
7 #include <stdio.h>
8 #include <limits.h>
9 
10 namespace android {
11 
12 #define FAIL_HERE() do { \
13             printf("Parsing failed at line %d\n", __LINE__); \
14             return UNKNOWN_ERROR; \
15         } while(0)
16 
EventRecurrence()17 EventRecurrence::EventRecurrence()
18     :freq((freq_t)0),
19      until(),
20      count(0),
21      interval(0),
22      bysecond(0),
23      bysecondCount(0),
24      byminute(0),
25      byminuteCount(0),
26      byhour(0),
27      byhourCount(0),
28      byday(0),
29      bydayNum(0),
30      bydayCount(0),
31      bymonthday(0),
32      bymonthdayCount(0),
33      byyearday(0),
34      byyeardayCount(0),
35      byweekno(0),
36      byweeknoCount(0),
37      bymonth(0),
38      bymonthCount(0),
39      bysetpos(0),
40      bysetposCount(0),
41      wkst(0)
42 {
43 }
44 
~EventRecurrence()45 EventRecurrence::~EventRecurrence()
46 {
47     delete[] bysecond;
48     delete[] byminute;
49     delete[] byhour;
50     delete[] byday;
51     delete[] bydayNum;
52     delete[] byyearday;
53     delete[] bymonthday;
54     delete[] byweekno;
55     delete[] bymonth;
56     delete[] bysetpos;
57 }
58 
59 enum LHS {
60     NONE_LHS = 0,
61     FREQ,
62     UNTIL,
63     COUNT,
64     INTERVAL,
65     BYSECOND,
66     BYMINUTE,
67     BYHOUR,
68     BYDAY,
69     BYMONTHDAY,
70     BYYEARDAY,
71     BYWEEKNO,
72     BYMONTH,
73     BYSETPOS,
74     WKST
75 };
76 
77 struct LHSProc
78 {
79     const char16_t* text;
80     size_t textSize;
81     uint32_t value;
82 };
83 
84 const char16_t FREQ_text[] = { 'F', 'R', 'E', 'Q' };
85 const char16_t UNTIL_text[] = { 'U', 'N', 'T', 'I', 'L' };
86 const char16_t COUNT_text[] = { 'C', 'O', 'U', 'N', 'T' };
87 const char16_t INTERVAL_text[] = { 'I', 'N', 'T', 'E', 'R', 'V', 'A', 'L'};
88 const char16_t BYSECOND_text[] = { 'B', 'Y', 'S', 'E', 'C', 'O', 'N', 'D' };
89 const char16_t BYMINUTE_text[] = { 'B', 'Y', 'M', 'I', 'N', 'U', 'T', 'E' };
90 const char16_t BYHOUR_text[] = { 'B', 'Y', 'H', 'O', 'U', 'R' };
91 const char16_t BYDAY_text[] = { 'B', 'Y', 'D', 'A', 'Y' };
92 const char16_t BYMONTHDAY_text[] = { 'B','Y','M','O','N','T','H','D','A','Y' };
93 const char16_t BYYEARDAY_text[] = { 'B','Y','Y','E','A','R','D','A','Y' };
94 const char16_t BYWEEKNO_text[] = { 'B', 'Y', 'W', 'E', 'E', 'K', 'N', 'O' };
95 const char16_t BYMONTH_text[] = { 'B', 'Y', 'M', 'O', 'N', 'T', 'H' };
96 const char16_t BYSETPOS_text[] = { 'B', 'Y', 'S', 'E', 'T', 'P', 'O', 'S' };
97 const char16_t WKST_text[] = { 'W', 'K', 'S', 'T' };
98 
99 #define SIZ(x) (sizeof(x)/sizeof(x[0]))
100 
101 const LHSProc LHSPROC[] = {
102     { FREQ_text, SIZ(FREQ_text), FREQ },
103     { UNTIL_text, SIZ(UNTIL_text), UNTIL },
104     { COUNT_text, SIZ(COUNT_text), COUNT },
105     { INTERVAL_text, SIZ(INTERVAL_text), INTERVAL },
106     { BYSECOND_text, SIZ(BYSECOND_text), BYSECOND },
107     { BYMINUTE_text, SIZ(BYMINUTE_text), BYMINUTE },
108     { BYHOUR_text, SIZ(BYHOUR_text), BYHOUR },
109     { BYDAY_text, SIZ(BYDAY_text), BYDAY },
110     { BYMONTHDAY_text, SIZ(BYMONTHDAY_text), BYMONTHDAY },
111     { BYYEARDAY_text, SIZ(BYYEARDAY_text), BYYEARDAY },
112     { BYWEEKNO_text, SIZ(BYWEEKNO_text), BYWEEKNO },
113     { BYMONTH_text, SIZ(BYMONTH_text), BYMONTH },
114     { BYSETPOS_text, SIZ(BYSETPOS_text), BYSETPOS },
115     { WKST_text, SIZ(WKST_text), WKST },
116     { NULL, 0, NONE_LHS },
117 };
118 
119 const char16_t SECONDLY_text[] = { 'S','E','C','O','N','D','L','Y' };
120 const char16_t MINUTELY_text[] = { 'M','I','N','U','T','E','L','Y' };
121 const char16_t HOURLY_text[] = { 'H','O','U','R','L','Y' };
122 const char16_t DAILY_text[] = { 'D','A','I','L','Y' };
123 const char16_t WEEKLY_text[] = { 'W','E','E','K','L','Y' };
124 const char16_t MONTHLY_text[] = { 'M','O','N','T','H','L','Y' };
125 const char16_t YEARLY_text[] = { 'Y','E','A','R','L','Y' };
126 
127 typedef LHSProc FreqProc;
128 
129 const FreqProc FREQPROC[] = {
130     { SECONDLY_text, SIZ(SECONDLY_text), EventRecurrence::SECONDLY },
131     { MINUTELY_text, SIZ(MINUTELY_text), EventRecurrence::MINUTELY },
132     { HOURLY_text, SIZ(HOURLY_text), EventRecurrence::HOURLY },
133     { DAILY_text, SIZ(DAILY_text), EventRecurrence::DAILY },
134     { WEEKLY_text, SIZ(WEEKLY_text), EventRecurrence::WEEKLY },
135     { MONTHLY_text, SIZ(MONTHLY_text), EventRecurrence::MONTHLY },
136     { YEARLY_text, SIZ(YEARLY_text), EventRecurrence::YEARLY },
137     { NULL, 0, NONE_LHS },
138 };
139 
140 const char16_t SU_text[] = { 'S','U' };
141 const char16_t MO_text[] = { 'M','O' };
142 const char16_t TU_text[] = { 'T','U' };
143 const char16_t WE_text[] = { 'W','E' };
144 const char16_t TH_text[] = { 'T','H' };
145 const char16_t FR_text[] = { 'F','R' };
146 const char16_t SA_text[] = { 'S','A' };
147 
148 const FreqProc WEEKDAYPROC[] = {
149     { SU_text, SIZ(SU_text), EventRecurrence::SU },
150     { MO_text, SIZ(MO_text), EventRecurrence::MO },
151     { TU_text, SIZ(TU_text), EventRecurrence::TU },
152     { WE_text, SIZ(WE_text), EventRecurrence::WE },
153     { TH_text, SIZ(TH_text), EventRecurrence::TH },
154     { FR_text, SIZ(FR_text), EventRecurrence::FR },
155     { SA_text, SIZ(SA_text), EventRecurrence::SA },
156     { NULL, 0, NONE_LHS },
157 };
158 
159 // returns the index into LHSPROC for the match or -1 if not found
160 inline static int
match_proc(const LHSProc * p,const char16_t * str,size_t len)161 match_proc(const LHSProc* p, const char16_t* str, size_t len)
162 {
163     int i = 0;
164     while (p->text != NULL) {
165         if (p->textSize == len) {
166             if (0 == memcmp(p->text, str, len*sizeof(char16_t))) {
167                 return i;
168             }
169         }
170         p++;
171         i++;
172     }
173     return -1;
174 }
175 
176 // rangeMin and rangeMax are inclusive
177 static status_t
parse_int(const char16_t * str,size_t len,int * out,int rangeMin,int rangeMax,bool zeroOK)178 parse_int(const char16_t* str, size_t len, int* out,
179             int rangeMin, int rangeMax, bool zeroOK)
180 {
181     char16_t c;
182     size_t i=0;
183 
184     if (len == 0) {
185         FAIL_HERE();
186     }
187     bool negative = false;
188     c = str[0];
189     if (c == '-' ) {
190         negative = true;
191         i++;
192     }
193     else if (c == '+') {
194         i++;
195     }
196     int n = 0;
197     for (; i<len; i++) {
198         c = str[i];
199         if (c < '0' || c > '9') {
200             FAIL_HERE();
201         }
202         int prev = n;
203         n *= 10;
204         // the spec doesn't address how big these numbers can be,
205         // so we're not going to worry about not being able to represent
206         // INT_MIN, and if we're going to wrap, we'll just clamp to
207         // INT_MAX instead
208         if (n < prev) {
209             n = INT_MAX;
210         } else {
211             n += c - '0';
212         }
213     }
214     if (negative) {
215         n = -n;
216     }
217     if (n < rangeMin || n > rangeMax) {
218         FAIL_HERE();
219     }
220     if (!zeroOK && n == 0) {
221         FAIL_HERE();
222     }
223     *out = n;
224     return NO_ERROR;
225 }
226 
227 static status_t
parse_int_list(const char16_t * str,size_t len,int * countOut,int ** listOut,int rangeMin,int rangeMax,bool zeroOK,status_t (* func)(const char16_t *,size_t,int *,int,int,bool)=parse_int)228 parse_int_list(const char16_t* str, size_t len, int* countOut, int** listOut,
229           int rangeMin, int rangeMax, bool zeroOK,
230           status_t (*func)(const char16_t*,size_t,int*,int,int,bool)=parse_int)
231 {
232     status_t err;
233 
234     if (len == 0) {
235         *countOut = 0;
236         *listOut = NULL;
237         return NO_ERROR;
238     }
239 
240     // make one pass through looking for commas so we know how big to make our
241     // out array.
242     int count = 1;
243     for (size_t i=0; i<len; i++) {
244         if (str[i] == ',') {
245             count++;
246         }
247     }
248 
249     int* list = new int[count];
250     const char16_t* p = str;
251     int commaIndex = 0;
252     size_t i;
253 
254     for (i=0; i<len; i++) {
255         if (str[i] == ',') {
256             err = func(p, (str+i-p), list+commaIndex, rangeMin,
257                     rangeMax, zeroOK);
258             if (err != NO_ERROR) {
259                 goto bail;
260             }
261             commaIndex++;
262             p = str+i+1;
263         }
264     }
265 
266     err = func(p, (str+i-p), list+commaIndex, rangeMin, rangeMax, zeroOK);
267     if (err != NO_ERROR) {
268         goto bail;
269     }
270     commaIndex++;
271 
272     *countOut = count;
273     *listOut = list;
274 
275     return NO_ERROR;
276 
277 bail:
278     delete[] list;
279     FAIL_HERE();
280 }
281 
282 // the numbers here are small, so we pack them both into one value, and then
283 // split it out later.  it lets us reuse all the comma separated list code.
284 static status_t
parse_byday(const char16_t * s,size_t len,int * out,int rangeMin,int rangeMax,bool zeroOK)285 parse_byday(const char16_t* s, size_t len, int* out,
286             int rangeMin, int rangeMax, bool zeroOK)
287 {
288     status_t err;
289     int n = 0;
290     const char16_t* p = s;
291     size_t plen = len;
292 
293     if (len > 0) {
294         char16_t c = s[0];
295         if (c == '-' || c == '+' || (c >= '0' && c <= '9')) {
296             if (len > 1) {
297                 size_t nlen = 0;
298                 c = s[nlen];
299                 while (nlen < len
300                         && (c == '-' || c == '+' || (c >= '0' && c <= '9'))) {
301                     c = s[nlen];
302                     nlen++;
303                 }
304                 if (nlen > 0) {
305                     nlen--;
306                     err = parse_int(s, nlen, &n, rangeMin, rangeMax, zeroOK);
307                     if (err != NO_ERROR) {
308                         FAIL_HERE();
309                     }
310                     p += nlen;
311                     plen -= nlen;
312                 }
313             }
314         }
315 
316         int index = match_proc(WEEKDAYPROC, p, plen);
317         if (index >= 0) {
318             *out = (0xffff0000 & WEEKDAYPROC[index].value)
319                     | (0x0000ffff & n);
320             return NO_ERROR;
321         }
322     }
323     return UNKNOWN_ERROR;
324 }
325 
326 static void
postprocess_byday(int count,int * byday,int ** bydayNum)327 postprocess_byday(int count, int* byday, int** bydayNum)
328 {
329     int* bdn = new int[count];
330     *bydayNum = bdn;
331     for (int i=0; i<count; i++) {
332         uint32_t v = byday[i];
333         int16_t num = v & 0x0000ffff;
334         byday[i] = v & 0xffff0000;
335         // will sign extend:
336         bdn[i] = num;
337     }
338 }
339 
340 #define PARSE_INT_LIST_CHECKED(name, rangeMin, rangeMax, zeroOK) \
341     if (name##Count != 0 || NO_ERROR != parse_int_list(s, slen, \
342                          &name##Count, &name, rangeMin, rangeMax, zeroOK)) { \
343         FAIL_HERE(); \
344     }
345 status_t
parse(const String16 & str)346 EventRecurrence::parse(const String16& str)
347 {
348     char16_t const* work = str.string();
349     size_t len = str.size();
350 
351     int lhsIndex = NONE_LHS;
352     int index;
353 
354     size_t start = 0;
355     for (size_t i=0; i<len; i++) {
356         char16_t c = work[i];
357         if (c != ';' && i == len-1) {
358             c = ';';
359             i++;
360         }
361         if (c == ';' || c == '=') {
362             if (i != start) {
363                 const char16_t* s = work+start;
364                 const size_t slen = i-start;
365 
366                 String8 thestring(String16(s, slen));
367 
368                 switch (c)
369                 {
370                     case '=':
371                         if (lhsIndex == NONE_LHS) {
372                             lhsIndex = match_proc(LHSPROC, s, slen);
373                             if (lhsIndex >= 0) {
374                                 break;
375                             }
376                         }
377                         FAIL_HERE();
378                     case ';':
379                     {
380                         switch (LHSPROC[lhsIndex].value)
381                         {
382                             case FREQ:
383                                 if (this->freq != 0) {
384                                     FAIL_HERE();
385                                 }
386                                 index = match_proc(FREQPROC, s, slen);
387                                 if (index >= 0) {
388                                     this->freq = (freq_t)FREQPROC[index].value;
389                                 }
390                                 break;
391                             case UNTIL:
392                                 // XXX should check that this is a valid time
393                                 until.setTo(String16(s, slen));
394                                 break;
395                             case COUNT:
396                                 if (count != 0
397                                      || NO_ERROR != parse_int(s, slen,
398                                              &count, INT_MIN, INT_MAX, true)) {
399                                     FAIL_HERE();
400                                 }
401                                 break;
402                             case INTERVAL:
403                                 if (interval != 0
404                                      || NO_ERROR != parse_int(s, slen,
405                                          &interval, INT_MIN, INT_MAX, false)) {
406                                     FAIL_HERE();
407                                 }
408                                 break;
409                             case BYSECOND:
410                                 PARSE_INT_LIST_CHECKED(bysecond, 0, 59, true)
411                                 break;
412                             case BYMINUTE:
413                                 PARSE_INT_LIST_CHECKED(byminute, 0, 59, true)
414                                 break;
415                             case BYHOUR:
416                                 PARSE_INT_LIST_CHECKED(byhour, 0, 23, true)
417                                 break;
418                             case BYDAY:
419                                 if (bydayCount != 0 || NO_ERROR !=
420                                         parse_int_list(s, slen, &bydayCount,
421                                               &byday, -53, 53, false,
422                                               parse_byday)) {
423                                     FAIL_HERE();
424                                 }
425                                 postprocess_byday(bydayCount, byday, &bydayNum);
426                                 break;
427                             case BYMONTHDAY:
428                                 PARSE_INT_LIST_CHECKED(bymonthday, -31, 31,
429                                                         false)
430                                 break;
431                             case BYYEARDAY:
432                                 PARSE_INT_LIST_CHECKED(byyearday, -366, 366,
433                                                         false)
434                                 break;
435                             case BYWEEKNO:
436                                 PARSE_INT_LIST_CHECKED(byweekno, -53, 53,
437                                                         false)
438                                 break;
439                             case BYMONTH:
440                                 PARSE_INT_LIST_CHECKED(bymonth, 1, 12, false)
441                                 break;
442                             case BYSETPOS:
443                                 PARSE_INT_LIST_CHECKED(bysetpos,
444                                                         INT_MIN, INT_MAX, true)
445                                 break;
446                             case WKST:
447                                 if (this->wkst != 0) {
448                                     FAIL_HERE();
449                                 }
450                                 index = match_proc(WEEKDAYPROC, s, slen);
451                                 if (index >= 0) {
452                                     this->wkst = (int)WEEKDAYPROC[index].value;
453                                 }
454                                 break;
455                             default:
456                                 FAIL_HERE();
457                         }
458                         lhsIndex = NONE_LHS;
459                         break;
460                     }
461                 }
462 
463                 start = i+1;
464             }
465         }
466     }
467 
468     // enforce that there was a FREQ
469     if (freq == 0) {
470         FAIL_HERE();
471     }
472 
473     // default wkst to MO if it wasn't specified
474     if (wkst == 0) {
475         wkst = MO;
476     }
477 
478     return NO_ERROR;
479 }
480 
481 
482 }; // namespace android
483 
484 
485