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