From 17839b53371b648f803509f2bf78eb4b772c4091 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Wed, 29 May 2024 18:36:34 -0400 Subject: [PATCH 01/16] html_to_ascii() "ansi" parameter is now "flags" parameter. H2A_ANSI is a bit that can be set in that flag bucket to tell the renderer that the terminal supports ANSI escape sequences such as color. --- libcitadel/lib/html_to_ascii.c | 4 +- libcitadel/lib/libcitadel.h | 5 +- textclient/messages.c | 379 +++++++++++++++------------------ 3 files changed, 175 insertions(+), 213 deletions(-) diff --git a/libcitadel/lib/html_to_ascii.c b/libcitadel/lib/html_to_ascii.c index ca2de8df8..42f643863 100644 --- a/libcitadel/lib/html_to_ascii.c +++ b/libcitadel/lib/html_to_ascii.c @@ -25,7 +25,7 @@ // screenwidth = desired output screenwidth // ansi = if nonzero, assume output is to a terminal that supports ANSI escape codes // -char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, int ansi) { +char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned int flags) { char inbuf[SIZ]; int inbuf_len = 0; char outbuf[SIZ]; @@ -43,6 +43,8 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, int ansi) int bytes_processed = 0; char nl[128]; + int ansi = (flags & H2A_ANSI) ? 1 : 0; + tag[0] = '\0'; strcpy(nl, "\n"); inptr = inputmsg; diff --git a/libcitadel/lib/libcitadel.h b/libcitadel/lib/libcitadel.h index 655ebe445..202b309b7 100644 --- a/libcitadel/lib/libcitadel.h +++ b/libcitadel/lib/libcitadel.h @@ -430,10 +430,13 @@ void CtdlMakeTempFileName(char *name, int len); char *rfc2047encode(const char *line, long length); int is_msg_in_mset(const char *mset, long msgnum); int pattern2(char *search, char *patn); -char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, int ansi); void LoadEntityList(char *FileName); void utf8ify_rfc822_string(char *buf); +// flags for html_to_ascii +#define H2A_ANSI 0x01 +char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned int flags); + typedef struct { void *the_elements; diff --git a/textclient/messages.c b/textclient/messages.c index 343d2064f..54779db0e 100644 --- a/textclient/messages.c +++ b/textclient/messages.c @@ -4,7 +4,7 @@ // late 1980s when my coding style was absolute garbage. It // works, but we probably should replace most of it. // -// Copyright (c) 1987-2022 by the citadel.org team +// Copyright (c) 1987-2024 by the citadel.org team // // This program is open source software. Use, duplication, or disclosure is subject to the GNU General Public License version 3. @@ -54,12 +54,12 @@ extern int rc_allow_attachments; extern int rc_display_message_numbers; extern int rc_force_mail_prompts; extern int editor_pid; -extern CtdlIPC *ipc_for_signal_handlers; /* KLUDGE cover your eyes */ +extern CtdlIPC *ipc_for_signal_handlers; // KLUDGE cover your eyes int num_urls = 0; char urls[MAXURLS][SIZ]; char imagecmd[SIZ]; -int has_images = 0; /* Current msg has images */ -struct parts *last_message_parts = NULL; /* Parts from last msg */ +int has_images = 0; // Current msg has images +struct parts *last_message_parts = NULL; // Parts from last msg void ka_sigcatch(int signum) { @@ -69,9 +69,7 @@ void ka_sigcatch(int signum) { } -/* - * server keep-alive version of wait() (needed for external editor) - */ +// server keep-alive version of wait() (needed for external editor) pid_t ka_wait(int *kstatus) { pid_t p; @@ -87,9 +85,7 @@ pid_t ka_wait(int *kstatus) { } -/* - * version of system() that uses ka_wait() - */ +// version of system() that uses ka_wait() int ka_system(char *shc) { pid_t childpid; pid_t waitpid; @@ -119,9 +115,7 @@ int ka_system(char *shc) { } -/* - * add a newline to the buffer... - */ +// add a newline to the buffer... void add_newline(struct cittext *textlist) { struct cittext *ptr; @@ -140,9 +134,7 @@ void add_newline(struct cittext *textlist) { } -/* - * add a word to the buffer... - */ +// add a word to the buffer... void add_word(struct cittext *textlist, char *wordbuf) { struct cittext *ptr; @@ -163,9 +155,7 @@ void add_word(struct cittext *textlist, char *wordbuf) { } -/* - * begin editing of an opened file pointed to by fp - */ +// begin editing of an opened file pointed to by fp void citedit(FILE * fp) { int a, prev, finished, b, last_space; int appending = 0; @@ -174,7 +164,7 @@ void citedit(FILE * fp) { char wordbuf[MAXWORDBUF]; int rv = 0; - /* first, load the text into the buffer */ + // first, load the text into the buffer fseek(fp, 0L, 0); textlist = (struct cittext *) malloc(sizeof(struct cittext)); textlist->next = NULL; @@ -204,7 +194,7 @@ void citedit(FILE * fp) { prev = a; } - /* get text */ + // get text finished = 0; prev = (appending ? 13 : (-1)); strcpy(wordbuf, ""); @@ -293,7 +283,7 @@ void citedit(FILE * fp) { prev = a; } while (finished == 0); - /* write the buffer back to disk */ + // write the buffer back to disk fseek(fp, 0L, 0); for (ptr = textlist; ptr != NULL; ptr = ptr->next) { fprintf(fp, "%s", ptr->text); @@ -305,7 +295,7 @@ void citedit(FILE * fp) { scr_printf("failed to set message buffer: %s\n", strerror(errno)); - /* and deallocate the memory we used */ + // and deallocate the memory we used while (textlist != NULL) { ptr = textlist->next; free(textlist); @@ -314,9 +304,7 @@ void citedit(FILE * fp) { } -/* - * Free the struct parts - */ +// Free the struct parts void free_parts(struct parts *p) { struct parts *a_part = p; @@ -330,11 +318,9 @@ void free_parts(struct parts *p) { } -/* - * This is a mini RFC2047 decoder. - * It only handles strings encoded from UTF-8 as Quoted-printable. - * We can do this "in place" because the converted string will always be smaller than the source string. - */ +// This is a mini RFC2047 decoder. +// It only handles strings encoded from UTF-8 as Quoted-printable. +// We can do this "in place" because the converted string will always be smaller than the source string. void mini_2047_decode(char *s) { if (!s) { // no null strings allowed! return; @@ -389,20 +375,19 @@ void mini_2047_decode(char *s) { } -/* - * Read a message from the server - */ -int read_message(CtdlIPC * ipc, long num, /* message number */ - int pagin, /* 0 = normal read, 1 = read with pagination, 2 = header */ - FILE * dest /* Destination file, NULL for screen */ - ) { +// Read a message from the server +int read_message(CtdlIPC *ipc, + long num, // message number + int pagin, // 0 = normal read, 1 = read with pagination, 2 = header + FILE *dest // Destination file, NULL for screen +) { char buf[SIZ]; char now[256]; int format_type = 0; int fr = 0; int nhdr = 0; struct ctdlipcmessage *message = NULL; - int r; /* IPC response code */ + int r; // IPC response code char *converted_text = NULL; char *lineptr; char *nextline; @@ -444,7 +429,7 @@ int read_message(CtdlIPC * ipc, long num, /* message number */ color(BRIGHT_CYAN); } - /* View headers only */ + // View headers only if (pagin == 2) { scr_printf("nhdr=%s\nfrom=%s\ntype=%d\nmsgn=%s\n", message->nhdr ? "yes" : "no", message->author, message->type, message->msgid); @@ -598,14 +583,11 @@ int read_message(CtdlIPC * ipc, long num, /* message number */ color(BRIGHT_WHITE); } - /******* end of header output, start of message text output *******/ + // ****** end of header output, start of message text output ****** - /* - * Convert HTML to plain text, formatting for the actual width - * of the client screen. - */ + // Convert HTML to plain text, formatting for the actual width of the client screen. if (!strcasecmp(message->content_type, "text/html")) { - converted_text = html_to_ascii(message->text, 0, screenwidth, (enable_color ? 1 : 0)); + converted_text = html_to_ascii(message->text, 0, screenwidth, (enable_color ? H2A_ANSI : 0)); if (converted_text != NULL) { free(message->text); message->text = converted_text; @@ -613,24 +595,24 @@ int read_message(CtdlIPC * ipc, long num, /* message number */ } } - /* Text/plain is a different type */ + // Text/plain is a different type if (!strcasecmp(message->content_type, "text/plain")) { format_type = 1; } - /* Render text/x-markdown as plain text */ + // Render text/x-markdown as plain text if (!strcasecmp(message->content_type, "text/x-markdown")) { format_type = 1; } - /* Extract URL's */ + // Extract URL's static char *urlprefixes[] = { "http://", "https://", "ftp://" }; int p = 0; - num_urls = 0; /* Start with a clean slate */ + num_urls = 0; // Start with a clean slate for (p = 0; p < (sizeof urlprefixes / sizeof(char *)); ++p) { searchptr = message->text; while ((searchptr != NULL) && (num_urls < MAXURLS)) { @@ -650,17 +632,14 @@ int read_message(CtdlIPC * ipc, long num, /* message number */ } } - /* - * Here we go - */ + // Here we go if (format_type == 0) { + // renderer for legacy Citadel format fr = fmout(screenwidth, NULL, message->text, dest, 1); } else { - /* renderer for text/plain */ - + // renderer for text/plain lineptr = message->text; - do { nextline = strchr(lineptr, '\n'); if (nextline != NULL) { @@ -700,18 +679,18 @@ int read_message(CtdlIPC * ipc, long num, /* message number */ } } - /* Enumerate any attachments */ + // Enumerate any attachments if ((pagin == 1) && (message->attachments)) { struct parts *ptr; for (ptr = message->attachments; ptr; ptr = ptr->next) { - if ((!strcasecmp(ptr->disposition, "attachment")) - || (!strcasecmp(ptr->disposition, "inline")) - || (!strcasecmp(ptr->disposition, "")) - ) { - if ((strcasecmp(ptr->number, message->mime_chosen)) - && (!IsEmptyStr(ptr->mimetype)) - ) { + if ( (!strcasecmp(ptr->disposition, "attachment")) + || (!strcasecmp(ptr->disposition, "inline")) + || (!strcasecmp(ptr->disposition, "")) + ) { + if ( (strcasecmp(ptr->number, message->mime_chosen)) + && (!IsEmptyStr(ptr->mimetype)) + ) { color(DIM_WHITE); scr_printf("Part "); color(BRIGHT_MAGENTA); @@ -730,23 +709,22 @@ int read_message(CtdlIPC * ipc, long num, /* message number */ } } - /* Save the attachments info for later */ + // Save the attachments info for later last_message_parts = message->attachments; - /* Now we're done */ + // Now we're done free(message->text); free(message); - if (pagin == 1 && !dest) + if (pagin == 1 && !dest) { color(DIM_WHITE); + } stty_ctdl(0); return (fr); } -/* - * replace string function for the built-in editor - */ +// replace string function for the built-in editor void replace_string(char *filename, long int startpos) { char buf[512]; char srch_str[128]; @@ -791,7 +769,7 @@ void replace_string(char *filename, long int startpos) { rv = fwrite((char *) buf, 128, 1, fp); if (rv < 0) { scr_printf("failed to replace string: %s\n", strerror(errno)); - break; /*whoopsi! */ + break; // No replacement happened; break out of the loop } strcpy(buf, &buf[128]); wpos = ftell(fp); @@ -810,10 +788,15 @@ void replace_string(char *filename, long int startpos) { // Function to begin composing a new message -int client_make_message(CtdlIPC * ipc, char *filename, // temporary file name - char *recipient, // NULL if it's not mail - int is_anonymous, int format_type, int mode, char *subject, // buffer to store subject line - int subject_required) { +int client_make_message(CtdlIPC *ipc, + char *filename, // temporary file name + char *recipient, // NULL if it's not mail + int is_anonymous, + int format_type, + int mode, + char *subject, // buffer to store subject line + int subject_required +) { FILE *fp; int a, b, e_ex_code; long beg; @@ -875,7 +858,7 @@ int client_make_message(CtdlIPC * ipc, char *filename, // temporary file name } } - ME1:switch (mode) { +ME1: switch (mode) { case 0: fp = fopen(filename, "r+"); @@ -911,8 +894,8 @@ int client_make_message(CtdlIPC * ipc, char *filename, // temporary file name break; case 2: - default: /* allow 2+ modes */ - e_ex_code = 1; /* start with a failed exit code */ + default: // allow 2+ modes + e_ex_code = 1; // start with a failed exit code stty_ctdl(SB_RESTORE); editor_pid = fork(); cksum = file_checksum(filename); @@ -1001,9 +984,7 @@ int client_make_message(CtdlIPC * ipc, char *filename, // temporary file name } -/* - * Make sure there's room in msg_arr[] for at least one more. - */ +// Make sure there's room in msg_arr[] for at least one more. void check_msg_arr_size(void) { if ((num_msgs + 1) > msg_arr_size) { msg_arr_size += 512; @@ -1012,10 +993,7 @@ void check_msg_arr_size(void) { } -/* - * break_big_lines() - break up lines that are >1024 characters - * otherwise the server will truncate - */ +// break_big_lines() - break up lines that are >1024 characters, otherwise the server will truncate them. void break_big_lines(char *msg) { char *ptr; char *break_here; @@ -1036,14 +1014,13 @@ void break_big_lines(char *msg) { } -/* - * entmsg() - edit and create a message - * returns 0 if message was saved - */ -int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a eply command */ - int c, /* mode */ - int masquerade /* prompt for a non-default display name? */ - ) { +// entmsg() - edit and create a message +// returns 0 if message was saved +int entmsg(CtdlIPC *ipc, + int is_reply, // nonzero if this was a eply command + int c, // mode + int masquerade // prompt for a non-default display name? +) { char buf[SIZ]; int a, b; int need_recp = 0; @@ -1053,15 +1030,13 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a eply command char subject[SIZ]; struct ctdlipcmessage message; unsigned long *msgarr = NULL; - int r; /* IPC response code */ + int r; // IPC response code int subject_required = 0; - /* - * First, check to see if we have permission to enter a message in - * this room. The server will return an error code if we can't. - */ + // First, check to see if we have permission to enter a message in + // this room. The server will return an error code if we can't. if (entmsg_ok == ENTMSG_OK_YES) { - /* no problem, go right ahead */ + // no problem, go right ahead } else if (entmsg_ok == ENTMSG_OK_BLOG) { if (!is_reply) { @@ -1092,7 +1067,7 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a eply command strcpy(message.author, ""); strcpy(message.subject, ""); strcpy(message.references, ""); - message.text = ""; /* point to "", changes later */ + message.text = ""; // point to "", changes later message.anonymous = 0; message.type = mode; @@ -1111,9 +1086,8 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a eply command } } - /* Trim down excessively long lists of thread references. We eliminate the - * second one in the list so that the thread root remains intact. - */ + // Trim down excessively long lists of thread references. We eliminate the + // second one in the list so that the thread root remains intact. int rrtok = num_tokens(reply_references, '|'); int rrlen = strlen(reply_references); if (((rrtok >= 3) && (rrlen > 900)) || (rrtok > 10)) { @@ -1121,7 +1095,8 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a eply command } snprintf(message.references, sizeof message.references, "%s%s%s", - reply_references, (IsEmptyStr(reply_references) ? "" : "|"), reply_inreplyto); + reply_references, (IsEmptyStr(reply_references) ? "" : "|"), reply_inreplyto + ); } r = CtdlIPCPostMessage(ipc, 0, &subject_required, &message, buf); @@ -1131,22 +1106,20 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a eply command return (1); } - /* Error code 570 is special. It means that we CAN enter a message - * in this room, but a recipient needs to be specified. - */ + // Error code 570 is special. It means that we CAN enter a message in this room, but a recipient needs to be specified. need_recp = 0; if (r / 10 == 57) { need_recp = 1; } - /* If the user is a dumbass, tell them how to type. */ + // If the user is a dumbass, tell them how to type. if ((userflags & US_EXPERT) == 0) { scr_printf("Entering message. Word wrap will give you soft linebreaks. Pressing the\n"); scr_printf("'enter' key will give you a hard linebreak and an indent. Press 'enter' twice\n"); scr_printf("when finished.\n"); } - /* Handle the selection of a recipient, if necessary. */ + // Handle the selection of a recipient, if necessary. strcpy(buf, ""); if (need_recp == 1) { if (axlevel >= AxProbU) { @@ -1172,7 +1145,7 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a eply command message.anonymous = 1; } - /* If it's mail, we've got to check the validity of the recipient... */ + // If it's mail, we've got to check the validity of the recipient... if (!IsEmptyStr(message.recipient)) { r = CtdlIPCPostMessage(ipc, 0, &subject_required, &message, buf); if (r / 100 != 2) { @@ -1181,9 +1154,7 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a eply command } } - /* Learn the number of the newest message in in the room, so we can - * tell upon saving whether someone else has posted too. - */ + // Learn the number of the newest message in in the room, so we can tell upon saving whether someone else has posted too. num_msgs = 0; r = CtdlIPCGetMessages(ipc, LastMessages, 1, NULL, &msgarr, buf); if (r / 100 != 1) { @@ -1193,14 +1164,14 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a eply command for (num_msgs = 0; msgarr[num_msgs]; num_msgs++); } - /* Now compose the message... */ + // Now compose the message... if (client_make_message(ipc, temp, message.recipient, message.anonymous, 0, c, message.subject, subject_required) != 0) { if (msgarr) free(msgarr); return (2); } - /* Reopen the temp file that was created, so we can send it */ + // Reopen the temp file that was created, so we can send it fp = fopen(temp, "r"); if (!fp || !(message.text = load_message_from_file(fp))) { @@ -1212,19 +1183,17 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a eply command if (fp) fclose(fp); - /* Break lines that are >1024 characters, otherwise the server - * will truncate them. - */ + // Break lines that are >1024 characters, otherwise the server will truncate them. break_big_lines(message.text); - /* Transmit message to the server */ + // Transmit message to the server r = CtdlIPCPostMessage(ipc, 1, NULL, &message, buf); if (r / 100 != 4) { scr_printf("%s\n", buf); return (1); } - /* Yes, unlink it now, so it doesn't stick around if we crash */ + // Yes, unlink it now, so it doesn't stick around if we crash unlink(temp); if (num_msgs >= 1) @@ -1241,10 +1210,10 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a eply command for (num_msgs = 0; msgarr[num_msgs]; num_msgs++); } - /* get new highest message number in room to set lrp for goto... */ + // get new highest message number in room to set lrp for goto... maxmsgnum = msgarr[num_msgs - 1]; - /* now see if anyone else has posted in here */ + // now see if anyone else has posted in here b = (-1); for (a = 0; a < num_msgs; ++a) { if (msgarr[a] > highmsg) { @@ -1256,9 +1225,7 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a eply command } msgarr = NULL; - /* In the Mail> room, this algorithm always counts one message - * higher than in public rooms, so we decrement it by one. - */ + // In the Mail> room, this algorithm always counts one message higher than in public rooms, so we decrement it by one. if (need_recp) { --b; } @@ -1270,28 +1237,24 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a eply command scr_printf("*** %d additional messages have been entered in this room by other users.\n", b); } free(message.text); - return (0); } -/* - * Do editing on a quoted file - */ +// Do editing on a quoted file void process_quote(void) { FILE *qfile, *tfile; char buf[128]; int line, qstart, qend; - // Unlink the second temp file as soon as it's opened, so it'll get - // deleted even if the program dies + // Unlink the second temp file as soon as it's opened, so it'll get deleted even if the program dies qfile = fopen(temp2, "r"); unlink(temp2); - /* Display the quotable text with line numbers added */ + // Display the quotable text with line numbers added line = 0; if (fgets(buf, 128, qfile) == NULL) { - /* we're skipping a line here */ + // we're skipping a line here } while (fgets(buf, 128, qfile) != NULL) { scr_printf("%3d %s", ++line, buf); @@ -1303,7 +1266,7 @@ void process_quote(void) { rewind(qfile); line = 0; if (fgets(buf, 128, qfile) == NULL) { - /* we're skipping a line here */ + // we're skipping a line here } tfile = fopen(temp, "w"); while (fgets(buf, 128, qfile) != NULL) { @@ -1318,9 +1281,7 @@ void process_quote(void) { } -/* - * List the URLs which were embedded in the previous message - */ +// List the URLs which were embedded in the previous message void list_urls(CtdlIPC * ipc) { int i; char cmd[SIZ]; @@ -1345,9 +1306,7 @@ void list_urls(CtdlIPC * ipc) { } -/* - * Run image viewer in background - */ +// Run image viewer in background int do_image_view(const char *filename) { char cmd[SIZ]; pid_t childpid; @@ -1406,19 +1365,18 @@ int do_image_view(const char *filename) { } -/* - * View an image attached to a message - */ +// View an image attached to a message void image_view(CtdlIPC * ipc, unsigned long msg) { struct parts *ptr = last_message_parts; char part[SIZ]; int found = 0; - /* Run through available parts */ + // Run through available parts for (ptr = last_message_parts; ptr; ptr = ptr->next) { - if ((!strcasecmp(ptr->disposition, "attachment") - || !strcasecmp(ptr->disposition, "inline")) - && !strncmp(ptr->mimetype, "image/", 6)) { + if ( (!strcasecmp(ptr->disposition, "attachment") + || !strcasecmp(ptr->disposition, "inline")) + && !strncmp(ptr->mimetype, "image/", 6) + ) { found++; if (found == 1) { strcpy(part, ptr->number); @@ -1427,8 +1385,9 @@ void image_view(CtdlIPC * ipc, unsigned long msg) { } while (found > 0) { - if (found > 1) + if (found > 1) { strprompt("View which part (0 when done)", part, SIZ - 1); + } found = -found; for (ptr = last_message_parts; ptr; ptr = ptr->next) { if ((!strcasecmp(ptr->disposition, "attachment") @@ -1437,10 +1396,10 @@ void image_view(CtdlIPC * ipc, unsigned long msg) { && !strcasecmp(ptr->number, part)) { char tmp[PATH_MAX]; char buf[SIZ]; - void *file = NULL; /* The downloaded file */ + void *file = NULL; // The downloaded file int r; - /* view image */ + // view image found = -found; r = CtdlIPCAttachmentDownload(ipc, msg, ptr->number, &file, progress, buf); if (r / 100 != 2) { @@ -1467,13 +1426,12 @@ void image_view(CtdlIPC * ipc, unsigned long msg) { } -/* - * Read the messages in the current room - */ -void readmsgs(CtdlIPC * ipc, enum MessageList c, // see listing in citadel_ipc.h - enum MessageDirection rdir, // 1=Forward (-1)=Reverse - int q // Number of msgs to read (if c==3) - ) { +// Read the messages in the current room +void readmsgs(CtdlIPC *ipc, + enum MessageList c, // see listing in citadel_ipc.h + enum MessageDirection rdir, // 1=Forward (-1)=Reverse + int q // Number of msgs to read (if c==3) +) { int a, e, f, g, start; int savedpos; int hold_sw = 0; @@ -1486,11 +1444,11 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c, // see listing in citadel_ipc.h char targ[ROOMNAMELEN]; char filename[PATH_MAX]; char save_to[PATH_MAX]; - void *attachment = NULL; /* Downloaded attachment */ - FILE *dest = NULL; /* Alternate destination other than screen */ - int r; /* IPC response code */ - static int att_seq = 0; /* Attachment download sequence number */ - int rv = 0; /* silence the stupid warn_unused_result warnings */ + void *attachment = NULL; // Downloaded attachment + FILE *dest = NULL; // Alternate destination other than screen + int r; // IPC response code + static int att_seq = 0; // Attachment download sequence number + int rv = 0; // silence the stupid warn_unused_result warnings CtdlMakeTempFileName(prtfile, sizeof prtfile); @@ -1519,7 +1477,7 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c, // see listing in citadel_ipc.h return; } - /* this loop cycles through each message... */ + // this loop cycles through each message... start = ((rdir == 1) ? 0 : (num_msgs - 1)); for (a = start; ((a < num_msgs) && (a >= 0)); a = a + rdir) { while (msg_arr[a] == 0L) { @@ -1532,30 +1490,30 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c, // see listing in citadel_ipc.h && (quotflag == 0) && (userflags & US_PAGINATOR)) ? 1 : 0; - /* If we're doing a quote, set the screenwidth to 72 */ + // If we're doing a quote, set the screenwidth to 72 if (quotflag) { hold_sw = screenwidth; screenwidth = 72; } - /* If printing or archiving, set the screenwidth to 80 */ + // If printing or archiving, set the screenwidth to 80 if (arcflag) { hold_sw = screenwidth; screenwidth = 80; } - /* clear parts list */ + // clear parts list free_parts(last_message_parts); last_message_parts = NULL; - /* now read the message... */ + // now read the message... e = read_message(ipc, msg_arr[a], pagin, dest); - /* ...and set the screenwidth back if we have to */ + // ...and set the screenwidth back if we have to if ((quotflag) || (arcflag)) { screenwidth = hold_sw; } - RMSGREAD: +RMSGREAD: highest_msg_read = msg_arr[a]; if (quotflag) { fclose(dest); @@ -1574,7 +1532,7 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c, // see listing in citadel_ipc.h f = fork(); if (f == 0) { if (freopen(prtfile, "r", stdin) == NULL) { - /* we probably should handle the error condition here */ + // we probably should handle the error condition here } stty_ctdl(SB_RESTORE); ka_system(printcmd); @@ -1614,43 +1572,46 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c, // see listing in citadel_ipc.h e = (inkey() & 127); e = tolower(e); -/* return key same as */ if (e == 10) + if (e == 10) { // return key same as e = 'n'; + } -/* space key same as */ if (e == 32) + if (e == 32) { // space key same as e = 'n'; + } -/* del/move for aides only */ - if ((!is_room_aide) - && ((room_flags & QR_MAILBOX) == 0) - && ((room_flags2 & QR2_COLLABDEL) == 0) - ) { - if ((e == 'd') || (e == 'm')) + if ( (!is_room_aide) // delete/move are available only to admins + && ((room_flags & QR_MAILBOX) == 0) + && ((room_flags2 & QR2_COLLABDEL) == 0) + ) { + if ((e == 'd') || (e == 'm')) { e = 0; + } } -/* print only if available */ - if ((e == 'p') && (IsEmptyStr(printcmd))) + if ((e == 'p') && (IsEmptyStr(printcmd))) { // print, if available e = 0; + } -/* can't file if not allowed */ - if ((e == 'f') - && (rc_allow_attachments == 0)) + if ((e == 'f') && (rc_allow_attachments == 0)) { // file attachments, if available e = 0; + } -/* link only if browser avail*/ - if ((e == 'u') - && (IsEmptyStr(rc_url_cmd))) + if ((e == 'u') && (IsEmptyStr(rc_url_cmd))) { // display urls, if a browser is available e = 0; - if ((e == 'i') - && (IsEmptyStr(imagecmd) || !has_images)) + } + + if ((e == 'i') && (IsEmptyStr(imagecmd) || !has_images)) { // display images, if available e = 0; + } + } while ((e != 'a') && (e != 'n') && (e != 's') && (e != 'd') && (e != 'm') && (e != 'p') && (e != 'q') && (e != 'b') && (e != 'h') && (e != 'r') && (e != 'f') && (e != '?') && (e != 'u') && (e != 'c') && (e != 'y') - && (e != 'i') && (e != 'o')); + && (e != 'i') && (e != 'o') + ); switch (e) { case 's': scr_printf("Stop"); @@ -1708,7 +1669,7 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c, // see listing in citadel_ipc.h else scr_printf("\n"); } - DONE_QUOTING:switch (e) { +DONE_QUOTING: switch (e) { case '?': scr_printf("Options available here:\n" " ? Help (prints this message)\n" @@ -1770,8 +1731,9 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c, // see listing in citadel_ipc.h else { goto RMSGREAD; } - if (r / 100 != 2) /* r will be init'ed, FIXME */ - goto RMSGREAD; /* the logic here sucks */ + if (r / 100 != 2) { // r will be initialized. The logic here sucks. + goto RMSGREAD; + } break; case 'o': case 'f': @@ -1782,23 +1744,21 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c, // see listing in citadel_ipc.h } else { extract_token(filename, cmd, 2, '|', sizeof filename); - /* - * Part 1 won't have a filename; use the - * subject of the message instead. IO - */ + // Part 1 won't have a filename; use the subject of the message instead. --IO if (IsEmptyStr(filename)) { strcpy(filename, reply_subject); } - if (e == 'o') { /* open attachment */ + if (e == 'o') { // open attachment mkdir(tempdir, 0700); snprintf(save_to, sizeof save_to, "%s/%04x.%s", tempdir, ++att_seq, filename); save_buffer(attachment, extract_unsigned_long(cmd, 0), save_to); snprintf(cmd, sizeof cmd, rc_open_cmd, save_to); rv = system(cmd); - if (rv != 0) + if (rv != 0) { scr_printf("failed to save %s Reason %d\n", cmd, rv); + } } - else { /* save attachment to disk */ + else { // save attachment to disk destination_directory(save_to, filename); save_buffer(attachment, extract_unsigned_long(cmd, 0), save_to); } @@ -1865,9 +1825,7 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c, // see listing in citadel_ipc.h } /* end read routine */ -/* - * View and edit a system message - */ +// View and edit a system message void edit_system_message(CtdlIPC * ipc, char *which_message) { char desc[SIZ]; char read_cmd[SIZ]; @@ -1880,10 +1838,8 @@ void edit_system_message(CtdlIPC * ipc, char *which_message) { } -/* - * Loads the contents of a file into memory. Caller must free the allocated memory. - */ -char *load_message_from_file(FILE * src) { +// Loads the contents of a file into memory. Caller must free the allocated memory. +char *load_message_from_file(FILE *src) { size_t i; size_t got = 0; char *dest = NULL; @@ -1893,8 +1849,9 @@ char *load_message_from_file(FILE * src) { rewind(src); dest = (char *) calloc(1, i + 1); - if (!dest) + if (!dest) { return NULL; + } while (got < i) { size_t g; @@ -1902,10 +1859,10 @@ char *load_message_from_file(FILE * src) { g = fread(dest + got, 1, i - got, src); got += g; if (g < i - got) { - /* Interrupted system call, keep going */ - if (errno == EINTR) - continue; - /* At this point we have either EOF or error */ + if (errno == EINTR) { + continue; // Interrupted system call, keep going + } + // At this point we have either EOF or error i = got; break; } -- 2.39.2 From 8e57b1aca19c9b8003abf8e225b7f1351f1e12a4 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Thu, 30 May 2024 14:58:48 +0000 Subject: [PATCH 02/16] html_to_ascii.c : prepare to modernize! --- libcitadel/lib/html_to_ascii.c | 798 ++++++++++++++------------- webcit-ng/static/js/view_calendar.js | 10 +- 2 files changed, 413 insertions(+), 395 deletions(-) diff --git a/libcitadel/lib/html_to_ascii.c b/libcitadel/lib/html_to_ascii.c index 42f643863..3d02bb7fd 100644 --- a/libcitadel/lib/html_to_ascii.c +++ b/libcitadel/lib/html_to_ascii.c @@ -1,8 +1,7 @@ -// Functions which handle translation between HTML and plain text -// Copyright (c) 2000-2023 by the citadel.org team +// This is an HTML to plain text converter. +// Copyright (c) 2000-2024 by the citadel.org team (Art Cancro et al.) // -// This program is open source software. Use, duplication, or disclosure -// is subject to the terms of the GNU General Public License, version 3. +// This program is open source software. Use, duplication, or disclosure is subject to the GNU General Public License version 3. #include #include @@ -16,14 +15,15 @@ #include #include #include "libcitadel.h" - + // Convert HTML to plain text. // -// inputmsg = pointer to raw HTML message -// msglen = stop reading after this many bytes -// screenwidth = desired output screenwidth -// ansi = if nonzero, assume output is to a terminal that supports ANSI escape codes +// inputmsg = pointer to raw HTML message +// msglen = stop reading after this many bytes +// screenwidth = desired output screenwidth +// flags = Flags that can be set: +// H2A_ANSI = Output ANSI terminal escape sequences // char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned int flags) { char inbuf[SIZ]; @@ -36,7 +36,7 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned size_t outptr_buffer_size; size_t output_len = 0; int i, j, ch, did_out, rb, scanch; - int nest = 0; // Bracket nesting level + int nest = 0; // angle bracket nesting level int blockquote = 0; // BLOCKQUOTE nesting level int styletag = 0; // STYLE tag nesting level int styletag_start = 0; @@ -50,24 +50,28 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned inptr = inputmsg; strcpy(inbuf, ""); strcpy(outbuf, ""); - if (msglen == 0) msglen = strlen(inputmsg); + if (msglen == 0) { + msglen = strlen(inputmsg); + } outptr_buffer_size = strlen(inptr) + SIZ; outptr = malloc(outptr_buffer_size); - if (outptr == NULL) return NULL; + if (outptr == NULL) { + return NULL; + } strcpy(outptr, ""); output_len = 0; do { // Fill the input buffer inbuf_len = strlen(inbuf); - if ( (done_reading == 0) && (inbuf_len < (SIZ-128)) ) { + if ((done_reading == 0) && (inbuf_len < (SIZ - 128))) { ch = *inptr++; if (ch != 0) { inbuf[inbuf_len++] = ch; inbuf[inbuf_len] = 0; - } + } else { done_reading = 1; } @@ -82,449 +86,462 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned // Do some parsing if (!IsEmptyStr(inbuf)) { - // Fold in all the spacing - for (i=0; !IsEmptyStr(&inbuf[i]); ++i) { - if (inbuf[i]==10) inbuf[i]=32; - if (inbuf[i]==13) inbuf[i]=32; - if (inbuf[i]==9) inbuf[i]=32; - } - for (i=0; !IsEmptyStr(&inbuf[i]); ++i) { - while ((inbuf[i]==32)&&(inbuf[i+1]==32)) { - strcpy(&inbuf[i], &inbuf[i+1]); + // Fold in all the spacing + for (i = 0; !IsEmptyStr(&inbuf[i]); ++i) { + if (inbuf[i] == 10) + inbuf[i] = 32; + if (inbuf[i] == 13) + inbuf[i] = 32; + if (inbuf[i] == 9) + inbuf[i] = 32; } - } - - for (i=0; !IsEmptyStr(&inbuf[i]); ++i) { - ch = inbuf[i]; - - if (ch == '<') { - ++nest; - strcpy(tag, ""); + for (i = 0; !IsEmptyStr(&inbuf[i]); ++i) { + while ((inbuf[i] == 32) && (inbuf[i + 1] == 32)) { + strcpy(&inbuf[i], &inbuf[i + 1]); + } } - else if (ch == '>') { // We have a tag. - if (nest > 0) --nest; + for (i = 0; !IsEmptyStr(&inbuf[i]); ++i) { + ch = inbuf[i]; - // Unqualify the tag (truncate at first space) - if (strchr(tag, ' ') != NULL) { - strcpy(strchr(tag, ' '), ""); - } - - if (!strcasecmp(tag, "P")) { - strcat(outbuf, nl); - strcat(outbuf, nl); + if (ch == '<') { + ++nest; + strcpy(tag, ""); } - if (!strcasecmp(tag, "/DIV")) { - strcat(outbuf, nl); - strcat(outbuf, nl); - } + else if (ch == '>') { // We have a tag. + if (nest > 0) { + --nest; + } - if (!strcasecmp(tag, "LI")) { - strcat(outbuf, nl); - strcat(outbuf, " * "); - } - else if (!strcasecmp(tag, "/UL")) { - strcat(outbuf, nl); - strcat(outbuf, nl); - } + // right about here is where we could add things + // like img2sixel handling or url footnote gathering - else if (!strcasecmp(tag, "H1")) { - strcat(outbuf, nl); - strcat(outbuf, nl); - } - else if (!strcasecmp(tag, "H2")) { - strcat(outbuf, nl); - strcat(outbuf, nl); - } + // Unqualify the tag (truncate at first space) + char *tagsp = strchr(tag, ' '); + if (tagsp) { + *tagsp = 0; + } - else if (!strcasecmp(tag, "H3")) { - strcat(outbuf, nl); - strcat(outbuf, nl); - } + if (!strcasecmp(tag, "P")) { + strcat(outbuf, nl); + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "H4")) { - strcat(outbuf, nl); - strcat(outbuf, nl); - } + if (!strcasecmp(tag, "/DIV")) { + strcat(outbuf, nl); + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "/H1")) { - strcat(outbuf, nl); - } + if (!strcasecmp(tag, "LI")) { + strcat(outbuf, nl); + strcat(outbuf, " * "); + } - else if (!strcasecmp(tag, "/H2")) { - strcat(outbuf, nl); - } + else if (!strcasecmp(tag, "/UL")) { + strcat(outbuf, nl); + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "/H3")) { - strcat(outbuf, nl); - } + else if (!strcasecmp(tag, "H1")) { + strcat(outbuf, nl); + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "/H4")) { - strcat(outbuf, nl); - } + else if (!strcasecmp(tag, "H2")) { + strcat(outbuf, nl); + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "HR")) { - strcat(outbuf, nl); - strcat(outbuf, " "); - for (j=0; j"); - strcat(outbuf, nl); - } - else if (!strcasecmp(tag, "/BLOCKQUOTE")) { - strcat(outbuf, "\n"); - --blockquote; - if ( (blockquote == 0) && (ansi) ) { - strcat(outbuf, "\033[22m\033[22m"); + else if (!strcasecmp(tag, "/U")) { + if (ansi) { + strcat(outbuf, "\033[24m"); + } } - strcpy(nl, "\n"); - for (j=0; j"); - strcat(outbuf, nl); - } - else if (!strcasecmp(tag, "STYLE")) { - ++styletag; - if (styletag == 1) { - styletag_start = strlen(outbuf); + else if (!strcasecmp(tag, "BR")) { + strcat(outbuf, nl); + } + + else if (!strcasecmp(tag, "TR")) { + strcat(outbuf, nl); + } + + else if (!strcasecmp(tag, "/TABLE")) { + strcat(outbuf, nl); + } + + else if (!strcasecmp(tag, "BLOCKQUOTE")) { + ++blockquote; + strcpy(nl, "\n"); + if ((blockquote == 1) && (ansi)) { + strcat(nl, "\033[2m\033[2m"); + } + for (j = 0; j < blockquote; ++j) { + strcat(nl, ">"); + } + strcat(outbuf, nl); } - } - else if (!strcasecmp(tag, "/STYLE")) { - --styletag; - if (styletag == 0) { - outbuf[styletag_start] = 0; + else if (!strcasecmp(tag, "/BLOCKQUOTE")) { + strcat(outbuf, "\n"); + --blockquote; + if ((blockquote == 0) && (ansi)) { + strcat(outbuf, "\033[22m\033[22m"); + } + strcpy(nl, "\n"); + for (j = 0; j < blockquote; ++j) { + strcat(nl, ">"); + } + strcat(outbuf, nl); } + + else if (!strcasecmp(tag, "STYLE")) { + ++styletag; + if (styletag == 1) { + styletag_start = strlen(outbuf); + } + } + + else if (!strcasecmp(tag, "/STYLE")) { + --styletag; + if (styletag == 0) { + outbuf[styletag_start] = 0; + } + } + } - } + else if ((nest > 0) && (strlen(tag) < (sizeof(tag) - 1))) { + tag[strlen(tag) + 1] = 0; + tag[strlen(tag)] = ch; + } - else if ((nest > 0) && (strlen(tag)<(sizeof(tag)-1))) { - tag[strlen(tag)+1] = 0; - tag[strlen(tag)] = ch; - } - - else if ((!nest) && (styletag == 0)) { - outbuf[strlen(outbuf)+1] = 0; - outbuf[strlen(outbuf)] = ch; + else if ((!nest) && (styletag == 0)) { + outbuf[strlen(outbuf) + 1] = 0; + outbuf[strlen(outbuf)] = ch; + } } - } - strcpy(inbuf, &inbuf[i]); + strcpy(inbuf, &inbuf[i]); } // Convert &; tags to the forbidden characters - if (!IsEmptyStr(outbuf)) for (i=0; !IsEmptyStr(&outbuf[i]); ++i) { + if (!IsEmptyStr(outbuf)) + for (i = 0; !IsEmptyStr(&outbuf[i]); ++i) { - // Character entity references - if (!strncasecmp(&outbuf[i], " ", 6)) { - outbuf[i] = ' '; - strcpy(&outbuf[i+1], &outbuf[i+6]); - } + // Character entity references + if (!strncasecmp(&outbuf[i], " ", 6)) { + outbuf[i] = ' '; + strcpy(&outbuf[i + 1], &outbuf[i + 6]); + } - if (!strncasecmp(&outbuf[i], " ", 6)) { - outbuf[i] = ' '; - strcpy(&outbuf[i+1], &outbuf[i+6]); - } + if (!strncasecmp(&outbuf[i], " ", 6)) { + outbuf[i] = ' '; + strcpy(&outbuf[i + 1], &outbuf[i + 6]); + } - if (!strncasecmp(&outbuf[i], " ", 6)) { - outbuf[i] = ' '; - strcpy(&outbuf[i+1], &outbuf[i+6]); - } + if (!strncasecmp(&outbuf[i], " ", 6)) { + outbuf[i] = ' '; + strcpy(&outbuf[i + 1], &outbuf[i + 6]); + } - if (!strncasecmp(&outbuf[i], " ", 8)) { - outbuf[i] = ' '; - strcpy(&outbuf[i+1], &outbuf[i+8]); - } + if (!strncasecmp(&outbuf[i], " ", 8)) { + outbuf[i] = ' '; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "<", 4)) { - outbuf[i] = '<'; - strcpy(&outbuf[i+1], &outbuf[i+4]); - } + else if (!strncasecmp(&outbuf[i], "<", 4)) { + outbuf[i] = '<'; + strcpy(&outbuf[i + 1], &outbuf[i + 4]); + } - else if (!strncasecmp(&outbuf[i], ">", 4)) { - outbuf[i] = '>'; - strcpy(&outbuf[i+1], &outbuf[i+4]); - } + else if (!strncasecmp(&outbuf[i], ">", 4)) { + outbuf[i] = '>'; + strcpy(&outbuf[i + 1], &outbuf[i + 4]); + } - else if (!strncasecmp(&outbuf[i], "&", 5)) { - strcpy(&outbuf[i+1], &outbuf[i+5]); - } + else if (!strncasecmp(&outbuf[i], "&", 5)) { + strcpy(&outbuf[i + 1], &outbuf[i + 5]); + } - else if (!strncasecmp(&outbuf[i], """, 6)) { - outbuf[i] = '\"'; - strcpy(&outbuf[i+1], &outbuf[i+6]); - } + else if (!strncasecmp(&outbuf[i], """, 6)) { + outbuf[i] = '\"'; + strcpy(&outbuf[i + 1], &outbuf[i + 6]); + } - else if (!strncasecmp(&outbuf[i], "‘", 7)) { - outbuf[i] = '`'; - strcpy(&outbuf[i+1], &outbuf[i+7]); - } + else if (!strncasecmp(&outbuf[i], "‘", 7)) { + outbuf[i] = '`'; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - else if (!strncasecmp(&outbuf[i], "’", 7)) { - outbuf[i] = '\''; - strcpy(&outbuf[i+1], &outbuf[i+7]); - } + else if (!strncasecmp(&outbuf[i], "’", 7)) { + outbuf[i] = '\''; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - else if (!strncasecmp(&outbuf[i], "©", 6)) { - outbuf[i] = '('; - outbuf[i+1] = 'c'; - outbuf[i+2] = ')'; - strcpy(&outbuf[i+3], &outbuf[i+6]); - } + else if (!strncasecmp(&outbuf[i], "©", 6)) { + outbuf[i] = '('; + outbuf[i + 1] = 'c'; + outbuf[i + 2] = ')'; + strcpy(&outbuf[i + 3], &outbuf[i + 6]); + } - else if (!strncasecmp(&outbuf[i], "•", 6)) { - outbuf[i] = ' '; - outbuf[i+1] = '*'; - outbuf[i+2] = ' '; - strcpy(&outbuf[i+3], &outbuf[i+6]); - } + else if (!strncasecmp(&outbuf[i], "•", 6)) { + outbuf[i] = ' '; + outbuf[i + 1] = '*'; + outbuf[i + 2] = ' '; + strcpy(&outbuf[i + 3], &outbuf[i + 6]); + } - else if (!strncasecmp(&outbuf[i], "…", 8)) { - outbuf[i] = '.'; - outbuf[i+1] = '.'; - outbuf[i+2] = '.'; - strcpy(&outbuf[i+3], &outbuf[i+8]); - } + else if (!strncasecmp(&outbuf[i], "…", 8)) { + outbuf[i] = '.'; + outbuf[i + 1] = '.'; + outbuf[i + 2] = '.'; + strcpy(&outbuf[i + 3], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "™", 7)) { - outbuf[i] = '('; - outbuf[i+1] = 't'; - outbuf[i+2] = 'm'; - outbuf[i+3] = ')'; - strcpy(&outbuf[i+4], &outbuf[i+7]); - } + else if (!strncasecmp(&outbuf[i], "™", 7)) { + outbuf[i] = '('; + outbuf[i + 1] = 't'; + outbuf[i + 2] = 'm'; + outbuf[i + 3] = ')'; + strcpy(&outbuf[i + 4], &outbuf[i + 7]); + } - else if (!strncasecmp(&outbuf[i], "®", 5)) { - outbuf[i] = '('; - outbuf[i+1] = 'r'; - outbuf[i+2] = ')'; - strcpy(&outbuf[i+3], &outbuf[i+5]); - } + else if (!strncasecmp(&outbuf[i], "®", 5)) { + outbuf[i] = '('; + outbuf[i + 1] = 'r'; + outbuf[i + 2] = ')'; + strcpy(&outbuf[i + 3], &outbuf[i + 5]); + } - else if (!strncasecmp(&outbuf[i], "¼", 8)) { - outbuf[i] = '1'; - outbuf[i+1] = '/'; - outbuf[i+2] = '4'; - strcpy(&outbuf[i+3], &outbuf[i+8]); - } + else if (!strncasecmp(&outbuf[i], "¼", 8)) { + outbuf[i] = '1'; + outbuf[i + 1] = '/'; + outbuf[i + 2] = '4'; + strcpy(&outbuf[i + 3], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "½", 8)) { - outbuf[i] = '1'; - outbuf[i+1] = '/'; - outbuf[i+2] = '2'; - strcpy(&outbuf[i+3], &outbuf[i+8]); - } + else if (!strncasecmp(&outbuf[i], "½", 8)) { + outbuf[i] = '1'; + outbuf[i + 1] = '/'; + outbuf[i + 2] = '2'; + strcpy(&outbuf[i + 3], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "¾", 8)) { - outbuf[i] = '3'; - outbuf[i+1] = '/'; - outbuf[i+2] = '4'; - strcpy(&outbuf[i+3], &outbuf[i+8]); - } + else if (!strncasecmp(&outbuf[i], "¾", 8)) { + outbuf[i] = '3'; + outbuf[i + 1] = '/'; + outbuf[i + 2] = '4'; + strcpy(&outbuf[i + 3], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "–", 7)) { - outbuf[i] = '-'; - outbuf[i+1] = '-'; - strcpy(&outbuf[i+2], &outbuf[i+7]); - } + else if (!strncasecmp(&outbuf[i], "–", 7)) { + outbuf[i] = '-'; + outbuf[i + 1] = '-'; + strcpy(&outbuf[i + 2], &outbuf[i + 7]); + } - else if (!strncasecmp(&outbuf[i], "—", 7)) { - outbuf[i] = '-'; - outbuf[i+1] = '-'; - outbuf[i+2] = '-'; - strcpy(&outbuf[i+3], &outbuf[i+7]); - } + else if (!strncasecmp(&outbuf[i], "—", 7)) { + outbuf[i] = '-'; + outbuf[i + 1] = '-'; + outbuf[i + 2] = '-'; + strcpy(&outbuf[i + 3], &outbuf[i + 7]); + } - else if (!strncmp(&outbuf[i], "Ç", 8)) { - outbuf[i] = 'C'; - strcpy(&outbuf[i+1], &outbuf[i+8]); - } + else if (!strncmp(&outbuf[i], "Ç", 8)) { + outbuf[i] = 'C'; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "ç", 8)) { - outbuf[i] = 'c'; - strcpy(&outbuf[i+1], &outbuf[i+8]); - } + else if (!strncasecmp(&outbuf[i], "ç", 8)) { + outbuf[i] = 'c'; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncmp(&outbuf[i], "È", 8)) { - outbuf[i] = 'E'; - strcpy(&outbuf[i+1], &outbuf[i+8]); - } + else if (!strncmp(&outbuf[i], "È", 8)) { + outbuf[i] = 'E'; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "è", 8)) { - outbuf[i] = 'e'; - strcpy(&outbuf[i+1], &outbuf[i+8]); - } + else if (!strncasecmp(&outbuf[i], "è", 8)) { + outbuf[i] = 'e'; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncmp(&outbuf[i], "Ê", 7)) { - outbuf[i] = 'E'; - strcpy(&outbuf[i+1], &outbuf[i+7]); - } + else if (!strncmp(&outbuf[i], "Ê", 7)) { + outbuf[i] = 'E'; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - else if (!strncasecmp(&outbuf[i], "ê", 7)) { - outbuf[i] = 'e'; - strcpy(&outbuf[i+1], &outbuf[i+7]); - } + else if (!strncasecmp(&outbuf[i], "ê", 7)) { + outbuf[i] = 'e'; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - else if (!strncmp(&outbuf[i], "É", 8)) { - outbuf[i] = 'E'; - strcpy(&outbuf[i+1], &outbuf[i+8]); - } + else if (!strncmp(&outbuf[i], "É", 8)) { + outbuf[i] = 'E'; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "é", 8)) { - outbuf[i] = 'e'; - strcpy(&outbuf[i+1], &outbuf[i+8]); - } + else if (!strncasecmp(&outbuf[i], "é", 8)) { + outbuf[i] = 'e'; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncmp(&outbuf[i], "À", 8)) { - outbuf[i] = 'A'; - strcpy(&outbuf[i+1], &outbuf[i+8]); - } + else if (!strncmp(&outbuf[i], "À", 8)) { + outbuf[i] = 'A'; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "à", 8)) { - outbuf[i] = 'a'; - strcpy(&outbuf[i+1], &outbuf[i+8]); - } + else if (!strncasecmp(&outbuf[i], "à", 8)) { + outbuf[i] = 'a'; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "“", 7)) { - outbuf[i] = '\"'; - strcpy(&outbuf[i+1], &outbuf[i+7]); - } + else if (!strncasecmp(&outbuf[i], "“", 7)) { + outbuf[i] = '\"'; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - else if (!strncasecmp(&outbuf[i], "”", 7)) { - outbuf[i] = '\"'; - strcpy(&outbuf[i+1], &outbuf[i+7]); - } + else if (!strncasecmp(&outbuf[i], "”", 7)) { + outbuf[i] = '\"'; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - else if (!strncasecmp(&outbuf[i], "´", 7)) { - outbuf[i] = '\''; - strcpy(&outbuf[i+1], &outbuf[i+7]); - } + else if (!strncasecmp(&outbuf[i], "´", 7)) { + outbuf[i] = '\''; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - else if (!strncasecmp(&outbuf[i], "’", 7)) { - outbuf[i] = '\''; - strcpy(&outbuf[i+1], &outbuf[i+7]); - } + else if (!strncasecmp(&outbuf[i], "’", 7)) { + outbuf[i] = '\''; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - else if (!strncasecmp(&outbuf[i], "–", 7)) { - outbuf[i] = '-'; - strcpy(&outbuf[i+1], &outbuf[i+7]); - } + else if (!strncasecmp(&outbuf[i], "–", 7)) { + outbuf[i] = '-'; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - // two-digit decimal equivalents - else if (outbuf[i] == '&' && - outbuf[i + 1] == '#' && - isdigit(outbuf[i + 2]) && - isdigit(outbuf[i + 3]) && - (outbuf[i+4] == ';') ) - { - scanch = 0; - sscanf(&outbuf[i+2], "%02d", &scanch); - outbuf[i] = scanch; - strcpy(&outbuf[i+1], &outbuf[i+5]); - } + // two-digit decimal equivalents + else if ( outbuf[i] == '&' + && outbuf[i + 1] == '#' + && isdigit(outbuf[i + 2]) + && isdigit(outbuf[i + 3]) + && (outbuf[i + 4] == ';') + ) { + scanch = 0; + sscanf(&outbuf[i + 2], "%02d", &scanch); + outbuf[i] = scanch; + strcpy(&outbuf[i + 1], &outbuf[i + 5]); + } - // three-digit decimal equivalents - else if (outbuf[i] == '&' && - outbuf[i + 1] == '#' && - isdigit(outbuf[i + 2]) && - isdigit(outbuf[i + 3]) && - isdigit(outbuf[i + 4]) && - (outbuf[i + 5] == ';') ) - { - scanch = 0; - sscanf(&outbuf[i+2], "%03d", &scanch); - outbuf[i] = scanch; - strcpy(&outbuf[i+1], &outbuf[i+6]); - } + // three-digit decimal equivalents + else if ( outbuf[i] == '&' + && outbuf[i + 1] == '#' + && isdigit(outbuf[i + 2]) + && isdigit(outbuf[i + 3]) + && isdigit(outbuf[i + 4]) + && (outbuf[i + 5] == ';') + ) { + scanch = 0; + sscanf(&outbuf[i + 2], "%03d", &scanch); + outbuf[i] = scanch; + strcpy(&outbuf[i + 1], &outbuf[i + 6]); + } - // four-digit decimal equivalents - else if (outbuf[i] == '&' && - outbuf[i + 1] == '#' && - isdigit(outbuf[i + 2]) && - isdigit(outbuf[i + 3]) && - isdigit(outbuf[i + 4]) && - isdigit(outbuf[i + 5]) && - (outbuf[i + 6] == ';') ) - { - scanch = 0; - sscanf(&outbuf[i+2], "%04d", &scanch); - outbuf[i] = scanch; - strcpy(&outbuf[i+1], &outbuf[i+7]); - } + // four-digit decimal equivalents + else if ( outbuf[i] == '&' + && outbuf[i + 1] == '#' + && isdigit(outbuf[i + 2]) + && isdigit(outbuf[i + 3]) + && isdigit(outbuf[i + 4]) + && isdigit(outbuf[i + 5]) + && (outbuf[i + 6] == ';') + ) { + scanch = 0; + sscanf(&outbuf[i + 2], "%04d", &scanch); + outbuf[i] = scanch; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - } + } // Make sure the output buffer is big enough if ((output_len + strlen(outbuf) + SIZ) > outptr_buffer_size) { @@ -539,39 +556,40 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned do { did_out = 0; if (strlen(outbuf) > 0) { - for (i = 0; i (screenwidth - 2 )) { + if (strlen(outbuf) > (screenwidth - 2)) { rb = (-1); - for (i=0; i<(screenwidth-2); ++i) { - if (outbuf[i]==32) rb = i; + for (i = 0; i < (screenwidth - 2); ++i) { + if (outbuf[i] == 32) + rb = i; } - if (rb>=0) { + if (rb >= 0) { strncpy(&outptr[output_len], outbuf, rb); output_len += rb; strcpy(&outptr[output_len], nl); output_len += strlen(nl); - strcpy(outbuf, &outbuf[rb+1]); + strcpy(outbuf, &outbuf[rb + 1]); } else { - strncpy(&outptr[output_len], outbuf, screenwidth-2); - output_len += (screenwidth-2); + strncpy(&outptr[output_len], outbuf, screenwidth - 2); + output_len += (screenwidth - 2); strcpy(&outptr[output_len], nl); output_len += strlen(nl); - strcpy(outbuf, &outbuf[screenwidth-2]); + strcpy(outbuf, &outbuf[screenwidth - 2]); } } @@ -585,13 +603,13 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned strcpy(outptr, &outptr[1]); --output_len; } - while ((output_len > 0) && (isspace(outptr[output_len-1]))) { - outptr[output_len-1] = 0; + while ((output_len > 0) && (isspace(outptr[output_len - 1]))) { + outptr[output_len - 1] = 0; --output_len; } // Make sure the final line ends with a newline character. - if ((output_len > 0) && (outptr[output_len-1] != '\n')) { + if ((output_len > 0) && (outptr[output_len - 1] != '\n')) { strcat(outptr, "\n"); ++output_len; } diff --git a/webcit-ng/static/js/view_calendar.js b/webcit-ng/static/js/view_calendar.js index e5820a3bb..01967701d 100644 --- a/webcit-ng/static/js/view_calendar.js +++ b/webcit-ng/static/js/view_calendar.js @@ -64,13 +64,13 @@ var calendar_initialized = 0; function update_calendar_display() { // Get y-m-d to display - day_of_month = date_being_displayed.getDate(); - month = date_being_displayed.getMonth() + 1; - year = date_being_displayed.getFullYear(); + let day_of_month = date_being_displayed.getDate(); + let month = date_being_displayed.getMonth() + 1; + let year = date_being_displayed.getFullYear(); document.getElementById("ctdl-main").innerHTML = - "There ought to be a calendar here.
" - + "Displaying " + year + "-" + month + "-" + day_of_month + "
" + "Displaying " + year + "-" + month + "-" + day_of_month + "
" + + "Temporary navigation links: " + "←Y | " + "←M | " + "←W | " -- 2.39.2 From d73f319eedb3c7268e511d8aa18ebe03cd4e7996 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Thu, 30 May 2024 20:59:24 +0000 Subject: [PATCH 03/16] html_to_ascii: read the entire message in at once Instead of 4096 bytes at a time. That wasn't even working. --- libcitadel/lib/html_to_ascii.c | 848 ++++++++++++++++----------------- 1 file changed, 416 insertions(+), 432 deletions(-) diff --git a/libcitadel/lib/html_to_ascii.c b/libcitadel/lib/html_to_ascii.c index 3d02bb7fd..29796f8d3 100644 --- a/libcitadel/lib/html_to_ascii.c +++ b/libcitadel/lib/html_to_ascii.c @@ -26,11 +26,10 @@ // H2A_ANSI = Output ANSI terminal escape sequences // char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned int flags) { - char inbuf[SIZ]; + char *inbuf = NULL; int inbuf_len = 0; char outbuf[SIZ]; char tag[1024]; - int done_reading = 0; const char *inptr; char *outptr; size_t outptr_buffer_size; @@ -40,7 +39,6 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned int blockquote = 0; // BLOCKQUOTE nesting level int styletag = 0; // STYLE tag nesting level int styletag_start = 0; - int bytes_processed = 0; char nl[128]; int ansi = (flags & H2A_ANSI) ? 1 : 0; @@ -48,7 +46,6 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned tag[0] = '\0'; strcpy(nl, "\n"); inptr = inputmsg; - strcpy(inbuf, ""); strcpy(outbuf, ""); if (msglen == 0) { msglen = strlen(inputmsg); @@ -62,538 +59,525 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned strcpy(outptr, ""); output_len = 0; - do { - // Fill the input buffer - inbuf_len = strlen(inbuf); - if ((done_reading == 0) && (inbuf_len < (SIZ - 128))) { + inbuf = strdup(inputmsg); + if (!inbuf) { + return NULL; + } - ch = *inptr++; - if (ch != 0) { - inbuf[inbuf_len++] = ch; - inbuf[inbuf_len] = 0; - } - else { - done_reading = 1; - } + // "inbuf" ingests the unparsed HTML while we work with it. + inbuf_len = strlen(inbuf); + if (inbuf_len > msglen) { + inbuf[msglen] = 0; + inbuf_len = msglen; + } - ++bytes_processed; - if (bytes_processed > msglen) { - done_reading = 1; - } + // Do some parsing + if (!IsEmptyStr(inbuf)) { + // Convert newlines, carriage returns, and tabs to spaces + char *sp; + while ( (sp = strchr(inbuf, '\r')) + || (sp = strchr(inbuf, '\n')) + || (sp = strchr(inbuf, '\t')) + ) { + *sp = ' '; } - // Do some parsing - if (!IsEmptyStr(inbuf)) { + // Convert multiple spaces to a single space. + while (sp = strstr(inbuf, " "), sp!=NULL) { + strcpy(sp, sp+1); + } - // Fold in all the spacing - for (i = 0; !IsEmptyStr(&inbuf[i]); ++i) { - if (inbuf[i] == 10) - inbuf[i] = 32; - if (inbuf[i] == 13) - inbuf[i] = 32; - if (inbuf[i] == 9) - inbuf[i] = 32; - } + for (i = 0; inbuf[i]; ++i) { + ch = inbuf[i]; - for (i = 0; !IsEmptyStr(&inbuf[i]); ++i) { - while ((inbuf[i] == 32) && (inbuf[i + 1] == 32)) { - strcpy(&inbuf[i], &inbuf[i + 1]); - } + if (ch == '<') { + ++nest; + strcpy(tag, ""); } - for (i = 0; !IsEmptyStr(&inbuf[i]); ++i) { - ch = inbuf[i]; - - if (ch == '<') { - ++nest; - strcpy(tag, ""); + else if (ch == '>') { // We have a tag. + if (nest > 0) { + --nest; } - else if (ch == '>') { // We have a tag. - if (nest > 0) { - --nest; - } - + // right about here is where we could add things + // like img2sixel handling or url footnote gathering - // right about here is where we could add things - // like img2sixel handling or url footnote gathering + // Unqualify the tag (truncate at first space) + char *tagsp = strchr(tag, ' '); + if (tagsp) { + *tagsp = 0; + } + if (!strcasecmp(tag, "P")) { + strcat(outbuf, nl); + strcat(outbuf, nl); + } - // Unqualify the tag (truncate at first space) - char *tagsp = strchr(tag, ' '); - if (tagsp) { - *tagsp = 0; - } + if (!strcasecmp(tag, "/DIV")) { + strcat(outbuf, nl); + strcat(outbuf, nl); + } - if (!strcasecmp(tag, "P")) { - strcat(outbuf, nl); - strcat(outbuf, nl); - } + if (!strcasecmp(tag, "LI")) { + strcat(outbuf, nl); + strcat(outbuf, " * "); + } - if (!strcasecmp(tag, "/DIV")) { - strcat(outbuf, nl); - strcat(outbuf, nl); - } + else if (!strcasecmp(tag, "/UL")) { + strcat(outbuf, nl); + strcat(outbuf, nl); + } - if (!strcasecmp(tag, "LI")) { - strcat(outbuf, nl); - strcat(outbuf, " * "); - } + else if (!strcasecmp(tag, "H1")) { + strcat(outbuf, nl); + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "/UL")) { - strcat(outbuf, nl); - strcat(outbuf, nl); - } + else if (!strcasecmp(tag, "H2")) { + strcat(outbuf, nl); + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "H1")) { - strcat(outbuf, nl); - strcat(outbuf, nl); - } + else if (!strcasecmp(tag, "H3")) { + strcat(outbuf, nl); + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "H2")) { - strcat(outbuf, nl); - strcat(outbuf, nl); - } + else if (!strcasecmp(tag, "H4")) { + strcat(outbuf, nl); + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "H3")) { - strcat(outbuf, nl); - strcat(outbuf, nl); - } + else if (!strcasecmp(tag, "/H1")) { + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "H4")) { - strcat(outbuf, nl); - strcat(outbuf, nl); - } + else if (!strcasecmp(tag, "/H2")) { + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "/H1")) { - strcat(outbuf, nl); - } + else if (!strcasecmp(tag, "/H3")) { + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "/H2")) { - strcat(outbuf, nl); - } + else if (!strcasecmp(tag, "/H4")) { + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "/H3")) { - strcat(outbuf, nl); + else if (!strcasecmp(tag, "HR")) { + strcat(outbuf, nl); + strcat(outbuf, " "); + for (j = 0; j < screenwidth - 2; ++j) { + strcat(outbuf, "-"); } + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "/H4")) { - strcat(outbuf, nl); + else if ( (!strcasecmp(tag, "B")) + || (!strcasecmp(tag, "STRONG")) + ) { + if (ansi) { + strcat(outbuf, "\033[1m"); } - - else if (!strcasecmp(tag, "HR")) { - strcat(outbuf, nl); - strcat(outbuf, " "); - for (j = 0; j < screenwidth - 2; ++j) { - strcat(outbuf, "-"); - } - strcat(outbuf, nl); + } + else if ( (!strcasecmp(tag, "/B")) + || (!strcasecmp(tag, "/STRONG")) + ) { + if (ansi) { + strcat(outbuf, "\033[22m"); } + } - else if ( (!strcasecmp(tag, "B")) - || (!strcasecmp(tag, "STRONG")) - ) { - if (ansi) { - strcat(outbuf, "\033[1m"); - } - } - else if ( (!strcasecmp(tag, "/B")) - || (!strcasecmp(tag, "/STRONG")) - ) { - if (ansi) { - strcat(outbuf, "\033[22m"); - } + else if ( (!strcasecmp(tag, "I")) + || (!strcasecmp(tag, "EM")) + ) { + if (ansi) { + strcat(outbuf, "\033[3m"); } + } - else if ( (!strcasecmp(tag, "I")) - || (!strcasecmp(tag, "EM")) - ) { - if (ansi) { - strcat(outbuf, "\033[3m"); - } + else if ( (!strcasecmp(tag, "/I")) + || (!strcasecmp(tag, "/EM")) + ) { + if (ansi) { + strcat(outbuf, "\033[23m"); } + } - else if ( (!strcasecmp(tag, "/I")) - || (!strcasecmp(tag, "/EM")) - ) { - if (ansi) { - strcat(outbuf, "\033[23m"); - } + else if (!strcasecmp(tag, "U")) { + if (ansi) { + strcat(outbuf, "\033[4m"); } + } - else if (!strcasecmp(tag, "U")) { - if (ansi) { - strcat(outbuf, "\033[4m"); - } + else if (!strcasecmp(tag, "/U")) { + if (ansi) { + strcat(outbuf, "\033[24m"); } + } - else if (!strcasecmp(tag, "/U")) { - if (ansi) { - strcat(outbuf, "\033[24m"); - } - } + else if (!strcasecmp(tag, "BR")) { + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "BR")) { - strcat(outbuf, nl); - } + else if (!strcasecmp(tag, "TR")) { + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "TR")) { - strcat(outbuf, nl); - } + else if (!strcasecmp(tag, "/TABLE")) { + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "/TABLE")) { - strcat(outbuf, nl); + else if (!strcasecmp(tag, "BLOCKQUOTE")) { + ++blockquote; + strcpy(nl, "\n"); + if ((blockquote == 1) && (ansi)) { + strcat(nl, "\033[2m\033[2m"); } - - else if (!strcasecmp(tag, "BLOCKQUOTE")) { - ++blockquote; - strcpy(nl, "\n"); - if ((blockquote == 1) && (ansi)) { - strcat(nl, "\033[2m\033[2m"); - } - for (j = 0; j < blockquote; ++j) { - strcat(nl, ">"); - } - strcat(outbuf, nl); + for (j = 0; j < blockquote; ++j) { + strcat(nl, ">"); } + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "/BLOCKQUOTE")) { - strcat(outbuf, "\n"); - --blockquote; - if ((blockquote == 0) && (ansi)) { - strcat(outbuf, "\033[22m\033[22m"); - } - strcpy(nl, "\n"); - for (j = 0; j < blockquote; ++j) { - strcat(nl, ">"); - } - strcat(outbuf, nl); + else if (!strcasecmp(tag, "/BLOCKQUOTE")) { + strcat(outbuf, "\n"); + --blockquote; + if ((blockquote == 0) && (ansi)) { + strcat(outbuf, "\033[22m\033[22m"); } - - else if (!strcasecmp(tag, "STYLE")) { - ++styletag; - if (styletag == 1) { - styletag_start = strlen(outbuf); - } + strcpy(nl, "\n"); + for (j = 0; j < blockquote; ++j) { + strcat(nl, ">"); } + strcat(outbuf, nl); + } - else if (!strcasecmp(tag, "/STYLE")) { - --styletag; - if (styletag == 0) { - outbuf[styletag_start] = 0; - } + else if (!strcasecmp(tag, "STYLE")) { + ++styletag; + if (styletag == 1) { + styletag_start = strlen(outbuf); } - } - else if ((nest > 0) && (strlen(tag) < (sizeof(tag) - 1))) { - tag[strlen(tag) + 1] = 0; - tag[strlen(tag)] = ch; + else if (!strcasecmp(tag, "/STYLE")) { + --styletag; + if (styletag == 0) { + outbuf[styletag_start] = 0; + } } - else if ((!nest) && (styletag == 0)) { - outbuf[strlen(outbuf) + 1] = 0; - outbuf[strlen(outbuf)] = ch; - } } - strcpy(inbuf, &inbuf[i]); - } - // Convert &; tags to the forbidden characters - if (!IsEmptyStr(outbuf)) - for (i = 0; !IsEmptyStr(&outbuf[i]); ++i) { + else if ((nest > 0) && (strlen(tag) < (sizeof(tag) - 1))) { + tag[strlen(tag) + 1] = 0; + tag[strlen(tag)] = ch; + } - // Character entity references - if (!strncasecmp(&outbuf[i], " ", 6)) { - outbuf[i] = ' '; - strcpy(&outbuf[i + 1], &outbuf[i + 6]); - } + else if ((!nest) && (styletag == 0)) { + outbuf[strlen(outbuf) + 1] = 0; + outbuf[strlen(outbuf)] = ch; + } + } + strcpy(inbuf, &inbuf[i]); + } - if (!strncasecmp(&outbuf[i], " ", 6)) { - outbuf[i] = ' '; - strcpy(&outbuf[i + 1], &outbuf[i + 6]); - } + // Convert &; tags to the forbidden characters + if (!IsEmptyStr(outbuf)) + for (i = 0; !IsEmptyStr(&outbuf[i]); ++i) { - if (!strncasecmp(&outbuf[i], " ", 6)) { - outbuf[i] = ' '; - strcpy(&outbuf[i + 1], &outbuf[i + 6]); - } + // Character entity references + if (!strncasecmp(&outbuf[i], " ", 6)) { + outbuf[i] = ' '; + strcpy(&outbuf[i + 1], &outbuf[i + 6]); + } - if (!strncasecmp(&outbuf[i], " ", 8)) { - outbuf[i] = ' '; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } + if (!strncasecmp(&outbuf[i], " ", 6)) { + outbuf[i] = ' '; + strcpy(&outbuf[i + 1], &outbuf[i + 6]); + } - else if (!strncasecmp(&outbuf[i], "<", 4)) { - outbuf[i] = '<'; - strcpy(&outbuf[i + 1], &outbuf[i + 4]); - } + if (!strncasecmp(&outbuf[i], " ", 6)) { + outbuf[i] = ' '; + strcpy(&outbuf[i + 1], &outbuf[i + 6]); + } - else if (!strncasecmp(&outbuf[i], ">", 4)) { - outbuf[i] = '>'; - strcpy(&outbuf[i + 1], &outbuf[i + 4]); - } + if (!strncasecmp(&outbuf[i], " ", 8)) { + outbuf[i] = ' '; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "&", 5)) { - strcpy(&outbuf[i + 1], &outbuf[i + 5]); - } + else if (!strncasecmp(&outbuf[i], "<", 4)) { + outbuf[i] = '<'; + strcpy(&outbuf[i + 1], &outbuf[i + 4]); + } - else if (!strncasecmp(&outbuf[i], """, 6)) { - outbuf[i] = '\"'; - strcpy(&outbuf[i + 1], &outbuf[i + 6]); - } + else if (!strncasecmp(&outbuf[i], ">", 4)) { + outbuf[i] = '>'; + strcpy(&outbuf[i + 1], &outbuf[i + 4]); + } - else if (!strncasecmp(&outbuf[i], "‘", 7)) { - outbuf[i] = '`'; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } + else if (!strncasecmp(&outbuf[i], "&", 5)) { + strcpy(&outbuf[i + 1], &outbuf[i + 5]); + } - else if (!strncasecmp(&outbuf[i], "’", 7)) { - outbuf[i] = '\''; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } + else if (!strncasecmp(&outbuf[i], """, 6)) { + outbuf[i] = '\"'; + strcpy(&outbuf[i + 1], &outbuf[i + 6]); + } - else if (!strncasecmp(&outbuf[i], "©", 6)) { - outbuf[i] = '('; - outbuf[i + 1] = 'c'; - outbuf[i + 2] = ')'; - strcpy(&outbuf[i + 3], &outbuf[i + 6]); - } + else if (!strncasecmp(&outbuf[i], "‘", 7)) { + outbuf[i] = '`'; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - else if (!strncasecmp(&outbuf[i], "•", 6)) { - outbuf[i] = ' '; - outbuf[i + 1] = '*'; - outbuf[i + 2] = ' '; - strcpy(&outbuf[i + 3], &outbuf[i + 6]); - } + else if (!strncasecmp(&outbuf[i], "’", 7)) { + outbuf[i] = '\''; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - else if (!strncasecmp(&outbuf[i], "…", 8)) { - outbuf[i] = '.'; - outbuf[i + 1] = '.'; - outbuf[i + 2] = '.'; - strcpy(&outbuf[i + 3], &outbuf[i + 8]); - } + else if (!strncasecmp(&outbuf[i], "©", 6)) { + outbuf[i] = '('; + outbuf[i + 1] = 'c'; + outbuf[i + 2] = ')'; + strcpy(&outbuf[i + 3], &outbuf[i + 6]); + } - else if (!strncasecmp(&outbuf[i], "™", 7)) { - outbuf[i] = '('; - outbuf[i + 1] = 't'; - outbuf[i + 2] = 'm'; - outbuf[i + 3] = ')'; - strcpy(&outbuf[i + 4], &outbuf[i + 7]); - } + else if (!strncasecmp(&outbuf[i], "•", 6)) { + outbuf[i] = ' '; + outbuf[i + 1] = '*'; + outbuf[i + 2] = ' '; + strcpy(&outbuf[i + 3], &outbuf[i + 6]); + } - else if (!strncasecmp(&outbuf[i], "®", 5)) { - outbuf[i] = '('; - outbuf[i + 1] = 'r'; - outbuf[i + 2] = ')'; - strcpy(&outbuf[i + 3], &outbuf[i + 5]); - } + else if (!strncasecmp(&outbuf[i], "…", 8)) { + outbuf[i] = '.'; + outbuf[i + 1] = '.'; + outbuf[i + 2] = '.'; + strcpy(&outbuf[i + 3], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "¼", 8)) { - outbuf[i] = '1'; - outbuf[i + 1] = '/'; - outbuf[i + 2] = '4'; - strcpy(&outbuf[i + 3], &outbuf[i + 8]); - } + else if (!strncasecmp(&outbuf[i], "™", 7)) { + outbuf[i] = '('; + outbuf[i + 1] = 't'; + outbuf[i + 2] = 'm'; + outbuf[i + 3] = ')'; + strcpy(&outbuf[i + 4], &outbuf[i + 7]); + } - else if (!strncasecmp(&outbuf[i], "½", 8)) { - outbuf[i] = '1'; - outbuf[i + 1] = '/'; - outbuf[i + 2] = '2'; - strcpy(&outbuf[i + 3], &outbuf[i + 8]); - } + else if (!strncasecmp(&outbuf[i], "®", 5)) { + outbuf[i] = '('; + outbuf[i + 1] = 'r'; + outbuf[i + 2] = ')'; + strcpy(&outbuf[i + 3], &outbuf[i + 5]); + } - else if (!strncasecmp(&outbuf[i], "¾", 8)) { - outbuf[i] = '3'; - outbuf[i + 1] = '/'; - outbuf[i + 2] = '4'; - strcpy(&outbuf[i + 3], &outbuf[i + 8]); - } + else if (!strncasecmp(&outbuf[i], "¼", 8)) { + outbuf[i] = '1'; + outbuf[i + 1] = '/'; + outbuf[i + 2] = '4'; + strcpy(&outbuf[i + 3], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "–", 7)) { - outbuf[i] = '-'; - outbuf[i + 1] = '-'; - strcpy(&outbuf[i + 2], &outbuf[i + 7]); - } + else if (!strncasecmp(&outbuf[i], "½", 8)) { + outbuf[i] = '1'; + outbuf[i + 1] = '/'; + outbuf[i + 2] = '2'; + strcpy(&outbuf[i + 3], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "—", 7)) { - outbuf[i] = '-'; - outbuf[i + 1] = '-'; - outbuf[i + 2] = '-'; - strcpy(&outbuf[i + 3], &outbuf[i + 7]); - } + else if (!strncasecmp(&outbuf[i], "¾", 8)) { + outbuf[i] = '3'; + outbuf[i + 1] = '/'; + outbuf[i + 2] = '4'; + strcpy(&outbuf[i + 3], &outbuf[i + 8]); + } - else if (!strncmp(&outbuf[i], "Ç", 8)) { - outbuf[i] = 'C'; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } + else if (!strncasecmp(&outbuf[i], "–", 7)) { + outbuf[i] = '-'; + outbuf[i + 1] = '-'; + strcpy(&outbuf[i + 2], &outbuf[i + 7]); + } - else if (!strncasecmp(&outbuf[i], "ç", 8)) { - outbuf[i] = 'c'; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } + else if (!strncasecmp(&outbuf[i], "—", 7)) { + outbuf[i] = '-'; + outbuf[i + 1] = '-'; + outbuf[i + 2] = '-'; + strcpy(&outbuf[i + 3], &outbuf[i + 7]); + } - else if (!strncmp(&outbuf[i], "È", 8)) { - outbuf[i] = 'E'; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } + else if (!strncmp(&outbuf[i], "Ç", 8)) { + outbuf[i] = 'C'; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "è", 8)) { - outbuf[i] = 'e'; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } + else if (!strncasecmp(&outbuf[i], "ç", 8)) { + outbuf[i] = 'c'; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncmp(&outbuf[i], "Ê", 7)) { - outbuf[i] = 'E'; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } + else if (!strncmp(&outbuf[i], "È", 8)) { + outbuf[i] = 'E'; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "ê", 7)) { - outbuf[i] = 'e'; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } + else if (!strncasecmp(&outbuf[i], "è", 8)) { + outbuf[i] = 'e'; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncmp(&outbuf[i], "É", 8)) { - outbuf[i] = 'E'; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } + else if (!strncmp(&outbuf[i], "Ê", 7)) { + outbuf[i] = 'E'; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - else if (!strncasecmp(&outbuf[i], "é", 8)) { - outbuf[i] = 'e'; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } + else if (!strncasecmp(&outbuf[i], "ê", 7)) { + outbuf[i] = 'e'; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - else if (!strncmp(&outbuf[i], "À", 8)) { - outbuf[i] = 'A'; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } + else if (!strncmp(&outbuf[i], "É", 8)) { + outbuf[i] = 'E'; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "à", 8)) { - outbuf[i] = 'a'; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } + else if (!strncasecmp(&outbuf[i], "é", 8)) { + outbuf[i] = 'e'; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "“", 7)) { - outbuf[i] = '\"'; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } + else if (!strncmp(&outbuf[i], "À", 8)) { + outbuf[i] = 'A'; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "”", 7)) { - outbuf[i] = '\"'; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } + else if (!strncasecmp(&outbuf[i], "à", 8)) { + outbuf[i] = 'a'; + strcpy(&outbuf[i + 1], &outbuf[i + 8]); + } - else if (!strncasecmp(&outbuf[i], "´", 7)) { - outbuf[i] = '\''; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } + else if (!strncasecmp(&outbuf[i], "“", 7)) { + outbuf[i] = '\"'; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - else if (!strncasecmp(&outbuf[i], "’", 7)) { - outbuf[i] = '\''; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } + else if (!strncasecmp(&outbuf[i], "”", 7)) { + outbuf[i] = '\"'; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - else if (!strncasecmp(&outbuf[i], "–", 7)) { - outbuf[i] = '-'; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } + else if (!strncasecmp(&outbuf[i], "´", 7)) { + outbuf[i] = '\''; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - // two-digit decimal equivalents - else if ( outbuf[i] == '&' - && outbuf[i + 1] == '#' - && isdigit(outbuf[i + 2]) - && isdigit(outbuf[i + 3]) - && (outbuf[i + 4] == ';') - ) { - scanch = 0; - sscanf(&outbuf[i + 2], "%02d", &scanch); - outbuf[i] = scanch; - strcpy(&outbuf[i + 1], &outbuf[i + 5]); - } + else if (!strncasecmp(&outbuf[i], "’", 7)) { + outbuf[i] = '\''; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - // three-digit decimal equivalents - else if ( outbuf[i] == '&' - && outbuf[i + 1] == '#' - && isdigit(outbuf[i + 2]) - && isdigit(outbuf[i + 3]) - && isdigit(outbuf[i + 4]) - && (outbuf[i + 5] == ';') - ) { - scanch = 0; - sscanf(&outbuf[i + 2], "%03d", &scanch); - outbuf[i] = scanch; - strcpy(&outbuf[i + 1], &outbuf[i + 6]); - } + else if (!strncasecmp(&outbuf[i], "–", 7)) { + outbuf[i] = '-'; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); + } - // four-digit decimal equivalents - else if ( outbuf[i] == '&' - && outbuf[i + 1] == '#' - && isdigit(outbuf[i + 2]) - && isdigit(outbuf[i + 3]) - && isdigit(outbuf[i + 4]) - && isdigit(outbuf[i + 5]) - && (outbuf[i + 6] == ';') - ) { - scanch = 0; - sscanf(&outbuf[i + 2], "%04d", &scanch); - outbuf[i] = scanch; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } + // two-digit decimal equivalents + else if ( outbuf[i] == '&' + && outbuf[i + 1] == '#' + && isdigit(outbuf[i + 2]) + && isdigit(outbuf[i + 3]) + && (outbuf[i + 4] == ';') + ) { + scanch = 0; + sscanf(&outbuf[i + 2], "%02d", &scanch); + outbuf[i] = scanch; + strcpy(&outbuf[i + 1], &outbuf[i + 5]); + } + // three-digit decimal equivalents + else if ( outbuf[i] == '&' + && outbuf[i + 1] == '#' + && isdigit(outbuf[i + 2]) + && isdigit(outbuf[i + 3]) + && isdigit(outbuf[i + 4]) + && (outbuf[i + 5] == ';') + ) { + scanch = 0; + sscanf(&outbuf[i + 2], "%03d", &scanch); + outbuf[i] = scanch; + strcpy(&outbuf[i + 1], &outbuf[i + 6]); } - // Make sure the output buffer is big enough - if ((output_len + strlen(outbuf) + SIZ) > outptr_buffer_size) { - outptr_buffer_size += SIZ; - outptr = realloc(outptr, outptr_buffer_size); - if (outptr == NULL) { - abort(); + // four-digit decimal equivalents + else if ( outbuf[i] == '&' + && outbuf[i + 1] == '#' + && isdigit(outbuf[i + 2]) + && isdigit(outbuf[i + 3]) + && isdigit(outbuf[i + 4]) + && isdigit(outbuf[i + 5]) + && (outbuf[i + 6] == ';') + ) { + scanch = 0; + sscanf(&outbuf[i + 2], "%04d", &scanch); + outbuf[i] = scanch; + strcpy(&outbuf[i + 1], &outbuf[i + 7]); } + } - // Output any lines terminated with hard line breaks - do { - did_out = 0; - if (strlen(outbuf) > 0) { - for (i = 0; i < strlen(outbuf); ++i) { - if ((i < (screenwidth - 2)) && (outbuf[i] == '\n')) { + // Make sure the output buffer is big enough + if ((output_len + strlen(outbuf) + SIZ) > outptr_buffer_size) { + outptr_buffer_size += SIZ; + outptr = realloc(outptr, outptr_buffer_size); + if (outptr == NULL) { + abort(); + } + } - strncpy(&outptr[output_len], outbuf, i + 1); - output_len += (i + 1); + // Output any lines terminated with hard line breaks + do { + did_out = 0; + if (strlen(outbuf) > 0) { + for (i = 0; i < strlen(outbuf); ++i) { + if ((i < (screenwidth - 2)) && (outbuf[i] == '\n')) { - strcpy(outbuf, &outbuf[i + 1]); - i = 0; - did_out = 1; - } + strncpy(&outptr[output_len], outbuf, i + 1); + output_len += (i + 1); + + strcpy(outbuf, &outbuf[i + 1]); + i = 0; + did_out = 1; } } - } while (did_out); - - // Add soft line breaks - if (strlen(outbuf) > (screenwidth - 2)) { - rb = (-1); - for (i = 0; i < (screenwidth - 2); ++i) { - if (outbuf[i] == 32) - rb = i; - } - if (rb >= 0) { - strncpy(&outptr[output_len], outbuf, rb); - output_len += rb; - strcpy(&outptr[output_len], nl); - output_len += strlen(nl); - strcpy(outbuf, &outbuf[rb + 1]); - } - else { - strncpy(&outptr[output_len], outbuf, screenwidth - 2); - output_len += (screenwidth - 2); - strcpy(&outptr[output_len], nl); - output_len += strlen(nl); - strcpy(outbuf, &outbuf[screenwidth - 2]); - } } + } while (did_out); + + // Add soft line breaks + if (strlen(outbuf) > (screenwidth - 2)) { + rb = (-1); + for (i = 0; i < (screenwidth - 2); ++i) { + if (outbuf[i] == 32) + rb = i; + } + if (rb >= 0) { + strncpy(&outptr[output_len], outbuf, rb); + output_len += rb; + strcpy(&outptr[output_len], nl); + output_len += strlen(nl); + strcpy(outbuf, &outbuf[rb + 1]); + } + else { + strncpy(&outptr[output_len], outbuf, screenwidth - 2); + output_len += (screenwidth - 2); + strcpy(&outptr[output_len], nl); + output_len += strlen(nl); + strcpy(outbuf, &outbuf[screenwidth - 2]); + } + } - } while (done_reading == 0); + free(inbuf); strcpy(&outptr[output_len], outbuf); output_len += strlen(outbuf); -- 2.39.2 From d45f8494bbc9bec9f9ca0dc1cb1d1760f84974be Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Fri, 31 May 2024 01:21:00 +0000 Subject: [PATCH 04/16] html_to_ascii: display alt text --- libcitadel/lib/html_to_ascii.c | 328 +++++++++++++++++++-------------- libcitadel/lib/libcitadel.h | 3 +- 2 files changed, 194 insertions(+), 137 deletions(-) diff --git a/libcitadel/lib/html_to_ascii.c b/libcitadel/lib/html_to_ascii.c index 29796f8d3..4e5f7d2ac 100644 --- a/libcitadel/lib/html_to_ascii.c +++ b/libcitadel/lib/html_to_ascii.c @@ -30,6 +30,8 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned int inbuf_len = 0; char outbuf[SIZ]; char tag[1024]; + char *tag_start = NULL; + char *tag_end = NULL; const char *inptr; char *outptr; size_t outptr_buffer_size; @@ -42,6 +44,7 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned char nl[128]; int ansi = (flags & H2A_ANSI) ? 1 : 0; + int sixel = (flags & H2A_SIXEL) ? 1 : 0; tag[0] = '\0'; strcpy(nl, "\n"); @@ -88,200 +91,253 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned strcpy(sp, sp+1); } + // Run through the markup performing the conversion. for (i = 0; inbuf[i]; ++i) { ch = inbuf[i]; - if (ch == '<') { + + // Keep track of how many angle brackets were found in case someone is sloppy with them + // or tries to nest tags. If nest is 0 then we are within text; if it is nonzero then we + // are within a tag. + + if (ch == '<') { // We have hit the beginning of a tag. ++nest; + tag_start = &inbuf[i+1]; strcpy(tag, ""); } - else if (ch == '>') { // We have a tag. + else if (ch == '>') { // We have hit the end of a tag. if (nest > 0) { --nest; } + if (nest == 0) { + tag_end = &inbuf[i]; - // right about here is where we could add things - // like img2sixel handling or url footnote gathering - - // Unqualify the tag (truncate at first space) - char *tagsp = strchr(tag, ' '); - if (tagsp) { - *tagsp = 0; - } + size_t tag_len = tag_end - tag_start; + if (tag_len >= sizeof(tag)) { + tag_len = sizeof(tag); + } + strncpy(tag, tag_start, tag_len); + tag[tag_len] = 0; - if (!strcasecmp(tag, "P")) { - strcat(outbuf, nl); - strcat(outbuf, nl); - } + // Unqualify the tag (truncate at first space) + char *tagsp = strchr(tag, ' '); + if (tagsp) { + *tagsp = 0; + } - if (!strcasecmp(tag, "/DIV")) { - strcat(outbuf, nl); - strcat(outbuf, nl); - } + // IMG tag on sixel terminals -- try to display the image + if ( (!strcasecmp(tag, "img")) && sixel) { + char *q1, *q2; + + // look for src attribute + char *src = bmstrcasestr(tag_start, "src="); + q1 = q2 = NULL; + if (src && src"); + + else if (!strcasecmp(tag, "/TABLE")) { + strcat(outbuf, nl); } - strcat(outbuf, nl); - } - else if (!strcasecmp(tag, "/BLOCKQUOTE")) { - strcat(outbuf, "\n"); - --blockquote; - if ((blockquote == 0) && (ansi)) { - strcat(outbuf, "\033[22m\033[22m"); + else if (!strcasecmp(tag, "BLOCKQUOTE")) { + ++blockquote; + strcpy(nl, "\n"); + if ((blockquote == 1) && (ansi)) { + strcat(nl, "\033[2m\033[2m"); + } + for (j = 0; j < blockquote; ++j) { + strcat(nl, ">"); + } + strcat(outbuf, nl); } - strcpy(nl, "\n"); - for (j = 0; j < blockquote; ++j) { - strcat(nl, ">"); + + else if (!strcasecmp(tag, "/BLOCKQUOTE")) { + strcat(outbuf, "\n"); + --blockquote; + if ((blockquote == 0) && (ansi)) { + strcat(outbuf, "\033[22m\033[22m"); + } + strcpy(nl, "\n"); + for (j = 0; j < blockquote; ++j) { + strcat(nl, ">"); + } + strcat(outbuf, nl); } - strcat(outbuf, nl); - } - else if (!strcasecmp(tag, "STYLE")) { - ++styletag; - if (styletag == 1) { - styletag_start = strlen(outbuf); + else if (!strcasecmp(tag, "STYLE")) { + ++styletag; + if (styletag == 1) { + styletag_start = strlen(outbuf); + } } - } - else if (!strcasecmp(tag, "/STYLE")) { - --styletag; - if (styletag == 0) { - outbuf[styletag_start] = 0; + else if (!strcasecmp(tag, "/STYLE")) { + --styletag; + if (styletag == 0) { + outbuf[styletag_start] = 0; + } } } } - else if ((nest > 0) && (strlen(tag) < (sizeof(tag) - 1))) { - tag[strlen(tag) + 1] = 0; - tag[strlen(tag)] = ch; - } - + // copy non-tag text to the output buffer else if ((!nest) && (styletag == 0)) { outbuf[strlen(outbuf) + 1] = 0; outbuf[strlen(outbuf)] = ch; } } - strcpy(inbuf, &inbuf[i]); } // Convert &; tags to the forbidden characters diff --git a/libcitadel/lib/libcitadel.h b/libcitadel/lib/libcitadel.h index 202b309b7..c088e9ddf 100644 --- a/libcitadel/lib/libcitadel.h +++ b/libcitadel/lib/libcitadel.h @@ -434,7 +434,8 @@ void LoadEntityList(char *FileName); void utf8ify_rfc822_string(char *buf); // flags for html_to_ascii -#define H2A_ANSI 0x01 +#define H2A_ANSI 0x01 // it is acceptable to display ANSI graphics on this terminal +#define H2A_SIXEL 0x02 // sixel graphics are supported (not yet fully implemented) char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned int flags); -- 2.39.2 From 3a2ae837f165cd0e567e55446e78fef7432c4508 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Fri, 31 May 2024 02:29:41 +0000 Subject: [PATCH 05/16] Don't run this, it has a bug --- libcitadel/lib/html_to_ascii.c | 40 ++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/libcitadel/lib/html_to_ascii.c b/libcitadel/lib/html_to_ascii.c index 4e5f7d2ac..c05b1cd6a 100644 --- a/libcitadel/lib/html_to_ascii.c +++ b/libcitadel/lib/html_to_ascii.c @@ -28,11 +28,10 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned int flags) { char *inbuf = NULL; int inbuf_len = 0; - char outbuf[SIZ]; + char outbuf[128]; char tag[1024]; char *tag_start = NULL; char *tag_end = NULL; - const char *inptr; char *outptr; size_t outptr_buffer_size; size_t output_len = 0; @@ -48,13 +47,12 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned tag[0] = '\0'; strcpy(nl, "\n"); - inptr = inputmsg; strcpy(outbuf, ""); if (msglen == 0) { msglen = strlen(inputmsg); } - outptr_buffer_size = strlen(inptr) + SIZ; + outptr_buffer_size = msglen + SIZ; outptr = malloc(outptr_buffer_size); if (outptr == NULL) { return NULL; @@ -92,9 +90,10 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned } // Run through the markup performing the conversion. - for (i = 0; inbuf[i]; ++i) { - ch = inbuf[i]; - + char *inptr = inbuf; + while (ch = inptr[0], ch != 0) { + //for (i = 0; inbuf[i]; ++i) { + //ch = inbuf[i]; // Keep track of how many angle brackets were found in case someone is sloppy with them // or tries to nest tags. If nest is 0 then we are within text; if it is nonzero then we @@ -102,7 +101,7 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned if (ch == '<') { // We have hit the beginning of a tag. ++nest; - tag_start = &inbuf[i+1]; + tag_start = inptr + 1; strcpy(tag, ""); } @@ -111,7 +110,7 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned --nest; } if (nest == 0) { - tag_end = &inbuf[i]; + tag_end = inptr; size_t tag_len = tag_end - tag_start; if (tag_len >= sizeof(tag)) { @@ -337,9 +336,16 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned outbuf[strlen(outbuf) + 1] = 0; outbuf[strlen(outbuf)] = ch; } + + inptr++; } } + + + + + // Convert &; tags to the forbidden characters if (!IsEmptyStr(outbuf)) for (i = 0; !IsEmptyStr(&outbuf[i]); ++i) { @@ -592,6 +598,11 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned } } + + + + + // Output any lines terminated with hard line breaks do { did_out = 0; @@ -610,6 +621,9 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned } } while (did_out); + + free(inbuf); + // Add soft line breaks if (strlen(outbuf) > (screenwidth - 2)) { rb = (-1); @@ -633,16 +647,20 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned } } - free(inbuf); strcpy(&outptr[output_len], outbuf); output_len += strlen(outbuf); - // Strip leading/trailing whitespace. + + + + // Strip leading whitespace while ((output_len > 0) && (isspace(outptr[0]))) { strcpy(outptr, &outptr[1]); --output_len; } + + // Strip trailing whitespace while ((output_len > 0) && (isspace(outptr[output_len - 1]))) { outptr[output_len - 1] = 0; --output_len; -- 2.39.2 From be9e72c9c8081d96c397d2a815d5841dc783bea2 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Fri, 31 May 2024 05:27:00 +0000 Subject: [PATCH 06/16] html_to_ascii.c: major overhaul to buffer use. This commit does away with the smaller buffers for reading and writing individual lines. A single input buffer is scanned and a single output buffer is written. The output buffer is a StrBuf during operation but is converted to a plain C string for final output. I did this because I want to be able to support inline graphics when the terminal supports Sixel. That isn't written yet but the overhaul now allows us to extract arbitrarily long image urls (they could be huge if they are data urls) and output the sixel data in the correct place. Theoretically that should write pretty fast. --- libcitadel/lib/html_to_ascii.c | 564 +++++++++++---------------------- libcitadel/lib/libcitadel.h | 1 + libcitadel/lib/stringbuf.c | 17 +- 3 files changed, 204 insertions(+), 378 deletions(-) diff --git a/libcitadel/lib/html_to_ascii.c b/libcitadel/lib/html_to_ascii.c index c05b1cd6a..66c7a3b6c 100644 --- a/libcitadel/lib/html_to_ascii.c +++ b/libcitadel/lib/html_to_ascii.c @@ -16,6 +16,36 @@ #include #include "libcitadel.h" +// silly but this shuts up the compiler warning about prototypes +int u8_wc_toutf8(char *dest, u_int32_t ch); + +int u8_wc_toutf8(char *dest, u_int32_t ch) { + if (ch < 0x80) { + dest[0] = (char)ch; + return 1; + } + if (ch < 0x800) { + dest[0] = (ch>>6) | 0xC0; + dest[1] = (ch & 0x3F) | 0x80; + return 2; + } + if (ch < 0x10000) { + dest[0] = (ch>>12) | 0xE0; + dest[1] = ((ch>>6) & 0x3F) | 0x80; + dest[2] = (ch & 0x3F) | 0x80; + return 3; + } + if (ch < 0x110000) { + dest[0] = (ch>>18) | 0xF0; + dest[1] = ((ch>>12) & 0x3F) | 0x80; + dest[2] = ((ch>>6) & 0x3F) | 0x80; + dest[3] = (ch & 0x3F) | 0x80; + return 4; + } + return 0; +} + + // Convert HTML to plain text. // @@ -23,43 +53,38 @@ // msglen = stop reading after this many bytes // screenwidth = desired output screenwidth // flags = Flags that can be set: -// H2A_ANSI = Output ANSI terminal escape sequences +// H2A_ANSI = Output ANSI terminal escape sequences +// H2A_SIXEL = Output Sixel graphics (not yet implemented) // char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned int flags) { char *inbuf = NULL; int inbuf_len = 0; - char outbuf[128]; char tag[1024]; char *tag_start = NULL; char *tag_end = NULL; + StrBuf *out; char *outptr; - size_t outptr_buffer_size; - size_t output_len = 0; - int i, j, ch, did_out, rb, scanch; - int nest = 0; // angle bracket nesting level + int j; + char ch; + int tag_nesting_level = 0; // angle bracket nesting level int blockquote = 0; // BLOCKQUOTE nesting level int styletag = 0; // STYLE tag nesting level - int styletag_start = 0; - char nl[128]; + char nl[128]; // The current value of what a "newline" looks like (changes during blockquotes) - int ansi = (flags & H2A_ANSI) ? 1 : 0; - int sixel = (flags & H2A_SIXEL) ? 1 : 0; + int ansi = (flags & H2A_ANSI) ? 1 : 0; // Output to a terminal that can accept ANSI escape sequences + int sixel = (flags & H2A_SIXEL) ? 1 : 0; // Output to a terminal that can accept Sixel graphics + + out = NewStrBuf(); + if (!out) { + return(NULL); + } tag[0] = '\0'; strcpy(nl, "\n"); - strcpy(outbuf, ""); if (msglen == 0) { msglen = strlen(inputmsg); } - outptr_buffer_size = msglen + SIZ; - outptr = malloc(outptr_buffer_size); - if (outptr == NULL) { - return NULL; - } - strcpy(outptr, ""); - output_len = 0; - inbuf = strdup(inputmsg); if (!inbuf) { return NULL; @@ -91,25 +116,24 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned // Run through the markup performing the conversion. char *inptr = inbuf; + int linelen = 0; while (ch = inptr[0], ch != 0) { - //for (i = 0; inbuf[i]; ++i) { - //ch = inbuf[i]; // Keep track of how many angle brackets were found in case someone is sloppy with them // or tries to nest tags. If nest is 0 then we are within text; if it is nonzero then we // are within a tag. if (ch == '<') { // We have hit the beginning of a tag. - ++nest; + ++tag_nesting_level; tag_start = inptr + 1; strcpy(tag, ""); } else if (ch == '>') { // We have hit the end of a tag. - if (nest > 0) { - --nest; + if (tag_nesting_level > 0) { + --tag_nesting_level; } - if (nest == 0) { + if (tag_nesting_level == 0) { tag_end = inptr; size_t tag_len = tag_end - tag_start; @@ -164,88 +188,101 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned q2 = strchr(q1, '\''); } if (q1 && q1"); } - strcat(outbuf, nl); + StrBufAppendBufPlain(out, nl, -1, 0); + linelen = 0; } else if (!strcasecmp(tag, "/BLOCKQUOTE")) { - strcat(outbuf, "\n"); + StrBufAppendBufPlain(out, HKEY("\n"), 0); --blockquote; if ((blockquote == 0) && (ansi)) { - strcat(outbuf, "\033[22m\033[22m"); + StrBufAppendBufPlain(out, HKEY("\033[22m\033[22m"), 0); } strcpy(nl, "\n"); for (j = 0; j < blockquote; ++j) { strcat(nl, ">"); } - strcat(outbuf, nl); + StrBufAppendBufPlain(out, nl, -1, 0); + linelen = 0; } else if (!strcasecmp(tag, "STYLE")) { ++styletag; - if (styletag == 1) { - styletag_start = strlen(outbuf); - } } else if (!strcasecmp(tag, "/STYLE")) { --styletag; - if (styletag == 0) { - outbuf[styletag_start] = 0; - } } } } // copy non-tag text to the output buffer - else if ((!nest) && (styletag == 0)) { - outbuf[strlen(outbuf) + 1] = 0; - outbuf[strlen(outbuf)] = ch; - } - - inptr++; - } - } - - - - - - - // Convert &; tags to the forbidden characters - if (!IsEmptyStr(outbuf)) - for (i = 0; !IsEmptyStr(&outbuf[i]); ++i) { - - // Character entity references - if (!strncasecmp(&outbuf[i], " ", 6)) { - outbuf[i] = ' '; - strcpy(&outbuf[i + 1], &outbuf[i + 6]); - } - - if (!strncasecmp(&outbuf[i], " ", 6)) { - outbuf[i] = ' '; - strcpy(&outbuf[i + 1], &outbuf[i + 6]); - } - - if (!strncasecmp(&outbuf[i], " ", 6)) { - outbuf[i] = ' '; - strcpy(&outbuf[i + 1], &outbuf[i + 6]); - } - - if (!strncasecmp(&outbuf[i], " ", 8)) { - outbuf[i] = ' '; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } - - else if (!strncasecmp(&outbuf[i], "<", 4)) { - outbuf[i] = '<'; - strcpy(&outbuf[i + 1], &outbuf[i + 4]); - } - - else if (!strncasecmp(&outbuf[i], ">", 4)) { - outbuf[i] = '>'; - strcpy(&outbuf[i + 1], &outbuf[i + 4]); - } - - else if (!strncasecmp(&outbuf[i], "&", 5)) { - strcpy(&outbuf[i + 1], &outbuf[i + 5]); - } - - else if (!strncasecmp(&outbuf[i], """, 6)) { - outbuf[i] = '\"'; - strcpy(&outbuf[i + 1], &outbuf[i + 6]); - } - - else if (!strncasecmp(&outbuf[i], "‘", 7)) { - outbuf[i] = '`'; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } - - else if (!strncasecmp(&outbuf[i], "’", 7)) { - outbuf[i] = '\''; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } - - else if (!strncasecmp(&outbuf[i], "©", 6)) { - outbuf[i] = '('; - outbuf[i + 1] = 'c'; - outbuf[i + 2] = ')'; - strcpy(&outbuf[i + 3], &outbuf[i + 6]); - } - - else if (!strncasecmp(&outbuf[i], "•", 6)) { - outbuf[i] = ' '; - outbuf[i + 1] = '*'; - outbuf[i + 2] = ' '; - strcpy(&outbuf[i + 3], &outbuf[i + 6]); - } - - else if (!strncasecmp(&outbuf[i], "…", 8)) { - outbuf[i] = '.'; - outbuf[i + 1] = '.'; - outbuf[i + 2] = '.'; - strcpy(&outbuf[i + 3], &outbuf[i + 8]); - } - - else if (!strncasecmp(&outbuf[i], "™", 7)) { - outbuf[i] = '('; - outbuf[i + 1] = 't'; - outbuf[i + 2] = 'm'; - outbuf[i + 3] = ')'; - strcpy(&outbuf[i + 4], &outbuf[i + 7]); - } - - else if (!strncasecmp(&outbuf[i], "®", 5)) { - outbuf[i] = '('; - outbuf[i + 1] = 'r'; - outbuf[i + 2] = ')'; - strcpy(&outbuf[i + 3], &outbuf[i + 5]); - } - - else if (!strncasecmp(&outbuf[i], "¼", 8)) { - outbuf[i] = '1'; - outbuf[i + 1] = '/'; - outbuf[i + 2] = '4'; - strcpy(&outbuf[i + 3], &outbuf[i + 8]); - } - - else if (!strncasecmp(&outbuf[i], "½", 8)) { - outbuf[i] = '1'; - outbuf[i + 1] = '/'; - outbuf[i + 2] = '2'; - strcpy(&outbuf[i + 3], &outbuf[i + 8]); - } - - else if (!strncasecmp(&outbuf[i], "¾", 8)) { - outbuf[i] = '3'; - outbuf[i + 1] = '/'; - outbuf[i + 2] = '4'; - strcpy(&outbuf[i + 3], &outbuf[i + 8]); - } - - else if (!strncasecmp(&outbuf[i], "–", 7)) { - outbuf[i] = '-'; - outbuf[i + 1] = '-'; - strcpy(&outbuf[i + 2], &outbuf[i + 7]); - } - - else if (!strncasecmp(&outbuf[i], "—", 7)) { - outbuf[i] = '-'; - outbuf[i + 1] = '-'; - outbuf[i + 2] = '-'; - strcpy(&outbuf[i + 3], &outbuf[i + 7]); - } - - else if (!strncmp(&outbuf[i], "Ç", 8)) { - outbuf[i] = 'C'; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } - - else if (!strncasecmp(&outbuf[i], "ç", 8)) { - outbuf[i] = 'c'; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } - - else if (!strncmp(&outbuf[i], "È", 8)) { - outbuf[i] = 'E'; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } - - else if (!strncasecmp(&outbuf[i], "è", 8)) { - outbuf[i] = 'e'; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } - - else if (!strncmp(&outbuf[i], "Ê", 7)) { - outbuf[i] = 'E'; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); + else if ((!tag_nesting_level) && (styletag == 0)) { + StrBufAppendBufPlain(out, &ch, 1, 0); + ++linelen; } - else if (!strncasecmp(&outbuf[i], "ê", 7)) { - outbuf[i] = 'e'; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } + // Handle numeric entities + if (ch == ';') { - else if (!strncmp(&outbuf[i], "É", 8)) { - outbuf[i] = 'E'; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } + u_int32_t scanch = 0; + int elen = 0; - else if (!strncasecmp(&outbuf[i], "é", 8)) { - outbuf[i] = 'e'; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } - - else if (!strncmp(&outbuf[i], "À", 8)) { - outbuf[i] = 'A'; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } - - else if (!strncasecmp(&outbuf[i], "à", 8)) { - outbuf[i] = 'a'; - strcpy(&outbuf[i + 1], &outbuf[i + 8]); - } - - else if (!strncasecmp(&outbuf[i], "“", 7)) { - outbuf[i] = '\"'; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } - - else if (!strncasecmp(&outbuf[i], "”", 7)) { - outbuf[i] = '\"'; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } - - else if (!strncasecmp(&outbuf[i], "´", 7)) { - outbuf[i] = '\''; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } - - else if (!strncasecmp(&outbuf[i], "’", 7)) { - outbuf[i] = '\''; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } - - else if (!strncasecmp(&outbuf[i], "–", 7)) { - outbuf[i] = '-'; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); - } + if ( (linelen >= 5) && (*(inptr-4) == '&') && (*(inptr-3) == '#') ) { + sscanf(inptr-2, "%02d", &scanch); + elen = 5; + } + else if ( (linelen >= 6) && (*(inptr-5) == '&') && (*(inptr-4) == '#') ) { + sscanf(inptr-3, "%03d", &scanch); + elen = 6; + } + else if ( (linelen >= 7) && (*(inptr-6) == '&') && (*(inptr-5) == '#') ) { + sscanf(inptr-3, "%04d", &scanch); + elen = 7; + } + else if ( (linelen >= 8) && (*(inptr-7) == '&') && (*(inptr-6) == '#') ) { + sscanf(inptr-4, "%05d", &scanch); + elen = 8; + } - // two-digit decimal equivalents - else if ( outbuf[i] == '&' - && outbuf[i + 1] == '#' - && isdigit(outbuf[i + 2]) - && isdigit(outbuf[i + 3]) - && (outbuf[i + 4] == ';') - ) { - scanch = 0; - sscanf(&outbuf[i + 2], "%02d", &scanch); - outbuf[i] = scanch; - strcpy(&outbuf[i + 1], &outbuf[i + 5]); - } + if (scanch) { + StrBufCutRight(out, elen); + linelen -= elen; - // three-digit decimal equivalents - else if ( outbuf[i] == '&' - && outbuf[i + 1] == '#' - && isdigit(outbuf[i + 2]) - && isdigit(outbuf[i + 3]) - && isdigit(outbuf[i + 4]) - && (outbuf[i + 5] == ';') - ) { - scanch = 0; - sscanf(&outbuf[i + 2], "%03d", &scanch); - outbuf[i] = scanch; - strcpy(&outbuf[i + 1], &outbuf[i + 6]); - } + char utf[5]; + int ulen = u8_wc_toutf8(utf, scanch); + utf[ulen] = 0; + StrBufAppendBufPlain(out, utf, ulen, 0); + linelen += elen; + } - // four-digit decimal equivalents - else if ( outbuf[i] == '&' - && outbuf[i + 1] == '#' - && isdigit(outbuf[i + 2]) - && isdigit(outbuf[i + 3]) - && isdigit(outbuf[i + 4]) - && isdigit(outbuf[i + 5]) - && (outbuf[i + 6] == ';') - ) { - scanch = 0; - sscanf(&outbuf[i + 2], "%04d", &scanch); - outbuf[i] = scanch; - strcpy(&outbuf[i + 1], &outbuf[i + 7]); } - } - - // Make sure the output buffer is big enough - if ((output_len + strlen(outbuf) + SIZ) > outptr_buffer_size) { - outptr_buffer_size += SIZ; - outptr = realloc(outptr, outptr_buffer_size); - if (outptr == NULL) { - abort(); - } - } - - - - - - - // Output any lines terminated with hard line breaks - do { - did_out = 0; - if (strlen(outbuf) > 0) { - for (i = 0; i < strlen(outbuf); ++i) { - if ((i < (screenwidth - 2)) && (outbuf[i] == '\n')) { - - strncpy(&outptr[output_len], outbuf, i + 1); - output_len += (i + 1); - - strcpy(outbuf, &outbuf[i + 1]); - i = 0; - did_out = 1; + // Add soft line breaks when necessary + if (linelen > (screenwidth - 8)) { + char *ptr = (char *)ChrPtr(out) + StrLength(out) - linelen; + char *rightmost_space = strrchr(ptr, ' '); + if (rightmost_space && rightmost_space > ptr) { + int space_pos = rightmost_space - ChrPtr(out); + StrBufReplaceToken(out, (long)space_pos, 1, nl, strlen(nl)); + linelen = strlen(rightmost_space) - 1; } } - } - } while (did_out); - - free(inbuf); - - // Add soft line breaks - if (strlen(outbuf) > (screenwidth - 2)) { - rb = (-1); - for (i = 0; i < (screenwidth - 2); ++i) { - if (outbuf[i] == 32) - rb = i; - } - if (rb >= 0) { - strncpy(&outptr[output_len], outbuf, rb); - output_len += rb; - strcpy(&outptr[output_len], nl); - output_len += strlen(nl); - strcpy(outbuf, &outbuf[rb + 1]); - } - else { - strncpy(&outptr[output_len], outbuf, screenwidth - 2); - output_len += (screenwidth - 2); - strcpy(&outptr[output_len], nl); - output_len += strlen(nl); - strcpy(outbuf, &outbuf[screenwidth - 2]); + // Advance to the next byte of input. + inptr++; } } + free(inbuf); - - strcpy(&outptr[output_len], outbuf); - output_len += strlen(outbuf); - - - + // Convert entity tags to printable characters + StrBufReplaceAllOccurrences(out, " ", " "); + StrBufReplaceAllOccurrences(out, " ", " "); + StrBufReplaceAllOccurrences(out, " ", " "); + StrBufReplaceAllOccurrences(out, " ", " "); + StrBufReplaceAllOccurrences(out, "<", "<"); + StrBufReplaceAllOccurrences(out, ">", ">"); + StrBufReplaceAllOccurrences(out, "&", "&"); + StrBufReplaceAllOccurrences(out, """, "\""); + StrBufReplaceAllOccurrences(out, "‘", "`"); + StrBufReplaceAllOccurrences(out, "’", "'"); + StrBufReplaceAllOccurrences(out, "•", " * "); + StrBufReplaceAllOccurrences(out, "…", "…"); + StrBufReplaceAllOccurrences(out, "©", "©"); + StrBufReplaceAllOccurrences(out, "™", "™"); + StrBufReplaceAllOccurrences(out, "®", "®"); + StrBufReplaceAllOccurrences(out, "¼", "¼"); + StrBufReplaceAllOccurrences(out, "½", "½"); + StrBufReplaceAllOccurrences(out, "¾", "¾"); + StrBufReplaceAllOccurrences(out, "–", "–"); + StrBufReplaceAllOccurrences(out, "—", "—"); + StrBufReplaceAllOccurrences(out, "Ç", "Ç"); + StrBufReplaceAllOccurrences(out, "ç", "ç"); + StrBufReplaceAllOccurrences(out, "È", "È"); + StrBufReplaceAllOccurrences(out, "è", "è"); + StrBufReplaceAllOccurrences(out, "Ê", "Ê"); + StrBufReplaceAllOccurrences(out, "ê", "ê"); + StrBufReplaceAllOccurrences(out, "É", "É"); + StrBufReplaceAllOccurrences(out, "é", "é"); + StrBufReplaceAllOccurrences(out, "À", "À"); + StrBufReplaceAllOccurrences(out, "à", "à"); + StrBufReplaceAllOccurrences(out, "“", "\""); + StrBufReplaceAllOccurrences(out, "”", "\""); + StrBufReplaceAllOccurrences(out, "´", "'"); + StrBufReplaceAllOccurrences(out, "’", "'"); + StrBufReplaceAllOccurrences(out, "–", "-"); + + // Convert from a StrBuf to a plain C string + int output_len = StrLength(out); + outptr = SmashStrBuf(&out); // Strip leading whitespace while ((output_len > 0) && (isspace(outptr[0]))) { @@ -673,5 +486,4 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned } return outptr; - } diff --git a/libcitadel/lib/libcitadel.h b/libcitadel/lib/libcitadel.h index c088e9ddf..495d78da5 100644 --- a/libcitadel/lib/libcitadel.h +++ b/libcitadel/lib/libcitadel.h @@ -252,6 +252,7 @@ int StrBufReplaceToken(StrBuf *Buf, long where, long HowLong, const char *Repl, int StrBufExtract_tokenFromStr(StrBuf *dest, const char *Source, long SourceLen, int parmnum, char separator); int StrBufExtract_token(StrBuf *dest, const StrBuf *Source, int parmnum, char separator); int StrBufSub(StrBuf *dest, const StrBuf *Source, unsigned long Offset, size_t nChars); +int StrBufReplaceAllOccurrences(StrBuf *Buf, const char *fromstr, const char *tostr); unsigned long StrBufExtract_unsigned_long(const StrBuf* Source, int parmnum, char separator); long StrBufExtract_long(const StrBuf* Source, int parmnum, char separator); diff --git a/libcitadel/lib/stringbuf.c b/libcitadel/lib/stringbuf.c index 7f136deb0..fb1eb1426 100644 --- a/libcitadel/lib/stringbuf.c +++ b/libcitadel/lib/stringbuf.c @@ -913,7 +913,7 @@ void StrBufCutLeft(StrBuf *Buf, int nChars) { // Cut the trailing n Chars from the string // Buf Buffer to modify -// nChars how many chars should be trunkated? +// nChars how many chars should be truncated? void StrBufCutRight(StrBuf *Buf, int nChars) { if ((Buf == NULL) || (Buf->BufUsed == 0) || (Buf->buf == NULL)) return; @@ -929,7 +929,7 @@ void StrBufCutRight(StrBuf *Buf, int nChars) { // Cut the string after n Chars // Buf Buffer to modify -// AfternChars after how many chars should we trunkate the string? +// AfternChars after how many chars should we truncate the string? // At if non-null and points inside of our string, cut it there. void StrBufCutAt(StrBuf *Buf, int AfternChars, const char *At) { if ((Buf == NULL) || (Buf->BufUsed == 0)) return; @@ -1065,6 +1065,19 @@ int StrBufReplaceToken(StrBuf *Buf, long where, long HowLong, const char *Repl, return Buf->BufUsed; } + +int StrBufReplaceAllOccurrences(StrBuf *Buf, const char *fromstr, const char *tostr) { + + char *found; + while (found = strstr(Buf->buf, fromstr), (found != NULL)) { + StrBufReplaceToken(Buf, (found - Buf->buf), strlen(fromstr), tostr, strlen(tostr)); + } + + return Buf->BufUsed; +} + + + // Counts the numbmer of tokens in a buffer // source String to count tokens in // tok Tokenizer char to count -- 2.39.2 From 317d5e87a49835ac4da585f836611b089f406018 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Fri, 31 May 2024 17:00:10 +0000 Subject: [PATCH 07/16] work on sixel support --- libcitadel/lib/html_to_ascii.c | 30 ++++++++++++++++++++++++++---- libcitadel/lib/libcitadel.h | 2 ++ textclient/citadel.c | 1 + textclient/citadel.rc | 7 +++++++ textclient/commands.c | 6 ++++++ textclient/messages.c | 5 ++++- 6 files changed, 46 insertions(+), 5 deletions(-) diff --git a/libcitadel/lib/html_to_ascii.c b/libcitadel/lib/html_to_ascii.c index 66c7a3b6c..47e0f0c8b 100644 --- a/libcitadel/lib/html_to_ascii.c +++ b/libcitadel/lib/html_to_ascii.c @@ -16,9 +16,6 @@ #include #include "libcitadel.h" -// silly but this shuts up the compiler warning about prototypes -int u8_wc_toutf8(char *dest, u_int32_t ch); - int u8_wc_toutf8(char *dest, u_int32_t ch) { if (ch < 0x80) { dest[0] = (char)ch; @@ -46,6 +43,27 @@ int u8_wc_toutf8(char *dest, u_int32_t ch) { } +// Try to embed an image in the display stream. +// out = the StrBuf to which we are writing the display stream +// url = the URL of the image (warning: it might be a data: URL) +// display_protocol = currently only H2A_SIXEL is supported +void h2a_embed_image(StrBuf *out, char *url, int display_protocol) { + + char buf[4096]; + snprintf(buf, sizeof(buf), "curl -s '%s' | img2sixel -", url); + + FILE *cmd = popen(buf, "r"); + if (!cmd) { + return; + } + + size_t bytes; + while (bytes = fread(buf, 1, sizeof(buf), cmd), bytes>0) { + StrBufAppendBufPlain(out, buf, bytes, 0); + } + pclose(cmd); +} + // Convert HTML to plain text. // @@ -166,7 +184,11 @@ char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned q2 = strchr(q1, '\''); } if (q1 && q1content_type, "text/html")) { - converted_text = html_to_ascii(message->text, 0, screenwidth, (enable_color ? H2A_ANSI : 0)); + converted_text = html_to_ascii(message->text, 0, screenwidth, + ((enable_color ? H2A_ANSI : 0) | (rc_sixel ? H2A_SIXEL : 0)) + ); if (converted_text != NULL) { free(message->text); message->text = converted_text; -- 2.39.2 From 017a204a9ac92458a2b901cc80595ca95bc8f9bc Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Sat, 1 Jun 2024 16:31:49 +0000 Subject: [PATCH 08/16] Still working on sixel --- libcitadel/lib/html_to_ascii.c | 2 +- textclient/README.txt | 2 +- textclient/citadel.rc | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libcitadel/lib/html_to_ascii.c b/libcitadel/lib/html_to_ascii.c index 47e0f0c8b..8f3c9eca0 100644 --- a/libcitadel/lib/html_to_ascii.c +++ b/libcitadel/lib/html_to_ascii.c @@ -50,7 +50,7 @@ int u8_wc_toutf8(char *dest, u_int32_t ch) { void h2a_embed_image(StrBuf *out, char *url, int display_protocol) { char buf[4096]; - snprintf(buf, sizeof(buf), "curl -s '%s' | img2sixel -", url); + snprintf(buf, sizeof(buf), "curl -s '%s' | img2sixel - | fold", url); FILE *cmd = popen(buf, "r"); if (!cmd) { diff --git a/textclient/README.txt b/textclient/README.txt index 88fca9d5b..72abda458 100644 --- a/textclient/README.txt +++ b/textclient/README.txt @@ -2,7 +2,7 @@ This is a text mode user interface for the Citadel system. It presents a Citadel site to users in the form of a traditional BBS. -All code is Copyright (c) 1987-2022 by the citadel.org team. +All code is Copyright (c) 1987-2024 by the citadel.org team. This program is open source software. Use, duplication, and/or disclosure are subject to the GNU General Purpose License, version 3. diff --git a/textclient/citadel.rc b/textclient/citadel.rc index 263577e2b..a21113ee4 100644 --- a/textclient/citadel.rc +++ b/textclient/citadel.rc @@ -36,11 +36,11 @@ encrypt=default # ansi_color=user -# Sixel graphics support (experimental) +# Sixel graphics support (experimental and probably insecure) # Set this to "on" to output images embedded in HTML messages to the terminal # in Sixel graphics format. This obviously requires a terminal that has -# support for Sixel. It also requires the "curl" and "img2sixel" commands to -# be available on your system. +# support for Sixel. It also requires the "curl", "img2sixel", and "fold" +# commands to be available on your system. use_sixel=off # USE_BACKGROUND controls Citadel's use of the background. If it is turned -- 2.39.2 From d4be3c7fe47721ec8f4627a1c26fc19bf2682119 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Sun, 2 Jun 2024 19:25:33 +0000 Subject: [PATCH 09/16] random messing with text client I'm gonna put the sixel stuff on hold for a while and work on webcit-ng --- textclient/citadel_ipc.c | 140 +++++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 64 deletions(-) diff --git a/textclient/citadel_ipc.c b/textclient/citadel_ipc.c index 3b3cb4593..0eb5bdae5 100644 --- a/textclient/citadel_ipc.c +++ b/textclient/citadel_ipc.c @@ -144,32 +144,29 @@ static void CtdlIPC_putline(CtdlIPC * ipc, const char *buf); const char *svn_revision(void); -/* - * Does nothing. The server should always return 200. - */ -int CtdlIPCNoop(CtdlIPC * ipc) { +// Does nothing. The server should always return 200. +int CtdlIPCNoop(CtdlIPC *ipc) { char aaa[128]; - return CtdlIPCGenericCommand(ipc, "NOOP", NULL, 0, NULL, NULL, aaa); } -/* - * Does nothing interesting. The server should always return 200 - * along with your string. - */ +// Does nothing interesting. The server should always return 200 along with your string. int CtdlIPCEcho(CtdlIPC * ipc, const char *arg, char *cret) { int ret; char *aaa; - if (!arg) + if (!arg) { return -2; - if (!cret) + } + if (!cret) { return -2; + } aaa = (char *) malloc((size_t) (strlen(arg) + 6)); - if (!aaa) + if (!aaa) { return -1; + } sprintf(aaa, "ECHO %s", arg); ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret); @@ -178,12 +175,9 @@ int CtdlIPCEcho(CtdlIPC * ipc, const char *arg, char *cret) { } -/* - * Asks the server to close the connecction. - * Should always return 200. - */ +// Asks the server to close the connection. Should always return 200. int CtdlIPCQuit(CtdlIPC * ipc) { - int ret = 221; /* Default to successful quit */ + int ret = 221; // Default to successful quit char aaa[SIZ]; CtdlIPC_lock(ipc); @@ -193,22 +187,22 @@ int CtdlIPCQuit(CtdlIPC * ipc) { ret = atoi(aaa); } #ifdef HAVE_OPENSSL - if (ipc->ssl) + if (ipc->ssl) { SSL_shutdown(ipc->ssl); + } ipc->ssl = NULL; #endif - if (ipc->sock) - shutdown(ipc->sock, 2); /* Close connection; we're dead */ + if (ipc->sock) { + shutdown(ipc->sock, 2); // Close connection; we're dead. + } ipc->sock = -1; CtdlIPC_unlock(ipc); return ret; } -/* - * Asks the server to log out. Should always return 200, even if no user - * was logged in. The user will not be logged in after this! - */ +// Asks the server to log out. Should always return 200, even if no user +// was logged in. The user will not be logged in after this! int CtdlIPCLogout(CtdlIPC * ipc) { int ret; char aaa[SIZ]; @@ -222,23 +216,24 @@ int CtdlIPCLogout(CtdlIPC * ipc) { } -/* - * First stage of authentication - pass the username. Returns 300 if the - * username is able to log in, with the username correctly spelled in cret. - * Returns various 500 error codes if the user doesn't exist, etc. - */ +// First stage of authentication - pass the username. Returns 300 if the +// username is able to log in, with the username correctly spelled in cret. +// Returns various 500 error codes if the user doesn't exist, etc. int CtdlIPCTryLogin(CtdlIPC * ipc, const char *username, char *cret) { int ret; char *aaa; - if (!username) + if (!username) { return -2; - if (!cret) + } + if (!cret) { return -2; + } aaa = (char *) malloc((size_t) (strlen(username) + 6)); - if (!aaa) + if (!aaa) { return -1; + } sprintf(aaa, "USER %s", username); ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret); @@ -247,22 +242,23 @@ int CtdlIPCTryLogin(CtdlIPC * ipc, const char *username, char *cret) { } -/* - * Second stage of authentication - provide password. The server returns - * 200 and several arguments in cret relating to the user's account. - */ +// Second stage of authentication - provide password. The server returns +// 200 and several arguments in cret relating to the user's account. int CtdlIPCTryPassword(CtdlIPC * ipc, const char *passwd, char *cret) { int ret; char *aaa; - if (!passwd) + if (!passwd) { return -2; - if (!cret) + } + if (!cret) { return -2; + } aaa = (char *) malloc((size_t) (strlen(passwd) + 6)); - if (!aaa) + if (!aaa) { return -1; + } sprintf(aaa, "PASS %s", passwd); ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret); @@ -271,25 +267,26 @@ int CtdlIPCTryPassword(CtdlIPC * ipc, const char *passwd, char *cret) { } -/* - * Create a new user. This returns 200 plus the same arguments as TryPassword - * if selfservice is nonzero, unless there was a problem creating the account. - * If selfservice is zero, creates a new user but does not log out the existing - * user - intended for use by system administrators to create accounts on - * behalf of other users. - */ +// Create a new user. This returns 200 plus the same arguments as TryPassword +// if selfservice is nonzero, unless there was a problem creating the account. +// If selfservice is zero, creates a new user but does not log out the existing +// user - intended for use by system administrators to create accounts on +// behalf of other users. int CtdlIPCCreateUser(CtdlIPC * ipc, const char *username, int selfservice, char *cret) { int ret; char *aaa; - if (!username) + if (!username) { return -2; - if (!cret) + } + if (!cret) { return -2; + } aaa = (char *) malloc((size_t) (strlen(username) + 6)); - if (!aaa) + if (!aaa) { return -1; + } sprintf(aaa, "%s %s", selfservice ? "NEWU" : "CREU", username); ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret); @@ -298,21 +295,22 @@ int CtdlIPCCreateUser(CtdlIPC * ipc, const char *username, int selfservice, char } -/* - * Changes the user's password. Returns 200 if changed, errors otherwise. - */ +// Changes the user's password. Returns 200 if changed, errors otherwise. int CtdlIPCChangePassword(CtdlIPC * ipc, const char *passwd, char *cret) { int ret; char *aaa; - if (!passwd) + if (!passwd) { return -2; - if (!cret) + } + if (!cret) { return -2; + } aaa = (char *) malloc((size_t) (strlen(passwd) + 6)); - if (!aaa) + if (!aaa) { return -1; + } sprintf(aaa, "SETP %s", passwd); ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret); @@ -551,7 +549,7 @@ int CtdlIPCGetMessages(CtdlIPC * ipc, enum MessageList which, int whicharg, cons } -/* MSG0, MSG2 */ +/* MSG0, MSG2, MSG4 */ int CtdlIPCGetSingleMessage(CtdlIPC * ipc, long msgnum, int headers, int as_mime, struct ctdlipcmessage **mret, char *cret) { int ret; char aaa[SIZ]; @@ -2570,10 +2568,15 @@ int CtdlIPCWriteUpload(CtdlIPC * ipc, FILE * uploadFP, void (*progress_gauge_cal * protocol_response as described above. Some commands send additional * data in this string. */ -int CtdlIPCGenericCommand(CtdlIPC * ipc, - const char *command, - const char *to_send, - size_t bytes_to_send, char **to_receive, size_t *bytes_to_receive, char *proto_response) { +int CtdlIPCGenericCommand( + CtdlIPC * ipc, + const char *command, + const char *to_send, + size_t bytes_to_send, + char **to_receive, + size_t *bytes_to_receive, + char *proto_response +) { char buf[SIZ]; int ret; @@ -3023,15 +3026,24 @@ static void CtdlIPC_getline(CtdlIPC * ipc, char *buf) { } /* If we got a long line, discard characters until the newline. */ - if (i == (SIZ - 1)) - while (buf[i] != '\n') + if (i == (SIZ - 1)) { + + + abort(); + + + while (buf[i] != '\n') { serv_read(ipc, &buf[i], 1); + } + } /* Strip the trailing newline (and carriage return, if present) */ - if (i >= 0 && buf[i] == 10) + if (i >= 0 && buf[i] == 10) { buf[i--] = 0; - if (i >= 0 && buf[i] == 13) + } + if (i >= 0 && buf[i] == 13) { buf[i--] = 0; + } } else #endif -- 2.39.2 From 72571aff86712dea5713da84049fc10a3cfe1ec8 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Sun, 2 Jun 2024 19:36:52 +0000 Subject: [PATCH 10/16] Really just a commit test. I'm going to try to put commit messages back into the Citadel Development room now that the communists at Google have stopped throwing them into the spam bucket. But I didn't like the format that the RSS feed was using so I'm trying out my own commit hook. --- textclient/citadel_ipc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/textclient/citadel_ipc.c b/textclient/citadel_ipc.c index 0eb5bdae5..57acf4b6b 100644 --- a/textclient/citadel_ipc.c +++ b/textclient/citadel_ipc.c @@ -136,7 +136,7 @@ static void serv_write(CtdlIPC * ipc, const char *buf, unsigned int nbytes); static void serv_read_ssl(CtdlIPC * ipc, char *buf, unsigned int bytes); static void serv_write_ssl(CtdlIPC * ipc, const char *buf, unsigned int nbytes); static void endtls(SSL * ssl); -#endif /* HAVE_OPENSSL */ +#endif // HAVE_OPENSSL static void CtdlIPC_getline(CtdlIPC * ipc, char *buf); static void CtdlIPC_putline(CtdlIPC * ipc, const char *buf); @@ -319,11 +319,11 @@ int CtdlIPCChangePassword(CtdlIPC * ipc, const char *passwd, char *cret) { } -/* LKRN */ +// LKRN -/* Caller must free the march list */ +// Caller must free the march list -/* Room types are defined in enum RoomList; keep these in sync! */ +// Room types are defined in enum RoomList; keep these in sync! /* floor is -1 for all, or floornum */ int CtdlIPCKnownRooms(CtdlIPC * ipc, enum RoomList which, int floor, struct march **listing, char *cret) { -- 2.39.2 From d0f23937a826e53a565c739b1d3f18168f04aae9 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Sun, 2 Jun 2024 19:37:59 +0000 Subject: [PATCH 11/16] Really just a commit test. I'm going to try to put commit messages back into the Citadel Development room now that the communists at Google have stopped throwing them into the spam bucket. But I didn't like the format that the RSS feed was using so I'm trying out my own commit hook. --- textclient/citadel_ipc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/textclient/citadel_ipc.c b/textclient/citadel_ipc.c index 57acf4b6b..5d5f0b965 100644 --- a/textclient/citadel_ipc.c +++ b/textclient/citadel_ipc.c @@ -325,7 +325,7 @@ int CtdlIPCChangePassword(CtdlIPC * ipc, const char *passwd, char *cret) { // Room types are defined in enum RoomList; keep these in sync! -/* floor is -1 for all, or floornum */ +// floor is -1 for all, or floornum int CtdlIPCKnownRooms(CtdlIPC * ipc, enum RoomList which, int floor, struct march **listing, char *cret) { int ret; struct march *march = NULL; @@ -337,7 +337,7 @@ int CtdlIPCKnownRooms(CtdlIPC * ipc, enum RoomList which, int floor, struct marc if (!listing) return -2; if (*listing) - return -2; /* Free the listing first */ + return -2; // Free the listing first if (!cret) return -2; /* if (which < 0 || which > 4) return -2; */ -- 2.39.2 From 91d6aaf02742a88d273009f932bf2829864a1262 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Sun, 2 Jun 2024 19:40:16 +0000 Subject: [PATCH 12/16] Really just a commit test. I'm going to try to put commit messages back into the Citadel Development room now that the communists at Google have stopped throwing them into the spam bucket. But I didn't like the format that the RSS feed was using so I'm trying out my own commit hook. --- textclient/citadel_ipc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/textclient/citadel_ipc.c b/textclient/citadel_ipc.c index 5d5f0b965..b0e6cfc63 100644 --- a/textclient/citadel_ipc.c +++ b/textclient/citadel_ipc.c @@ -8,7 +8,7 @@ static SSL_CTX *ssl_ctx; char arg_encrypt; char rc_encrypt; -#endif /* HAVE_OPENSSL */ +#endif // HAVE_OPENSSL #ifndef INADDR_NONE #define INADDR_NONE 0xffffffff -- 2.39.2 From da5701d8ec39900e3f91039529d4a98287215d32 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Sun, 2 Jun 2024 19:42:33 +0000 Subject: [PATCH 13/16] Really just a commit test. I'm going to try to put commit messages back into the Citadel Development room now that the communists at Google have stopped throwing them into the spam bucket. But I didn't like the format that the RSS feed was using so I'm trying out my own commit hook. --- textclient/citadel_ipc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/textclient/citadel_ipc.c b/textclient/citadel_ipc.c index b0e6cfc63..c426a29e0 100644 --- a/textclient/citadel_ipc.c +++ b/textclient/citadel_ipc.c @@ -340,9 +340,9 @@ int CtdlIPCKnownRooms(CtdlIPC * ipc, enum RoomList which, int floor, struct marc return -2; // Free the listing first if (!cret) return -2; - /* if (which < 0 || which > 4) return -2; */ + // if (which < 0 || which > 4) return -2; if (floor < -1) - return -2; /* Can't validate upper bound, sorry */ + return -2; // Can't validate upper bound, sorry sprintf(aaa, "%s %d", proto[which], floor); ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret); -- 2.39.2 From 579f9980603aa5c1f6f5aad01551ace781538dbb Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Sun, 2 Jun 2024 20:55:38 -0400 Subject: [PATCH 14/16] Reduced hi_from_stu.wav by -20db --- webcit-ng/static/sounds/hi_from_stu.wav | Bin 77226 -> 67532 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/webcit-ng/static/sounds/hi_from_stu.wav b/webcit-ng/static/sounds/hi_from_stu.wav index c0f2cdc8d3a09002bf6392e1b1e4cd87ad9a3e83..05f2a3840638332999ac0ae22089ba65d26db8ad 100644 GIT binary patch literal 67532 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≪TQVz;7S$F7*eoEpwr-??B9;=U)C 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 CbN$c& literal 77226 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%0Dn0;7|7OLw?(PLHv~b$KvlB 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 L^-$4V`1bz*uTle# -- 2.39.2 From 9beb118acbd5258e85c98c206392e6eba44f8413 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Mon, 3 Jun 2024 01:09:42 +0000 Subject: [PATCH 15/16] Initial commit for calendar week view flexbox. --- webcit-ng/static/css/webcit.css | 25 ++++++++++++++++ webcit-ng/static/index.html | 1 + webcit-ng/static/js/view_calendar.js | 45 +++++++++++++++++++--------- webcit-ng/static/js/views.js | 2 +- 4 files changed, 58 insertions(+), 15 deletions(-) diff --git a/webcit-ng/static/css/webcit.css b/webcit-ng/static/css/webcit.css index 01c168bb1..c9f6f4d5f 100644 --- a/webcit-ng/static/css/webcit.css +++ b/webcit-ng/static/css/webcit.css @@ -696,3 +696,28 @@ blockquote pre { font-size: 2em; color: White; } + +.ctdl-calendar-week { /* Flexbox container for day, 3-day, 5-day, week view */ + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; + align-items: center; + margin: 0; + width: 100%; + height: 100%; + overflow: hidden; +} + +.ctdl-calendar-ruler { + height: 100%; /* Occupy the entire height of the main pane */ + border: 1px solid GhostWhite; +} + +.ctdl-calendar-day { /* Flexbox child for one day of the week */ + flex-grow: 0; /* Keep all columns the same size */ + background-color: White; + border: 1px solid GhostWhite; + height: 100%; /* Occupy the entire height of the main pane */ + width: 100%; /* Fill the width of the main pane; don't compress them into narrow boxes */ +} diff --git a/webcit-ng/static/index.html b/webcit-ng/static/index.html index cb42038e0..2c155ac21 100644 --- a/webcit-ng/static/index.html +++ b/webcit-ng/static/index.html @@ -37,6 +37,7 @@
  • +
  • diff --git a/webcit-ng/static/js/view_calendar.js b/webcit-ng/static/js/view_calendar.js index 01967701d..7c48178f9 100644 --- a/webcit-ng/static/js/view_calendar.js +++ b/webcit-ng/static/js/view_calendar.js @@ -58,7 +58,7 @@ function XXXXXview_render_calendar() { var date_being_displayed; var calendar_initialized = 0; - +var days_being_displayed = 7; /* 1 = day view; 5 = work week view; 7 = week view */ // Update the calendar display (we might have changed dates, or added/removed data) function update_calendar_display() { @@ -68,19 +68,23 @@ function update_calendar_display() { let month = date_being_displayed.getMonth() + 1; let year = date_being_displayed.getFullYear(); - document.getElementById("ctdl-main").innerHTML = - "Displaying " + year + "-" + month + "-" + day_of_month + "
    " - + "Temporary navigation links: " - + "←Y | " - + "←M | " - + "←W | " - + "←D | " - + "today | " - + "D→ | " - + "W→ | " - + "M→ | " - + "Y→ " - ; + let caldisplay = `
    `; + + caldisplay += `
    `; + for (let i=0; i<24; ++i) { + caldisplay += i + "
    "; + }; + caldisplay += `
    `; + + for (let i=0; iHi!
    `; + } + caldisplay += ""; + document.getElementById("ctdl-main").innerHTML = caldisplay; + + // Populate the columns + document.getElementById("ctdl-cal-day0").innerHTML = year + "-" + month + "-" + day_of_month; + } @@ -150,6 +154,19 @@ function go_forward_one_year() { // RENDERER FOR THIS VIEW function view_render_calendar() { + document.getElementById("ctdl-calendar-nav").innerHTML = + "←Y | " + + "←M | " + + "←W | " + + "←D | " + + "today | " + + "D→ | " + + "W→ | " + + "M→ | " + + "Y→ " + ; + document.getElementById("ctdl-calendar-nav").style.display = "block"; + // Calendar will persist the year/month/day where the user left it, // but if it's rendering for the first time, set it to "today". if (!calendar_initialized) { diff --git a/webcit-ng/static/js/views.js b/webcit-ng/static/js/views.js index e6b769d19..af53ade41 100644 --- a/webcit-ng/static/js/views.js +++ b/webcit-ng/static/js/views.js @@ -23,7 +23,7 @@ function clear_drop_handlers() { // Clear the top bar navigation buttons. The view renderer will set its own buttons. function clear_navigation_buttons() { - for (const d of ["ctdl-newmsg-button", "ctdl-ungoto-button", "ctdl-skip-button", "ctdl-goto-button", "ctdl-delete-button"]) { + for (const d of ["ctdl-newmsg-button", "ctdl-ungoto-button", "ctdl-skip-button", "ctdl-goto-button", "ctdl-delete-button", "ctdl-calendar-nav"]) { document.getElementById(d).style.display = "none"; } } -- 2.39.2 From fa7cb8f73882562f0dd9e3a4c74d3b4cf985a961 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Sun, 2 Jun 2024 23:53:08 -0400 Subject: [PATCH 16/16] Getting navigation in place for week view. Week View is actually also Day View, because it's using a flexbox and can display a arbitrary number of day columns. So we will use 1 column for day view, 5 columns for work week view, and 7 columns for week view. --- webcit-ng/static/js/view_calendar.js | 64 +++++++++++++++++----------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/webcit-ng/static/js/view_calendar.js b/webcit-ng/static/js/view_calendar.js index 7c48178f9..75d2e3a2d 100644 --- a/webcit-ng/static/js/view_calendar.js +++ b/webcit-ng/static/js/view_calendar.js @@ -57,34 +57,35 @@ function XXXXXview_render_calendar() { } var date_being_displayed; -var calendar_initialized = 0; var days_being_displayed = 7; /* 1 = day view; 5 = work week view; 7 = week view */ +var start_of_week = 0; /* 0 = Sunday; 1 = Monday */ // Update the calendar display (we might have changed dates, or added/removed data) function update_calendar_display() { - // Get y-m-d to display - let day_of_month = date_being_displayed.getDate(); - let month = date_being_displayed.getMonth() + 1; - let year = date_being_displayed.getFullYear(); + let this_column_date = date_being_displayed; - let caldisplay = `
    `; - - caldisplay += `
    `; - for (let i=0; i<24; ++i) { - caldisplay += i + "
    "; - }; - caldisplay += `
    `; + // If displaying a whole week, start the week on the correct day. + if (days_being_displayed == 7) { + while (this_column_date.getDay() != start_of_week) { + this_column_date.setDate(this_column_date.getDate() - 1); + } + } + // Go through every day on the screen (up to a week I guess) for (let i=0; iHi!
    `; + // Get y-m-d to display + let day_of_month = this_column_date.getDate(); + let month = this_column_date.getMonth() + 1; + let year = this_column_date.getFullYear(); + + // Populate the columns + var weekday = this_column_date.toLocaleString("default", { weekday: "short" }) + document.getElementById("ctdl-cal-day" + i).innerHTML = weekday + " " + year + "-" + month + "-" + day_of_month; + + // Get ready for the next column + this_column_date.setDate(this_column_date.getDate() + 1); } - caldisplay += ""; - document.getElementById("ctdl-main").innerHTML = caldisplay; - - // Populate the columns - document.getElementById("ctdl-cal-day0").innerHTML = year + "-" + month + "-" + day_of_month; - } @@ -104,7 +105,7 @@ function go_back_one_month() { // Go back by one week. function go_back_one_week() { - date_being_displayed.setDate(date_being_displayed.getDate() - 7 ); + date_being_displayed.setDate(date_being_displayed.getDate() - 8 ); // why 8 instead of 7? update_calendar_display(); } @@ -132,7 +133,7 @@ function go_forward_one_day() { // Advance the calendar by one week. function go_forward_one_week() { - date_being_displayed.setDate(date_being_displayed.getDate() + 7); + date_being_displayed.setDate(date_being_displayed.getDate() + 6); // why 6 instead of 7? update_calendar_display(); } @@ -167,9 +168,22 @@ function view_render_calendar() { ; document.getElementById("ctdl-calendar-nav").style.display = "block"; - // Calendar will persist the year/month/day where the user left it, - // but if it's rendering for the first time, set it to "today". - if (!calendar_initialized) { - go_to_today(); + + let caldisplay = ` +
    +
    + `; + for (let i=0; i<24; ++i) { + caldisplay += i + "
    "; + }; + caldisplay += `
    `; + + for (let i=0; iHi!
    `; } + caldisplay += ""; + document.getElementById("ctdl-main").innerHTML = caldisplay; + + // Start on today. + go_to_today(); } -- 2.39.2