Coverage Report

Created: 2026-08-08 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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(&timestamp);
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
}