4 * Functions which deal with the fetching and displaying of messages.
14 #include <sys/types.h>
16 #include <sys/socket.h>
18 #include <netinet/in.h>
33 #include "webserver.h"
36 /* Address book entry (keep it short and sweet, it's just a quickie lookup
37 * which we can use to get to the real meat and bones later)
46 * Look for URL's embedded in a buffer and make them linkable. We use a
47 * target window in order to keep the BBS session in its own window.
63 for (pos = 0; pos < strlen(buf); ++pos) {
64 if (!strncasecmp(&buf[pos], "http://", 7))
66 if (!strncasecmp(&buf[pos], "ftp://", 6))
73 if ((start > 0) && (buf[start - 1] == '<'))
75 if ((start > 0) && (buf[start - 1] == '['))
77 if ((start > 0) && (buf[start - 1] == '('))
79 if ((start > 0) && (buf[start - 1] == '{'))
82 for (pos = strlen(buf); pos > start; --pos) {
83 if ((buf[pos] == ' ') || (buf[pos] == ench))
87 strncpy(urlbuf, &buf[start], end - start);
88 urlbuf[end - start] = 0;
90 strncpy(outbuf, buf, start);
91 sprintf(&outbuf[start], "%cA HREF=%c%s%c TARGET=%c%s%c%c%s%c/A%c",
92 LB, QU, urlbuf, QU, QU, TARGET, QU, RB, urlbuf, LB, RB);
93 strcat(outbuf, &buf[end]);
94 if ( strlen(outbuf) < 250 )
100 * Turn a vCard "n" (name) field into something displayable.
102 void vcard_n_prettyize(char *name)
107 original_name = strdup(name);
108 for (i=0; i<5; ++i) {
109 if (strlen(original_name) > 0) {
110 if (original_name[strlen(original_name)-1] == ' ') {
111 original_name[strlen(original_name)-1] = 0;
113 if (original_name[strlen(original_name)-1] == ';') {
114 original_name[strlen(original_name)-1] = 0;
119 for (i=0; i<strlen(original_name); ++i) {
120 if (original_name[i] == ';') {
124 name[strlen(name)+1] = 0;
125 name[strlen(name)] = original_name[i];
134 /* display_vcard() calls this after parsing the textual vCard into
135 * our 'struct vCard' data object.
136 * This gets called instead of display_parsed_vcard() if we are only looking
137 * to extract the person's name instead of displaying the card.
139 void fetchname_parsed_vcard(struct vCard *v, char *storename) {
142 strcpy(storename, "");
144 name = vcard_get_prop(v, "n", 1, 0, 0);
146 strcpy(storename, name);
147 /* vcard_n_prettyize(storename); */
154 /* display_vcard() calls this after parsing the textual vCard into
155 * our 'struct vCard' data object.
157 * Set 'full' to nonzero to display the full card, otherwise it will only
158 * show a summary line.
160 * This code is a bit ugly, so perhaps an explanation is due: we do this
161 * in two passes through the vCard fields. On the first pass, we process
162 * fields we understand, and then render them in a pretty fashion at the
163 * end. Then we make a second pass, outputting all the fields we don't
164 * understand in a simple two-column name/value format.
166 void display_parsed_vcard(struct vCard *v, int full) {
172 char *thisname, *thisvalue;
173 char firsttoken[SIZ];
176 char displayname[SIZ];
182 strcpy(displayname, "");
190 name = vcard_get_prop(v, "fn", 1, 0, 0);
194 else if (name = vcard_get_prop(v, "n", 1, 0, 0), name != NULL) {
195 strcpy(displayname, name);
196 vcard_n_prettyize(displayname);
197 escputs(displayname);
206 wprintf("<div align=center><table bgcolor=#aaaaaa width=50%%>");
207 for (pass=1; pass<=2; ++pass) {
209 if (v->numprops) for (i=0; i<(v->numprops); ++i) {
211 thisname = strdup(v->prop[i].name);
212 extract_token(firsttoken, thisname, 0, ';', sizeof firsttoken);
214 for (j=0; j<num_tokens(thisname, ';'); ++j) {
215 extract_token(buf, thisname, j, ';', sizeof buf);
216 if (!strcasecmp(buf, "encoding=quoted-printable")) {
218 remove_token(thisname, j, ';');
220 if (!strcasecmp(buf, "encoding=base64")) {
222 remove_token(thisname, j, ';');
227 thisvalue = malloc(strlen(v->prop[i].value) + 50);
228 j = CtdlDecodeQuotedPrintable(
229 thisvalue, v->prop[i].value,
230 strlen(v->prop[i].value) );
234 thisvalue = malloc(strlen(v->prop[i].value) + 50);
236 thisvalue, v->prop[i].value,
237 strlen(v->prop[i].value) );
240 thisvalue = strdup(v->prop[i].value);
243 /*** Various fields we may encounter ***/
245 /* N is name, but only if there's no FN already there */
246 if (!strcasecmp(firsttoken, "n")) {
247 if (strlen(displayname) == 0) {
248 strcpy(displayname, thisvalue);
249 vcard_n_prettyize(displayname);
253 /* FN (full name) is a true 'display name' field */
254 else if (!strcasecmp(firsttoken, "fn")) {
255 strcpy(displayname, thisvalue);
259 else if (!strcasecmp(firsttoken, "title")) {
260 strcpy(title, thisvalue);
264 else if (!strcasecmp(firsttoken, "org")) {
265 strcpy(org, thisvalue);
268 else if (!strcasecmp(firsttoken, "email")) {
269 if (strlen(mailto) > 0) strcat(mailto, "<br />");
271 "<A HREF=\"/display_enter"
272 "?force_room=_MAIL_&recp=");
273 urlesc(&mailto[strlen(mailto)], thisvalue);
274 strcat(mailto, "\">");
275 urlesc(&mailto[strlen(mailto)], thisvalue);
276 strcat(mailto, "</A>");
278 else if (!strcasecmp(firsttoken, "tel")) {
279 if (strlen(phone) > 0) strcat(phone, "<br />");
280 strcat(phone, thisvalue);
281 for (j=0; j<num_tokens(thisname, ';'); ++j) {
282 extract_token(buf, thisname, j, ';', sizeof buf);
283 if (!strcasecmp(buf, "tel"))
285 else if (!strcasecmp(buf, "work"))
286 strcat(phone, " (work)");
287 else if (!strcasecmp(buf, "home"))
288 strcat(phone, " (home)");
289 else if (!strcasecmp(buf, "cell"))
290 strcat(phone, " (cell)");
298 else if (!strcasecmp(firsttoken, "adr")) {
300 wprintf("<TR><TD>Address:</TD><TD>");
301 for (j=0; j<num_tokens(thisvalue, ';'); ++j) {
302 extract_token(buf, thisvalue, j, ';', sizeof buf);
303 if (strlen(buf) > 0) {
305 if (j<3) wprintf("<br />");
309 wprintf("</TD></TR>\n");
312 else if (!strcasecmp(firsttoken, "version")) {
315 else if (!strcasecmp(firsttoken, "rev")) {
318 else if (!strcasecmp(firsttoken, "label")) {
323 /*** Don't show extra fields. They're ugly.
327 wprintf("</TD><TD>");
329 wprintf("</TD></TR>\n");
339 wprintf("<TR BGCOLOR=\"#AAAAAA\">"
340 "<TD COLSPAN=2 BGCOLOR=\"#FFFFFF\">"
341 "<IMG ALIGN=CENTER SRC=\"/static/viewcontacts_48x.gif\">"
342 "<FONT SIZE=+1><B>");
343 escputs(displayname);
344 wprintf("</B></FONT>");
345 if (strlen(title) > 0) {
346 wprintf("<div align=right>");
350 if (strlen(org) > 0) {
351 wprintf("<div align=right>");
355 wprintf("</TD></TR>\n");
357 if (strlen(phone) > 0)
358 wprintf("<TR><TD>Telephone:</TD><TD>%s</TD></TR>\n", phone);
359 if (strlen(mailto) > 0)
360 wprintf("<TR><TD>E-mail:</TD><TD>%s</TD></TR>\n", mailto);
365 wprintf("</table></div>\n");
371 * Display a textual vCard
372 * (Converts to a vCard object and then calls the actual display function)
373 * Set 'full' to nonzero to display the whole card instead of a one-liner.
374 * Or, if "storename" is non-NULL, just store the person's name in that
375 * buffer instead of displaying the card at all.
377 void display_vcard(char *vcard_source, char alpha, int full, char *storename) {
383 v = vcard_load(vcard_source);
384 if (v == NULL) return;
386 name = vcard_get_prop(v, "n", 1, 0, 0);
392 if (storename != NULL) {
393 fetchname_parsed_vcard(v, storename);
395 else if ( (alpha == 0)
396 || ((isalpha(alpha)) && (tolower(alpha) == tolower(this_alpha)) )
397 || ((!isalpha(alpha)) && (!isalpha(this_alpha)))
399 display_parsed_vcard(v, full);
409 * I wanna SEE that message!
411 void read_message(long msgnum) {
413 char mime_partnum[256];
414 char mime_filename[256];
415 char mime_content_type[256];
416 char mime_charset[256];
417 char mime_disposition[256];
430 char vcard_partnum[256];
431 char cal_partnum[256];
432 char *part_source = NULL;
434 iconv_t ic = (iconv_t)(-1) ;
435 char *ibuf; /* Buffer of characters to be converted */
436 char *obuf; /* Buffer for converted characters */
437 size_t ibuflen; /* Length of input buffer */
438 size_t obuflen; /* Length of output buffer */
439 char *osav; /* Saved pointer to output buffer */
445 strcpy(reply_to, "");
446 strcpy(vcard_partnum, "");
447 strcpy(cal_partnum, "");
448 strcpy(mime_http, "");
449 strcpy(mime_content_type, "text/plain");
450 strcpy(mime_charset, "us-ascii");
452 serv_printf("MSG4 %ld", msgnum);
453 serv_getln(buf, sizeof buf);
455 wprintf("<STRONG>ERROR:</STRONG> %s<br />\n", &buf[4]);
459 /* begin everythingamundo table */
460 wprintf("<div id=\"fix_scrollbar_bug\">\n");
461 wprintf("<table width=100%% border=1 cellspacing=0 "
462 "cellpadding=0><TR><TD>\n");
464 /* begin message header table */
465 wprintf("<TABLE WIDTH=100%% BORDER=0 CELLSPACING=0 "
466 "CELLPADDING=1 BGCOLOR=\"#CCCCCC\"><TR><TD>\n");
468 wprintf("<SPAN CLASS=\"message_header\">");
469 strcpy(m_subject, "");
471 while (serv_getln(buf, sizeof buf), strcasecmp(buf, "text")) {
472 if (!strcmp(buf, "000")) {
473 wprintf("<I>unexpected end of message</I><br /><br />\n");
474 wprintf("</SPAN>\n");
477 if (!strncasecmp(buf, "nhdr=yes", 8))
481 if (!strncasecmp(buf, "type=", 5))
482 format_type = atoi(&buf[5]);
483 if (!strncasecmp(buf, "from=", 5)) {
484 strcpy(from, &buf[5]);
485 wprintf("from <A HREF=\"/showuser&who=");
491 if (!strncasecmp(buf, "subj=", 5))
492 strcpy(m_subject, &buf[5]);
493 if ((!strncasecmp(buf, "hnod=", 5))
494 && (strcasecmp(&buf[5], serv_info.serv_humannode)))
495 wprintf("(%s) ", &buf[5]);
496 if ((!strncasecmp(buf, "room=", 5))
497 && (strcasecmp(&buf[5], WC->wc_roomname))
498 && (strlen(&buf[5])>0) )
499 wprintf("in %s> ", &buf[5]);
500 if (!strncasecmp(buf, "rfca=", 5)) {
501 strcpy(rfca, &buf[5]);
507 if (!strncasecmp(buf, "node=", 5)) {
508 strcpy(node, &buf[5]);
509 if ( ((WC->room_flags & QR_NETWORK)
510 || ((strcasecmp(&buf[5], serv_info.serv_nodename)
511 && (strcasecmp(&buf[5], serv_info.serv_fqdn)))))
514 wprintf("@%s ", &buf[5]);
517 if (!strncasecmp(buf, "rcpt=", 5))
518 wprintf("to %s ", &buf[5]);
519 if (!strncasecmp(buf, "time=", 5)) {
520 fmt_date(now, atol(&buf[5]), 0);
524 if (!strncasecmp(buf, "part=", 5)) {
525 extract_token(mime_filename, &buf[5], 1, '|', sizeof mime_filename);
526 extract_token(mime_partnum, &buf[5], 2, '|', sizeof mime_partnum);
527 extract_token(mime_disposition, &buf[5], 3, '|', sizeof mime_disposition);
528 extract_token(mime_content_type, &buf[5], 4, '|', sizeof mime_content_type);
529 mime_length = extract_int(&buf[5], 5);
531 if (!strcasecmp(mime_disposition, "attachment")) {
532 snprintf(&mime_http[strlen(mime_http)],
533 (sizeof(mime_http) - strlen(mime_http) - 1),
534 "<A HREF=\"/output_mimepart?"
535 "msgnum=%ld&partnum=%s\" "
536 "TARGET=\"wc.%ld.%s\">"
537 "<IMG SRC=\"/static/diskette_24x.gif\" "
538 "BORDER=0 ALIGN=MIDDLE>\n"
539 "Part %s: %s (%s, %d bytes)</A><br />\n",
540 msgnum, mime_partnum,
541 msgnum, mime_partnum,
542 mime_partnum, mime_filename,
543 mime_content_type, mime_length);
546 if ((!strcasecmp(mime_disposition, "inline"))
547 && (!strncasecmp(mime_content_type, "image/", 6)) ){
548 snprintf(&mime_http[strlen(mime_http)],
549 (sizeof(mime_http) - strlen(mime_http) - 1),
550 "<IMG SRC=\"/output_mimepart?"
551 "msgnum=%ld&partnum=%s\">",
552 msgnum, mime_partnum);
555 /*** begin handler prep ***/
556 if (!strcasecmp(mime_content_type, "text/x-vcard")) {
557 strcpy(vcard_partnum, mime_partnum);
560 if (!strcasecmp(mime_content_type, "text/calendar")) {
561 strcpy(cal_partnum, mime_partnum);
564 /*** end handler prep ***/
570 /* Generate a reply-to address */
571 if (strlen(rfca) > 0) {
572 strcpy(reply_to, rfca);
575 if ( (strlen(node) > 0)
576 && (strcasecmp(node, serv_info.serv_nodename))
577 && (strcasecmp(node, serv_info.serv_humannode)) ) {
578 snprintf(reply_to, sizeof(reply_to), "%s @ %s",
582 snprintf(reply_to, sizeof(reply_to), "%s", from);
591 if (strlen(m_subject) > 0) {
593 "<SPAN CLASS=\"message_subject\">"
600 /* start msg buttons */
601 wprintf("<TD ALIGN=RIGHT>\n");
604 wprintf("<a href=\"/display_enter?recp=");
605 urlescputs(reply_to);
606 wprintf("&subject=");
607 if (strncasecmp(m_subject, "Re:", 3)) wprintf("Re:%20");
608 urlescputs(m_subject);
609 wprintf("\">[Reply]</a> ");
611 if (WC->is_room_aide) {
614 wprintf("<a href=\"/confirm_move_msg?msgid=%ld\">[Move]</a> ",
618 wprintf("<a href=\"/delete_msg?msgid=%ld\" "
619 "onClick=\"return confirm('Delete this message?');\">"
620 "[Delete]</a>", msgnum);
624 wprintf("</TD></TR></TABLE>\n");
627 wprintf("<TABLE BORDER=0 WIDTH=100%% BGCOLOR=#FFFFFF "
628 "CELLPADDING=1 CELLSPACING=0><TR><TD>");
631 * Learn the content type
633 strcpy(mime_content_type, "text/plain");
634 while (serv_getln(buf, sizeof buf), (strlen(buf) > 0)) {
635 if (!strcmp(buf, "000")) {
636 wprintf("<I>unexpected end of message</I><br /><br />\n");
639 if (!strncasecmp(buf, "Content-type: ", 14)) {
640 safestrncpy(mime_content_type, &buf[14],
641 sizeof(mime_content_type));
642 for (i=0; i<strlen(mime_content_type); ++i) {
643 if (!strncasecmp(&mime_content_type[i], "charset=", 8)) {
644 safestrncpy(mime_charset, &mime_content_type[i+8],
645 sizeof mime_charset);
648 for (i=0; i<strlen(mime_content_type); ++i) {
649 if (mime_content_type[i] == ';') {
650 mime_content_type[i] = 0;
656 /* Set up a character set conversion if we need to (and if we can) */
658 if ( (strcasecmp(mime_charset, "us-ascii"))
659 && (strcasecmp(mime_charset, "UTF-8")) ) {
660 ic = iconv_open("UTF-8", mime_charset);
661 if (ic == (iconv_t)(-1) ) {
662 lprintf(5, "iconv_open() failed: %s\n", strerror(errno));
667 /* Messages in legacy Citadel variformat get handled thusly... */
668 if (!strcasecmp(mime_content_type, "text/x-citadel-variformat")) {
669 fmout(NULL, "JUSTIFY");
672 /* Boring old 80-column fixed format text gets handled this way... */
673 else if (!strcasecmp(mime_content_type, "text/plain")) {
674 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
675 if (buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = 0;
676 if (buf[strlen(buf)-1] == '\r') buf[strlen(buf)-1] = 0;
679 if (ic != (iconv_t)(-1) ) {
681 ibuflen = strlen(ibuf);
683 obuf = (char *) malloc(obuflen);
685 iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
686 osav[SIZ-obuflen] = 0;
687 safestrncpy(buf, osav, sizeof buf);
692 while ((strlen(buf) > 0) && (isspace(buf[strlen(buf) - 1])))
693 buf[strlen(buf) - 1] = 0;
695 ((!strncmp(buf, ">", 1)) || (!strncmp(buf, " >", 2)) || (!strncmp(buf, " :-)", 4)))) {
696 wprintf("<BLOCKQUOTE>");
698 } else if ((bq == 1) &&
699 (strncmp(buf, ">", 1)) && (strncmp(buf, " >", 2)) && (strncmp(buf, " :-)", 4))) {
700 wprintf("</BLOCKQUOTE>");
706 wprintf("</TT><br />\n");
708 wprintf("</I><br />");
711 else /* HTML is fun, but we've got to strip it first */
712 if (!strcasecmp(mime_content_type, "text/html")) {
716 /* Unknown weirdness */
718 wprintf("I don't know how to display %s<br />\n",
720 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) { }
723 /* Afterwards, offer links to download attachments 'n' such */
724 if (strlen(mime_http) > 0) {
725 wprintf("%s", mime_http);
728 /* Handler for vCard parts */
729 if (strlen(vcard_partnum) > 0) {
730 part_source = load_mimepart(msgnum, vcard_partnum);
731 if (part_source != NULL) {
733 /* If it's my vCard I can edit it */
734 if ( (!strcasecmp(WC->wc_roomname, USERCONFIGROOM))
735 || (!strcasecmp(&WC->wc_roomname[11], USERCONFIGROOM))
736 || (WC->wc_view == VIEW_ADDRESSBOOK)
738 wprintf("<A HREF=\"/edit_vcard?"
739 "msgnum=%ld&partnum=%s\">",
740 msgnum, vcard_partnum);
741 wprintf("[edit]</A>");
744 /* In all cases, display the full card */
745 display_vcard(part_source, 0, 1, NULL);
749 /* Handler for calendar parts */
750 if (strlen(cal_partnum) > 0) {
751 part_source = load_mimepart(msgnum, cal_partnum);
752 if (part_source != NULL) {
753 cal_process_attachment(part_source,
754 msgnum, cal_partnum);
764 wprintf("</TD></TR></TABLE>\n");
766 /* end everythingamundo table */
767 wprintf("</TD></TR></TABLE>\n");
768 wprintf("</div><br />\n");
771 if (ic != (iconv_t)(-1) ) {
778 void summarize_message(long msgnum, int is_new) {
789 memset(&summ, 0, sizeof(summ));
790 strcpy(summ.subj, "(no subject)");
792 /* ask for headers only with no MIME */
793 sprintf(buf, "MSG0 %ld|3", msgnum);
795 serv_getln(buf, sizeof buf);
796 if (buf[0] != '1') return;
798 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
799 if (!strncasecmp(buf, "from=", 5)) {
800 strcpy(summ.from, &buf[5]);
802 if (!strncasecmp(buf, "subj=", 5)) {
803 if (strlen(&buf[5]) > 0) {
804 strcpy(summ.subj, &buf[5]);
805 if (strlen(summ.subj) > 75) {
806 strcpy(&summ.subj[72], "...");
810 /* if (!strncasecmp(buf, "rfca=", 5)) {
811 strcat(summ.from, " <");
812 strcat(summ.from, &buf[5]);
813 strcat(summ.from, ">");
816 if (!strncasecmp(buf, "node=", 5)) {
817 if ( ((WC->room_flags & QR_NETWORK)
818 || ((strcasecmp(&buf[5], serv_info.serv_nodename)
819 && (strcasecmp(&buf[5], serv_info.serv_fqdn)))))
821 strcat(summ.from, " @ ");
822 strcat(summ.from, &buf[5]);
826 if (!strncasecmp(buf, "rcpt=", 5)) {
827 strcpy(summ.to, &buf[5]);
830 if (!strncasecmp(buf, "time=", 5)) {
831 fmt_date(summ.date, atol(&buf[5]), 1); /* brief */
835 if (strlen(summ.from) > 25) {
836 strcpy(&summ.from[22], "...");
840 if (is_new) wprintf("<B>");
841 wprintf("<A HREF=\"/readfwd?startmsg=%ld"
842 "&maxmsgs=1&summary=0\">",
846 if (is_new) wprintf("</B>");
847 wprintf("</TD><TD>");
848 if (is_new) wprintf("<B>");
850 if (is_new) wprintf("</B>");
851 wprintf(" </TD><TD>");
852 if (is_new) wprintf("<B>");
854 if (is_new) wprintf("</B>");
857 "<INPUT TYPE=\"checkbox\" NAME=\"msg_%ld\" VALUE=\"yes\">"
865 void display_addressbook(long msgnum, char alpha) {
867 char mime_partnum[SIZ];
868 char mime_filename[SIZ];
869 char mime_content_type[SIZ];
870 char mime_disposition[SIZ];
872 char vcard_partnum[SIZ];
873 char *vcard_source = NULL;
883 memset(&summ, 0, sizeof(summ));
884 strcpy(summ.subj, "(no subject)");
886 sprintf(buf, "MSG0 %ld|1", msgnum); /* ask for headers only */
888 serv_getln(buf, sizeof buf);
889 if (buf[0] != '1') return;
891 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
892 if (!strncasecmp(buf, "part=", 5)) {
893 extract_token(mime_filename, &buf[5], 1, '|', sizeof mime_filename);
894 extract_token(mime_partnum, &buf[5], 2, '|', sizeof mime_partnum);
895 extract_token(mime_disposition, &buf[5], 3, '|', sizeof mime_disposition);
896 extract_token(mime_content_type, &buf[5], 4, '|', sizeof mime_content_type);
897 mime_length = extract_int(&buf[5], 5);
899 if (!strcasecmp(mime_content_type, "text/x-vcard")) {
900 strcpy(vcard_partnum, mime_partnum);
906 if (strlen(vcard_partnum) > 0) {
907 vcard_source = load_mimepart(msgnum, vcard_partnum);
908 if (vcard_source != NULL) {
910 /* Display the summary line */
911 display_vcard(vcard_source, alpha, 0, NULL);
913 /* If it's my vCard I can edit it */
914 if ( (!strcasecmp(WC->wc_roomname, USERCONFIGROOM))
915 || (!strcasecmp(&WC->wc_roomname[11], USERCONFIGROOM))
916 || (WC->wc_view == VIEW_ADDRESSBOOK)
918 wprintf("<A HREF=\"/edit_vcard?"
919 "msgnum=%ld&partnum=%s\">",
920 msgnum, vcard_partnum);
921 wprintf("[edit]</A>");
932 /* If it's an old "Firstname Lastname" style record, try to
935 void lastfirst_firstlast(char *namebuf) {
940 if (namebuf == NULL) return;
941 if (strchr(namebuf, ';') != NULL) return;
943 i = num_tokens(namebuf, ' ');
946 extract_token(lastname, namebuf, i-1, ' ', sizeof lastname);
947 remove_token(namebuf, i-1, ' ');
948 strcpy(firstname, namebuf);
949 sprintf(namebuf, "%s; %s", lastname, firstname);
953 void fetch_ab_name(long msgnum, char *namebuf) {
955 char mime_partnum[SIZ];
956 char mime_filename[SIZ];
957 char mime_content_type[SIZ];
958 char mime_disposition[SIZ];
960 char vcard_partnum[SIZ];
961 char *vcard_source = NULL;
972 if (namebuf == NULL) return;
975 memset(&summ, 0, sizeof(summ));
976 strcpy(summ.subj, "(no subject)");
978 sprintf(buf, "MSG0 %ld|1", msgnum); /* ask for headers only */
980 serv_getln(buf, sizeof buf);
981 if (buf[0] != '1') return;
983 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
984 if (!strncasecmp(buf, "part=", 5)) {
985 extract_token(mime_filename, &buf[5], 1, '|', sizeof mime_filename);
986 extract_token(mime_partnum, &buf[5], 2, '|', sizeof mime_partnum);
987 extract_token(mime_disposition, &buf[5], 3, '|', sizeof mime_disposition);
988 extract_token(mime_content_type, &buf[5], 4, '|', sizeof mime_content_type);
989 mime_length = extract_int(&buf[5], 5);
991 if (!strcasecmp(mime_content_type, "text/x-vcard")) {
992 strcpy(vcard_partnum, mime_partnum);
998 if (strlen(vcard_partnum) > 0) {
999 vcard_source = load_mimepart(msgnum, vcard_partnum);
1000 if (vcard_source != NULL) {
1002 /* Grab the name off the card */
1003 display_vcard(vcard_source, 0, 0, namebuf);
1009 lastfirst_firstlast(namebuf);
1011 for (i=0; i<strlen(namebuf); ++i) {
1012 if (namebuf[i] != ';') return;
1014 strcpy(namebuf, "(no name)");
1020 * Record compare function for sorting address book indices
1022 int abcmp(const void *ab1, const void *ab2) {
1024 (((const struct addrbookent *)ab1)->ab_name),
1025 (((const struct addrbookent *)ab2)->ab_name)
1031 * Helper function for do_addrbook_view()
1032 * Converts a name into a three-letter tab label
1034 void nametab(char *tabbuf, char *name) {
1035 stresc(tabbuf, name, 0, 0);
1036 tabbuf[0] = toupper(tabbuf[0]);
1037 tabbuf[1] = tolower(tabbuf[1]);
1038 tabbuf[2] = tolower(tabbuf[2]);
1044 * Render the address book using info we gathered during the scan
1046 void do_addrbook_view(struct addrbookent *addrbook, int num_ab) {
1050 static int NAMESPERPAGE = 60;
1054 char tabfirst_label[SIZ];
1056 char tablast_label[SIZ];
1059 wprintf("<I>This address book is empty.</I>\n");
1064 qsort(addrbook, num_ab, sizeof(struct addrbookent), abcmp);
1067 num_pages = num_ab / NAMESPERPAGE;
1069 page = atoi(bstr("page"));
1072 for (i=0; i<=num_pages; ++i) {
1074 wprintf("<A HREF=\"/readfwd?page=%d\">", i);
1079 tabfirst = i * NAMESPERPAGE;
1080 tablast = tabfirst + NAMESPERPAGE - 1;
1081 if (tablast > (num_ab - 1)) tablast = (num_ab - 1);
1082 nametab(tabfirst_label, addrbook[tabfirst].ab_name);
1083 nametab(tablast_label, addrbook[tablast].ab_name);
1084 wprintf("[%s - %s]",
1085 tabfirst_label, tablast_label
1094 wprintf("<br />\n");
1096 wprintf("<TABLE border=0 cellspacing=0 "
1097 "cellpadding=3 width=100%%>\n"
1100 for (i=0; i<num_ab; ++i) {
1102 if ((i / NAMESPERPAGE) == page) {
1104 if ((displayed % 4) == 0) {
1105 if (displayed > 0) {
1109 wprintf("<TR BGCOLOR=\"#%s\">",
1110 (bg ? "DDDDDD" : "FFFFFF")
1116 wprintf("<A HREF=\"/readfwd?startmsg=%ld&is_singlecard=1",
1117 addrbook[i].ab_msgnum);
1118 wprintf("&maxmsgs=1&summary=0&alpha=%s\">", bstr("alpha"));
1119 vcard_n_prettyize(addrbook[i].ab_name);
1120 escputs(addrbook[i].ab_name);
1121 wprintf("</A></TD>\n");
1126 wprintf("</TR></TABLE>\n");
1132 * load message pointers from the server
1134 int load_msg_ptrs(char *servcmd)
1141 maxload = sizeof(WC->msgarr) / sizeof(long) ;
1143 serv_getln(buf, sizeof buf);
1144 if (buf[0] != '1') {
1145 wprintf("<EM>%s</EM><br />\n", &buf[4]);
1148 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
1149 if (nummsgs < maxload) {
1150 WC->msgarr[nummsgs] = atol(buf);
1159 * command loop for reading messages
1161 void readloop(char *oper)
1171 int num_displayed = 0;
1173 int is_addressbook = 0;
1174 int is_singlecard = 0;
1175 int is_calendar = 0;
1178 int remaining_messages;
1180 int lowest_displayed = (-1);
1181 int highest_displayed = 0;
1182 long pn_previous = 0L;
1183 long pn_current = 0L;
1186 struct addrbookent *addrbook = NULL;
1189 startmsg = atol(bstr("startmsg"));
1190 maxmsgs = atoi(bstr("maxmsgs"));
1191 is_summary = atoi(bstr("summary"));
1192 if (maxmsgs == 0) maxmsgs = DEFAULT_MAXMSGS;
1194 output_headers(1, 1, 1, 0, 0, 0, 0);
1196 /* When in summary mode, always show ALL messages instead of just
1197 * new or old. Otherwise, show what the user asked for.
1199 if (!strcmp(oper, "readnew")) {
1200 strcpy(cmd, "MSGS NEW");
1202 else if (!strcmp(oper, "readold")) {
1203 strcpy(cmd, "MSGS OLD");
1206 strcpy(cmd, "MSGS ALL");
1209 if ((WC->wc_view == VIEW_MAILBOX) && (maxmsgs > 1)) {
1211 strcpy(cmd, "MSGS ALL");
1214 if ((WC->wc_view == VIEW_ADDRESSBOOK) && (maxmsgs > 1)) {
1216 strcpy(cmd, "MSGS ALL");
1221 strcpy(cmd, "MSGS ALL");
1224 /* Are we doing a summary view? If so, we need to know old messages
1225 * and new messages, so we can do that pretty boldface thing for the
1228 strcpy(old_msgs, "");
1231 serv_getln(buf, sizeof buf);
1232 if (buf[0] == '2') {
1233 strcpy(old_msgs, &buf[4]);
1237 is_singlecard = atoi(bstr("is_singlecard"));
1239 if (WC->wc_view == VIEW_CALENDAR) { /* calendar */
1241 strcpy(cmd, "MSGS ALL");
1244 if (WC->wc_view == VIEW_TASKS) { /* tasks */
1246 strcpy(cmd, "MSGS ALL");
1249 if (WC->wc_view == VIEW_NOTES) { /* notes */
1251 strcpy(cmd, "MSGS ALL");
1255 nummsgs = load_msg_ptrs(cmd);
1258 if ((!is_tasks) && (!is_calendar) && (!is_notes)) {
1259 if (!strcmp(oper, "readnew")) {
1260 wprintf("<EM>No new messages.</EM>\n");
1261 } else if (!strcmp(oper, "readold")) {
1262 wprintf("<EM>No old messages.</EM>\n");
1264 wprintf("<EM>No messages here.</EM>\n");
1271 if (startmsg == 0L) startmsg = WC->msgarr[0];
1272 remaining_messages = 0;
1274 for (a = 0; a < nummsgs; ++a) {
1275 if (WC->msgarr[a] >= startmsg) {
1276 ++remaining_messages;
1280 wprintf("<form name=\"msgomatic\" "
1281 "METHOD=\"POST\" ACTION=\"/do_stuff_to_msgs\">\n");
1283 wprintf("<div id=\"fix_scrollbar_bug\">"
1284 "<table border=0 cellspacing=0 "
1285 "cellpadding=0 width=100%%>\n"
1287 "<TD align=center><b><i>Subject</i></b></TD>"
1288 "<TD align=center><b><i>Sender</i></b></TD>"
1289 "<TD align=center><b><i>Date</i></b></TD>"
1295 for (a = 0; a < nummsgs; ++a) {
1296 if ((WC->msgarr[a] >= startmsg) && (num_displayed < maxmsgs)) {
1298 /* Are you a new message, or an old message? */
1301 if (is_msg_in_mset(old_msgs, WC->msgarr[a])) {
1309 /* Learn which msgs "Prev" & "Next" buttons go to */
1310 pn_current = WC->msgarr[a];
1311 if (a > 0) pn_previous = WC->msgarr[a-1];
1312 if (a < (nummsgs-1)) pn_next = WC->msgarr[a+1];
1314 /* If a tabular view, set up the line */
1317 wprintf("<TR BGCOLOR=\"#%s\">",
1318 (bg ? "DDDDDD" : "FFFFFF")
1322 /* Display the message */
1324 summarize_message(WC->msgarr[a], is_new);
1326 else if (is_addressbook) {
1327 fetch_ab_name(WC->msgarr[a], buf);
1329 addrbook = realloc(addrbook,
1330 (sizeof(struct addrbookent) * num_ab) );
1331 safestrncpy(addrbook[num_ab-1].ab_name, buf,
1332 sizeof(addrbook[num_ab-1].ab_name));
1333 addrbook[num_ab-1].ab_msgnum = WC->msgarr[a];
1335 else if (is_calendar) {
1336 display_calendar(WC->msgarr[a]);
1338 else if (is_tasks) {
1339 display_task(WC->msgarr[a]);
1341 else if (is_notes) {
1342 display_note(WC->msgarr[a]);
1345 read_message(WC->msgarr[a]);
1348 /* If a tabular view, finish the line */
1353 if (lowest_displayed < 0) lowest_displayed = a;
1354 highest_displayed = a;
1357 --remaining_messages;
1362 wprintf("</table></div>\n");
1365 /* Bump these because although we're thinking in zero base, the user
1366 * is a drooling idiot and is thinking in one base.
1369 ++highest_displayed;
1371 /* If we're only looking at one message, do a prev/next thing */
1372 if (num_displayed == 1) {
1373 if ((!is_tasks) && (!is_calendar) && (!is_addressbook) && (!is_notes) && (!is_singlecard)) {
1375 wprintf("<div id=\"fix_scrollbar_bug\">"
1376 "<table border=0 width=100%% bgcolor=\"#dddddd\"><tr><td>"
1377 "Reading #%d of %d messages.</TD>\n"
1378 "<TD ALIGN=RIGHT><FONT SIZE=+1>",
1379 lowest_displayed, nummsgs);
1382 wprintf("<INPUT TYPE=\"submit\" NAME=\"sc\" "
1383 "VALUE=\"Delete selected\">\n");
1386 if (pn_previous > 0L) {
1387 wprintf("<A HREF=\"/%s"
1397 wprintf("<A HREF=\"/%s"
1406 wprintf("<A HREF=\"/%s?startmsg=%ld"
1407 "&maxmsgs=%d&summary=1\">"
1415 wprintf("</td></tr></table></div>\n");
1420 * If we're not currently looking at ALL requested
1421 * messages, then display the selector bar
1423 if (num_displayed > 1) {
1424 if ((!is_tasks) && (!is_calendar) && (!is_addressbook)
1425 && (!is_notes) && (!is_singlecard)) {
1427 wprintf("Reading #", lowest_displayed, highest_displayed);
1429 wprintf("<SELECT NAME=\"whichones\" SIZE=\"1\" "
1430 "OnChange=\"location.href=msgomatic.whichones.options"
1431 "[selectedIndex].value\">\n");
1433 for (b=0; b<nummsgs; b = b + maxmsgs) {
1436 if (hi > nummsgs) hi = nummsgs;
1437 wprintf("<OPTION %s VALUE="
1442 "%d-%d</OPTION> \n",
1443 ((WC->msgarr[b] == startmsg) ? "SELECTED" : ""),
1450 wprintf("<OPTION VALUE=\"/%s?startmsg=%ld"
1451 "&maxmsgs=9999999&summary=%d\">"
1455 WC->msgarr[0], is_summary);
1457 wprintf("</SELECT> of %d messages.", nummsgs);
1460 wprintf("<INPUT TYPE=\"submit\" NAME=\"sc\" "
1461 "VALUE=\"Delete selected\">\n");
1466 wprintf("</form>\n");
1470 do_tasks_view(); /* Render the task list */
1474 do_calendar_view(); /* Render the calendar */
1477 if (is_addressbook) {
1478 do_addrbook_view(addrbook, num_ab); /* Render the address book */
1482 if (addrbook != NULL) free(addrbook);
1484 /* If we got here via a mailbox view and are reading a single
1485 * message, mark it as "seen." We do this after rendering the web page
1486 * so it doesn't keep the user waiting.
1488 if ( (maxmsgs == 1) && (WC->wc_view == VIEW_MAILBOX) ) {
1489 serv_printf("SEEN %ld|1", startmsg);
1490 serv_getln(buf, sizeof buf);
1496 * Back end for post_message() ... this is where the actual message
1497 * gets transmitted to the server.
1499 void post_mime_to_server(void) {
1501 int is_multipart = 0;
1503 struct wc_attachment *att;
1505 size_t encoded_length;
1507 /* If there are attachments, we have to do multipart/mixed */
1508 if (WC->first_attachment != NULL) {
1513 sprintf(boundary, "---Citadel-Multipart-%s-%04x%04x---",
1514 serv_info.serv_fqdn,
1519 /* Remember, serv_printf() appends an extra newline */
1520 serv_printf("Content-type: multipart/mixed; "
1521 "boundary=\"%s\"\n", boundary);
1522 serv_printf("This is a multipart message in MIME format.\n");
1523 serv_printf("--%s", boundary);
1526 serv_puts("Content-type: text/html; charset=utf-8");
1528 serv_puts("<HTML><BODY>\n");
1529 text_to_server(bstr("msgtext"), 0);
1530 serv_puts("</BODY></HTML>\n");
1535 /* Add in the attachments */
1536 for (att = WC->first_attachment; att!=NULL; att=att->next) {
1538 encoded_length = ((att->length * 150) / 100);
1539 encoded = malloc(encoded_length);
1540 if (encoded == NULL) break;
1541 CtdlEncodeBase64(encoded, att->data, att->length);
1543 serv_printf("--%s", boundary);
1544 serv_printf("Content-type: %s", att->content_type);
1545 serv_printf("Content-disposition: attachment; "
1546 "filename=\"%s\"", att->filename);
1547 serv_puts("Content-transfer-encoding: base64");
1549 serv_write(encoded, strlen(encoded));
1554 serv_printf("--%s--", boundary);
1562 * Post message (or don't post message)
1564 * Note regarding the "dont_post" variable:
1565 * A random value (actually, it's just a timestamp) is inserted as a hidden
1566 * field called "postseq" when the display_enter page is generated. This
1567 * value is checked when posting, using the static variable dont_post. If a
1568 * user attempts to post twice using the same dont_post value, the message is
1569 * discarded. This prevents the accidental double-saving of the same message
1570 * if the user happens to click the browser "back" button.
1572 void post_message(void)
1575 static long dont_post = (-1L);
1576 struct wc_attachment *att, *aptr;
1578 if (WC->upload_length > 0) {
1580 /* There's an attachment. Save it to this struct... */
1581 att = malloc(sizeof(struct wc_attachment));
1582 memset(att, 0, sizeof(struct wc_attachment));
1583 att->length = WC->upload_length;
1584 strcpy(att->content_type, WC->upload_content_type);
1585 strcpy(att->filename, WC->upload_filename);
1588 /* And add it to the list. */
1589 if (WC->first_attachment == NULL) {
1590 WC->first_attachment = att;
1593 aptr = WC->first_attachment;
1594 while (aptr->next != NULL) aptr = aptr->next;
1598 /* Netscape sends a simple filename, which is what we want,
1599 * but Satan's browser sends an entire pathname. Reduce
1600 * the path to just a filename if we need to.
1602 while (num_tokens(att->filename, '/') > 1) {
1603 remove_token(att->filename, 0, '/');
1605 while (num_tokens(att->filename, '\\') > 1) {
1606 remove_token(att->filename, 0, '\\');
1609 /* Transfer control of this memory from the upload struct
1610 * to the attachment struct.
1612 att->data = WC->upload;
1613 WC->upload_length = 0;
1619 if (!strcasecmp(bstr("sc"), "Cancel")) {
1620 sprintf(WC->ImportantMessage,
1621 "Cancelled. Message was not posted.");
1622 } else if (!strcasecmp(bstr("attach"), "Add")) {
1625 } else if (atol(bstr("postseq")) == dont_post) {
1626 sprintf(WC->ImportantMessage,
1627 "Automatically cancelled because you have already "
1628 "saved this message.");
1630 sprintf(buf, "ENT0 1|%s|0|4|%s",
1634 serv_getln(buf, sizeof buf);
1635 if (buf[0] == '4') {
1636 post_mime_to_server();
1637 if (strlen(bstr("recp")) > 0) {
1638 sprintf(WC->ImportantMessage, "Message has been sent.\n");
1641 sprintf(WC->ImportantMessage, "Message has been posted.\n");
1643 dont_post = atol(bstr("postseq"));
1645 sprintf(WC->ImportantMessage,
1650 free_attachments(WC);
1651 readloop("readnew");
1658 * display the message entry screen
1660 void display_enter(void)
1664 struct wc_attachment *att;
1666 if (strlen(bstr("force_room")) > 0) {
1667 gotoroom(bstr("force_room"));
1670 /* Are we perhaps in an address book view? If so, then an "enter
1671 * message" command really means "add new entry."
1673 if (WC->wc_view == VIEW_ADDRESSBOOK) {
1674 do_edit_vcard(-1, "", "");
1678 #ifdef WEBCIT_WITH_CALENDAR_SERVICE
1679 /* Are we perhaps in a calendar view? If so, then an "enter
1680 * message" command really means "add new calendar item."
1682 if (WC->wc_view == VIEW_CALENDAR) {
1683 display_edit_event();
1687 /* Are we perhaps in a tasks view? If so, then an "enter
1688 * message" command really means "add new task."
1690 if (WC->wc_view == VIEW_TASKS) {
1691 display_edit_task();
1696 /* Otherwise proceed normally. Do a custom room banner with no navbar... */
1697 output_headers(1, 1, 2, 0, 0, 0, 0);
1698 wprintf("<div id=\"banner\">\n");
1699 embed_room_banner(NULL, navbar_none);
1700 wprintf("</div>\n");
1701 wprintf("<div id=\"content\">\n"
1702 "<div id=\"fix_scrollbar_bug\">"
1703 "<table width=100%% border=0 bgcolor=\"#ffffff\"><tr><td>");
1705 sprintf(buf, "ENT0 0|%s|0|0", bstr("recp"));
1707 serv_getln(buf, sizeof buf);
1709 if (!strncmp(buf, "570", 3)) {
1710 if (strlen(bstr("recp")) > 0) {
1711 svprintf("RECPERROR", WCS_STRING,
1712 "<SPAN CLASS=\"errormsg\">%s</SPAN><br />\n",
1716 do_template("prompt_for_recipient");
1719 if (buf[0] != '2') {
1720 wprintf("<EM>%s</EM><br />\n", &buf[4]);
1725 fmt_date(buf, now, 0);
1726 strcat(&buf[strlen(buf)], " <I>from</I> ");
1727 stresc(&buf[strlen(buf)], WC->wc_username, 1, 1);
1728 if (strlen(bstr("recp")) > 0) {
1729 strcat(&buf[strlen(buf)], " <I>to</I> ");
1730 stresc(&buf[strlen(buf)], bstr("recp"), 1, 1);
1732 strcat(&buf[strlen(buf)], " <I>in</I> ");
1733 stresc(&buf[strlen(buf)], WC->wc_roomname, 1, 1);
1735 /* begin message entry screen */
1736 // wprintf("<div style=\"position:absolute; left:1%%; width:96%%; height:100%%\">\n");
1738 wprintf("<form enctype=\"multipart/form-data\" "
1739 "method=\"POST\" action=\"/post\" "
1740 "name=\"enterform\""
1741 "onSubmit=\"return submitForm();\""
1743 wprintf("<input type=\"hidden\" name=\"recp\" value=\"%s\">\n",
1745 wprintf("<input type=\"hidden\" name=\"postseq\" value=\"%ld\">\n",
1748 wprintf("%s<br>\n", buf); /* header bar */
1749 wprintf("<img src=\"static/newmess3_24x.gif\" align=middle alt=\" \">");
1750 /* "onLoad=\"document.enterform.msgtext.focus();\" " */
1751 wprintf("<font size=-1>Subject (optional):</font>"
1752 "<input type=\"text\" name=\"subject\" value=\"");
1753 escputs(bstr("subject"));
1754 wprintf("\" size=40 maxlength=70>"
1758 wprintf("<input type=\"submit\" name=\"sc\" value=\"");
1759 if (strlen(bstr("recp")) > 0) {
1760 wprintf("Send message");
1762 wprintf("Post message");
1765 "<input type=\"submit\" name=\"sc\" value=\"Cancel\">\n");
1767 wprintf("<center><script type=\"text/javascript\" "
1768 "src=\"static/richtext.js\"></script>\n"
1769 "<script type=\"text/javascript\">\n"
1770 "function submitForm() { \n"
1771 " updateRTE('msgtext'); \n"
1775 "initRTE(\"static/\", \"static/\", \"\"); \n"
1777 "<noscript>JavaScript must be enabled.</noscript> \n"
1778 "<script type=\"text/javascript\"> \n"
1779 "writeRichText('msgtext', '");
1780 msgescputs(bstr("msgtext"));
1781 wprintf("', '96%%', '200', true, false); \n"
1782 "</script></center><br />\n");
1784 /* Enumerate any attachments which are already in place... */
1785 wprintf("<img src=\"/static/diskette_24x.gif\" border=0 "
1786 "align=middle height=16 width=16> Attachments: ");
1787 wprintf("<select name=\"which_attachment\" size=1>");
1788 for (att = WC->first_attachment; att != NULL; att = att->next) {
1789 wprintf("<option value=\"");
1790 urlescputs(att->filename);
1792 escputs(att->filename);
1793 /* wprintf(" (%s, %d bytes)",att->content_type,att->length); */
1794 wprintf("</option>\n");
1796 wprintf("</select>");
1798 /* Now offer the ability to attach additional files... */
1799 wprintf(" "
1800 "Attach file: <input NAME=\"attachfile\" "
1801 "SIZE=16 TYPE=\"file\">\n "
1802 "<input type=\"submit\" name=\"attach\" value=\"Add\">\n");
1804 wprintf("</form>\n");
1806 wprintf("</td></tr></table></div>\n");
1807 DONE: wDumpContent(1);
1817 void delete_msg(void)
1822 msgid = atol(bstr("msgid"));
1824 output_headers(1, 1, 1, 0, 0, 0, 0);
1826 sprintf(buf, "DELE %ld", msgid);
1828 serv_getln(buf, sizeof buf);
1829 wprintf("<EM>%s</EM><br />\n", &buf[4]);
1838 * Confirm move of a message
1840 void confirm_move_msg(void)
1846 msgid = atol(bstr("msgid"));
1848 output_headers(1, 1, 1, 0, 0, 0, 0);
1850 wprintf("<div id=\"fix_scrollbar_bug\">"
1851 "<table width=100%% border=0 bgcolor=\"#444455\"><tr><td>");
1852 wprintf("<font size=+1 color=\"#ffffff\"");
1853 wprintf("<b>Confirm move of message</b>\n");
1854 wprintf("</font></td></tr></table></div>\n");
1856 wprintf("<CENTER>");
1858 wprintf("Move this message to:<br />\n");
1860 wprintf("<form METHOD=\"POST\" ACTION=\"/move_msg\">\n");
1861 wprintf("<INPUT TYPE=\"hidden\" NAME=\"msgid\" VALUE=\"%s\">\n",
1865 wprintf("<SELECT NAME=\"target_room\" SIZE=5>\n");
1867 serv_getln(buf, sizeof buf);
1868 if (buf[0] == '1') {
1869 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
1870 extract_token(targ, buf, 0, '|', sizeof targ);
1871 wprintf("<OPTION>");
1876 wprintf("</SELECT>\n");
1877 wprintf("<br />\n");
1879 wprintf("<INPUT TYPE=\"submit\" NAME=\"yesno\" VALUE=\"Move\">");
1881 wprintf("<INPUT TYPE=\"submit\" NAME=\"yesno\" VALUE=\"Cancel\">");
1882 wprintf("</form></CENTER>\n");
1884 wprintf("</CENTER>\n");
1895 msgid = atol(bstr("msgid"));
1897 output_headers(1, 1, 1, 0, 0, 0, 0);
1899 if (!strcasecmp(bstr("yesno"), "Move")) {
1900 sprintf(buf, "MOVE %ld|%s", msgid, bstr("target_room"));
1902 serv_getln(buf, sizeof buf);
1903 wprintf("<EM>%s</EM><br />\n", &buf[4]);
1905 wprintf("<EM>Message not moved.</EM><br />\n");
1912 * This gets called when a user selects multiple messages in a summary
1913 * list and then clicks to perform a transformation of some sort on them
1914 * (such as deleting them).
1916 void do_stuff_to_msgs(void) {
1921 struct stuff_t *next;
1925 struct stuff_t *stuff = NULL;
1926 struct stuff_t *ptr;
1929 serv_puts("MSGS ALL");
1930 serv_getln(buf, sizeof buf);
1932 if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
1933 ptr = malloc(sizeof(struct stuff_t));
1934 ptr->msgnum = atol(buf);
1939 strcpy(sc, bstr("sc"));
1941 while (stuff != NULL) {
1943 sprintf(buf, "msg_%ld", stuff->msgnum);
1944 if (!strcasecmp(bstr(buf), "yes")) {
1946 if (!strcasecmp(sc, "Delete selected")) {
1947 serv_printf("DELE %ld", stuff->msgnum);
1948 serv_getln(buf, sizeof buf);
1958 readloop("readfwd");