1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2013 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Derick Rethans <derick@derickrethans.nl>                    |
16   +----------------------------------------------------------------------+
17 */
18
19/* $Id$ */
20
21#include "timelib.h"
22
23#include <stdio.h>
24#include <ctype.h>
25
26#ifdef HAVE_STDLIB_H
27#include <stdlib.h>
28#endif
29#ifdef HAVE_STRING_H
30#include <string.h>
31#else
32#include <strings.h>
33#endif
34
35#if defined(_MSC_VER)
36# define strtoll(s, f, b) _atoi64(s)
37#elif !defined(HAVE_STRTOLL)
38# if defined(HAVE_ATOLL)
39#  define strtoll(s, f, b) atoll(s)
40# else
41#  define strtoll(s, f, b) strtol(s, f, b)
42# endif
43#endif
44
45#define TIMELIB_UNSET   -99999
46
47#define TIMELIB_SECOND  1
48#define TIMELIB_MINUTE  2
49#define TIMELIB_HOUR    3
50#define TIMELIB_DAY     4
51#define TIMELIB_MONTH   5
52#define TIMELIB_YEAR    6
53
54#define EOI      257
55
56#define TIMELIB_PERIOD  260
57#define TIMELIB_ISO_DATE 261
58#define TIMELIB_ERROR   999
59
60typedef unsigned char uchar;
61
62#define   BSIZE    8192
63
64#define   YYCTYPE      uchar
65#define   YYCURSOR     cursor
66#define   YYLIMIT      s->lim
67#define   YYMARKER     s->ptr
68#define   YYFILL(n)    return EOI;
69
70#define   RET(i)       {s->cur = cursor; return i;}
71
72#define timelib_string_free free
73
74#define TIMELIB_INIT  s->cur = cursor; str = timelib_string(s); ptr = str
75#define TIMELIB_DEINIT timelib_string_free(str)
76
77#ifdef DEBUG_PARSER
78#define DEBUG_OUTPUT(s) printf("%s\n", s);
79#define YYDEBUG(s,c) { if (s != -1) { printf("state: %d ", s); printf("[%c]\n", c); } }
80#else
81#define DEBUG_OUTPUT(s)
82#define YYDEBUG(s,c)
83#endif
84
85#include "timelib_structs.h"
86
87typedef struct Scanner {
88    int           fd;
89    uchar        *lim, *str, *ptr, *cur, *tok, *pos;
90    unsigned int  line, len;
91    struct timelib_error_container *errors;
92
93    struct timelib_time     *begin;
94    struct timelib_time     *end;
95    struct timelib_rel_time *period;
96    int                      recurrences;
97
98    int have_period;
99    int have_recurrences;
100    int have_date;
101    int have_begin_date;
102    int have_end_date;
103} Scanner;
104
105#define HOUR(a) (int)(a * 60)
106
107static void add_warning(Scanner *s, char *error)
108{
109    s->errors->warning_count++;
110    s->errors->warning_messages = realloc(s->errors->warning_messages, s->errors->warning_count * sizeof(timelib_error_message));
111    s->errors->warning_messages[s->errors->warning_count - 1].position = s->tok ? s->tok - s->str : 0;
112    s->errors->warning_messages[s->errors->warning_count - 1].character = s->tok ? *s->tok : 0;
113    s->errors->warning_messages[s->errors->warning_count - 1].message = strdup(error);
114}
115
116static void add_error(Scanner *s, char *error)
117{
118    s->errors->error_count++;
119    s->errors->error_messages = realloc(s->errors->error_messages, s->errors->error_count * sizeof(timelib_error_message));
120    s->errors->error_messages[s->errors->error_count - 1].position = s->tok ? s->tok - s->str : 0;
121    s->errors->error_messages[s->errors->error_count - 1].character = s->tok ? *s->tok : 0;
122    s->errors->error_messages[s->errors->error_count - 1].message = strdup(error);
123}
124
125static char *timelib_string(Scanner *s)
126{
127    char *tmp = calloc(1, s->cur - s->tok + 1);
128    memcpy(tmp, s->tok, s->cur - s->tok);
129
130    return tmp;
131}
132
133static timelib_sll timelib_get_nr(char **ptr, int max_length)
134{
135    char *begin, *end, *str;
136    timelib_sll tmp_nr = TIMELIB_UNSET;
137    int len = 0;
138
139    while ((**ptr < '0') || (**ptr > '9')) {
140        if (**ptr == '\0') {
141            return TIMELIB_UNSET;
142        }
143        ++*ptr;
144    }
145    begin = *ptr;
146    while ((**ptr >= '0') && (**ptr <= '9') && len < max_length) {
147        ++*ptr;
148        ++len;
149    }
150    end = *ptr;
151    str = calloc(1, end - begin + 1);
152    memcpy(str, begin, end - begin);
153    tmp_nr = strtoll(str, NULL, 10);
154    free(str);
155    return tmp_nr;
156}
157
158static timelib_ull timelib_get_unsigned_nr(char **ptr, int max_length)
159{
160    timelib_ull dir = 1;
161
162    while (((**ptr < '0') || (**ptr > '9')) && (**ptr != '+') && (**ptr != '-')) {
163        if (**ptr == '\0') {
164            return TIMELIB_UNSET;
165        }
166        ++*ptr;
167    }
168
169    while (**ptr == '+' || **ptr == '-')
170    {
171        if (**ptr == '-') {
172            dir *= -1;
173        }
174        ++*ptr;
175    }
176    return dir * timelib_get_nr(ptr, max_length);
177}
178
179static long timelib_parse_tz_cor(char **ptr)
180{
181    char *begin = *ptr, *end;
182    long  tmp;
183
184    while (isdigit(**ptr) || **ptr == ':') {
185        ++*ptr;
186    }
187    end = *ptr;
188    switch (end - begin) {
189        case 1:
190        case 2:
191            return HOUR(strtol(begin, NULL, 10));
192            break;
193        case 3:
194        case 4:
195            if (begin[1] == ':') {
196                tmp = HOUR(strtol(begin, NULL, 10)) + strtol(begin + 2, NULL, 10);
197                return tmp;
198            } else if (begin[2] == ':') {
199                tmp = HOUR(strtol(begin, NULL, 10)) + strtol(begin + 3, NULL, 10);
200                return tmp;
201            } else {
202                tmp = strtol(begin, NULL, 10);
203                return HOUR(tmp / 100) + tmp % 100;
204            }
205        case 5:
206            tmp = HOUR(strtol(begin, NULL, 10)) + strtol(begin + 3, NULL, 10);
207            return tmp;
208    }
209    return 0;
210}
211
212static void timelib_eat_spaces(char **ptr)
213{
214    while (**ptr == ' ' || **ptr == '\t') {
215        ++*ptr;
216    }
217}
218
219static void timelib_eat_until_separator(char **ptr)
220{
221    while (strchr(" \t.,:;/-0123456789", **ptr) == NULL) {
222        ++*ptr;
223    }
224}
225
226static long timelib_get_zone(char **ptr, int *dst, timelib_time *t, int *tz_not_found, const timelib_tzdb *tzdb)
227{
228    long retval = 0;
229
230    *tz_not_found = 0;
231
232    while (**ptr == ' ' || **ptr == '\t' || **ptr == '(') {
233        ++*ptr;
234    }
235    if ((*ptr)[0] == 'G' && (*ptr)[1] == 'M' && (*ptr)[2] == 'T' && ((*ptr)[3] == '+' || (*ptr)[3] == '-')) {
236        *ptr += 3;
237    }
238    if (**ptr == '+') {
239        ++*ptr;
240        t->is_localtime = 1;
241        t->zone_type = TIMELIB_ZONETYPE_OFFSET;
242        *tz_not_found = 0;
243        t->dst = 0;
244
245        retval = -1 * timelib_parse_tz_cor(ptr);
246    } else if (**ptr == '-') {
247        ++*ptr;
248        t->is_localtime = 1;
249        t->zone_type = TIMELIB_ZONETYPE_OFFSET;
250        *tz_not_found = 0;
251        t->dst = 0;
252
253        retval = timelib_parse_tz_cor(ptr);
254    }
255    while (**ptr == ')') {
256        ++*ptr;
257    }
258    return retval;
259}
260
261#define timelib_split_free(arg) {       \
262    int i;                         \
263    for (i = 0; i < arg.c; i++) {  \
264        free(arg.v[i]);            \
265    }                              \
266    if (arg.v) {                   \
267        free(arg.v);               \
268    }                              \
269}
270
271/* date parser's scan function too large for VC6 - VC7.x
272   drop the optimization solves the problem */
273#ifdef PHP_WIN32
274#pragma optimize( "", off )
275#endif
276static int scan(Scanner *s)
277{
278    uchar *cursor = s->cur;
279    char *str, *ptr = NULL;
280
281std:
282    s->tok = cursor;
283    s->len = 0;
284/*!re2c
285
286/* */
287any = [\000-\377];
288number = [0-9]+;
289
290hour24lz = [01][0-9] | "2"[0-4];
291minutelz = [0-5][0-9];
292monthlz = "0" [1-9] | "1" [0-2];
293monthlzz = "0" [0-9] | "1" [0-2];
294daylz   = "0" [1-9] | [1-2][0-9] | "3" [01];
295daylzz  = "0" [0-9] | [1-2][0-9] | "3" [01];
296secondlz = minutelz;
297year4 = [0-9]{4};
298weekofyear = "0"[1-9] | [1-4][0-9] | "5"[0-3];
299
300space = [ \t]+;
301datetimebasic  = year4 monthlz daylz "T" hour24lz minutelz secondlz "Z";
302datetimeextended  = year4 "-" monthlz "-" daylz "T" hour24lz ':' minutelz ':' secondlz "Z";
303period   = "P" (number "Y")? (number "M")? (number "W")? (number "D")? ("T" (number "H")? (number "M")? (number "S")?)?;
304combinedrep = "P" year4 "-" monthlzz "-" daylzz "T" hour24lz ':' minutelz ':' secondlz;
305
306recurrences = "R" number;
307
308isoweekday       = year4 "-"? "W" weekofyear "-"? [0-7];
309isoweek          = year4 "-"? "W" weekofyear;
310
311*/
312
313/*!re2c
314    /* so that vim highlights correctly */
315    recurrences
316    {
317        DEBUG_OUTPUT("recurrences");
318        TIMELIB_INIT;
319        ptr++;
320        s->recurrences = timelib_get_unsigned_nr((char **) &ptr, 9);
321        TIMELIB_DEINIT;
322        s->have_recurrences = 1;
323        return TIMELIB_PERIOD;
324    }
325
326    datetimebasic| datetimeextended
327    {
328        timelib_time *current;
329
330        if (s->have_date || s->have_period) {
331            current = s->end;
332            s->have_end_date = 1;
333        } else {
334            current = s->begin;
335            s->have_begin_date = 1;
336        }
337        DEBUG_OUTPUT("datetimebasic | datetimeextended");
338        TIMELIB_INIT;
339        current->y = timelib_get_nr((char **) &ptr, 4);
340        current->m = timelib_get_nr((char **) &ptr, 2);
341        current->d = timelib_get_nr((char **) &ptr, 2);
342        current->h = timelib_get_nr((char **) &ptr, 2);
343        current->i = timelib_get_nr((char **) &ptr, 2);
344        current->s = timelib_get_nr((char **) &ptr, 2);
345        s->have_date = 1;
346        TIMELIB_DEINIT;
347        return TIMELIB_ISO_DATE;
348    }
349
350    period
351    {
352        timelib_sll nr;
353        int         in_time = 0;
354        DEBUG_OUTPUT("period");
355        TIMELIB_INIT;
356        ptr++;
357        do {
358            if ( *ptr == 'T' ) {
359                in_time = 1;
360                ptr++;
361            }
362            if ( *ptr == '\0' ) {
363                add_error(s, "Missing expected time part");
364                break;
365            }
366
367            nr = timelib_get_unsigned_nr((char **) &ptr, 12);
368            switch (*ptr) {
369                case 'Y': s->period->y = nr; break;
370                case 'W': s->period->d = nr * 7; break;
371                case 'D': s->period->d = nr; break;
372                case 'H': s->period->h = nr; break;
373                case 'S': s->period->s = nr; break;
374                case 'M':
375                    if (in_time) {
376                        s->period->i = nr;
377                    } else {
378                        s->period->m = nr;
379                    }
380                    break;
381                default:
382                    add_error(s, "Undefined period specifier");
383                    break;
384            }
385            ptr++;
386        } while (!s->errors->error_count && *ptr);
387        s->have_period = 1;
388        TIMELIB_DEINIT;
389        return TIMELIB_PERIOD;
390    }
391
392    combinedrep
393    {
394        DEBUG_OUTPUT("combinedrep");
395        TIMELIB_INIT;
396        s->period->y = timelib_get_unsigned_nr((char **) &ptr, 4);
397        ptr++;
398        s->period->m = timelib_get_unsigned_nr((char **) &ptr, 2);
399        ptr++;
400        s->period->d = timelib_get_unsigned_nr((char **) &ptr, 2);
401        ptr++;
402        s->period->h = timelib_get_unsigned_nr((char **) &ptr, 2);
403        ptr++;
404        s->period->i = timelib_get_unsigned_nr((char **) &ptr, 2);
405        ptr++;
406        s->period->s = timelib_get_unsigned_nr((char **) &ptr, 2);
407        s->have_period = 1;
408        TIMELIB_DEINIT;
409        return TIMELIB_PERIOD;
410    }
411
412    [ .,\t/]
413    {
414        goto std;
415    }
416
417    "\000"|"\n"
418    {
419        s->pos = cursor; s->line++;
420        goto std;
421    }
422
423    any
424    {
425        add_error(s, "Unexpected character");
426        goto std;
427    }
428*/
429}
430#ifdef PHP_WIN32
431#pragma optimize( "", on )
432#endif
433
434/*!max:re2c */
435
436void timelib_strtointerval(char *s, int len,
437                           timelib_time **begin, timelib_time **end,
438                           timelib_rel_time **period, int *recurrences,
439                           struct timelib_error_container **errors)
440{
441    Scanner in;
442    int t;
443    char *e = s + len - 1;
444
445    memset(&in, 0, sizeof(in));
446    in.errors = malloc(sizeof(struct timelib_error_container));
447    in.errors->warning_count = 0;
448    in.errors->warning_messages = NULL;
449    in.errors->error_count = 0;
450    in.errors->error_messages = NULL;
451
452    if (len > 0) {
453        while (isspace(*s) && s < e) {
454            s++;
455        }
456        while (isspace(*e) && e > s) {
457            e--;
458        }
459    }
460    if (e - s < 0) {
461        add_error(&in, "Empty string");
462        if (errors) {
463            *errors = in.errors;
464        } else {
465            timelib_error_container_dtor(in.errors);
466        }
467        return;
468    }
469    e++;
470
471    /* init cursor */
472    in.str = malloc((e - s) + YYMAXFILL);
473    memset(in.str, 0, (e - s) + YYMAXFILL);
474    memcpy(in.str, s, (e - s));
475    in.lim = in.str + (e - s) + YYMAXFILL;
476    in.cur = in.str;
477
478    /* init value containers */
479    in.begin = timelib_time_ctor();
480    in.begin->y = TIMELIB_UNSET;
481    in.begin->d = TIMELIB_UNSET;
482    in.begin->m = TIMELIB_UNSET;
483    in.begin->h = TIMELIB_UNSET;
484    in.begin->i = TIMELIB_UNSET;
485    in.begin->s = TIMELIB_UNSET;
486    in.begin->f = 0;
487    in.begin->z = 0;
488    in.begin->dst = 0;
489    in.begin->is_localtime = 0;
490    in.begin->zone_type = TIMELIB_ZONETYPE_OFFSET;
491
492    in.end = timelib_time_ctor();
493    in.end->y = TIMELIB_UNSET;
494    in.end->d = TIMELIB_UNSET;
495    in.end->m = TIMELIB_UNSET;
496    in.end->h = TIMELIB_UNSET;
497    in.end->i = TIMELIB_UNSET;
498    in.end->s = TIMELIB_UNSET;
499    in.end->f = 0;
500    in.end->z = 0;
501    in.end->dst = 0;
502    in.end->is_localtime = 0;
503    in.end->zone_type = TIMELIB_ZONETYPE_OFFSET;
504
505    in.period = timelib_rel_time_ctor();
506    in.period->y = 0;
507    in.period->d = 0;
508    in.period->m = 0;
509    in.period->h = 0;
510    in.period->i = 0;
511    in.period->s = 0;
512    in.period->weekday = 0;
513    in.period->weekday_behavior = 0;
514    in.period->first_last_day_of = 0;
515    in.period->days = TIMELIB_UNSET;
516
517    in.recurrences = 1;
518
519    do {
520        t = scan(&in);
521#ifdef DEBUG_PARSER
522        printf("%d\n", t);
523#endif
524    } while(t != EOI);
525
526    free(in.str);
527    if (errors) {
528        *errors = in.errors;
529    } else {
530        timelib_error_container_dtor(in.errors);
531    }
532    if (in.have_begin_date) {
533        *begin = in.begin;
534    } else {
535        timelib_time_dtor(in.begin);
536    }
537    if (in.have_end_date) {
538        *end   = in.end;
539    } else {
540        timelib_time_dtor(in.end);
541    }
542    if (in.have_period) {
543        *period = in.period;
544    } else {
545        timelib_rel_time_dtor(in.period);
546    }
547    if (in.have_recurrences) {
548        *recurrences = in.recurrences;
549    }
550}
551
552
553/*
554 * vim: syntax=c
555 */
556