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