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