* The TAB key allows autocomplete in the Fcc field in the composer headers,
[alpine.git] / pith / text.c
blob478bba0660e1837056c6aec1f7967e3ce3480c06
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: text.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2006-2008 University of Washington
8 * Copyright 2013-2016 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
21 text.c
22 Implements message text fetching and decoding
24 ====*/
27 #include "../pith/headers.h"
28 #include "../pith/text.h"
29 #include "../pith/state.h"
30 #include "../pith/conf.h"
31 #include "../pith/handle.h"
32 #include "../pith/margin.h"
33 #include "../pith/editorial.h"
34 #include "../pith/color.h"
35 #include "../pith/filter.h"
36 #include "../pith/charset.h"
37 #include "../pith/status.h"
38 #include "../pith/mailview.h"
39 #include "../pith/mimedesc.h"
40 #include "../pith/detach.h"
43 /* internal prototypes */
44 int charset_editorial(char *, long, HANDLE_S **, int, int, int, gf_io_t);
45 int replace_quotes(long, char *, LT_INS_S **, void *);
46 void mark_handles_in_line(char *, HANDLE_S **, int);
47 void clear_html_risk(void);
48 void set_html_risk(void);
49 int get_html_risk(void);
52 #define CHARSET_DISCLAIMER_1 \
53 "The following text is in the \"%.40s\" character set.\015\012Your "
54 #define CHARSET_DISCLAIMER_2 "display is set"
55 #define CHARSET_DISCLAIMER_3 \
56 " for the \"%.40s\" character set. \015\012Some %.40scharacters may be displayed incorrectly."
57 #define ENCODING_DISCLAIMER \
58 "The following text contains the unknown encoding type \"%.20s\". \015\012Some or all of the text may be displayed incorrectly."
59 #define HTML_WARNING \
60 "The following HTML text may contain deceptive links. Carefully note the destination URL before visiting any links."
64 * HTML risk state holder.
66 static int _html_risk;
70 /*----------------------------------------------------------------------
71 Handle fetching and filtering a text message segment to be displayed
72 by scrolltool or printed or exported or piped.
74 Args: att -- segment to fetch
75 msgno -- message number segment is a part of
76 pc -- function to write characters from segment with
77 style -- Indicates special handling for error messages
78 flags -- Indicates special necessary handling
80 Returns: 1 if errors encountered, 0 if everything went A-OK
82 ----*/
83 int
84 decode_text(ATTACH_S *att,
85 long int msgno,
86 gf_io_t pc,
87 HANDLE_S **handlesp,
88 DetachErrStyle style,
89 int flags)
91 FILTLIST_S filters[14];
92 char *err, *charset;
93 int filtcnt = 0, error_found = 0, column, wrapit;
94 int is_in_sig = OUT_SIG_BLOCK;
95 int is_flowed_msg = 0;
96 int is_delsp_yes = 0;
97 int filt_only_c0 = 0;
98 char *parmval;
99 char *free_this = NULL;
100 STORE_S *warn_so = NULL;
101 DELQ_S dq;
102 URL_HILITE_S uh;
104 column = (flags & FM_DISPLAY) ? ps_global->ttyo->screen_cols : 80;
106 if(!(flags & FM_DISPLAY))
107 flags |= FM_NOINDENT;
109 wrapit = column;
111 memset(filters, 0, sizeof(filters));
113 /* charset the body part is in */
114 charset = parameter_val(att->body->parameter, "charset");
116 /* determined if it's flowed, affects wrapping and quote coloring */
117 if(att->body->type == TYPETEXT
118 && !strucmp(att->body->subtype, "plain")
119 && (parmval = parameter_val(att->body->parameter, "format"))){
120 if(!strucmp(parmval, "flowed"))
121 is_flowed_msg = 1;
122 fs_give((void **) &parmval);
124 if(is_flowed_msg){
125 if((parmval = parameter_val(att->body->parameter, "delsp")) != NULL){
126 if(!strucmp(parmval, "yes"))
127 is_delsp_yes = 1;
129 fs_give((void **) &parmval);
134 if(!ps_global->pass_ctrl_chars){
135 filters[filtcnt++].filter = gf_escape_filter;
136 filters[filtcnt].filter = gf_control_filter;
138 filt_only_c0 = 1;
139 filters[filtcnt++].data = gf_control_filter_opt(&filt_only_c0);
142 if(flags & FM_DISPLAY)
143 filters[filtcnt++].filter = gf_tag_filter;
146 * if it's just plain old text, look for url's
148 if(!(att->body->subtype && strucmp(att->body->subtype, "plain"))){
149 struct variable *vars = ps_global->vars;
151 if((F_ON(F_VIEW_SEL_URL, ps_global)
152 || F_ON(F_VIEW_SEL_URL_HOST, ps_global)
153 || F_ON(F_SCAN_ADDR, ps_global))
154 && handlesp){
157 * The url_hilite filter really ought to come
158 * after flowing, because flowing with the DelSp=yes parameter
159 * can reassemble broken urls back into identifiable urls.
160 * We add the preflow filter to do only the reassembly part
161 * of the flowing so that we can spot the urls.
162 * At this time (2005-03-29) we know that Apple Mail does
163 * send mail like this sometimes. This filter removes the
164 * sequence SP CRLF if that seems safe.
166 if(ps_global->full_header != 2 && is_delsp_yes)
167 filters[filtcnt++].filter = gf_preflow;
169 filters[filtcnt].filter = gf_line_test;
170 filters[filtcnt++].data = gf_line_test_opt(url_hilite,
171 gf_url_hilite_opt(&uh,handlesp,0));
175 * First, paint the signature.
176 * Disclaimers noted below for coloring quotes apply here as well.
178 if((flags & FM_DISPLAY)
179 && !(flags & FM_NOCOLOR)
180 && pico_usingcolor()
181 && VAR_SIGNATURE_FORE_COLOR
182 && VAR_SIGNATURE_BACK_COLOR){
183 filters[filtcnt].filter = gf_line_test;
184 filters[filtcnt++].data = gf_line_test_opt(color_signature,
185 &is_in_sig);
189 * Gotta be careful with this. The color_a_quote filter adds color
190 * to the beginning and end of the line. This will break some
191 * line_test-style filters which come after it. For example, if they
192 * are looking for something at the start of a line (like color_a_quote
193 * itself). I guess we could fix that by ignoring tags at the
194 * beginning of the line when doing the search.
196 if((flags & FM_DISPLAY)
197 && !(flags & FM_NOCOLOR)
198 && pico_usingcolor()
199 && VAR_QUOTE1_FORE_COLOR
200 && VAR_QUOTE1_BACK_COLOR){
201 filters[filtcnt].filter = gf_line_test;
202 filters[filtcnt++].data = gf_line_test_opt(color_a_quote,
203 &is_flowed_msg);
206 else if(!strucmp(att->body->subtype, "richtext")){
207 int plain_opt;
209 plain_opt = !(flags&FM_DISPLAY);
211 /* maybe strip everything! */
212 filters[filtcnt].filter = gf_rich2plain;
213 filters[filtcnt++].data = gf_rich2plain_opt(&plain_opt);
214 /* width to use for file or printer */
215 if(wrapit - 5 > 0)
216 wrapit -= 5;
218 else if(!strucmp(att->body->subtype, "enriched")){
219 int plain_opt;
221 plain_opt = !(flags&FM_DISPLAY);
223 filters[filtcnt].filter = gf_enriched2plain;
224 filters[filtcnt++].data = gf_enriched2plain_opt(&plain_opt);
225 /* width to use for file or printer */
226 if(wrapit - 5 > 0)
227 wrapit -= 5;
229 else if(!strucmp(att->body->subtype, "html") && ps_global->full_header < 2){
230 /*BUG: sniff the params for "version=2.0" ala draft-ietf-html-spec-01 */
231 int opts = 0;
233 clear_html_risk();
235 if(flags & FM_DISPLAY){
236 gf_io_t warn_pc;
238 if(handlesp){ /* pass on handles awareness */
239 opts |= GFHP_HANDLES;
241 if(F_OFF(F_QUELL_HOST_AFTER_URL, ps_global) && !(flags & FM_HIDESERVER))
242 opts |= GFHP_SHOW_SERVER;
245 if(!(flags & FM_NOEDITORIAL)){
246 warn_so = so_get(CharStar, NULL, EDIT_ACCESS);
247 gf_set_so_writec(&warn_pc, warn_so);
248 format_editorial(HTML_WARNING, column, flags, handlesp, warn_pc);
249 gf_clear_so_writec(warn_so);
250 so_puts(warn_so, "\015\012");
251 so_writec('\0', warn_so);
254 else
255 opts |= GFHP_STRIPPED; /* don't embed anything! */
257 if(flags & FM_NOHTMLREL)
258 opts |= GFHP_NO_RELATIVE;
260 if(flags & FM_HTMLRELATED)
261 opts |= GFHP_RELATED_CONTENT;
263 if(flags & FM_HTML)
264 opts |= GFHP_HTML;
266 if(flags & FM_HTMLIMAGES)
267 opts |= GFHP_HTML_IMAGES;
269 wrapit = 0; /* wrap already handled! */
270 filters[filtcnt].filter = gf_html2plain;
271 filters[filtcnt++].data = gf_html2plain_opt(NULL, column,
272 (flags & (FM_NOINDENT | FM_HTML))
274 : format_view_margin(),
275 handlesp, set_html_risk, opts);
277 if(warn_so){
278 filters[filtcnt].filter = gf_prepend_editorial;
279 filters[filtcnt++].data = gf_prepend_editorial_opt(get_html_risk,
280 (char *) so_text(warn_so));
285 * If the message is not flowed, we do the quote suppression before
286 * the wrapping, because the wrapping does not preserve the quote
287 * characters at the beginnings of the lines in that case.
288 * Otherwise, we defer until after the wrapping.
290 * Also, this is a good place to do quote-replacement on nonflowed
291 * messages because no other filters depend on the "> ".
292 * Quote-replacement is easier in the flowed case and occurs
293 * automatically in the flowed wrapping filter.
295 if(!is_flowed_msg
296 && ps_global->full_header == 0
297 && !(att->body->subtype && strucmp(att->body->subtype, "plain"))
298 && (flags & FM_DISPLAY)){
299 if(ps_global->quote_suppression_threshold != 0){
300 memset(&dq, 0, sizeof(dq));
301 dq.lines = ps_global->quote_suppression_threshold;
302 dq.is_flowed = is_flowed_msg;
303 dq.indent_length = 0; /* indent didn't happen yet */
304 dq.saved_line = &free_this;
305 dq.handlesp = handlesp;
306 dq.do_color = (!(flags & FM_NOCOLOR) && pico_usingcolor());
308 filters[filtcnt].filter = gf_line_test;
309 filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq);
311 if(ps_global->VAR_QUOTE_REPLACE_STRING
312 && F_ON(F_QUOTE_REPLACE_NOFLOW, ps_global)){
313 filters[filtcnt].filter = gf_line_test;
314 filters[filtcnt++].data = gf_line_test_opt(replace_quotes, NULL);
318 if(wrapit && !(flags & FM_NOWRAP)){
319 int wrapflags = (flags & FM_DISPLAY) ? (GFW_HANDLES|GFW_SOFTHYPHEN)
320 : GFW_NONE;
322 if(flags & FM_DISPLAY
323 && !(flags & FM_NOCOLOR)
324 && pico_usingcolor())
325 wrapflags |= GFW_USECOLOR;
327 /* text/flowed (RFC 2646 + draft)? */
328 if(ps_global->full_header != 2 && is_flowed_msg){
329 wrapflags |= GFW_FLOWED;
331 if(is_delsp_yes)
332 wrapflags |= GFW_DELSP;
335 filters[filtcnt].filter = gf_wrap;
336 filters[filtcnt++].data = gf_wrap_filter_opt(wrapit, column,
337 (flags & FM_NOINDENT)
339 : format_view_margin(),
340 0, wrapflags);
344 * This has to come after wrapping has happened because the user tells
345 * us how many quoted lines to display, and we want that number to be
346 * the wrapped lines, not the pre-wrapped lines. We do it before the
347 * wrapping if the message is not flowed, because the wrapping does not
348 * preserve the quote characters at the beginnings of the lines in that
349 * case.
351 if(is_flowed_msg
352 && ps_global->full_header == 0
353 && !(att->body->subtype && strucmp(att->body->subtype, "plain"))
354 && (flags & FM_DISPLAY)
355 && ps_global->quote_suppression_threshold != 0){
356 memset(&dq, 0, sizeof(dq));
357 dq.lines = ps_global->quote_suppression_threshold;
358 dq.is_flowed = is_flowed_msg;
359 dq.indent_length = (wrapit && !(flags & FM_NOWRAP)
360 && !(flags & FM_NOINDENT))
361 ? format_view_margin()[0]
362 : 0;
363 dq.saved_line = &free_this;
364 dq.handlesp = handlesp;
365 dq.do_color = (!(flags & FM_NOCOLOR) && pico_usingcolor());
367 filters[filtcnt].filter = gf_line_test;
368 filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq);
371 if(charset){
372 if(F_OFF(F_QUELL_CHARSET_WARNING, ps_global)
373 && !(flags & FM_NOEDITORIAL)){
374 int rv = TRUE;
375 CONV_TABLE *ct = NULL;
378 * Need editorial if message charset is not ascii
379 * and the display charset is not either the same
380 * as the message charset or UTF-8.
382 if(strucmp(charset, "us-ascii")
383 && !((ps_global->display_charmap
384 && !strucmp(charset, ps_global->display_charmap))
385 || !strucmp("UTF-8", ps_global->display_charmap))){
388 * This is probably overkill. We're just using this
389 * conversion_table routine to get at the quality.
391 if(ps_global->display_charmap)
392 ct = conversion_table(charset, ps_global->display_charmap);
394 rv = charset_editorial(charset, msgno, handlesp, flags,
395 ct ? ct->quality : CV_NO_TRANSLATE_POSSIBLE,
396 column, pc);
398 if(!rv)
399 goto write_error;
402 fs_give((void **) &charset);
405 /* Format editorial comment about unknown encoding */
406 if(att->body->encoding > ENCQUOTEDPRINTABLE){
407 char buf[2048];
409 snprintf(buf, sizeof(buf), ENCODING_DISCLAIMER, body_encodings[att->body->encoding]);
411 if(!(format_editorial(buf, column, flags, handlesp, pc) == NULL
412 && gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc)))
413 goto write_error;
416 err = detach(ps_global->mail_stream, msgno, att->number, 0L,
417 NULL, pc, filtcnt ? filters : NULL, 0);
419 delete_unused_handles(handlesp);
421 if(free_this)
422 fs_give((void **) &free_this);
424 if(warn_so)
425 so_give(&warn_so);
427 if(err) {
428 error_found++;
429 if(style == QStatus) {
430 q_status_message1(SM_ORDER, 3, 4, "%.200s", err);
431 } else if(style == InLine) {
432 char buftmp[MAILTMPLEN];
434 snprintf(buftmp, sizeof(buftmp), "%s",
435 att->body->description ? att->body->description : "");
436 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s [Error: %s] %c%s%c%s%s",
437 NEWLINE, err,
438 att->body->description ? '\"' : ' ',
439 att->body->description ? buftmp : "",
440 att->body->description ? '\"' : ' ',
441 NEWLINE, NEWLINE);
442 if(!gf_puts(tmp_20k_buf, pc))
443 goto write_error;
447 if(att->body->subtype
448 && (!strucmp(att->body->subtype, "richtext")
449 || !strucmp(att->body->subtype, "enriched"))
450 && !(flags & FM_DISPLAY)){
451 if(!gf_puts(NEWLINE, pc) || !gf_puts(NEWLINE, pc))
452 goto write_error;
455 return(error_found);
457 write_error:
458 if(style == QStatus)
459 q_status_message1(SM_ORDER, 3, 4, "Error writing message: %.200s",
460 error_description(errno));
462 return(1);
468 * Format editorial comment about charset mismatch
471 charset_editorial(char *charset, long int msgno, HANDLE_S **handlesp, int flags,
472 int quality, int width, gf_io_t pc)
474 char *p, color[64], buf[2048];
475 int i, n;
476 HANDLE_S *h = NULL;
478 snprintf(buf, sizeof(buf), CHARSET_DISCLAIMER_1, charset ? charset : "US-ASCII");
479 p = &buf[strlen(buf)];
481 if(!(ps_global->display_charmap
482 && strucmp(ps_global->display_charmap, "US-ASCII"))
483 && handlesp
484 && (flags & FM_DISPLAY) == FM_DISPLAY){
485 h = new_handle(handlesp);
486 h->type = URL;
487 h->h.url.path = cpystr("x-alpine-help:h_config_char_set");
490 * Because this filter comes after the delete_quotes filter, we need
491 * to tell delete_quotes that this handle is used, so it won't
492 * delete it. This is a dangerous thing.
494 h->is_used = 1;
496 if(!(flags & FM_NOCOLOR)
497 && scroll_handle_start_color(color, sizeof(color), &n)){
498 for(i = 0; i < n; i++)
499 if(p-buf < sizeof(buf))
500 *p++ = color[i];
502 else if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)){
503 if(p-buf+1 < sizeof(buf)){
504 *p++ = TAG_EMBED;
505 *p++ = TAG_BOLDON;
509 if(p-buf+1 < sizeof(buf)){
510 *p++ = TAG_EMBED;
511 *p++ = TAG_HANDLE;
514 snprintf(p + 1, sizeof(buf)-(p+1-buf), "%d", h->key);
515 if(p-buf < sizeof(buf))
516 *p = strlen(p + 1);
518 p += (*p + 1);
521 sstrncpy(&p, CHARSET_DISCLAIMER_2, sizeof(buf)-(p-buf));
523 if(h){
524 /* in case it was the current selection */
525 if(p-buf+1 < sizeof(buf)){
526 *p++ = TAG_EMBED;
527 *p++ = TAG_INVOFF;
530 if(scroll_handle_end_color(color, sizeof(color), &n, 0)){
531 for(i = 0; i < n; i++)
532 if(p-buf < sizeof(buf))
533 *p++ = color[i];
535 else{
536 if(p-buf+1 < sizeof(buf)){
537 *p++ = TAG_EMBED;
538 *p++ = TAG_BOLDOFF;
542 if(p-buf < sizeof(buf))
543 *p = '\0';
546 snprintf(p, sizeof(buf)-(p-buf), CHARSET_DISCLAIMER_3,
547 ps_global->display_charmap ? ps_global->display_charmap : "US-ASCII",
548 (quality == CV_LOSES_SPECIAL_CHARS) ? "special " : "");
550 buf[sizeof(buf)-1] = '\0';
552 return(format_editorial(buf, width, flags, handlesp, pc) == NULL
553 && gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc));
560 * This one is a little more complicated because it comes late in the
561 * filtering sequence after colors have been added and url's hilited and
562 * so on. So if it wants to look at the beginning of the line it has to
563 * wade through some stuff done by the other filters first. This one is
564 * skipping the leading indent added by the viewer-margin stuff and leading
565 * tags.
568 delete_quotes(long int linenum, char *line, LT_INS_S **ins, void *local)
570 DELQ_S *dq;
571 char *lp;
572 int i, lines, not_a_quote = 0;
573 size_t len;
575 dq = (DELQ_S *) local;
576 if(!dq)
577 return(0);
579 if(dq->lines > 0 || dq->lines == Q_DEL_ALL){
581 lines = (dq->lines == Q_DEL_ALL) ? 0 : dq->lines;
584 * First determine if this line is part of a quote.
587 /* skip over indent added by viewer-margin-left */
588 lp = line;
589 for(i = dq->indent_length; i > 0 && !not_a_quote && *lp; i--)
590 if(*lp++ != SPACE)
591 not_a_quote++;
593 /* skip over leading tags */
594 while(!not_a_quote
595 && ((unsigned char) (*lp) == (unsigned char) TAG_EMBED)){
596 switch(*++lp){
597 case '\0':
598 not_a_quote++;
599 break;
601 default:
602 ++lp;
603 break;
605 case TAG_FGCOLOR:
606 case TAG_BGCOLOR:
607 case TAG_HANDLE:
608 switch(*lp){
609 case TAG_FGCOLOR:
610 case TAG_BGCOLOR:
611 len = RGBLEN + 1;
612 break;
614 case TAG_HANDLE:
615 len = *++lp + 1;
616 break;
619 if(strlen(lp) < len)
620 not_a_quote++;
621 else
622 lp += len;
624 break;
626 case TAG_EMBED:
627 break;
631 /* skip over whitespace */
632 if(!dq->is_flowed)
633 while(isspace((unsigned char) *lp))
634 lp++;
636 /* check first character to see if it is a quote */
637 if(!not_a_quote && *lp != '>')
638 not_a_quote++;
640 if(not_a_quote){
641 if(dq->in_quote > lines+1 && !dq->delete_all){
642 char tmp[500];
643 COLOR_PAIR *col = NULL;
644 char cestart[2 * RGBLEN + 5];
645 char ceend[2 * RGBLEN + 5];
648 * Display how many lines were hidden.
651 cestart[0] = ceend[0] = '\0';
652 if(dq->do_color
653 && ps_global->VAR_METAMSG_FORE_COLOR
654 && ps_global->VAR_METAMSG_BACK_COLOR
655 && (col = new_color_pair(ps_global->VAR_METAMSG_FORE_COLOR,
656 ps_global->VAR_METAMSG_BACK_COLOR)))
657 if(!pico_is_good_colorpair(col))
658 free_color_pair(&col);
660 if(col){
661 strncpy(cestart, color_embed(col->fg, col->bg), sizeof(cestart));
662 cestart[sizeof(cestart)-1] = '\0';
663 strncpy(ceend, color_embed(ps_global->VAR_NORM_FORE_COLOR,
664 ps_global->VAR_NORM_BACK_COLOR), sizeof(ceend));
665 ceend[sizeof(ceend)-1] = '\0';
666 free_color_pair(&col);
669 snprintf(tmp, sizeof(tmp),
670 "%s[ %s%d lines of quoted text hidden from view%s ]\r\n",
671 repeat_char(dq->indent_length, SPACE),
672 cestart, dq->in_quote - lines, ceend);
673 if(strlen(tmp)-strlen(cestart)-strlen(ceend)-2 > ps_global->ttyo->screen_cols){
675 snprintf(tmp, sizeof(tmp), "%s[ %s%d lines of quoted text hidden%s ]\r\n",
676 repeat_char(dq->indent_length, SPACE),
677 cestart, dq->in_quote - lines, ceend);
679 if(strlen(tmp)-strlen(cestart)-strlen(ceend)-2 > ps_global->ttyo->screen_cols){
680 snprintf(tmp, sizeof(tmp), "%s[ %s%d lines hidden%s ]\r\n",
681 repeat_char(dq->indent_length, SPACE),
682 cestart, dq->in_quote - lines, ceend);
684 if(strlen(tmp)-strlen(cestart)-strlen(ceend)-2 > ps_global->ttyo->screen_cols){
685 snprintf(tmp, sizeof(tmp), "%s[ %s%d hidden%s ]\r\n",
686 repeat_char(dq->indent_length, SPACE),
687 cestart, dq->in_quote - lines, ceend);
689 if(strlen(tmp)-strlen(cestart)-strlen(ceend)-2 > ps_global->ttyo->screen_cols){
690 snprintf(tmp, sizeof(tmp), "%s[...]\r\n",
691 repeat_char(dq->indent_length, SPACE));
693 if(strlen(tmp)-2 > ps_global->ttyo->screen_cols){
694 snprintf(tmp, sizeof(tmp), "%s...\r\n",
695 repeat_char(dq->indent_length, SPACE));
697 if(strlen(tmp)-2 > ps_global->ttyo->screen_cols){
698 snprintf(tmp, sizeof(tmp), "%s\r\n",
699 repeat_char(MIN(ps_global->ttyo->screen_cols,3),
700 '.'));
708 ins = gf_line_test_new_ins(ins, line, tmp, strlen(tmp));
709 mark_handles_in_line(line, dq->handlesp, 1);
710 ps_global->some_quoting_was_suppressed = 1;
712 else if(dq->in_quote == lines+1
713 && dq->saved_line && *dq->saved_line){
715 * We would have only had to delete a single line. Instead
716 * of deleting it, just show it.
718 ins = gf_line_test_new_ins(ins, line,
719 *dq->saved_line,
720 strlen(*dq->saved_line));
721 mark_handles_in_line(*dq->saved_line, dq->handlesp, 1);
723 else
724 mark_handles_in_line(line, dq->handlesp, 1);
726 if(dq->saved_line && *dq->saved_line)
727 fs_give((void **) dq->saved_line);
729 dq->in_quote = 0;
731 else{
732 dq->in_quote++; /* count it */
733 if(dq->in_quote > lines){
735 * If the hidden part is a single line we'll show it instead.
737 if(dq->in_quote == lines+1 && !dq->delete_all){
738 if(dq->saved_line && *dq->saved_line)
739 fs_give((void **) dq->saved_line);
741 *dq->saved_line = fs_get(strlen(line) + 3);
742 snprintf(*dq->saved_line, strlen(line)+3, "%s\r\n", line);
745 mark_handles_in_line(line, dq->handlesp, 0);
746 /* skip it, at least for now */
747 return(2);
750 mark_handles_in_line(line, dq->handlesp, 1);
754 return(0);
759 * This is a line-at-a-time filter that replaces incoming UTF-8 text with
760 * ISO-2022-JP text.
763 translate_utf8_to_2022_jp(long int linenum, char *line, LT_INS_S **ins, void *local)
765 LOC_2022_JP *lpj;
766 int ret = 0;
768 lpj = (LOC_2022_JP *) local;
770 if(line && line[0]){
771 char *converted;
772 size_t len;
774 converted = utf8_to_charset(line, "ISO-2022-JP", lpj ? lpj->report_err : 0);
776 if(!converted)
777 ret = -1;
779 if(converted && converted != line){
780 /* delete the old line and replace it with the translation */
781 len = strlen(line);
782 ins = gf_line_test_new_ins(ins, line, "", -((int) len));
783 ins = gf_line_test_new_ins(ins, line+len, converted, strlen(converted));
784 fs_give((void **) &converted);
788 return ret;
793 * Replace quotes of nonflowed messages. This needs to happen
794 * towards the end of filtering because a lot of prior filters
795 * depend on "> ", such as quote coloring and suppression.
796 * Quotes are already colored here, so we have to account for
797 * that formatting.
800 replace_quotes(long int linenum, char *line, LT_INS_S **ins, void *local)
802 char *lp = line, *prefix = NULL, *last_prefix = NULL;
803 int no_more_quotes = 0, len, saw_quote = 0;
805 if(ps_global->VAR_QUOTE_REPLACE_STRING){
806 get_pair(ps_global->VAR_QUOTE_REPLACE_STRING, &prefix, &last_prefix, 0, 0);
807 if(!prefix && last_prefix){
808 prefix = last_prefix;
809 last_prefix = NULL;
812 else
813 return(0);
815 while(isspace((unsigned char)(*lp)))
816 lp++;
817 while(*lp && !no_more_quotes){
818 switch(*lp++){
819 case '>':
820 if(*lp == ' '){
821 ins = gf_line_test_new_ins(ins, lp - 1, "", -2);
822 lp++;
824 else
825 ins = gf_line_test_new_ins(ins, lp - 1, "", -1);
826 if(strlen(prefix))
827 ins = gf_line_test_new_ins(ins, lp, prefix, strlen(prefix));
828 saw_quote = 1;
829 break;
830 case TAG_EMBED:
831 switch(*lp++){
832 case TAG_FGCOLOR:
833 case TAG_BGCOLOR:
834 len = RGBLEN;
835 break;
836 case TAG_HANDLE:
837 len = *lp++ + 1;
838 break;
840 if(strlen(lp) < len)
841 no_more_quotes = 1;
842 else
843 lp += len;
844 break;
845 case ' ':
846 case '\t':
847 break;
848 default:
849 if(saw_quote && last_prefix)
850 ins = gf_line_test_new_ins(ins, lp - 1, last_prefix, strlen(last_prefix));
851 no_more_quotes = 1;
852 break;
855 if(prefix)
856 fs_give((void **)&prefix);
857 if(last_prefix)
858 fs_give((void **)&last_prefix);
859 return(0);
864 * We need to delete handles that we are removing from the displayed
865 * text. But there is a complication. Some handles appear at more than
866 * one location in the text. So we keep track of which handles we're
867 * actually using as we go, then at the end we delete the ones we
868 * didn't use.
870 * Args line -- the text line, which includes tags
871 * handlesp -- handles pointer
872 * used -- If 1, set handles in this line to used
873 * if 0, leave handles in this line set as they already are
875 void
876 mark_handles_in_line(char *line, HANDLE_S **handlesp, int used)
878 unsigned char c;
879 size_t length;
880 int key, n;
881 HANDLE_S *h;
883 if(!(line && handlesp && *handlesp))
884 return;
886 length = strlen(line);
888 /* mimic code in PutLine0n8b */
889 while(length--){
891 c = (unsigned char) *line++;
893 if(c == (unsigned char) TAG_EMBED && length){
895 length--;
897 switch(*line++){
898 case TAG_HANDLE :
899 length -= *line + 1; /* key length plus length tag */
901 for(key = 0, n = *line++; n; n--)
902 key = (key * 10) + (*line++ - '0');
904 h = get_handle(*handlesp, key);
905 if(h){
906 h->using_is_used = 1;
908 * If it's already marked is_used, leave it marked
909 * that way. We only call this function with used = 0
910 * in order to set using_is_used.
912 h->is_used = (h->is_used || used) ? 1 : 0;
915 break;
917 case TAG_FGCOLOR :
918 if(length < RGBLEN){
919 length = 0;
920 break;
923 length -= RGBLEN;
924 line += RGBLEN;
925 break;
927 case TAG_BGCOLOR :
928 if(length < RGBLEN){
929 length = 0;
930 break;
933 length -= RGBLEN;
934 line += RGBLEN;
935 break;
937 default:
938 break;
946 * parsed html risk state functions
948 void
949 clear_html_risk(void)
951 _html_risk = 0;
954 void
955 set_html_risk(void)
957 _html_risk = 1;
961 get_html_risk(void)
963 return(_html_risk);