Line data Source code
1 : /*
2 : * lwan - web server
3 : * Copyright (c) 2017 L. A. F. Pereira <l@tia.mat.br>
4 : *
5 : * This program is free software; you can redistribute it and/or
6 : * modify it under the terms of the GNU General Public License
7 : * as published by the Free Software Foundation; either version 2
8 : * of the License, or any later version.
9 : *
10 : * This program is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : * GNU General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU General Public License
16 : * along with this program; if not, write to the Free Software
17 : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 : */
19 :
20 : #define _GNU_SOURCE
21 : #include <stdio.h>
22 : #include <errno.h>
23 : #include <time.h>
24 : #include <limits.h>
25 :
26 : #include "lwan-private.h"
27 : #include "int-to-str.h"
28 :
29 0 : static int parse_2_digit_num_no_end_check(const char *str, unsigned int max)
30 : {
31 : static const unsigned int tens[16] = {
32 : 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, [11 ... 15] = UINT_MAX,
33 : };
34 0 : const unsigned int n_tens = (unsigned int)(str[0] - '0');
35 0 : const unsigned int n_ones = (unsigned int)(str[1] - '0');
36 0 : const unsigned int val = tens[n_tens & 15] + n_ones;
37 0 : return UNLIKELY(val > max) ? -EINVAL : (int)val;
38 : }
39 :
40 : static int
41 0 : parse_2_digit_num(const char *str, const char end_chr, unsigned int max)
42 : {
43 0 : if (UNLIKELY(str[2] != end_chr))
44 0 : return -EINVAL;
45 0 : return parse_2_digit_num_no_end_check(str, max);
46 : }
47 :
48 0 : int lwan_parse_rfc_time(const char in[static 30], time_t *out)
49 : {
50 : /* This function is used instead of strptime() because locale
51 : * information can affect the parsing. Instead of defining
52 : * the locale to "C", use hardcoded constants. */
53 : struct tm tm;
54 0 : const char *str = in;
55 :
56 0 : STRING_SWITCH(str) {
57 0 : case STR4_INT('S','u','n',','): tm.tm_wday = 0; break;
58 0 : case STR4_INT('M','o','n',','): tm.tm_wday = 1; break;
59 0 : case STR4_INT('T','u','e',','): tm.tm_wday = 2; break;
60 0 : case STR4_INT('W','e','d',','): tm.tm_wday = 3; break;
61 0 : case STR4_INT('T','h','u',','): tm.tm_wday = 4; break;
62 0 : case STR4_INT('F','r','i',','): tm.tm_wday = 5; break;
63 0 : case STR4_INT('S','a','t',','): tm.tm_wday = 6; break;
64 0 : default: return -EINVAL;
65 : }
66 0 : str += 5;
67 :
68 0 : tm.tm_mday = parse_2_digit_num(str, ' ', 31);
69 0 : if (UNLIKELY(tm.tm_mday <= 0))
70 0 : return -EINVAL;
71 0 : str += 3;
72 :
73 0 : STRING_SWITCH(str) {
74 0 : case STR4_INT('J','a','n',' '): tm.tm_mon = 0; break;
75 0 : case STR4_INT('F','e','b',' '): tm.tm_mon = 1; break;
76 0 : case STR4_INT('M','a','r',' '): tm.tm_mon = 2; break;
77 0 : case STR4_INT('A','p','r',' '): tm.tm_mon = 3; break;
78 0 : case STR4_INT('M','a','y',' '): tm.tm_mon = 4; break;
79 0 : case STR4_INT('J','u','n',' '): tm.tm_mon = 5; break;
80 0 : case STR4_INT('J','u','l',' '): tm.tm_mon = 6; break;
81 0 : case STR4_INT('A','u','g',' '): tm.tm_mon = 7; break;
82 0 : case STR4_INT('S','e','p',' '): tm.tm_mon = 8; break;
83 0 : case STR4_INT('O','c','t',' '): tm.tm_mon = 9; break;
84 0 : case STR4_INT('N','o','v',' '): tm.tm_mon = 10; break;
85 0 : case STR4_INT('D','e','c',' '): tm.tm_mon = 11; break;
86 0 : default: return -EINVAL;
87 : }
88 0 : str += 4;
89 :
90 0 : int year_hundreds = parse_2_digit_num_no_end_check(str, 21);
91 0 : int year_ones = parse_2_digit_num_no_end_check(str + 2, 99);
92 0 : if (UNLIKELY(year_hundreds < 0 || year_ones < 0))
93 0 : return -EINVAL;
94 0 : tm.tm_year = (year_hundreds * 100 + year_ones) - 1900;
95 0 : if (UNLIKELY(tm.tm_year < 0 || tm.tm_year > 1000))
96 0 : return -EINVAL;
97 0 : str += 5;
98 :
99 0 : tm.tm_hour = parse_2_digit_num(str, ':', 23);
100 0 : str += 3;
101 0 : tm.tm_min = parse_2_digit_num(str, ':', 59);
102 0 : str += 3;
103 0 : tm.tm_sec = parse_2_digit_num(str, ' ', 59);
104 0 : str += 3;
105 :
106 0 : STRING_SWITCH(str) {
107 0 : case STR4_INT('G','M','T','\0'):
108 0 : tm.tm_isdst = -1;
109 :
110 0 : *out = timegm(&tm);
111 :
112 0 : if (LIKELY(*out > 0))
113 0 : return 0;
114 :
115 : /* Fallthrough */
116 : default:
117 0 : return -EINVAL;
118 : }
119 : }
120 :
121 : static inline char *
122 2460 : append_two_digits(char *p, unsigned int digits)
123 : {
124 2460 : return mempcpy(p, uint_to_string_2_digits(digits), 2);
125 : }
126 :
127 410 : int lwan_format_rfc_time(const time_t in, char out[static 30])
128 : {
129 : static const char *weekdays = "Sun,Mon,Tue,Wed,Thu,Fri,Sat,";
130 : static const char *months = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ";
131 : struct tm tm;
132 : char *p;
133 :
134 410 : if (UNLIKELY(!gmtime_r(&in, &tm)))
135 0 : return -errno;
136 :
137 410 : p = mempcpy(out, weekdays + tm.tm_wday * 4, 4);
138 410 : *p++ = ' ';
139 :
140 410 : p = append_two_digits(p, (unsigned int)tm.tm_mday);
141 410 : *p++ = ' ';
142 410 : p = mempcpy(p, months + tm.tm_mon * 4, 4);
143 :
144 410 : tm.tm_year += 1900;
145 410 : p = append_two_digits(p, (unsigned int)tm.tm_year / 100);
146 410 : p = append_two_digits(p, (unsigned int)tm.tm_year % 100);
147 :
148 410 : *p++ = ' ';
149 :
150 410 : p = append_two_digits(p, (unsigned int)tm.tm_hour);
151 410 : *p++ = ':';
152 410 : p = append_two_digits(p, (unsigned int)tm.tm_min);
153 410 : *p++ = ':';
154 410 : p = append_two_digits(p, (unsigned int)tm.tm_sec);
155 :
156 410 : memcpy(p, " GMT", 5);
157 :
158 410 : return 0;
159 : }
|