/src/dovecot/src/lib-mail/mbox-from.c
Line | Count | Source |
1 | | /* Copyright (c) Dovecot authors, see top-level COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "ioloop.h" |
5 | | #include "str.h" |
6 | | #include "utc-mktime.h" |
7 | | #include "utc-offset.h" |
8 | | #include "mbox-from.h" |
9 | | |
10 | | #include <time.h> |
11 | | #include <ctype.h> |
12 | | |
13 | | static const char *weekdays[] = { |
14 | | "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" |
15 | | }; |
16 | | |
17 | | static const char *months[] = { |
18 | | "Jan", "Feb", "Mar", "Apr", "May", "Jun", |
19 | | "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" |
20 | | }; |
21 | | |
22 | | static int mbox_parse_month(const unsigned char *msg, struct tm *tm) |
23 | 0 | { |
24 | 0 | int i; |
25 | |
|
26 | 0 | for (i = 0; i < 12; i++) { |
27 | 0 | if (i_memcasecmp(months[i], msg, 3) == 0) { |
28 | 0 | tm->tm_mon = i; |
29 | 0 | break; |
30 | 0 | } |
31 | 0 | } |
32 | |
|
33 | 0 | if (i == 12 && memcmp(msg, "???", 3) == 0) { |
34 | | /* just a hack to parse one special mbox I have :) */ |
35 | 0 | i = 0; |
36 | 0 | } |
37 | |
|
38 | 0 | if (i == 12 || msg[3] != ' ') |
39 | 0 | return -1; |
40 | 0 | return 0; |
41 | 0 | } |
42 | | |
43 | | static int mbox_parse_year(const unsigned char *msg, struct tm *tm) |
44 | 0 | { |
45 | 0 | if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || |
46 | 0 | !i_isdigit(msg[2]) || !i_isdigit(msg[3])) |
47 | 0 | return -1; |
48 | | |
49 | 0 | tm->tm_year = (msg[0]-'0') * 1000 + (msg[1]-'0') * 100 + |
50 | 0 | (msg[2]-'0') * 10 + (msg[3]-'0') - 1900; |
51 | 0 | return 0; |
52 | 0 | } |
53 | | |
54 | | int mbox_from_parse(const unsigned char *msg, size_t size, |
55 | | time_t *time_r, int *tz_offset_r, char **sender_r) |
56 | 0 | { |
57 | 0 | const unsigned char *msg_start, *sender_end, *msg_end; |
58 | 0 | struct tm tm; |
59 | 0 | bool esc, alt_stamp, seen_timezone = FALSE; |
60 | 0 | int timezone_secs = 0; |
61 | 0 | time_t t; |
62 | |
|
63 | 0 | *time_r = (time_t)-1; |
64 | 0 | *sender_r = NULL; |
65 | | |
66 | | /* <sender> <date> <moreinfo> */ |
67 | 0 | msg_start = msg; |
68 | 0 | msg_end = msg + size; |
69 | | |
70 | | /* get sender */ |
71 | 0 | if (msg < msg_end && *msg == '"') { |
72 | | /* "x y z"@domain - skip the quoted part */ |
73 | 0 | esc = FALSE; |
74 | 0 | msg++; |
75 | 0 | while (msg < msg_end && (*msg != '"' || esc)) { |
76 | 0 | if (*msg == '\r' || *msg == '\n') |
77 | 0 | return -1; |
78 | 0 | esc = *msg == '\\'; |
79 | 0 | msg++; |
80 | 0 | } |
81 | 0 | msg++; |
82 | 0 | } |
83 | | |
84 | 0 | while (msg < msg_end && *msg != ' ') { |
85 | 0 | if (*msg == '\r' || *msg == '\n') |
86 | 0 | return -1; |
87 | 0 | msg++; |
88 | 0 | } |
89 | 0 | sender_end = msg; |
90 | 0 | while (msg < msg_end && *msg == ' ') msg++; |
91 | | |
92 | | /* next 29 chars should be in the date in asctime() format, eg. |
93 | | "Thu Nov 9 22:33:52 2001 +0300" |
94 | | |
95 | | - Some put the timezone before the year |
96 | | - Some use a named timezone before or after year, which we ignore |
97 | | - Some don't include seconds (-3) |
98 | | - Some don't include timezone (-5) |
99 | | */ |
100 | 0 | if (msg+29-3-5 > msg_end) |
101 | 0 | return -1; |
102 | | |
103 | 0 | i_zero(&tm); |
104 | | |
105 | | /* skip weekday */ |
106 | 0 | msg += 4; |
107 | | |
108 | | /* month */ |
109 | 0 | if (mbox_parse_month(msg, &tm) < 0) { |
110 | | /* Try alternate timestamp: "Thu, 9 Nov 2002 22:33:52" */ |
111 | 0 | alt_stamp = TRUE; |
112 | 0 | msg++; |
113 | |
|
114 | 0 | if (!i_isdigit(msg[0])) |
115 | 0 | return -1; |
116 | 0 | tm.tm_mday = msg[0]-'0'; |
117 | 0 | msg++; |
118 | |
|
119 | 0 | if (i_isdigit(msg[0])) { |
120 | 0 | tm.tm_mday = tm.tm_mday*10 + msg[0]-'0'; |
121 | 0 | msg++; |
122 | 0 | } |
123 | 0 | if (msg[0] != ' ') |
124 | 0 | return -1; |
125 | 0 | msg++; |
126 | |
|
127 | 0 | if (mbox_parse_month(msg, &tm) < 0) |
128 | 0 | return -1; |
129 | 0 | msg += 4; |
130 | |
|
131 | 0 | if (mbox_parse_year(msg, &tm) < 0) |
132 | 0 | return -1; |
133 | 0 | msg += 5; |
134 | 0 | } else { |
135 | 0 | alt_stamp = FALSE; |
136 | 0 | msg += 4; |
137 | | |
138 | | /* day. single digit is usually preceded by extra space */ |
139 | 0 | if (msg[0] == ' ') |
140 | 0 | msg++; |
141 | 0 | if (msg[1] == ' ') { |
142 | 0 | if (!i_isdigit(msg[0])) |
143 | 0 | return -1; |
144 | 0 | tm.tm_mday = msg[0]-'0'; |
145 | 0 | msg += 2; |
146 | 0 | } else { |
147 | 0 | if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || msg[2] != ' ') |
148 | 0 | return -1; |
149 | 0 | tm.tm_mday = (msg[0]-'0') * 10 + (msg[1]-'0'); |
150 | 0 | msg += 3; |
151 | 0 | } |
152 | 0 | } |
153 | 0 | if (tm.tm_mday == 0) |
154 | 0 | tm.tm_mday = 1; |
155 | | |
156 | | /* hour - need at least "HH:MM" = 5 bytes */ |
157 | 0 | if (msg + 5 > msg_end) |
158 | 0 | return -1; |
159 | 0 | if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || msg[2] != ':') |
160 | 0 | return -1; |
161 | 0 | tm.tm_hour = (msg[0]-'0') * 10 + (msg[1]-'0'); |
162 | 0 | msg += 3; |
163 | | |
164 | | /* minute */ |
165 | 0 | if (!i_isdigit(msg[0]) || !i_isdigit(msg[1])) |
166 | 0 | return -1; |
167 | 0 | tm.tm_min = (msg[0]-'0') * 10 + (msg[1]-'0'); |
168 | 0 | msg += 2; |
169 | | |
170 | | /* optional second */ |
171 | 0 | if (msg >= msg_end) |
172 | 0 | ; |
173 | 0 | else if (msg[0] == ':') { |
174 | 0 | if (msg + 3 > msg_end) |
175 | 0 | return -1; |
176 | 0 | msg++; |
177 | 0 | if (!i_isdigit(msg[0]) || !i_isdigit(msg[1])) |
178 | 0 | return -1; |
179 | 0 | tm.tm_sec = (msg[0]-'0') * 10 + (msg[1]-'0'); |
180 | 0 | msg += 2; |
181 | |
|
182 | 0 | if (!alt_stamp) { |
183 | 0 | if (msg[0] == ' ') |
184 | 0 | msg++; |
185 | 0 | else |
186 | 0 | return -1; |
187 | 0 | } |
188 | 0 | } else if (!alt_stamp) { |
189 | 0 | if (msg[0] != ' ') |
190 | 0 | return -1; |
191 | 0 | msg++; |
192 | 0 | } |
193 | | |
194 | | /* optional named timezone */ |
195 | 0 | if (alt_stamp) |
196 | 0 | ; |
197 | 0 | else if (msg + 4 <= msg_end && |
198 | 0 | (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || |
199 | 0 | !i_isdigit(msg[2]) || !i_isdigit(msg[3]))) { |
200 | | /* skip to next space */ |
201 | 0 | while (msg < msg_end && *msg != ' ') { |
202 | 0 | if (*msg == '\r' || *msg == '\n') |
203 | 0 | return -1; |
204 | 0 | msg++; |
205 | 0 | } |
206 | 0 | if (msg+5 > msg_end) |
207 | 0 | return -1; |
208 | 0 | msg++; |
209 | 0 | } else if (msg + 6 <= msg_end && |
210 | 0 | (msg[0] == '-' || msg[0] == '+') && |
211 | 0 | i_isdigit(msg[1]) && i_isdigit(msg[2]) && |
212 | 0 | i_isdigit(msg[3]) && i_isdigit(msg[4]) && msg[5] == ' ') { |
213 | | /* numeric timezone, use it */ |
214 | 0 | seen_timezone = TRUE; |
215 | 0 | timezone_secs = (msg[1]-'0') * 10*60*60 + (msg[2]-'0') * 60*60 + |
216 | 0 | (msg[3]-'0') * 10 + (msg[4]-'0'); |
217 | 0 | if (msg[0] == '-') timezone_secs = -timezone_secs; |
218 | 0 | msg += 6; |
219 | 0 | } |
220 | | |
221 | 0 | if (!alt_stamp) { |
222 | | /* year */ |
223 | 0 | if (mbox_parse_year(msg, &tm) < 0) |
224 | 0 | return -1; |
225 | 0 | msg += 4; |
226 | 0 | } |
227 | | |
228 | 0 | tm.tm_isdst = -1; |
229 | 0 | if (!seen_timezone && msg + 6 <= msg_end && |
230 | 0 | msg[0] == ' ' && (msg[1] == '-' || msg[1] == '+') && |
231 | 0 | i_isdigit(msg[2]) && i_isdigit(msg[3]) && |
232 | 0 | i_isdigit(msg[4]) && i_isdigit(msg[5])) { |
233 | 0 | seen_timezone = TRUE; |
234 | 0 | timezone_secs = (msg[2]-'0') * 10*60*60 + (msg[3]-'0') * 60*60 + |
235 | 0 | (msg[4]-'0') * 10 + (msg[5]-'0'); |
236 | 0 | if (msg[1] == '-') timezone_secs = -timezone_secs; |
237 | 0 | } |
238 | |
|
239 | 0 | if (seen_timezone) { |
240 | 0 | t = utc_mktime(&tm); |
241 | 0 | if (t == (time_t)-1) |
242 | 0 | return -1; |
243 | | |
244 | 0 | t -= timezone_secs; |
245 | 0 | *time_r = t; |
246 | 0 | *tz_offset_r = timezone_secs/60; |
247 | 0 | } else { |
248 | | /* assume local timezone */ |
249 | 0 | *time_r = mktime(&tm); |
250 | 0 | *tz_offset_r = utc_offset(localtime(time_r), *time_r); |
251 | 0 | } |
252 | | |
253 | 0 | *sender_r = i_strdup_until(msg_start, sender_end); |
254 | 0 | return 0; |
255 | 0 | } |
256 | | |
257 | | const char *mbox_from_create(const char *sender, time_t timestamp) |
258 | 0 | { |
259 | 0 | string_t *str; |
260 | 0 | struct tm *tm; |
261 | 0 | int year; |
262 | |
|
263 | 0 | str = t_str_new(256); |
264 | 0 | str_append(str, "From "); |
265 | 0 | str_append(str, sender); |
266 | 0 | str_append(str, " "); |
267 | | |
268 | | /* we could use simply asctime(), but i18n etc. may break it. |
269 | | Example: "Thu Nov 29 22:33:52 2001" */ |
270 | 0 | tm = localtime(×tamp); |
271 | | |
272 | | /* week day */ |
273 | 0 | str_append(str, weekdays[tm->tm_wday]); |
274 | 0 | str_append_c(str, ' '); |
275 | | |
276 | | /* month */ |
277 | 0 | str_append(str, months[tm->tm_mon]); |
278 | 0 | str_append_c(str, ' '); |
279 | | |
280 | | /* day */ |
281 | 0 | str_append_c(str, (tm->tm_mday / 10) + '0'); |
282 | 0 | str_append_c(str, (tm->tm_mday % 10) + '0'); |
283 | 0 | str_append_c(str, ' '); |
284 | | |
285 | | /* hour */ |
286 | 0 | str_append_c(str, (tm->tm_hour / 10) + '0'); |
287 | 0 | str_append_c(str, (tm->tm_hour % 10) + '0'); |
288 | 0 | str_append_c(str, ':'); |
289 | | |
290 | | /* minute */ |
291 | 0 | str_append_c(str, (tm->tm_min / 10) + '0'); |
292 | 0 | str_append_c(str, (tm->tm_min % 10) + '0'); |
293 | 0 | str_append_c(str, ':'); |
294 | | |
295 | | /* second */ |
296 | 0 | str_append_c(str, (tm->tm_sec / 10) + '0'); |
297 | 0 | str_append_c(str, (tm->tm_sec % 10) + '0'); |
298 | 0 | str_append_c(str, ' '); |
299 | | |
300 | | /* year */ |
301 | 0 | year = tm->tm_year + 1900; |
302 | 0 | str_append_c(str, (year / 1000) + '0'); |
303 | 0 | str_append_c(str, ((year / 100) % 10) + '0'); |
304 | 0 | str_append_c(str, ((year / 10) % 10) + '0'); |
305 | 0 | str_append_c(str, (year % 10) + '0'); |
306 | |
|
307 | 0 | str_append_c(str, '\n'); |
308 | 0 | return str_c(str); |
309 | 0 | } |