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