| /* | |
| * Copyright 2006 The Android Open Source Project | |
| */ | |
| #include <pim/EventRecurrence.h> | |
| #include <utils/String8.h> | |
| #include <stdio.h> | |
| #include <limits.h> | |
| namespace android { | |
| #define FAIL_HERE() do { \ | |
| printf("Parsing failed at line %d\n", __LINE__); \ | |
| return UNKNOWN_ERROR; \ | |
| } while(0) | |
| EventRecurrence::EventRecurrence() | |
| :freq((freq_t)0), | |
| until(), | |
| count(0), | |
| interval(0), | |
| bysecond(0), | |
| bysecondCount(0), | |
| byminute(0), | |
| byminuteCount(0), | |
| byhour(0), | |
| byhourCount(0), | |
| byday(0), | |
| bydayNum(0), | |
| bydayCount(0), | |
| bymonthday(0), | |
| bymonthdayCount(0), | |
| byyearday(0), | |
| byyeardayCount(0), | |
| byweekno(0), | |
| byweeknoCount(0), | |
| bymonth(0), | |
| bymonthCount(0), | |
| bysetpos(0), | |
| bysetposCount(0), | |
| wkst(0) | |
| { | |
| } | |
| EventRecurrence::~EventRecurrence() | |
| { | |
| delete[] bysecond; | |
| delete[] byminute; | |
| delete[] byhour; | |
| delete[] byday; | |
| delete[] bydayNum; | |
| delete[] byyearday; | |
| delete[] bymonthday; | |
| delete[] byweekno; | |
| delete[] bymonth; | |
| delete[] bysetpos; | |
| } | |
| enum LHS { | |
| NONE_LHS = 0, | |
| FREQ, | |
| UNTIL, | |
| COUNT, | |
| INTERVAL, | |
| BYSECOND, | |
| BYMINUTE, | |
| BYHOUR, | |
| BYDAY, | |
| BYMONTHDAY, | |
| BYYEARDAY, | |
| BYWEEKNO, | |
| BYMONTH, | |
| BYSETPOS, | |
| WKST | |
| }; | |
| struct LHSProc | |
| { | |
| const char16_t* text; | |
| size_t textSize; | |
| uint32_t value; | |
| }; | |
| const char16_t FREQ_text[] = { 'F', 'R', 'E', 'Q' }; | |
| const char16_t UNTIL_text[] = { 'U', 'N', 'T', 'I', 'L' }; | |
| const char16_t COUNT_text[] = { 'C', 'O', 'U', 'N', 'T' }; | |
| const char16_t INTERVAL_text[] = { 'I', 'N', 'T', 'E', 'R', 'V', 'A', 'L'}; | |
| const char16_t BYSECOND_text[] = { 'B', 'Y', 'S', 'E', 'C', 'O', 'N', 'D' }; | |
| const char16_t BYMINUTE_text[] = { 'B', 'Y', 'M', 'I', 'N', 'U', 'T', 'E' }; | |
| const char16_t BYHOUR_text[] = { 'B', 'Y', 'H', 'O', 'U', 'R' }; | |
| const char16_t BYDAY_text[] = { 'B', 'Y', 'D', 'A', 'Y' }; | |
| const char16_t BYMONTHDAY_text[] = { 'B','Y','M','O','N','T','H','D','A','Y' }; | |
| const char16_t BYYEARDAY_text[] = { 'B','Y','Y','E','A','R','D','A','Y' }; | |
| const char16_t BYWEEKNO_text[] = { 'B', 'Y', 'W', 'E', 'E', 'K', 'N', 'O' }; | |
| const char16_t BYMONTH_text[] = { 'B', 'Y', 'M', 'O', 'N', 'T', 'H' }; | |
| const char16_t BYSETPOS_text[] = { 'B', 'Y', 'S', 'E', 'T', 'P', 'O', 'S' }; | |
| const char16_t WKST_text[] = { 'W', 'K', 'S', 'T' }; | |
| #define SIZ(x) (sizeof(x)/sizeof(x[0])) | |
| const LHSProc LHSPROC[] = { | |
| { FREQ_text, SIZ(FREQ_text), FREQ }, | |
| { UNTIL_text, SIZ(UNTIL_text), UNTIL }, | |
| { COUNT_text, SIZ(COUNT_text), COUNT }, | |
| { INTERVAL_text, SIZ(INTERVAL_text), INTERVAL }, | |
| { BYSECOND_text, SIZ(BYSECOND_text), BYSECOND }, | |
| { BYMINUTE_text, SIZ(BYMINUTE_text), BYMINUTE }, | |
| { BYHOUR_text, SIZ(BYHOUR_text), BYHOUR }, | |
| { BYDAY_text, SIZ(BYDAY_text), BYDAY }, | |
| { BYMONTHDAY_text, SIZ(BYMONTHDAY_text), BYMONTHDAY }, | |
| { BYYEARDAY_text, SIZ(BYYEARDAY_text), BYYEARDAY }, | |
| { BYWEEKNO_text, SIZ(BYWEEKNO_text), BYWEEKNO }, | |
| { BYMONTH_text, SIZ(BYMONTH_text), BYMONTH }, | |
| { BYSETPOS_text, SIZ(BYSETPOS_text), BYSETPOS }, | |
| { WKST_text, SIZ(WKST_text), WKST }, | |
| { NULL, 0, NONE_LHS }, | |
| }; | |
| const char16_t SECONDLY_text[] = { 'S','E','C','O','N','D','L','Y' }; | |
| const char16_t MINUTELY_text[] = { 'M','I','N','U','T','E','L','Y' }; | |
| const char16_t HOURLY_text[] = { 'H','O','U','R','L','Y' }; | |
| const char16_t DAILY_text[] = { 'D','A','I','L','Y' }; | |
| const char16_t WEEKLY_text[] = { 'W','E','E','K','L','Y' }; | |
| const char16_t MONTHLY_text[] = { 'M','O','N','T','H','L','Y' }; | |
| const char16_t YEARLY_text[] = { 'Y','E','A','R','L','Y' }; | |
| typedef LHSProc FreqProc; | |
| const FreqProc FREQPROC[] = { | |
| { SECONDLY_text, SIZ(SECONDLY_text), EventRecurrence::SECONDLY }, | |
| { MINUTELY_text, SIZ(MINUTELY_text), EventRecurrence::MINUTELY }, | |
| { HOURLY_text, SIZ(HOURLY_text), EventRecurrence::HOURLY }, | |
| { DAILY_text, SIZ(DAILY_text), EventRecurrence::DAILY }, | |
| { WEEKLY_text, SIZ(WEEKLY_text), EventRecurrence::WEEKLY }, | |
| { MONTHLY_text, SIZ(MONTHLY_text), EventRecurrence::MONTHLY }, | |
| { YEARLY_text, SIZ(YEARLY_text), EventRecurrence::YEARLY }, | |
| { NULL, 0, NONE_LHS }, | |
| }; | |
| const char16_t SU_text[] = { 'S','U' }; | |
| const char16_t MO_text[] = { 'M','O' }; | |
| const char16_t TU_text[] = { 'T','U' }; | |
| const char16_t WE_text[] = { 'W','E' }; | |
| const char16_t TH_text[] = { 'T','H' }; | |
| const char16_t FR_text[] = { 'F','R' }; | |
| const char16_t SA_text[] = { 'S','A' }; | |
| const FreqProc WEEKDAYPROC[] = { | |
| { SU_text, SIZ(SU_text), EventRecurrence::SU }, | |
| { MO_text, SIZ(MO_text), EventRecurrence::MO }, | |
| { TU_text, SIZ(TU_text), EventRecurrence::TU }, | |
| { WE_text, SIZ(WE_text), EventRecurrence::WE }, | |
| { TH_text, SIZ(TH_text), EventRecurrence::TH }, | |
| { FR_text, SIZ(FR_text), EventRecurrence::FR }, | |
| { SA_text, SIZ(SA_text), EventRecurrence::SA }, | |
| { NULL, 0, NONE_LHS }, | |
| }; | |
| // returns the index into LHSPROC for the match or -1 if not found | |
| inline static int | |
| match_proc(const LHSProc* p, const char16_t* str, size_t len) | |
| { | |
| int i = 0; | |
| while (p->text != NULL) { | |
| if (p->textSize == len) { | |
| if (0 == memcmp(p->text, str, len*sizeof(char16_t))) { | |
| return i; | |
| } | |
| } | |
| p++; | |
| i++; | |
| } | |
| return -1; | |
| } | |
| // rangeMin and rangeMax are inclusive | |
| static status_t | |
| parse_int(const char16_t* str, size_t len, int* out, | |
| int rangeMin, int rangeMax, bool zeroOK) | |
| { | |
| char16_t c; | |
| size_t i=0; | |
| if (len == 0) { | |
| FAIL_HERE(); | |
| } | |
| bool negative = false; | |
| c = str[0]; | |
| if (c == '-' ) { | |
| negative = true; | |
| i++; | |
| } | |
| else if (c == '+') { | |
| i++; | |
| } | |
| int n = 0; | |
| for (; i<len; i++) { | |
| c = str[i]; | |
| if (c < '0' || c > '9') { | |
| FAIL_HERE(); | |
| } | |
| int prev = n; | |
| n *= 10; | |
| // the spec doesn't address how big these numbers can be, | |
| // so we're not going to worry about not being able to represent | |
| // INT_MIN, and if we're going to wrap, we'll just clamp to | |
| // INT_MAX instead | |
| if (n < prev) { | |
| n = INT_MAX; | |
| } else { | |
| n += c - '0'; | |
| } | |
| } | |
| if (negative) { | |
| n = -n; | |
| } | |
| if (n < rangeMin || n > rangeMax) { | |
| FAIL_HERE(); | |
| } | |
| if (!zeroOK && n == 0) { | |
| FAIL_HERE(); | |
| } | |
| *out = n; | |
| return NO_ERROR; | |
| } | |
| 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) | |
| { | |
| status_t err; | |
| if (len == 0) { | |
| *countOut = 0; | |
| *listOut = NULL; | |
| return NO_ERROR; | |
| } | |
| // make one pass through looking for commas so we know how big to make our | |
| // out array. | |
| int count = 1; | |
| for (size_t i=0; i<len; i++) { | |
| if (str[i] == ',') { | |
| count++; | |
| } | |
| } | |
| int* list = new int[count]; | |
| const char16_t* p = str; | |
| int commaIndex = 0; | |
| size_t i; | |
| for (i=0; i<len; i++) { | |
| if (str[i] == ',') { | |
| err = func(p, (str+i-p), list+commaIndex, rangeMin, | |
| rangeMax, zeroOK); | |
| if (err != NO_ERROR) { | |
| goto bail; | |
| } | |
| commaIndex++; | |
| p = str+i+1; | |
| } | |
| } | |
| err = func(p, (str+i-p), list+commaIndex, rangeMin, rangeMax, zeroOK); | |
| if (err != NO_ERROR) { | |
| goto bail; | |
| } | |
| commaIndex++; | |
| *countOut = count; | |
| *listOut = list; | |
| return NO_ERROR; | |
| bail: | |
| delete[] list; | |
| FAIL_HERE(); | |
| } | |
| // the numbers here are small, so we pack them both into one value, and then | |
| // split it out later. it lets us reuse all the comma separated list code. | |
| static status_t | |
| parse_byday(const char16_t* s, size_t len, int* out, | |
| int rangeMin, int rangeMax, bool zeroOK) | |
| { | |
| status_t err; | |
| int n = 0; | |
| const char16_t* p = s; | |
| size_t plen = len; | |
| if (len > 0) { | |
| char16_t c = s[0]; | |
| if (c == '-' || c == '+' || (c >= '0' && c <= '9')) { | |
| if (len > 1) { | |
| size_t nlen = 0; | |
| c = s[nlen]; | |
| while (nlen < len | |
| && (c == '-' || c == '+' || (c >= '0' && c <= '9'))) { | |
| c = s[nlen]; | |
| nlen++; | |
| } | |
| if (nlen > 0) { | |
| nlen--; | |
| err = parse_int(s, nlen, &n, rangeMin, rangeMax, zeroOK); | |
| if (err != NO_ERROR) { | |
| FAIL_HERE(); | |
| } | |
| p += nlen; | |
| plen -= nlen; | |
| } | |
| } | |
| } | |
| int index = match_proc(WEEKDAYPROC, p, plen); | |
| if (index >= 0) { | |
| *out = (0xffff0000 & WEEKDAYPROC[index].value) | |
| | (0x0000ffff & n); | |
| return NO_ERROR; | |
| } | |
| } | |
| return UNKNOWN_ERROR; | |
| } | |
| static void | |
| postprocess_byday(int count, int* byday, int** bydayNum) | |
| { | |
| int* bdn = new int[count]; | |
| *bydayNum = bdn; | |
| for (int i=0; i<count; i++) { | |
| uint32_t v = byday[i]; | |
| int16_t num = v & 0x0000ffff; | |
| byday[i] = v & 0xffff0000; | |
| // will sign extend: | |
| bdn[i] = num; | |
| } | |
| } | |
| #define PARSE_INT_LIST_CHECKED(name, rangeMin, rangeMax, zeroOK) \ | |
| if (name##Count != 0 || NO_ERROR != parse_int_list(s, slen, \ | |
| &name##Count, &name, rangeMin, rangeMax, zeroOK)) { \ | |
| FAIL_HERE(); \ | |
| } | |
| status_t | |
| EventRecurrence::parse(const String16& str) | |
| { | |
| char16_t const* work = str.string(); | |
| size_t len = str.size(); | |
| int lhsIndex = NONE_LHS; | |
| int index; | |
| size_t start = 0; | |
| for (size_t i=0; i<len; i++) { | |
| char16_t c = work[i]; | |
| if (c != ';' && i == len-1) { | |
| c = ';'; | |
| i++; | |
| } | |
| if (c == ';' || c == '=') { | |
| if (i != start) { | |
| const char16_t* s = work+start; | |
| const size_t slen = i-start; | |
| String8 thestring(String16(s, slen)); | |
| switch (c) | |
| { | |
| case '=': | |
| if (lhsIndex == NONE_LHS) { | |
| lhsIndex = match_proc(LHSPROC, s, slen); | |
| if (lhsIndex >= 0) { | |
| break; | |
| } | |
| } | |
| FAIL_HERE(); | |
| case ';': | |
| { | |
| switch (LHSPROC[lhsIndex].value) | |
| { | |
| case FREQ: | |
| if (this->freq != 0) { | |
| FAIL_HERE(); | |
| } | |
| index = match_proc(FREQPROC, s, slen); | |
| if (index >= 0) { | |
| this->freq = (freq_t)FREQPROC[index].value; | |
| } | |
| break; | |
| case UNTIL: | |
| // XXX should check that this is a valid time | |
| until.setTo(String16(s, slen)); | |
| break; | |
| case COUNT: | |
| if (count != 0 | |
| || NO_ERROR != parse_int(s, slen, | |
| &count, INT_MIN, INT_MAX, true)) { | |
| FAIL_HERE(); | |
| } | |
| break; | |
| case INTERVAL: | |
| if (interval != 0 | |
| || NO_ERROR != parse_int(s, slen, | |
| &interval, INT_MIN, INT_MAX, false)) { | |
| FAIL_HERE(); | |
| } | |
| break; | |
| case BYSECOND: | |
| PARSE_INT_LIST_CHECKED(bysecond, 0, 59, true) | |
| break; | |
| case BYMINUTE: | |
| PARSE_INT_LIST_CHECKED(byminute, 0, 59, true) | |
| break; | |
| case BYHOUR: | |
| PARSE_INT_LIST_CHECKED(byhour, 0, 23, true) | |
| break; | |
| case BYDAY: | |
| if (bydayCount != 0 || NO_ERROR != | |
| parse_int_list(s, slen, &bydayCount, | |
| &byday, -53, 53, false, | |
| parse_byday)) { | |
| FAIL_HERE(); | |
| } | |
| postprocess_byday(bydayCount, byday, &bydayNum); | |
| break; | |
| case BYMONTHDAY: | |
| PARSE_INT_LIST_CHECKED(bymonthday, -31, 31, | |
| false) | |
| break; | |
| case BYYEARDAY: | |
| PARSE_INT_LIST_CHECKED(byyearday, -366, 366, | |
| false) | |
| break; | |
| case BYWEEKNO: | |
| PARSE_INT_LIST_CHECKED(byweekno, -53, 53, | |
| false) | |
| break; | |
| case BYMONTH: | |
| PARSE_INT_LIST_CHECKED(bymonth, 1, 12, false) | |
| break; | |
| case BYSETPOS: | |
| PARSE_INT_LIST_CHECKED(bysetpos, | |
| INT_MIN, INT_MAX, true) | |
| break; | |
| case WKST: | |
| if (this->wkst != 0) { | |
| FAIL_HERE(); | |
| } | |
| index = match_proc(WEEKDAYPROC, s, slen); | |
| if (index >= 0) { | |
| this->wkst = (int)WEEKDAYPROC[index].value; | |
| } | |
| break; | |
| default: | |
| FAIL_HERE(); | |
| } | |
| lhsIndex = NONE_LHS; | |
| break; | |
| } | |
| } | |
| start = i+1; | |
| } | |
| } | |
| } | |
| // enforce that there was a FREQ | |
| if (freq == 0) { | |
| FAIL_HERE(); | |
| } | |
| // default wkst to MO if it wasn't specified | |
| if (wkst == 0) { | |
| wkst = MO; | |
| } | |
| return NO_ERROR; | |
| } | |
| }; // namespace android | |