Coverage Report

Created: 2026-06-15 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/util/rfc2822.c
Line
Count
Source
1
/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
2
 */
3
4
/* NOTE: much of the functionality implemented here should eventually appear
5
 * somewhere in Dovecot itself.
6
 */
7
8
#include "lib.h"
9
#include "str.h"
10
#include "unichar.h"
11
12
#include "rfc2822.h"
13
14
#include "message-header-encode.h"
15
16
#include <stdio.h>
17
#include <ctype.h>
18
19
bool rfc2822_header_field_name_verify
20
(const char *field_name, unsigned int len)
21
0
{
22
0
  const char *p = field_name;
23
0
  const char *pend = p + len;
24
25
  /* field-name   =   1*ftext
26
   * ftext        =   %d33-57 /               ; Any character except
27
   *                  %d59-126                ;  controls, SP, and
28
   *                                          ;  ":".
29
   */
30
31
0
  while ( p < pend ) {
32
0
    if ( *p < 33 || *p == ':' )
33
0
      return FALSE;
34
35
0
    p++;
36
0
  }
37
38
0
  return TRUE;
39
0
}
40
41
bool rfc2822_header_field_body_verify
42
(const char *field_body, unsigned int len, bool allow_crlf, bool allow_utf8)
43
0
{
44
0
  const unsigned char *p = (const unsigned char *)field_body;
45
0
  const unsigned char *pend = p + len;
46
0
  bool is8bit = FALSE;
47
48
  /* RFC5322:
49
   *
50
   * unstructured    =  (*([FWS] VCHAR) *WSP)
51
   * VCHAR           =  %x21-7E
52
   * FWS             =  ([*WSP CRLF] 1*WSP) /   ; Folding white space
53
   * WSP             =  SP / HTAB               ; White space
54
   */
55
56
0
  while ( p < pend ) {
57
0
    if ( *p < 0x20 ) {
58
0
      if ( (*p == '\r' || *p == '\n') ) {
59
0
        if ( !allow_crlf )
60
0
          return FALSE;
61
0
      } else if ( *p != '\t' ) {
62
0
        return FALSE;
63
0
      }
64
0
    }
65
66
0
    if ( !is8bit && *p > 127 ) {
67
0
      if ( !allow_utf8 )
68
0
        return FALSE;
69
70
0
      is8bit = TRUE;
71
0
    }
72
73
0
    p++;
74
0
  }
75
76
0
  if ( is8bit && !uni_utf8_str_is_valid(field_body) ) {
77
0
    return FALSE;
78
0
  }
79
80
0
  return TRUE;
81
0
}
82
83
/*
84
 *
85
 */
86
87
const char *rfc2822_header_field_name_sanitize(const char *name)
88
0
{
89
0
  char *result = t_strdup_noconst(name);
90
0
  char *p;
91
92
  /* Make the whole name lower case ... */
93
0
  result = str_lcase(result);
94
95
  /* ... except for the first letter and those that follow '-' */
96
0
  p = result;
97
0
  *p = i_toupper(*p);
98
0
  while ( *p != '\0' ) {
99
0
    if ( *p == '-' ) {
100
0
      p++;
101
102
0
      if ( *p != '\0' )
103
0
        *p = i_toupper(*p);
104
105
0
      continue;
106
0
    }
107
108
0
    p++;
109
0
  }
110
111
0
  return result;
112
0
}
113
114
/*
115
 * Message construction
116
 */
117
118
/* FIXME: This should be collected into a Dovecot API for composing internet
119
 * mail messages.
120
 */
121
122
unsigned int rfc2822_header_append
123
(string_t *header, const char *name, const char *body, bool crlf,
124
  uoff_t *body_offset_r)
125
0
{
126
0
  static const unsigned int max_line = 80;
127
128
0
  const char *bp = body;  /* Pointer */
129
0
  const char *sp = body;  /* Start pointer */
130
0
  const char *wp = NULL;  /* Whitespace pointer */
131
0
  const char *nlp = NULL; /* New-line pointer */
132
0
  unsigned int line_len = strlen(name);
133
0
  unsigned int lines = 0;
134
135
  /* Write header field name first */
136
0
  str_append(header, name);
137
0
  str_append(header, ": ");
138
139
0
  if ( body_offset_r != NULL )
140
0
    *body_offset_r = str_len(header);
141
142
0
  line_len +=  2;
143
144
  /* Add field body; fold it if necessary and account for existing folding */
145
0
  while ( *bp != '\0' ) {
146
0
    bool ws_first = TRUE;
147
148
0
    while ( *bp != '\0' && nlp == NULL &&
149
0
      (wp == NULL || line_len < max_line) ) {
150
0
      if ( *bp == ' ' || *bp == '\t' ) {
151
0
        if (ws_first)
152
0
          wp = bp;
153
0
        ws_first = FALSE;
154
0
      } else if ( *bp == '\r' || *bp == '\n' ) {
155
0
        if (ws_first)
156
0
          nlp = bp;
157
0
        else
158
0
          nlp = wp;
159
0
        break;
160
0
      } else {
161
0
        ws_first = TRUE;
162
0
      }
163
164
0
      bp++; line_len++;
165
0
    }
166
167
0
    if ( *bp == '\0' ) break;
168
169
    /* Existing newline ? */
170
0
    if ( nlp != NULL ) {     
171
      /* Replace any consecutive newline and whitespace for
172
         consistency */
173
0
      while ( *bp == ' ' || *bp == '\t' || *bp == '\r' || *bp == '\n' )
174
0
        bp++;
175
176
0
      str_append_data(header, sp, nlp-sp);
177
178
0
      if ( crlf )
179
0
        str_append(header, "\r\n");
180
0
      else
181
0
        str_append(header, "\n");
182
183
0
      while ( *bp == ' ' || *bp == '\t' )
184
0
        bp++;
185
0
      if ( *bp != '\0' ) {
186
        /* Continued line; replace leading whitespace with single TAB */
187
0
        str_append_c(header, '\t');
188
0
      }
189
190
0
      sp = bp;
191
0
    } else {
192
      /* Insert newline at last whitespace within the max_line limit */
193
0
      i_assert(wp >= sp);
194
0
      str_append_data(header, sp, wp-sp);
195
196
      /* Force continued line; drop any existing whitespace */
197
0
      while ( *wp == ' ' || *wp == '\t' )
198
0
        wp++;
199
200
0
      if ( crlf )
201
0
        str_append(header, "\r\n");
202
0
      else
203
0
        str_append(header, "\n");
204
205
      /* Insert single TAB instead of the original whitespace */
206
0
      str_append_c(header, '\t');
207
208
0
      sp = wp;
209
0
      if (sp > bp)
210
0
        bp = sp;
211
0
    }
212
213
0
    lines++;
214
215
0
    line_len = bp - sp;
216
0
    wp = NULL;
217
0
    nlp = NULL;
218
0
  }
219
220
0
  if ( bp != sp || lines == 0 ) {
221
0
    str_append_data(header, sp, bp-sp);
222
0
    if ( crlf )
223
0
      str_append(header, "\r\n");
224
0
    else
225
0
      str_append(header, "\n");
226
0
    lines++;
227
0
  }
228
229
0
  return lines;
230
0
}
231
232
void rfc2822_header_printf
233
(string_t *header, const char *name, const char *fmt, ...)
234
0
{
235
0
  const char *body;
236
0
  va_list args;
237
238
0
  va_start(args, fmt);
239
0
  body = t_strdup_vprintf(fmt, args);
240
0
  va_end(args);
241
242
0
  rfc2822_header_write(header, name, body);
243
0
}
244
245
void rfc2822_header_utf8_printf
246
(string_t *header, const char *name, const char *fmt, ...)
247
0
{
248
0
  string_t *body = t_str_new(256);
249
0
  va_list args;
250
251
0
  va_start(args, fmt);
252
0
  message_header_encode(t_strdup_vprintf(fmt, args), body);
253
0
  va_end(args);
254
255
0
  rfc2822_header_write(header, name, str_c(body));
256
0
}
257
258
259
void rfc2822_header_write_address(string_t *header,
260
  const char *name, const char *address)
261
0
{
262
0
  bool has_8bit = FALSE;
263
0
  const char *p;
264
265
0
  for (p = address; *p != '\0'; p++) {
266
0
    if ((*p & 0x80) != 0)
267
0
      has_8bit = TRUE;
268
0
  }
269
270
0
  if (!has_8bit) {
271
0
    rfc2822_header_write(header, name, address);
272
0
  } else {
273
0
    string_t *body = t_str_new(256);
274
0
    message_header_encode(address, body);
275
0
    rfc2822_header_write(header, name, str_c(body));
276
0
  }
277
0
}