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 "h/icalendar.h"
10 #include "sbr/icalparse.h"
11 #include <h/fmt_scan.h>
12 #include "h/addrsbr.h"
13 #include "h/mts.h"
14 #include "h/utils.h"
15 #include <time.h>
16
17 typedef enum act {
18 ACT_NONE,
19 ACT_ACCEPT,
20 ACE_DECLINE,
21 ACT_TENTATIVE,
22 ACT_DELEGATE,
23 ACT_CANCEL
24 } act;
25
26 static void convert_to_reply (contentline *, act);
27 static void convert_to_cancellation (contentline *);
28 static void convert_common (contentline *, act);
29 static void dump_unfolded (FILE *, contentline *);
30 static void output (FILE *, contentline *, int);
31 static void display (FILE *, contentline *, char *);
32 static const char *identity (const contentline *);
33 static char *format_params (char *, param_list *);
34 static char *fold (char *, int);
35
36 #define MHICAL_SWITCHES \
37 X("reply accept|decline|tentative", 0, REPLYSW) \
38 X("cancel", 0, CANCELSW) \
39 X("form formatfile", 0, FORMSW) \
40 X("format string", 5, FMTSW) \
41 X("infile", 0, INFILESW) \
42 X("outfile", 0, OUTFILESW) \
43 X("contenttype", 0, CONTENTTYPESW) \
44 X("nocontenttype", 0, NCONTENTTYPESW) \
45 X("unfold", 0, UNFOLDSW) \
46 X("debug", 0, DEBUGSW) \
47 X("version", 0, VERSIONSW) \
48 X("help", 0, HELPSW) \
49
50 #define X(sw, minchars, id) id,
51 DEFINE_SWITCH_ENUM(MHICAL);
52 #undef X
53
54 #define X(sw, minchars, id) { sw, minchars, id },
55 DEFINE_SWITCH_ARRAY(MHICAL, switches);
56 #undef X
57
58 vevent vevents = { NULL, NULL, NULL};
59 int parser_status = 0;
60
61 int
main(int argc,char * argv[])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 == '-' ? mh_xstrdup(cp) : 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 == '-' ? mh_xstrdup(cp) : 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 parser_status;
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
convert_to_reply(contentline * clines,act action)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, mh_xstrdup ("PARTSTAT"));
336 add_param_value (new_node, mh_xstrdup (partstat));
337 add_param_name (new_node, mh_xstrdup ("CN"));
338 add_param_value (new_node, mh_xstrdup (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
convert_to_cancellation(contentline * clines)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 = mh_xstrdup ("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 = mh_xstrdup (buf);
382 }
383 }
384
385 static void
convert_common(contentline * clines,act action)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 = mh_xstrdup (action == ACT_CANCEL ? "CANCEL" : "REPLY");
393 }
394
395 if ((node = find_contentline (clines, "PRODID", 0))) {
396 free (node->value);
397 node->value = mh_xstrdup ("nmh mhical v0.1");
398 }
399
400 if ((node = find_contentline (clines, "VERSION", 0))) {
401 if (! node->value) {
402 inform("Version property is missing value, assume 2.0, continuing...");
403 node->value = mh_xstrdup ("2.0");
404 }
405
406 if (strcmp (node->value, "2.0")) {
407 inform("supports the Version 2.0 specified by RFC 5545 "
408 "but iCalendar object has Version %s, continuing...",
409 node->value);
410 node->value = mh_xstrdup ("2.0");
411 }
412 }
413
414 if ((node = find_contentline (clines, "SUMMARY", 0))) {
415 char *insert = NULL;
416
417 switch (action) {
418 case ACT_ACCEPT:
419 insert = "Accepted: ";
420 break;
421 case ACE_DECLINE:
422 insert = "Declined: ";
423 break;
424 case ACT_TENTATIVE:
425 insert = "Tentative: ";
426 break;
427 case ACT_DELEGATE:
428 adios (NULL, "Delegate replies are not supported");
429 break;
430 case ACT_CANCEL:
431 insert = "Cancelled:";
432 break;
433 default:
434 ;
435 }
436
437 if (insert) {
438 const size_t len = strlen (insert) + strlen (node->value) + 1;
439 char *tmp = mh_xmalloc (len);
440
441 (void) strncpy (tmp, insert, len);
442 (void) strncat (tmp, node->value, len - strlen (insert) - 1);
443 free (node->value);
444 node->value = tmp;
445 } else {
446 /* Should never get here. */
447 adios (NULL, "Unknown action: %d", action);
448 }
449 }
450
451 if ((node = find_contentline (clines, "DTSTAMP", 0))) {
452 const time_t now = time (NULL);
453 struct tm now_tm;
454
455 if (gmtime_r (&now, &now_tm)) {
456 /* 17 would be sufficient given that RFC 5545 § 3.3.4
457 supports only a 4 digit year. */
458 char buf[32];
459
460 if (strftime (buf, sizeof buf, "%Y%m%dT%H%M%SZ", &now_tm)) {
461 free (node->value);
462 node->value = mh_xstrdup (buf);
463 } else {
464 inform("strftime unable to format current time, continuing...");
465 }
466 } else {
467 inform("gmtime_r failed on current time, continuing...");
468 }
469 }
470
471 /* Excise X- lines and VALARM section(s). */
472 in_valarm = 0;
473 for (node = clines; node; node = node->next) {
474 /* node->name will be NULL if the line was deleted. */
475 if (! node->name) { continue; }
476
477 if (in_valarm) {
478 if (! strcasecmp ("END", node->name) &&
479 ! strcasecmp ("VALARM", node->value)) {
480 in_valarm = 0;
481 }
482 remove_contentline (node);
483 } else {
484 if (! strcasecmp ("BEGIN", node->name) &&
485 ! strcasecmp ("VALARM", node->value)) {
486 in_valarm = 1;
487 remove_contentline (node);
488 } else if (! strncasecmp ("X-", node->name, 2)) {
489 remove_contentline (node);
490 }
491 }
492 }
493 }
494
495 /* Echo the input, but with unfolded lines. */
496 static void
dump_unfolded(FILE * file,contentline * clines)497 dump_unfolded (FILE *file, contentline *clines) {
498 contentline *node;
499
500 for (node = clines; node; node = node->next) {
501 fputs (node->input_line, file);
502 }
503 }
504
505 static void
output(FILE * file,contentline * clines,int contenttype)506 output (FILE *file, contentline *clines, int contenttype) {
507 contentline *node;
508
509 if (contenttype) {
510 /* Generate a Content-Type header to pass the method parameter
511 to mhbuild. Per RFC 5545 Secs. 6 and 8.1, it must be
512 UTF-8. But we don't attempt to do any conversion of the
513 input. */
514 if ((node = find_contentline (clines, "METHOD", 0))) {
515 fprintf (file,
516 "Content-Type: text/calendar; method=\"%s\"; "
517 "charset=\"UTF-8\"\n\n",
518 node->value);
519 }
520 }
521
522 for (node = clines; node; node = node->next) {
523 if (node->name) {
524 char *line = NULL;
525 size_t len;
526
527 line = mh_xstrdup (node->name);
528 line = format_params (line, node->params);
529
530 len = strlen (line);
531 line = mh_xrealloc (line, len + 2);
532 line[len] = ':';
533 line[len + 1] = '\0';
534
535 line = fold (add (node->value, line),
536 clines->cr_before_lf == CR_BEFORE_LF);
537
538 if (clines->cr_before_lf == LF_ONLY) {
539 fprintf (file, "%s\n", line);
540 } else {
541 fprintf (file, "%s\r\n", line);
542 }
543 free (line);
544 }
545 }
546 }
547
548 /*
549 * Display these fields of the iCalendar event:
550 * - method
551 * - organizer
552 * - summary
553 * - description, except for "\n\n" and in VALARM
554 * - location
555 * - dtstart in local timezone
556 * - dtend in local timezone
557 * - attendees (limited to number specified in initialization)
558 */
559 static void
display(FILE * file,contentline * clines,char * nfs)560 display (FILE *file, contentline *clines, char *nfs) {
561 tzdesc_t timezones = load_timezones (clines);
562 int in_vtimezone;
563 int in_valarm;
564 contentline *node;
565 struct format *fmt;
566 int dat[5] = { 0, 0, 0, INT_MAX, 0 };
567 struct comp *c;
568 charstring_t buffer = charstring_create (BUFSIZ);
569 charstring_t attendees = charstring_create (BUFSIZ);
570 const unsigned int max_attendees = 20;
571 unsigned int num_attendees;
572
573 /* Don't call on the END:VCALENDAR line. */
574 if (clines && clines->next) {
575 (void) fmt_compile (nfs, &fmt, 1);
576 }
577
578 if ((c = fmt_findcomp ("method"))) {
579 if ((node = find_contentline (clines, "METHOD", 0)) && node->value) {
580 c->c_text = mh_xstrdup (node->value);
581 }
582 }
583
584 if ((c = fmt_findcomp ("organizer"))) {
585 if ((node = find_contentline (clines, "ORGANIZER", 0)) &&
586 node->value) {
587 c->c_text = mh_xstrdup (identity (node));
588 }
589 }
590
591 if ((c = fmt_findcomp ("summary"))) {
592 if ((node = find_contentline (clines, "SUMMARY", 0)) && node->value) {
593 c->c_text = mh_xstrdup (node->value);
594 }
595 }
596
597 /* Only display DESCRIPTION lines that are outside VALARM section(s). */
598 in_valarm = 0;
599 if ((c = fmt_findcomp ("description"))) {
600 for (node = clines; node; node = node->next) {
601 /* node->name will be NULL if the line was deleted. */
602 if (node->name && node->value && ! in_valarm &&
603 ! strcasecmp ("DESCRIPTION", node->name) &&
604 strcasecmp (node->value, "\\n\\n")) {
605 c->c_text = mh_xstrdup (node->value);
606 } else if (in_valarm) {
607 if (! strcasecmp ("END", node->name) &&
608 ! strcasecmp ("VALARM", node->value)) {
609 in_valarm = 0;
610 }
611 } else {
612 if (! strcasecmp ("BEGIN", node->name) &&
613 ! strcasecmp ("VALARM", node->value)) {
614 in_valarm = 1;
615 }
616 }
617 }
618 }
619
620 if ((c = fmt_findcomp ("location"))) {
621 if ((node = find_contentline (clines, "LOCATION", 0)) &&
622 node->value) {
623 c->c_text = mh_xstrdup (node->value);
624 }
625 }
626
627 if ((c = fmt_findcomp ("dtstart"))) {
628 /* Find DTSTART outsize of a VTIMEZONE section. */
629 in_vtimezone = 0;
630 for (node = clines; node; node = node->next) {
631 /* node->name will be NULL if the line was deleted. */
632 if (! node->name) { continue; }
633
634 if (in_vtimezone) {
635 if (! strcasecmp ("END", node->name) &&
636 ! strcasecmp ("VTIMEZONE", node->value)) {
637 in_vtimezone = 0;
638 }
639 } else {
640 if (! strcasecmp ("BEGIN", node->name) &&
641 ! strcasecmp ("VTIMEZONE", node->value)) {
642 in_vtimezone = 1;
643 } else if (! strcasecmp ("DTSTART", node->name)) {
644 /* Got it: DTSTART outside of a VTIMEZONE section. */
645 char *datetime = format_datetime (timezones, node);
646 c->c_text = datetime ? datetime : mh_xstrdup(node->value);
647 }
648 }
649 }
650 }
651
652 if ((c = fmt_findcomp ("dtend"))) {
653 if ((node = find_contentline (clines, "DTEND", 0)) && node->value) {
654 char *datetime = format_datetime (timezones, node);
655 c->c_text = datetime ? datetime : strdup(node->value);
656 } else if ((node = find_contentline (clines, "DTSTART", 0)) &&
657 node->value) {
658 /* There is no DTEND. If there's a DTSTART, use it. If it
659 doesn't have a time, assume that the event is for the
660 entire day and append 23:59:59 to it so that it signifies
661 the end of the day. And assume local timezone. */
662 if (strchr(node->value, 'T')) {
663 char * datetime = format_datetime (timezones, node);
664 c->c_text = datetime ? datetime : strdup(node->value);
665 } else {
666 char *datetime;
667 contentline node_copy;
668
669 node_copy = *node;
670 node_copy.value = concat(node_copy.value, "T235959", NULL);
671 datetime = format_datetime (timezones, &node_copy);
672 c->c_text = datetime ? datetime : strdup(node_copy.value);
673 free(node_copy.value);
674 }
675 }
676 }
677
678 if ((c = fmt_findcomp ("attendees"))) {
679 /* Call find_contentline () with node as argument to find multiple
680 matching contentlines. */
681 charstring_append_cstring (attendees, "Attendees: ");
682 for (node = clines, num_attendees = 0;
683 (node = find_contentline (node, "ATTENDEE", 0)) &&
684 num_attendees++ < max_attendees;
685 node = node->next) {
686 const char *id = identity (node);
687
688 if (num_attendees > 1) {
689 charstring_append_cstring (attendees, ", ");
690 }
691 charstring_append_cstring (attendees, id);
692 }
693
694 if (num_attendees >= max_attendees) {
695 unsigned int not_shown = 0;
696
697 for ( ;
698 (node = find_contentline (node, "ATTENDEE", 0));
699 node = node->next) {
700 ++not_shown;
701 }
702
703 if (not_shown > 0) {
704 char buf[32];
705
706 (void) snprintf (buf, sizeof buf, ", and %d more", not_shown);
707 charstring_append_cstring (attendees, buf);
708 }
709 }
710
711 if (num_attendees > 0) {
712 c->c_text = charstring_buffer_copy (attendees);
713 }
714 }
715
716 /* Don't call on the END:VCALENDAR line. */
717 if (clines && clines->next) {
718 (void) fmt_scan (fmt, buffer, INT_MAX, dat, NULL);
719 fputs (charstring_buffer (buffer), file);
720 fmt_free (fmt, 1);
721 }
722
723 charstring_free (attendees);
724 charstring_free (buffer);
725 free_timezones (timezones);
726 }
727
728 static const char *
identity(const contentline * node)729 identity (const contentline *node) {
730 /* According to RFC 5545 § 3.3.3, an email address in the value
731 must be a mailto URI. */
732 if (! strncasecmp (node->value, "mailto:", 7)) {
733 char *addr;
734 param_list *p;
735
736 for (p = node->params; p && p->param_name; p = p->next) {
737 value_list *v;
738
739 for (v = p->values; v; v = v->next) {
740 if (! strcasecmp (p->param_name, "CN")) {
741 return v->value;
742 }
743 }
744 }
745
746 /* Did not find a CN parameter, so output the address. */
747 addr = node->value + 7;
748
749 /* Skip any leading whitespace. */
750 for ( ; isspace ((unsigned char) *addr); ++addr) { continue; }
751
752 return addr;
753 }
754
755 return "unknown";
756 }
757
758 static char *
format_params(char * line,param_list * p)759 format_params (char *line, param_list *p) {
760 for ( ; p && p->param_name; p = p->next) {
761 value_list *v;
762 size_t num_values = 0;
763
764 for (v = p->values; v; v = v->next) {
765 if (v->value) { ++num_values; }
766 }
767
768 if (num_values) {
769 size_t len = strlen (line);
770
771 line = mh_xrealloc (line, len + 2);
772 line[len] = ';';
773 line[len + 1] = '\0';
774
775 line = add (p->param_name, line);
776
777 for (v = p->values; v; v = v->next) {
778 len = strlen (line);
779 line = mh_xrealloc (line, len + 2);
780 line[len] = v == p->values ? '=' : ',';
781 line[len + 1] = '\0';
782
783 line = add (v->value, line);
784 }
785 }
786 }
787
788 return line;
789 }
790
791 static char *
fold(char * line,int uses_cr)792 fold (char *line, int uses_cr) {
793 size_t remaining = strlen (line);
794 size_t current_line_len = 0;
795 charstring_t folded_line = charstring_create (2 * remaining);
796 const char *cp = line;
797
798 #ifdef MULTIBYTE_SUPPORT
799 if (mbtowc (NULL, NULL, 0)) {} /* reset shift state */
800 #endif
801
802 while (*cp && remaining > 0) {
803 #ifdef MULTIBYTE_SUPPORT
804 int char_len = mbtowc (NULL, cp, (size_t) MB_CUR_MAX < remaining
805 ? (size_t) MB_CUR_MAX
806 : remaining);
807 if (char_len == -1) { char_len = 1; }
808 #else
809 const int char_len = 1;
810 #endif
811
812 charstring_push_back_chars (folded_line, cp, char_len, 1);
813 remaining -= max(char_len, 1);
814
815 /* remaining must be > 0 to pass the loop condition above, so
816 if it's not > 1, it is == 1. */
817 if (++current_line_len >= 75) {
818 if (remaining > 1 || (*(cp+1) != '\0' && *(cp+1) != '\r' &&
819 *(cp+1) != '\n')) {
820 /* fold */
821 if (uses_cr) { charstring_push_back (folded_line, '\r'); }
822 charstring_push_back (folded_line, '\n');
823 charstring_push_back (folded_line, ' ');
824 current_line_len = 0;
825 }
826 }
827
828 cp += max(char_len, 1);
829 }
830
831 free (line);
832 line = charstring_buffer_copy (folded_line);
833 charstring_free (folded_line);
834
835 return line;
836 }
837