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 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 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 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 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 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 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 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 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