]> diplodocus.org Git - nmh/blob - uip/mhical.c
Alter HasSuffixC()'s char * to be const.
[nmh] / uip / mhical.c
1 /*
2 * mhical.c -- operate on an iCalendar request
3 *
4 * This code is Copyright (c) 2014, by the authors of nmh.
5 * See the COPYRIGHT file in the root directory of the nmh
6 * distribution for complete copyright information.
7 */
8
9 #include "h/mh.h"
10 #include "h/icalendar.h"
11 #include "sbr/icalparse.h"
12 #include <h/fmt_scan.h>
13 #include "h/addrsbr.h"
14 #include "h/mts.h"
15 #include "h/utils.h"
16 #include <time.h>
17
18 typedef enum act {
19 ACT_NONE,
20 ACT_ACCEPT,
21 ACE_DECLINE,
22 ACT_TENTATIVE,
23 ACT_DELEGATE,
24 ACT_CANCEL
25 } act;
26
27 static void convert_to_reply (contentline *, act);
28 static void convert_to_cancellation (contentline *);
29 static void convert_common (contentline *, act);
30 static void dump_unfolded (FILE *, contentline *);
31 static void output (FILE *, contentline *, int);
32 static void display (FILE *, contentline *, char *);
33 static const char *identity (const contentline *);
34 static char *format_params (char *, param_list *);
35 static char *fold (char *, int);
36
37 #define MHICAL_SWITCHES \
38 X("reply accept|decline|tentative", 0, REPLYSW) \
39 X("cancel", 0, CANCELSW) \
40 X("form formatfile", 0, FORMSW) \
41 X("format string", 5, FMTSW) \
42 X("infile", 0, INFILESW) \
43 X("outfile", 0, OUTFILESW) \
44 X("contenttype", 0, CONTENTTYPESW) \
45 X("nocontenttype", 0, NCONTENTTYPESW) \
46 X("unfold", 0, UNFOLDSW) \
47 X("debug", 0, DEBUGSW) \
48 X("version", 0, VERSIONSW) \
49 X("help", 0, HELPSW) \
50
51 #define X(sw, minchars, id) id,
52 DEFINE_SWITCH_ENUM(MHICAL);
53 #undef X
54
55 #define X(sw, minchars, id) { sw, minchars, id },
56 DEFINE_SWITCH_ARRAY(MHICAL, switches);
57 #undef X
58
59 vevent vevents = { NULL, NULL, NULL};
60
61 int
62 main (int argc, char *argv[]) {
63 /* RFC 5322 § 3.3 date-time format, including the optional
64 day-of-week and not including the optional seconds. The
65 zone is required by the RFC but not always output by this
66 format, because RFC 5545 § 3.3.5 allows date-times not
67 bound to any time zone. */
68
69 act action = ACT_NONE;
70 char *infile = NULL, *outfile = NULL;
71 FILE *inputfile = NULL, *outputfile = NULL;
72 int contenttype = 0, unfold = 0;
73 vevent *v, *nextvevent;
74 char *form = "mhical.24hour", *format = NULL;
75 char **argp, **arguments, *cp;
76
77 icaldebug = 0; /* Global provided by bison (with name-prefix "ical"). */
78
79 if (nmh_init(argv[0], 2)) { return 1; }
80
81 arguments = getarguments (invo_name, argc, argv, 1);
82 argp = arguments;
83
84 /*
85 * Parse arguments
86 */
87 while ((cp = *argp++)) {
88 if (*cp == '-') {
89 switch (smatch (++cp, switches)) {
90 case AMBIGSW:
91 ambigsw (cp, switches);
92 done (1);
93 case UNKWNSW:
94 adios (NULL, "-%s unknown", cp);
95
96 case HELPSW: {
97 char buf[128];
98 snprintf (buf, sizeof buf, "%s [switches]", invo_name);
99 print_help (buf, switches, 1);
100 done (0);
101 }
102 case VERSIONSW:
103 print_version(invo_name);
104 done (0);
105 case DEBUGSW:
106 icaldebug = 1;
107 continue;
108
109 case REPLYSW:
110 if (! (cp = *argp++) || (*cp == '-' && cp[1]))
111 adios (NULL, "missing argument to %s", argp[-2]);
112 if (! strcasecmp (cp, "accept")) {
113 action = ACT_ACCEPT;
114 } else if (! strcasecmp (cp, "decline")) {
115 action = ACE_DECLINE;
116 } else if (! strcasecmp (cp, "tentative")) {
117 action = ACT_TENTATIVE;
118 } else if (! strcasecmp (cp, "delegate")) {
119 action = ACT_DELEGATE;
120 } else {
121 adios (NULL, "Unknown action: %s", cp);
122 }
123 continue;
124
125 case CANCELSW:
126 action = ACT_CANCEL;
127 continue;
128
129 case FORMSW:
130 if (! (form = *argp++) || *form == '-')
131 adios (NULL, "missing argument to %s", argp[-2]);
132 format = NULL;
133 continue;
134 case FMTSW:
135 if (! (format = *argp++) || *format == '-')
136 adios (NULL, "missing argument to %s", argp[-2]);
137 form = NULL;
138 continue;
139
140 case INFILESW:
141 if (! (cp = *argp++) || (*cp == '-' && cp[1]))
142 adios (NULL, "missing argument to %s", argp[-2]);
143 infile = *cp == '-' ? add (cp, NULL) : path (cp, TFILE);
144 continue;
145 case OUTFILESW:
146 if (! (cp = *argp++) || (*cp == '-' && cp[1]))
147 adios (NULL, "missing argument to %s", argp[-2]);
148 outfile = *cp == '-' ? add (cp, NULL) : path (cp, TFILE);
149 continue;
150
151 case CONTENTTYPESW:
152 contenttype = 1;
153 continue;
154 case NCONTENTTYPESW:
155 contenttype = 0;
156 continue;
157
158 case UNFOLDSW:
159 unfold = 1;
160 continue;
161 }
162 }
163 }
164
165 free (arguments);
166
167 if (infile) {
168 if ((inputfile = fopen (infile, "r"))) {
169 icalset_inputfile (inputfile);
170 } else {
171 adios (infile, "error opening");
172 }
173 } else {
174 inputfile = stdin;
175 }
176
177 if (outfile) {
178 if ((outputfile = fopen (outfile, "w"))) {
179 icalset_outputfile (outputfile);
180 } else {
181 adios (outfile, "error opening");
182 }
183 } else {
184 outputfile = stdout;
185 }
186
187 vevents.last = &vevents;
188 /* vevents is accessed by parser as global. */
189 icalparse ();
190
191 for (v = &vevents; v; v = nextvevent) {
192 if (! unfold && v != &vevents && v->contentlines &&
193 v->contentlines->name &&
194 strcasecmp (v->contentlines->name, "END") &&
195 v->contentlines->value &&
196 strcasecmp (v->contentlines->value, "VCALENDAR")) {
197 /* Output blank line between vevents. Not before
198 first vevent and not after last. */
199 putc ('\n', outputfile);
200 }
201
202 if (action == ACT_NONE) {
203 if (unfold) {
204 dump_unfolded (outputfile, v->contentlines);
205 } else {
206 char *nfs = new_fs (form, format, NULL);
207
208 display (outputfile, v->contentlines, nfs);
209 free_fs ();
210 }
211 } else {
212 if (action == ACT_CANCEL) {
213 convert_to_cancellation (v->contentlines);
214 } else {
215 convert_to_reply (v->contentlines, action);
216 }
217 output (outputfile, v->contentlines, contenttype);
218 }
219
220 free_contentlines (v->contentlines);
221 nextvevent = v->next;
222 if (v != &vevents) {
223 free (v);
224 }
225 }
226
227 if (infile) {
228 if (fclose (inputfile) != 0) {
229 advise (infile, "error closing");
230 }
231 free (infile);
232 }
233 if (outfile) {
234 if (fclose (outputfile) != 0) {
235 advise (outfile, "error closing");
236 }
237 free (outfile);
238 }
239
240 return 0;
241 }
242
243 /*
244 * - Change METHOD from REQUEST to REPLY.
245 * - Change PRODID.
246 * - Remove all ATTENDEE lines for other users (based on ismymbox ()).
247 * - For the user's ATTENDEE line:
248 * - Remove ROLE and RSVP parameters.
249 * - Change PARTSTAT value to indicate reply action, e.g., ACCEPTED,
250 * DECLINED, or TENTATIVE.
251 * - Insert action at beginning of SUMMARY value.
252 * - Remove all X- lines.
253 * - Update DTSTAMP with current timestamp.
254 * - Remove all DESCRIPTION lines.
255 * - Excise VALARM sections.
256 */
257 static void
258 convert_to_reply (contentline *clines, act action) {
259 char *partstat = NULL;
260 int found_my_attendee_line = 0;
261 contentline *node;
262
263 convert_common (clines, action);
264
265 switch (action) {
266 case ACT_ACCEPT:
267 partstat = "ACCEPTED";
268 break;
269 case ACE_DECLINE:
270 partstat = "DECLINED";
271 break;
272 case ACT_TENTATIVE:
273 partstat = "TENTATIVE";
274 break;
275 default:
276 ;
277 }
278
279 /* Call find_contentline () with node as argument to find multiple
280 matching contentlines. */
281 for (node = clines;
282 (node = find_contentline (node, "ATTENDEE", 0));
283 node = node->next) {
284 param_list *p;
285
286 ismymbox (NULL); /* need to prime ismymbox() */
287
288 /* According to RFC 5545 § 3.3.3, an email address in the
289 value must be a mailto URI. */
290 if (! strncasecmp (node->value, "mailto:", 7)) {
291 char *addr = node->value + 7;
292 struct mailname *mn;
293
294 /* Skip any leading whitespace. */
295 for ( ; isspace ((unsigned char) *addr); ++addr) { continue; }
296
297 addr = getname (addr);
298 mn = getm (addr, NULL, 0, NULL, 0);
299
300 /* Need to flush getname after use. */
301 while (getname ("")) { continue; }
302
303 if (ismymbox (mn)) {
304 found_my_attendee_line = 1;
305 for (p = node->params; p && p->param_name; p = p->next) {
306 value_list *v;
307
308 for (v = p->values; v; v = v->next) {
309 if (! strcasecmp (p->param_name, "ROLE") ||
310 ! strcasecmp (p->param_name, "RSVP")) {
311 remove_value (v);
312 } else if (! strcasecmp (p->param_name, "PARTSTAT")) {
313 free (v->value);
314 v->value = strdup (partstat);
315 }
316 }
317 }
318 } else {
319 remove_contentline (node);
320 }
321
322 mnfree (mn);
323 }
324 }
325
326 if (! found_my_attendee_line) {
327 /* Generate and attach an ATTENDEE line for me. */
328 contentline *node;
329
330 /* Add it after the ORGANIZER line, or if none, BEGIN:VEVENT line. */
331 if ((node = find_contentline (clines, "ORGANIZER", 0)) ||
332 (node = find_contentline (clines, "BEGIN", "VEVENT"))) {
333 contentline *new_node = add_contentline (node, "ATTENDEE");
334
335 add_param_name (new_node, strdup ("PARTSTAT"));
336 add_param_value (new_node, strdup (partstat));
337 add_param_name (new_node, strdup ("CN"));
338 add_param_value (new_node, strdup (getfullname ()));
339 new_node->value = concat ("MAILTO:", getlocalmbox (), NULL);
340 }
341 }
342
343 /* Call find_contentline () with node as argument to find multiple
344 matching contentlines. */
345 for (node = clines;
346 (node = find_contentline (node, "DESCRIPTION", 0));
347 node = node->next) {
348 /* ACCEPT, at least, replies don't seem to have DESCRIPTIONS. */
349 remove_contentline (node);
350 }
351 }
352
353 /*
354 * - Change METHOD from REQUEST to CANCEL.
355 * - Change PRODID.
356 * - Insert action at beginning of SUMMARY value.
357 * - Remove all X- lines.
358 * - Update DTSTAMP with current timestamp.
359 * - Change STATUS from CONFIRMED to CANCELLED.
360 * - Increment value of SEQUENCE.
361 * - Excise VALARM sections.
362 */
363 static void
364 convert_to_cancellation (contentline *clines) {
365 contentline *node;
366
367 convert_common (clines, ACT_CANCEL);
368
369 if ((node = find_contentline (clines, "STATUS", 0)) &&
370 ! strcasecmp (node->value, "CONFIRMED")) {
371 free (node->value);
372 node->value = strdup ("CANCELLED");
373 }
374
375 if ((node = find_contentline (clines, "SEQUENCE", 0))) {
376 int sequence = atoi (node->value);
377 char buf[32];
378
379 (void) snprintf (buf, sizeof buf, "%d", sequence + 1);
380 free (node->value);
381 node->value = strdup (buf);
382 }
383 }
384
385 static void
386 convert_common (contentline *clines, act action) {
387 contentline *node;
388 int in_valarm;
389
390 if ((node = find_contentline (clines, "METHOD", 0))) {
391 free (node->value);
392 node->value = strdup (action == ACT_CANCEL ? "CANCEL" : "REPLY");
393 }
394
395 if ((node = find_contentline (clines, "PRODID", 0))) {
396 free (node->value);
397 node->value = strdup ("nmh mhical v0.1");
398 }
399
400 if ((node = find_contentline (clines, "VERSION", 0))) {
401 if (! node->value) {
402 admonish (NULL, "Version property is missing value, assume 2.0");
403 node->value = strdup ("2.0");
404 }
405
406 if (strcmp (node->value, "2.0")) {
407 admonish (NULL, "supports the Version 2.0 specified by RFC 5545 "
408 "but iCalendar object has Version %s", node->value);
409 node->value = strdup ("2.0");
410 }
411 }
412
413 if ((node = find_contentline (clines, "SUMMARY", 0))) {
414 char *insert = NULL;
415
416 switch (action) {
417 case ACT_ACCEPT:
418 insert = "Accepted: ";
419 break;
420 case ACE_DECLINE:
421 insert = "Declined: ";
422 break;
423 case ACT_TENTATIVE:
424 insert = "Tentative: ";
425 break;
426 case ACT_DELEGATE:
427 adios (NULL, "Delegate replies are not supported");
428 break;
429 case ACT_CANCEL:
430 insert = "Cancelled:";
431 break;
432 default:
433 ;
434 }
435
436 if (insert) {
437 const size_t len = strlen (insert) + strlen (node->value) + 1;
438 char *tmp = mh_xmalloc (len);
439
440 (void) strncpy (tmp, insert, len);
441 (void) strncat (tmp, node->value, len - strlen (insert) - 1);
442 free (node->value);
443 node->value = tmp;
444 } else {
445 /* Should never get here. */
446 adios (NULL, "Unknown action: %d", action);
447 }
448 }
449
450 if ((node = find_contentline (clines, "DTSTAMP", 0))) {
451 const time_t now = time (NULL);
452 struct tm now_tm;
453
454 if (gmtime_r (&now, &now_tm)) {
455 /* 17 would be sufficient given that RFC 5545 § 3.3.4
456 supports only a 4 digit year. */
457 char buf[32];
458
459 if (strftime (buf, sizeof buf, "%Y%m%dT%H%M%SZ", &now_tm)) {
460 free (node->value);
461 node->value = strdup (buf);
462 } else {
463 admonish (NULL, "strftime unable to format current time");
464 }
465 } else {
466 admonish (NULL, "gmtime_r failed on current time");
467 }
468 }
469
470 /* Excise X- lines and VALARM section(s). */
471 in_valarm = 0;
472 for (node = clines; node; node = node->next) {
473 /* node->name will be NULL if the line was deleted. */
474 if (! node->name) { continue; }
475
476 if (in_valarm) {
477 if (! strcasecmp ("END", node->name) &&
478 ! strcasecmp ("VALARM", node->value)) {
479 in_valarm = 0;
480 }
481 remove_contentline (node);
482 } else {
483 if (! strcasecmp ("BEGIN", node->name) &&
484 ! strcasecmp ("VALARM", node->value)) {
485 in_valarm = 1;
486 remove_contentline (node);
487 } else if (! strncasecmp ("X-", node->name, 2)) {
488 remove_contentline (node);
489 }
490 }
491 }
492 }
493
494 /* Echo the input, but with unfolded lines. */
495 static void
496 dump_unfolded (FILE *file, contentline *clines) {
497 contentline *node;
498
499 for (node = clines; node; node = node->next) {
500 fputs (node->input_line, file);
501 }
502 }
503
504 static void
505 output (FILE *file, contentline *clines, int contenttype) {
506 contentline *node;
507
508 if (contenttype) {
509 /* Generate a Content-Type header to pass the method parameter
510 to mhbuild. Per RFC 5545 Secs. 6 and 8.1, it must be
511 UTF-8. But we don't attempt to do any conversion of the
512 input. */
513 if ((node = find_contentline (clines, "METHOD", 0))) {
514 fprintf (file,
515 "Content-Type: text/calendar; method=\"%s\"; "
516 "charset=\"UTF-8\"\n\n",
517 node->value);
518 }
519 }
520
521 for (node = clines; node; node = node->next) {
522 if (node->name) {
523 char *line = NULL;
524 size_t len;
525
526 line = strdup (node->name);
527 line = format_params (line, node->params);
528
529 len = strlen (line);
530 line = mh_xrealloc (line, len + 2);
531 line[len] = ':';
532 line[len + 1] = '\0';
533
534 line = fold (add (node->value, line),
535 clines->cr_before_lf == CR_BEFORE_LF);
536
537 if (clines->cr_before_lf == LF_ONLY) {
538 fprintf (file, "%s\n", line);
539 } else {
540 fprintf (file, "%s\r\n", line);
541 }
542 free (line);
543 }
544 }
545 }
546
547 /*
548 * Display these fields of the iCalendar event:
549 * - method
550 * - organizer
551 * - summary
552 * - description, except for "\n\n" and in VALARM
553 * - location
554 * - dtstart in local timezone
555 * - dtend in local timezone
556 * - attendees (limited to number specified in initialization)
557 */
558 static void
559 display (FILE *file, contentline *clines, char *nfs) {
560 tzdesc_t timezones = load_timezones (clines);
561 int in_vtimezone;
562 int in_valarm;
563 contentline *node;
564 struct format *fmt;
565 int dat[5] = { 0, 0, 0, INT_MAX, 0 };
566 struct comp *c;
567 charstring_t buffer = charstring_create (BUFSIZ);
568 charstring_t attendees = charstring_create (BUFSIZ);
569 const unsigned int max_attendees = 20;
570 unsigned int num_attendees;
571
572 /* Don't call on the END:VCALENDAR line. */
573 if (clines && clines->next) {
574 (void) fmt_compile (nfs, &fmt, 1);
575 }
576
577 if ((c = fmt_findcomp ("method"))) {
578 if ((node = find_contentline (clines, "METHOD", 0)) && node->value) {
579 c->c_text = strdup (node->value);
580 }
581 }
582
583 if ((c = fmt_findcomp ("organizer"))) {
584 if ((node = find_contentline (clines, "ORGANIZER", 0)) &&
585 node->value) {
586 c->c_text = strdup (identity (node));
587 }
588 }
589
590 if ((c = fmt_findcomp ("summary"))) {
591 if ((node = find_contentline (clines, "SUMMARY", 0)) && node->value) {
592 c->c_text = strdup (node->value);
593 }
594 }
595
596 /* Only display DESCRIPTION lines that are outside VALARM section(s). */
597 in_valarm = 0;
598 if ((c = fmt_findcomp ("description"))) {
599 for (node = clines; node; node = node->next) {
600 /* node->name will be NULL if the line was deleted. */
601 if (node->name && node->value && ! in_valarm &&
602 ! strcasecmp ("DESCRIPTION", node->name) &&
603 strcasecmp (node->value, "\\n\\n")) {
604 c->c_text = strdup (node->value);
605 } else if (in_valarm) {
606 if (! strcasecmp ("END", node->name) &&
607 ! strcasecmp ("VALARM", node->value)) {
608 in_valarm = 0;
609 }
610 } else {
611 if (! strcasecmp ("BEGIN", node->name) &&
612 ! strcasecmp ("VALARM", node->value)) {
613 in_valarm = 1;
614 }
615 }
616 }
617 }
618
619 if ((c = fmt_findcomp ("location"))) {
620 if ((node = find_contentline (clines, "LOCATION", 0)) &&
621 node->value) {
622 c->c_text = strdup (node->value);
623 }
624 }
625
626 if ((c = fmt_findcomp ("dtstart"))) {
627 /* Find DTSTART outsize of a VTIMEZONE section. */
628 in_vtimezone = 0;
629 for (node = clines; node; node = node->next) {
630 /* node->name will be NULL if the line was deleted. */
631 if (! node->name) { continue; }
632
633 if (in_vtimezone) {
634 if (! strcasecmp ("END", node->name) &&
635 ! strcasecmp ("VTIMEZONE", node->value)) {
636 in_vtimezone = 0;
637 }
638 } else {
639 if (! strcasecmp ("BEGIN", node->name) &&
640 ! strcasecmp ("VTIMEZONE", node->value)) {
641 in_vtimezone = 1;
642 } else if (! strcasecmp ("DTSTART", node->name)) {
643 /* Got it: DTSTART outside of a VTIMEZONE section. */
644 char *datetime = format_datetime (timezones, node);
645 c->c_text = datetime ? datetime : strdup(node->value);
646 }
647 }
648 }
649 }
650
651 if ((c = fmt_findcomp ("dtend"))) {
652 if ((node = find_contentline (clines, "DTEND", 0)) && node->value) {
653 char *datetime = format_datetime (timezones, node);
654 c->c_text = datetime ? datetime : strdup(node->value);
655 } else if ((node = find_contentline (clines, "DTSTART", 0)) &&
656 node->value) {
657 /* There is no DTEND. If there's a DTSTART, use it. If it
658 doesn't have a time, assume that the event is for the
659 entire day and append 23:59:59 to it so that it signifies
660 the end of the day. And assume local timezone. */
661 if (strchr(node->value, 'T')) {
662 char * datetime = format_datetime (timezones, node);
663 c->c_text = datetime ? datetime : strdup(node->value);
664 } else {
665 char *datetime;
666 contentline node_copy;
667
668 memcpy(&node_copy, node, sizeof node_copy);
669 node_copy.value = concat(node_copy.value, "T235959", NULL);
670 datetime = format_datetime (timezones, &node_copy);
671 c->c_text = datetime ? datetime : strdup(node_copy.value);
672 free(node_copy.value);
673 }
674 }
675 }
676
677 if ((c = fmt_findcomp ("attendees"))) {
678 /* Call find_contentline () with node as argument to find multiple
679 matching contentlines. */
680 charstring_append_cstring (attendees, "Attendees: ");
681 for (node = clines, num_attendees = 0;
682 (node = find_contentline (node, "ATTENDEE", 0)) &&
683 num_attendees++ < max_attendees;
684 node = node->next) {
685 const char *id = identity (node);
686
687 if (num_attendees > 1) {
688 charstring_append_cstring (attendees, ", ");
689 }
690 charstring_append_cstring (attendees, id);
691 }
692
693 if (num_attendees >= max_attendees) {
694 unsigned int not_shown = 0;
695
696 for ( ;
697 (node = find_contentline (node, "ATTENDEE", 0));
698 node = node->next) {
699 ++not_shown;
700 }
701
702 if (not_shown > 0) {
703 char buf[32];
704
705 (void) snprintf (buf, sizeof buf, ", and %d more", not_shown);
706 charstring_append_cstring (attendees, buf);
707 }
708 }
709
710 if (num_attendees > 0) {
711 c->c_text = charstring_buffer_copy (attendees);
712 }
713 }
714
715 /* Don't call on the END:VCALENDAR line. */
716 if (clines->next) {
717 (void) fmt_scan (fmt, buffer, INT_MAX, dat, NULL);
718 fputs (charstring_buffer (buffer), file);
719 fmt_free (fmt, 1);
720 }
721
722 charstring_free (attendees);
723 charstring_free (buffer);
724 free_timezones (timezones);
725 }
726
727 static const char *
728 identity (const contentline *node) {
729 /* According to RFC 5545 § 3.3.3, an email address in the value
730 must be a mailto URI. */
731 if (! strncasecmp (node->value, "mailto:", 7)) {
732 char *addr;
733 param_list *p;
734
735 for (p = node->params; p && p->param_name; p = p->next) {
736 value_list *v;
737
738 for (v = p->values; v; v = v->next) {
739 if (! strcasecmp (p->param_name, "CN")) {
740 return v->value;
741 }
742 }
743 }
744
745 /* Did not find a CN parameter, so output the address. */
746 addr = node->value + 7;
747
748 /* Skip any leading whitespace. */
749 for ( ; isspace ((unsigned char) *addr); ++addr) { continue; }
750
751 return addr;
752 }
753
754 return "unknown";
755 }
756
757 static char *
758 format_params (char *line, param_list *p) {
759 for ( ; p && p->param_name; p = p->next) {
760 value_list *v;
761 size_t num_values = 0;
762
763 for (v = p->values; v; v = v->next) {
764 if (v->value) { ++num_values; }
765 }
766
767 if (num_values) {
768 size_t len = strlen (line);
769
770 line = mh_xrealloc (line, len + 2);
771 line[len] = ';';
772 line[len + 1] = '\0';
773
774 line = add (p->param_name, line);
775
776 for (v = p->values; v; v = v->next) {
777 len = strlen (line);
778 line = mh_xrealloc (line, len + 2);
779 line[len] = v == p->values ? '=' : ',';
780 line[len + 1] = '\0';
781
782 line = add (v->value, line);
783 }
784 }
785 }
786
787 return line;
788 }
789
790 static char *
791 fold (char *line, int uses_cr) {
792 size_t remaining = strlen (line);
793 size_t current_line_len = 0;
794 charstring_t folded_line = charstring_create (2 * remaining);
795 const char *cp = line;
796
797 #ifdef MULTIBYTE_SUPPORT
798 if (mbtowc (NULL, NULL, 0)) {} /* reset shift state */
799 #endif
800
801 while (*cp && remaining > 0) {
802 #ifdef MULTIBYTE_SUPPORT
803 int char_len = mbtowc (NULL, cp, (size_t) MB_CUR_MAX < remaining
804 ? (size_t) MB_CUR_MAX
805 : remaining);
806 if (char_len == -1) { char_len = 1; }
807 #else
808 const int char_len = 1;
809 #endif
810
811 charstring_push_back_chars (folded_line, cp, char_len, 1);
812 remaining -= char_len > 0 ? char_len : 1;
813
814 /* remaining must be > 0 to pass the loop condition above, so
815 if it's not > 1, it is == 1. */
816 if (++current_line_len >= 75) {
817 if (remaining > 1 || (*(cp+1) != '\0' && *(cp+1) != '\r' &&
818 *(cp+1) != '\n')) {
819 /* fold */
820 if (uses_cr) { charstring_push_back (folded_line, '\r'); }
821 charstring_push_back (folded_line, '\n');
822 charstring_push_back (folded_line, ' ');
823 current_line_len = 0;
824 }
825 }
826
827 cp += char_len > 0 ? char_len : 1;
828 }
829
830 free (line);
831 line = charstring_buffer_copy (folded_line);
832 charstring_free (folded_line);
833
834 return line;
835 }