summaryrefslogtreecommitdiff
path: root/libmailutils/mime/mimehdrset.c
blob: 55c4c7b6a5965038ba3e5814c34f871f4fc45311 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
/* Functions for formatting RFC-2231-compliant mail headers fields.
   GNU Mailutils -- a suite of utilities for electronic mail
   Copyright (C) 1999-2021 Free Software Foundation, Inc.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 3 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General
   Public License along with this library.  If not,
   see <http://www.gnu.org/licenses/>. */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <mailutils/mime.h>
#include <mailutils/cctype.h>
#include <mailutils/cstr.h>
#include <mailutils/header.h>
#include <mailutils/stream.h>
#include <mailutils/filter.h>
#include <mailutils/assoc.h>
#include <mailutils/errno.h>

struct header_buffer
{
  mu_stream_t str;     /* Output stream */
  size_t line_len;     /* Length of current line */
  size_t line_max;     /* Max. line length */
};

static int
mime_store_parameter (char const *name, void *value, void *data)
{
  struct mu_mime_param *p = value;
  struct header_buffer *hbuf = data;
  size_t nlen;   /* Length of parameter name
		    (eq. sign, eventual seqence no. and language info mark
		    included) */
  size_t vlen;   /* Length of lang'charset' part */
  int langinfo;  /* True if language info is available */ 
  int quote = 0; /* 2 if the value should be quoted, 0 otherwise */
  int segment = -1; /* Segment sequence number */
  mu_stream_t valstr;  /* Value stream (properly encoded) */
  mu_off_t valsize;    /* Number of octets left in valstr */
  char const *filter_name = NULL; /* Name of the filter for the value */
  int rc;
    
  rc = mu_static_memory_stream_create (&valstr, p->value, strlen (p->value));
  if (rc)
    return rc;
  
  nlen = strlen (name);
  if (p->lang || p->cset)
    {
      vlen = 2;
      if (p->lang)
	vlen += strlen (p->lang);
      if (p->cset)
	vlen += strlen (p->cset);
      langinfo = 1;
      filter_name = "percent";
    }
  else
    {
      if (*mu_str_skip_class_comp (p->value, MU_CTYPE_TSPEC|MU_CTYPE_BLANK))
	{
	  /* Must be in quoted-string, to use within parameter values */
	  quote = 2;
	  filter_name = "dq";
	}
      else
	quote = 0;
      vlen = 0;
      langinfo = 0;
    }

  if (filter_name)
    {
      mu_stream_t tmp;
      
      rc = mu_filter_create (&tmp, valstr, filter_name, MU_FILTER_ENCODE,
			     MU_STREAM_READ | MU_STREAM_SEEK);
      if (rc)
	goto err;
      mu_stream_unref (valstr);
      valstr = tmp;
      rc = mu_memory_stream_create (&tmp, MU_STREAM_RDWR);
      if (rc == 0)
	{
	  rc = mu_stream_copy (tmp, valstr, 0, &valsize);
	  mu_stream_destroy (&tmp);
	}
    }
  else
    rc = mu_stream_size (valstr, &valsize);

  if (rc)
    goto err;

  nlen += langinfo;
  
  rc = mu_stream_seek (valstr, 0, MU_SEEK_SET, NULL);

  if (hbuf->line_max == 0)
    {
      /* No line wrapping requested.  Store the value as it is */
      mu_stream_printf (hbuf->str, "%s", name);
      if (langinfo)
	mu_stream_write (hbuf->str, "*", 1, NULL);
      mu_stream_write (hbuf->str, "=", 1, NULL);
      if (vlen)
	{
	  mu_stream_printf (hbuf->str, "%s'%s'",
			    mu_prstr (p->lang),
			    mu_prstr (p->cset));
	  vlen = 0;
	}
      else if (quote)
	mu_stream_write (hbuf->str, "\"", 1, NULL);
      mu_stream_copy (hbuf->str, valstr, 0, NULL);
      if (quote)
	mu_stream_write (hbuf->str, "\"", 1, NULL);
      if (mu_stream_err (hbuf->str))
	rc = mu_stream_last_error (hbuf->str);
    }
  else
    {
      /* Split the value into sequentially indexed segments, each one no
	 wider than the requested line width.

	 Without special precautions, an encoded character occurring at
	 the end of a segment can be split between this and the following
	 segment to satisfy line width requirements.  To avoid this, the
	 following approach is used:

	 1. The value stream is put to unbuffered mode.
	 2. Before each write, the size of the transcoder output buffer
	    in valstr is set to the number of bytes left in the current
	    line.

	 This way the transcoder will write as many bytes as possible
	 without breaking the encoded constructs while the unbuffered mode
	 will ensure that it will not be called again to fill up the stream
	 buffer.

	 If the line width is insufficient, MU_ERR_BUFSPACE will be returned.
      */
      char *iobuf;

      iobuf = malloc (hbuf->line_max + 1);
      if (!iobuf)
	{
	  rc = errno;
	  goto err;
	}
      
      mu_stream_set_buffer (valstr, mu_buffer_none, 0);

      while (rc == 0 && valsize)
	{
	  mu_off_t start, nr; /* Start and end positions in stream */
	  size_t sz, n;
      
	  mu_stream_write (hbuf->str, ";", 1, NULL);
	  mu_stream_seek (hbuf->str, 0, MU_SEEK_CUR, &start);
      
	  if (segment >= 0)
	    {
	      mu_stream_write (hbuf->str, "\n", 1, NULL);
	      hbuf->line_len = 0;
	      segment++;
	    }
	  else if (hbuf->line_len + valsize + quote + vlen + nlen + 1 >
		   hbuf->line_max)
	    {
	      mu_stream_write (hbuf->str, "\n", 1, NULL);
	      hbuf->line_len = 0;
	      if (hbuf->line_len + valsize + quote + vlen + nlen + 1 >
		   hbuf->line_max)
		segment++;
	    }
	  
	  mu_stream_write (hbuf->str, " ", 1, NULL);
	  
	  if (segment >= 0)
	    mu_stream_printf (hbuf->str, "%s*%d", name, segment);
	  else
	    mu_stream_printf (hbuf->str, "%s", name);
	  if (langinfo)
	    mu_stream_write (hbuf->str, "*", 1, NULL);
	  mu_stream_write (hbuf->str, "=", 1, NULL);
	  mu_stream_seek (hbuf->str, 0, MU_SEEK_CUR, &nr);
	  nlen = nr - start;
	  hbuf->line_len += nlen;
	  start = nr;

	  /* Compute the number of octets to put into the current line.
	     If the requested line width is not enough to accomodate
	     the line, signal the error */
	  if (hbuf->line_max <= (hbuf->line_len + quote + vlen))
	    {
	      rc = MU_ERR_BUFSPACE;
	      break;
	    }

	  sz = hbuf->line_max - (hbuf->line_len + quote + vlen);
	  mu_stream_ioctl (valstr, MU_IOCTL_FILTER,
			   MU_IOCTL_FILTER_SET_OUTBUF_SIZE, &sz);
	  
	  rc = mu_stream_read (valstr, iobuf, sz, &n);
	  if (rc || n == 0)
	    break;
	  
	  if (vlen)
	    {
	      mu_stream_printf (hbuf->str, "%s'%s'",
				mu_prstr (p->lang),
				mu_prstr (p->cset));
	      vlen = 0;
	    }
	  else if (quote)
	    mu_stream_write (hbuf->str, "\"", 1, NULL);

	  mu_stream_write (hbuf->str, iobuf, n, NULL);
	  
	  if (quote)
	    mu_stream_write (hbuf->str, "\"", 1, NULL);
	  mu_stream_seek (hbuf->str, 0, MU_SEEK_CUR, &nr);
	  nr -= start;
	  hbuf->line_len += nr;
	  valsize -= n;
	  
	  if (mu_stream_err (hbuf->str))
	    rc = mu_stream_last_error (hbuf->str);
	}
      free (iobuf);
    }
 err:
  mu_stream_destroy (&valstr);

  return rc;
}

static int
mime_header_format (const char *value, mu_assoc_t params,
		    struct header_buffer *hbuf)
{
  size_t l = strlen (value);
  
  mu_stream_write (hbuf->str, value, l, NULL);
  hbuf->line_len += l;
  return mu_assoc_foreach (params, mime_store_parameter, hbuf);
}

/* Store a header in accordance with RFC 2231, Section 3,
   "Parameter Value Continuations"

   HDR    -  Message header object
   NAME   -  Header name
   VALUE  -  Header value part
   PARAMS -  Named parameters (assoc of struct mu_mime_param *)
   LINE_WIDTH - Maximum line width.
*/

int
mu_mime_header_set_w (mu_header_t hdr, const char *name,
		      const char *value, mu_assoc_t params, size_t line_width)
{
  struct header_buffer hbuf;
  int rc;
  
  rc = mu_memory_stream_create (&hbuf.str, MU_STREAM_RDWR);
  if (rc)
    return rc;
  hbuf.line_len = strlen (name) + 2;
  hbuf.line_max = line_width;
  rc = mime_header_format (value, params, &hbuf);
  if (rc == 0)
    {
      mu_off_t pos;
      char *fmtval;
      
      mu_stream_seek (hbuf.str, 0, MU_SEEK_CUR, &pos);
      fmtval = malloc (pos + 1);
      mu_stream_seek (hbuf.str, 0, MU_SEEK_SET, NULL);
      mu_stream_read (hbuf.str, fmtval, pos, NULL);
      fmtval[pos] = 0;
      rc = mu_header_set_value (hdr, name, fmtval, 1);
      free (fmtval);
    }
  mu_stream_destroy (&hbuf.str);
  return rc;
}

int
mu_mime_header_set (mu_header_t hdr, const char *name,
		    const char *value, mu_assoc_t params)
{
  return mu_mime_header_set_w (hdr, name, value, params, 76);
}

    
  

Return to:

Send suggestions and report system problems to the System administrator.